Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
(ns pallet.crate.postgres
"Install and configure PostgreSQL."
(:require
[pallet.resource.package :as package]
[pallet.request-map :as request-map]
[pallet.resource :as resource]
[pallet.resource.file :as file]
[pallet.resource.exec-script :as exec-script]
[pallet.resource.remote-file :as remote-file]
[pallet.resource.resource-when :as resource-when]
[pallet.stevedore :as stevedore]
[pallet.parameter :as parameter]
[clojure.contrib.condition :as condition]
[clojure.contrib.logging :as logging]
[clojure.string :as str])
(:use
pallet.thread-expr
[pallet.script :only [defscript]]))
(defn postgres
"version should be a string identifying the major.minor version number desired
(e.g. \"9.0\")."
[request version]
(let [os-family (request-map/os-family request)]
(-> request
(when-> (= os-family :ubuntu)
(when-> (= version "9.0")
(package/package-source
"Martin Pitt backports"
:aptitude {:url "ppa:pitti/postgresql"}))
(package/package-manager :update))
(package/packages :aptitude [(str "postgresql-" version)])
(assoc-in [:parameters :postgresql :version] version))))
(def ^{:private true} pallet-cfg-preamble
"# This file was auto-generated by Pallet. Do not edit it manually unless you
# know what you are doing. If you are still using Pallet, you probably want to
# edit your Pallet scripts and rerun them.\n")
;;
;; pg_hba.conf
;;
(def ^{:private true}
auth-methods #{"trust" "reject" "md5" "password" "gss" "sspi" "krb5"
"ident" "ldap" "radius" "cert" "pam"})
(def ^{:private true}
ip-addr-regex #"[0-9]{1,3}.[0-9]{1,3}+.[0-9]{1,3}+.[0-9]{1,3}+")
(defn- valid-hba-record?
"Takes an hba-record as input and minimally checks that it could be a valid
record."
[{:keys [connection-type database user auth-method address ip-mask]
:as record-map}]
(and (#{"local" "host" "hostssl" "hostnossl"} (name connection-type))
(every? #(not (nil? %)) [database user auth-method])
(auth-methods (name auth-method))))
(defn- record-to-map
"Takes a record given as a map or vector, and turns it into the map version."
[record]
(cond
(map? record) record
(vector? record) (case (name (first record))
"local" (apply
hash-map
(interleave
[:connection-type :database :user :auth-method
:auth-options]
record))
("host"
"hostssl"
"hostnossl") (let [[connection-type database user address
& remainder] record]
(if (re-matches
ip-addr-regex (first remainder))
;; Not nil so must be an IP mask.
(apply
hash-map
(interleave
[:connection-type :database :user
:address :ip-mask :auth-method
:auth-options]
record))
;; Otherwise, it may be an auth-method.
(if (auth-methods
(name (first remainder)))
(apply
hash-map
(interleave
[:connection-type :database :user
:address :auth-method
:auth-options]
record))
(condition/raise
:type :postgres-invalid-hba-record
:message
(format
"The fifth item in %s does not appear to be an IP mask or auth method."
(name record))))))
(condition/raise
:type :postgres-invalid-hba-record
:message (format
"The first item in %s is not a valid connection type."
(name record))))
:else
(condition/raise :type :postgres-invalid-hba-record
:message (format "The record %s must be a vector or map."
(name record)))))
(defn- format-auth-options
"Given the auth-options map, returns a string suitable for inserting into the
file."
[auth-options]
(str/join "," (map #(str (first %) "=" (second %)) auth-options)))
(defn- format-hba-record
[record]
(let [record-map (record-to-map record)
record-map (assoc record-map :auth-options
(format-auth-options (:auth-options record-map)))
ordered-fields (map record-map
[:connection-type :database :user :address :ip-mask
:auth-method :auth-options])
ordered-fields (map name ordered-fields)]
(if (valid-hba-record? record-map)
(str (str/join "\t" ordered-fields) "\n"))))
(defn hba-conf
"Generates a pg_hba.conf file from the arguments. Each record is either a
vector or map of keywords/args.
Note that pg_hba.conf is case-sensitive: all means all databases, ALL is a
database named ALL.
Also note that if you intend to execute subsequent commands, you'd do best to
include entries in here that allow the admin user you are using easy access
to the database. For example, allow the postgres user to have ident access
over local.
Options:
:records - A sequence of records (either vectors or maps of
keywords/strings).
:conf-path - A string suitable for passing to format before a string of the
version."
[request & {:keys [records conf-path]
:or {records []
conf-path "/etc/postgresql/%s/main/pg_hba.conf"}}]
(let [hba-contents (apply str pallet-cfg-preamble
(map format-hba-record records))
version (parameter/get-for request [:postgresql :version])]
(-> request
(remote-file/remote-file (format conf-path version)
:content hba-contents
:literal true))))
;;
;; postgresql.conf
;;
(defn- parameter-escape-string
"Given a string, escapes any single-quotes."
[string]
(apply str (replace {\' "''"} string)))
(defn- format-parameter-value
[value]
(cond (number? value)
(str value)
(string? value)
(str "'" value "'")
(vector? value)
(str "'" (str/join "," (map name value)) "'")
:else
(condition/raise
:type :postgres-invalid-parameter
:message "Parameters must be numbers, strings, or vectors of such.")))
(defn- format-parameter
"Given a key/value pair in a vector, formats it suitably for the
postgresql.conf file.
The value should be either a number, a string, or a vector of such."
[[key value]]
(let [key-str (name key)
parameter-str (format-parameter-value value)]
(str key-str " = " parameter-str "\n")))
(defn postgresql-conf
"Generates a postgresql.conf file from the arguments.
Example: (postgresql-conf
:options {:listen_address [\"10.0.1.1\",\"localhost\"]})
=> listen_address = '10.0.1.1,localhost'
Options:
:options - A map of parameters (string(able)s, numbers, or vectors of
such).
:conf-path - A string suitable for passing to format before a string of
the version."
[request & {:keys [options conf-path]
:or {options {}
conf-path "/etc/postgresql/%s/main/postgresql.conf"}}]
(let [contents (apply str pallet-cfg-preamble
(map format-parameter options))
version (parameter/get-for request [:postgresql :version])]
(-> request
(remote-file/remote-file (format conf-path version)
:content contents
:literal true))))
;;
;; Scripts
;;
(defn postgresql-script
"Execute a postgresql script.
Options for how this script should be run:
:as-user username - Run this script having sudoed to this (system)
user. Default: postgres
:ignore-result - Ignore any error return value out of psql."
[request sql-script & {:keys [as-user ignore-result]
:as options
:or {as-user "postgres"}}]
(-> request
(exec-script/exec-checked-script
"PostgreSQL temp command file"
(var psql_commands (file/make-temp-file "postgresql")))
(remote-file/remote-file
(stevedore/script @psql_commands)
:no-versioning true
:literal true
:content sql-script)
(exec-script/exec-script
;; Note that we stuff all output. This is because certain commands in
;; PostgreSQL are idempotent but spit out an error and an error exit
;; anyways (eg, create database on a database that already exists does
;; nothing, but is counted as an error).
("{\n" sudo "-u" ~as-user psql "-f" @psql_commands > "/dev/null" "2>&1"
~(when ignore-result "|| 0") "\n}"))
(remote-file/remote-file
(stevedore/script @psql_commands)
:action :delete)))
(defn create-database
"Create a database if it does not exist.
You can specify database parameters by including a keyed parameter called
:db-parameters, which indicates a vector of strings or keywords that will get
translated in order to the options to the create database command. Passes on
key/value arguments it does not understand to postgresql-script.
Example: (create-database
\"my-database\" :db-parameters [:encoding \"'LATIN1'\"])"
[request db-name & rest]
(let [{:keys [db-parameters] :as options} rest
db-parameters-str (str/join " " (map name db-parameters))]
;; Postgres simply has no way to check if a database exists and issue a
;; "CREATE DATABASE" only in the case that it doesn't. That would require a
;; function, but create database can't be done within a transaction, so
;; you're screwed. Instead, we just use the fact that trying to create an
;; existing database does nothing and stuff the output/error return.
(apply postgresql-script
request
(format "CREATE DATABASE %s %s;" db-name db-parameters-str)
(conj (vec rest) :ignore-result true))))
;; This is a format string that generates a temporary PL/pgsql function to
;; check if a given role exists, and if not create it. The first argument
;; should be the role name, the second should be any user-parameters.
(def ^{:private true} create-role-pgsql
"create or replace function pg_temp.createuser() returns void as $$
declare user_rec record;
begin
select into user_rec * from pg_user where usename='%1$s';
if user_rec.usename is null then
create role %1$s %2$s;
end if;
end;
$$ language plpgsql;
select pg_temp.createuser();")
(defn create-role
"Create a postgres role if it does not exist.
You can specify user parameters by including a keyed parameter called
:user-parameters, which indicates a vector of strings or keywords that will
get translated in order to the options to the create user command. Passes on
key/value arguments to postgresql-script.
Example (create-role
\"myuser\" :user-parameters [:encrypted :password \"'mypasswd'\"])"
[request username & rest]
(let [{:keys [user-parameters] :as options} rest
user-parameters-str (str/join " " (map name user-parameters))]
(apply postgresql-script
request
(format create-role-pgsql username user-parameters-str)
rest)))