diff --git a/.gitignore b/.gitignore index 52d4c3e..eea0e79 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,16 @@ -target -doc -checkout -logs +/target +/lib +/classes +/checkouts +/logs +/doc +doc-src/VERSIONS.md +doc-src/INTRO.md +pom.xml +pom.xml.asc +*.jar +*.class +.lein-deps-sum +.lein-failures +.lein-plugins +.lein-repl-history diff --git a/README.md b/README.md index c05e65a..2815d32 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,85 @@ -# Pallet crate for postgres +[Repository](https://github.com/pallet/postgres-crate) · +[Issues](https://github.com/pallet/postgres-crate/issues) · +[API docs](http://palletops.com/postgres-crate/0.8/api) · +[Annotated source](http://palletops.com/postgres-crate/0.8/annotated/uberdoc.html) · +[Release Notes](https://github.com/pallet/postgres-crate/blob/develop/ReleaseNotes.md) + +A Pallet crate to install and configure postgres. + +### Dependency Information + +```clj +:dependencies [[com.palletops/postgres-crate "0.8.0-alpha.1"]] +``` + +### Releases + + + + + + + + + + + + + + + + + + + + + + + +
PalletCrate VersionRepoGroupId
0.8.0-beta.60.8.0-alpha.1clojarscom.palletopsRelease NotesSource
0.7.20.7.0-beta.2clojarscom.palletopsRelease NotesSource
+ +## Usage + +The `server-spec` function provides a convenient pallet server spec for +postgres. It takes a single map as an argument, specifying configuration +choices, as described below for the `settings` function. You can use this +in your own group or server specs in the :extends clause. + +```clj +(require '[pallet/crate/postgres :as postgres]) +(group-spec my-postgres-group + :extends [(postgres/server-spec {})]) +``` + +While `server-spec` provides an all-in-one function, you can use the individual +plan functions as you see fit. + +The `settings` function provides a plan function that should be called in the +`:settings` phase. The function puts the configuration options into the pallet +session, where they can be found by the other crate functions, or by other +crates wanting to interact with postgres. + +The `install` function is responsible for actually installing postgres. + +The `configure` function writes the postgres configuration file, using the form +passed to the :config key in the `settings` function. -This a crate to install and run postgres via [Pallet](http://pallet.github.com/pallet). - -[Release Notes](ReleaseNotes.md) - -## Server Spec - -The postgres crate defines the `postgres` function, that takes a settings map -and returns a server-spec for installing postgres. ## Settings The postgres crate uses the following settings: -* `:version` - a string to specify the point version of PostgreSQL (e.g., `"9.1"`). The default is the version provided by the system's packaging system +* `:version` + a string to specify the point version of PostgreSQL (e.g., `"9.1"`). The + default is the version provided by the system's packaging system * `:components` - a set of one or more recognized keywords. The set of every component is `#{:server :libs :client}`. + a set of one or more recognized keywords. The set of every component is + `#{:server :libs :client}`. * `:strategy` - allows override of the install strategy (`:packages`, `:package-source`, or `:rpm`) + allows override of the install strategy (`:packages`, `:package-source`, or + `:rpm`) * `:packages` the packages that are used to install @@ -29,7 +88,9 @@ The postgres crate uses the following settings: a non-default package source for the packages * `:rpm` - takes a map of [`remote-file` options](http://palletops.com/pallet/api/0.7/pallet.action.remote-file.html) specifying an RPM file to install + takes a map of + [`remote-file` options](http://palletops.com/pallet/api/0.7/pallet.action.remote-file.html) + specifying an RPM file to install * `:default-cluster-name` name of the default cluster created by the installer @@ -44,7 +105,8 @@ The postgres crate uses the following settings: path to `postgresql.conf` * `:has-pg-wrapper` - boolean flag for availability of a wrapper allowing command execution against a specified cluster. + boolean flag for availability of a wrapper allowing command execution against + a specified cluster. * `:has-multicluster-service` boolean flag specifying whether the init service is multi-cluster capable. @@ -74,4 +136,4 @@ The postgres crate uses the following settings: Licensed under [EPL](http://www.eclipse.org/legal/epl-v10.html) -Copyright 2010, 2011, 2012 Hugo Duncan. +Copyright 2013 Hugo Duncan. diff --git a/dev-resources/logback-test.xml b/dev-resources/logback-test.xml new file mode 100644 index 0000000..584feb5 --- /dev/null +++ b/dev-resources/logback-test.xml @@ -0,0 +1,116 @@ + + + + + + INFO + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - %msg%n + + + + + logs/jclouds-compute.log + + logs/old/jclouds-compute.%d{yyyy-MM-dd}.log + 3 + + + %date %level [%thread] %logger{10} [%file:%line] %msg%n + + + + + logs/jclouds-wire.log + + logs/old/jclouds-wire.%d{yyyy-MM-dd}.log + 3 + + + %date %level [%thread] %logger{10} [%file:%line] %msg%n + + + + + logs/pallet.log + + logs/old/pallet.%d{yyyy-MM-dd}.log + 3 + + + %date %level [%thread] %logger{10} %msg%n + + + + + logs/vmfest.log + + logs/old/vmfest.%d{yyyy-MM-dd}.log + 3 + + + %date %level [%thread] %logger{10} %msg%n + + + + + + target + unspecified + + + + logs/target-${target}.log + false + + %msg%n + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc-src/FOOTER.md b/doc-src/FOOTER.md new file mode 100644 index 0000000..fb3ffd9 --- /dev/null +++ b/doc-src/FOOTER.md @@ -0,0 +1,10 @@ +## Support + +[On the group](http://groups.google.com/group/pallet-clj), or +[#pallet](http://webchat.freenode.net/?channels=#pallet) on freenode irc. + +## License + +Licensed under [EPL](http://www.eclipse.org/legal/epl-v10.html) + +Copyright 2013 Hugo Duncan. diff --git a/doc-src/USAGE.md b/doc-src/USAGE.md new file mode 100644 index 0000000..1b8ddca --- /dev/null +++ b/doc-src/USAGE.md @@ -0,0 +1,88 @@ +## Usage + +The `server-spec` function provides a convenient pallet server spec for +postgres. It takes a single map as an argument, specifying configuration +choices, as described below for the `settings` function. You can use this +in your own group or server specs in the :extends clause. + +```clj +(require '[pallet/crate/postgres :as postgres]) +(group-spec my-postgres-group + :extends [(postgres/server-spec {})]) +``` + +While `server-spec` provides an all-in-one function, you can use the individual +plan functions as you see fit. + +The `settings` function provides a plan function that should be called in the +`:settings` phase. The function puts the configuration options into the pallet +session, where they can be found by the other crate functions, or by other +crates wanting to interact with postgres. + +The `install` function is responsible for actually installing postgres. + +The `configure` function writes the postgres configuration file, using the form +passed to the :config key in the `settings` function. + + +## Settings + +The postgres crate uses the following settings: + +* `:version` + a string to specify the point version of PostgreSQL (e.g., `"9.1"`). The + default is the version provided by the system's packaging system + +* `:components` + a set of one or more recognized keywords. The set of every component is + `#{:server :libs :client}`. + +* `:strategy` + allows override of the install strategy (`:packages`, `:package-source`, or + `:rpm`) + +* `:packages` + the packages that are used to install + +* `:package-source` + a non-default package source for the packages + +* `:rpm` + takes a map of + [`remote-file` options](http://palletops.com/pallet/api/0.7/pallet.action.remote-file.html) + specifying an RPM file to install + +* `:default-cluster-name` + name of the default cluster created by the installer + +* `:bin` + path to binaries + +* `:owner` + unix owner for Postgres files + +* `:postgresql_file` + path to `postgresql.conf` + +* `:has-pg-wrapper` + boolean flag for availability of a wrapper allowing command execution against + a specified cluster. + +* `:has-multicluster-service` + boolean flag specifying whether the init service is multi-cluster capable. + +* `:initdb-via` + whether to use the initdb (`:initdb`), or service (`:service`) to run initdb + +* `:options` + A map of options: + - `:data_directory` + path to storage location + - `:hba_file` + path to `pg_hba.conf` location + - `:ident_file` + path to `pg_ident.conf` location + - `:external_pid_file` + path to pid file + - `:unix_socket_directory` + path to directory for unix sockets diff --git a/pallet.clj b/pallet.clj new file mode 100644 index 0000000..f94ef48 --- /dev/null +++ b/pallet.clj @@ -0,0 +1,13 @@ +;;; Pallet project configuration file + +(require + '[pallet.crate.postgres-test + :refer [test-server-spec]] + '[pallet.crates.test-nodes :refer [node-specs]]) + +(defproject postgres-crate + :provider node-specs ; supported pallet nodes + :groups [(group-spec "pgtest" + :extends [with-automated-admin-user + test-server-spec] + :roles #{:live-test :default :postgres})]) diff --git a/pom.xml b/pom.xml deleted file mode 100644 index b90cd32..0000000 --- a/pom.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - 4.0.0 - - org.cloudhoist - pallet-crate-pom - 0.7.1 - - postgres - 0.7.0-SNAPSHOT - - - scm:git:git://github.com/pallet/postgres-crate.git - scm:git:ssh://git@github.com/pallet/postgres-crate.git - https://github.com/pallet/postgres-crate - - diff --git a/profiles.clj b/profiles.clj new file mode 100644 index 0000000..692eee0 --- /dev/null +++ b/profiles.clj @@ -0,0 +1,25 @@ +{:dev + {:dependencies [[com.palletops/pallet "0.8.0-SNAPSHOT" :classifier "tests"] + [com.palletops/crates "0.1.2-SNAPSHOT"] + [com.palletops/pallet-test-env "RELEASE"] + [ch.qos.logback/logback-classic "1.0.9"]] + :plugins [[com.palletops/lein-pallet-crate "RELEASE"] + [lein-pallet-release "RELEASE"] + [com.palletops/lein-test-env "RELEASE"]]} + :provided + {:dependencies [[org.clojure/clojure "1.6.0"] + [com.palletops/pallet "0.8.0-SNAPSHOT"]]} + :aws {:pallet/test-env + {:test-specs + [;; {:selector :ubuntu-13-10} + ;; {:selector :ubuntu-13-04 + ;; :expected [{:feature ["oracle-java-8"] + ;; :expected? :not-supported}]} + ;; {:selector :ubuntu-12-04} + ;; {:selector :amzn-linux-2013-092} + {:selector :centos-6-5} + ;; {:selector :debian-7-5} + ;; {:selector :debian-6-0} + ]}} + :vmfest {:pallet/test-env {:test-specs + [{:selector :ubuntu-13-04}]}}} diff --git a/project.clj b/project.clj new file mode 100644 index 0000000..5fe0f27 --- /dev/null +++ b/project.clj @@ -0,0 +1,7 @@ +(defproject com.palletops/postgres-crate "0.8.0-SNAPSHOT" + :description "Pallet crate to install, configure and use postgres" + :url "http://palletops.com" + :license {:name "Eclipse Public License" + :url "http://www.eclipse.org/legal/epl-v10.html"} + :scm {:url "git@github.com:pallet/runit-crate.git"} + :dependencies [[prismatic/schema "0.2.1"]]) diff --git a/resources/pallet_crate/postgres_crate/meta.edn b/resources/pallet_crate/postgres_crate/meta.edn new file mode 100644 index 0000000..567ce35 --- /dev/null +++ b/resources/pallet_crate/postgres_crate/meta.edn @@ -0,0 +1,8 @@ +{:header "postgres" + :title "A Pallet crate to install and configure postgres." + :git-repo "https://github.com/pallet/postgres-crate" + :api-docs "http://palletops.com/postgres-crate/0.8/api" + :annotated-docs "http://palletops.com/postgres-crate/0.8/annotated/uberdoc.html" + :tag-prefix "" + :versions [{:pallet "0.8.0-beta.6" :version "0.8.0-alpha.1"} + {:pallet "0.7.2" :version "0.7.0-beta.2"}]} diff --git a/src/pallet/crate/postgres.clj b/src/pallet/crate/postgres.clj index 8574486..c7a4144 100644 --- a/src/pallet/crate/postgres.clj +++ b/src/pallet/crate/postgres.clj @@ -39,195 +39,92 @@ Links: " (:require [pallet.action :as action] - [pallet.action.directory :as directory] - [pallet.action.exec-script :as exec-script] - [pallet.action.file :as file] - [pallet.action.package :as package] - [pallet.action.package.debian-backports :as debian-backports] - [pallet.action.remote-file :as remote-file] - [pallet.action.service :as service] + [pallet.actions :refer [add-rpm content-options directory exec-checked-script + exec-script file package package-manager + package-source remote-file] + :as actions] + [pallet.api :refer [plan-fn] :as api] + [pallet.compute :as compute] + [pallet.core.session :refer [session]] + [pallet.crate :refer [assoc-settings defplan get-settings os-family + target-node update-settings]] + [pallet.crate-install :as crate-install] [pallet.crate.etc-default :as etc-default] - [pallet.parameter :as parameter] + [pallet.crate.postgres.config :as config] + [pallet.crate.postgres.kb :as kb] + [pallet.crate.service + :refer [supervisor-config supervisor-config-map] :as service] [pallet.script.lib :as lib] - [pallet.session :as session] [pallet.stevedore :as stevedore] - [pallet.thread-expr :as thread-expr] + [pallet.node :refer [is-64bit?]] + [pallet.utils :refer [apply-map]] [clojure.tools.logging :as logging] [clojure.string :as string]) (:use - pallet.thread-expr - [pallet.action.package :only [package package-manager package-source]] - [pallet.action.package.debian-backports :only [debian-backports-repository]] - [pallet.core :only [server-spec]] - [pallet.phase :only [phase-fn]] + [pallet.crate.package.debian-backports :only [add-debian-backports]] [pallet.script :only [defscript]] [pallet.version-dispatch - :only [defmulti-version-crate defmulti-version defmulti-os-crate - multi-version-session-method multi-version-method - multi-os-session-method]] - [pallet.versions :only [as-version-vector version-string]] - [slingshot.slingshot :only [throw+]])) + :only [defmethod-version defmulti-version defmulti-version-plan + defmethod-version-plan defmulti-version-plan os-map os-map-lookup] + :as version-dispatch] + [pallet.versions :only [as-version-vector version-string]])) + + +(def facility + "The settings facility for postgres." + ::postgresql) (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\n") - (def ^{:doc "Flag for recognising changes to configuration"} postgresql-config-changed-flag "postgresql-config") -;;; Default Postgres package version -(defmulti-os-crate postgres-package-version [session]) - -(multi-os-session-method - postgres-package-version {:os :linux} - [os os-version session] - [8]) - -(multi-os-session-method - postgres-package-version {:os :ubuntu :os-version [12]} - [os os-version session] - [9 1]) - ;;; Install strategy -(defmulti-version-crate install-strategy [version session settings]) - -(def ^{:dynamic true} *pgdg-repo-versions* - {"9.0" "9.0-5" - "9.1" "9.1-5" - "9.2" "9.2-5"}) - -(defn pgdg-url - [version os-family] - (format - "http://yum.pgrpms.org/reporpms/%s/pgdg-%s%s-%s.noarch.rpm" - version - (name os-family) - (string/replace version "." "") - (*pgdg-repo-versions* version))) - -(multi-version-session-method - install-strategy {:os :rh-base} - [os os-version version session settings] - (-> - (cond - (:strategy settings) settings - (:package-source settings) (assoc settings :strategy :package-source) - :else (let [default-version (postgres-package-version session) - target-version (:version settings)] - (if (= (version-string default-version) target-version) - (assoc settings - :strategy :packages - :packages (map - #(str "postgresql-" (name %)) - (:components settings #{:server :libs})) - :layout :rh-base) - (assoc settings - :strategy :rpm-repo - :rpm {:name "pgdg.rpm" - :url (pgdg-url (version-string version) - (if (= os :rhel) "redhat" os))} - :packages (map - #(str "postgresql" - (string/replace target-version "." "") - "-" (name %)) - (:components settings #{:server :libs})) - :layout :pgdg)))))) - -(multi-version-session-method - install-strategy {:os :debian} - [os os-version version session settings] - (-> - (cond - (:strategy settings) settings - (:package-source settings) (assoc settings :strategy :package-source) - :else (let [default-version (postgres-package-version session) - target-version (:version settings)] - (if (= (version-string default-version) target-version) - (assoc settings - :strategy :packages - :packages ["postgresql"] - :layout :debian-base) - (assoc settings - :strategy :package-source - :package-source (debian-backports-repository) - :packages ["libpq5" (str "postgresql-" target-version)] - :layout :debian-base)))))) - -(multi-version-session-method - install-strategy {:os :ubuntu} - [os os-version version session settings] - (-> - (cond - (:strategy settings) settings - (:package-source settings) (assoc settings :strategy :package-source) - :else (let [default-version (postgres-package-version session) - target-version (:version settings)] - (if (= (version-string default-version) target-version) - (assoc settings - :strategy :packages - :packages ["postgresql"] - :layout :debian-base) - (assoc settings - :strategy :package-source - :package-source {:name "Martin Pitt backports" - :aptitude {:url "ppa:pitti/postgresql"} - :apt {:url "ppa:pitti/postgresql"}} - :packages [(str "postgresql-" target-version)] - :layout :debian-base)))))) - -(multi-version-session-method - install-strategy {:os :arch} - [os os-version version session settings] - (-> - (cond - (:strategy settings) settings - (:package-source settings) (assoc settings :strategy :package-source) - :else (let [default-version (postgres-package-version session) - target-version (:version settings)] - (if (= (version-string default-version) target-version) - (assoc settings - :strategy :packages - :packages ["postgresql"] - :layout :arch) - (throw+ - {:reason :no-install-strategy} - "No install strategy for postgres %s on %s %s" - version os os-version)))))) - -;;; Dispatch to install strategy -(defmulti install-method (fn [session settings] (:strategy settings))) - -(defmethod install-method :packages [session settings] - (reduce package session (:packages settings))) - -(defmethod install-method :package-source - [session {:keys [package-source packages] :as settings}] - (-> - session - (apply-map-> package/package-source (:name package-source) package-source) - (package/package-manager :update) - (when-> (= package-source :debian-backports) - (debian-backports/add-debian-backports) - (package/package-manager :update)) - (for-> [pkg packages] (package pkg)))) - -(defmethod install-method :rpm - [session {:keys [rpm] :as settings}] - (-> - session - (action/with-precedence {:always-before `package/package} - (apply-map-> package/add-rpm (:name rpm) rpm)))) - -(defmethod install-method :rpm-repo - [session {:keys [rpm packages] :as settings}] - (-> - session - (action/with-precedence {:always-before `package/package} - (apply-map-> package/add-rpm (:name rpm) rpm)) - (for-> [pkg packages] (package pkg)))) - +(defn postgres-rpm-settings + "Returns a settings map for install via the Postgres RPM repository." + [version components os-family os-version arch] + (let [arch (if (is-64bit? (target-node)) "x86_64" "i386")] + {:install-strategy :rpm-repo + :rpm {:name "pgdg.rpm" + :url (kb/pgdg-url + (version-string version) os-family os-version arch)} + :packages (kb/pgdg-packages + (version-string version) + components) + :layout :pgdg})) + +(defn postgres-apt-settings + [version] + {:install-strategy :package-source + :package-source + {:name "Postgres Apt" + :aptitude kb/postgres-apt + :apt kb/postgres-apt} + :packages (kb/postgres-apt-packages (version-string version)) + :layout :debian-base}) + +(defn packages-settings + [version components] + {:install-strategy :packages + :packages (kb/package-names + (os-family) + (version-string version) + components)}) + +(defmulti-version-plan install-strategy + ;; Default install strategy, if none supplied. + [version settings]) + +(defmethod-version-plan install-strategy {:os :rh-base} + [os os-version version settings] + (packages-settings version nil)) + +(defmethod-version-plan install-strategy {:os :debian-base} + [os os-version version settings] + (packages-settings version nil)) ;;; Default settings (def default-settings-map @@ -239,7 +136,8 @@ Links: :shared_buffers "24MB" :log_line_prefix "%t " :datestyle "iso, ymd" - :default_text_search_config "pg_catalog.english"} + :default_text_search_config "pg_catalog.english" + :logging_collector "on"} :permissions [["local" "all" "postgres" "ident" ""] ["local" "postgres" "postgres" "ident" ""]] :start {:start :auto} @@ -266,222 +164,6 @@ Links: [:permissions])))) settings)) -(defmulti default-settings - "Determine the default settings for the specified " - (fn [session os-family layout settings] - layout)) - -(defn base-settings [session] - {:service "postgresql" - :owner "postgres" - :initdb-via :initdb - :options {:external_pid_file (str (stevedore/script (~lib/pid-root)) - "/postgresql.pid")}}) - -(defmethod default-settings :debian-base - [session os-family layout settings] - (let [version (:version settings)] - (merge-settings - (base-settings session) - {:default-cluster-name "main" - :bin (format "/usr/lib/postgresql/%s/bin/" version) - :share (format "/usr/lib/postgresql/%s/share/" version) - :wal_directory (format "/var/lib/postgresql/%s/%%s/archive" version) - :postgresql_file (format - "/etc/postgresql/%s/%%s/postgresql.conf" version) - :has-pg-wrapper true - :has-multicluster-service true - :options - {:data_directory (format "/var/lib/postgresql/%s/%%s" version) - :hba_file (format "/etc/postgresql/%s/%%s/pg_hba.conf" version) - :ident_file (format "/etc/postgresql/%s/%%s/pg_ident.conf" version) - :external_pid_file (format "/var/run/postgresql/%s-%%s.pid" version) - :unix_socket_directory "/var/run/postgresql"}}))) - -(defmethod default-settings :rh-base - [session os-family layout settings] - (let [version (:version settings)] - (merge-settings - (base-settings session) - {:default-cluster-name "data" - :wal_directory (format "/var/lib/pgsql/%s/%%s/archive" version) - :postgresql_file (format "/var/lib/pgsql/%s/%%s/postgresql.conf" version) - :options - {:data_directory (format "/var/lib/pgsql/%s/%%s" version) - :hba_file (format "/var/lib/pgsql/%s/%%s/pg_hba.conf" version) - :ident_file (format "/var/lib/pgsql/%s/%%s/pg_ident.conf" version) - :external_pid_file (format "/var/run/postmaster-%s-%%s.pid" version)}}))) - -(defmethod default-settings :pgdg - [session os-family package-source settings] - (let [version (:version settings)] - (merge-settings - (base-settings session) - (default-settings session os-family :rh-base settings) - {:bin (format "/usr/pgsql-%s/bin/" version) - :share (format "/usr/pgsql-%s/share/" version) - :default-cluster-name "data" - :service (str "postgresql-" version "-%s") - :default-service (str "postgresql-" version) - :use-port-in-pidfile true - :wal_directory (format "/var/lib/pgsql/%s/%%s/archive" version) - :postgresql_file (format "/var/lib/pgsql/%s/%%s/postgresql.conf" version) - :options - {:data_directory (format "/var/lib/pgsql/%s/%%s" version) - :hba_file (format "/var/lib/pgsql/%s/%%s/pg_hba.conf" version) - :ident_file (format "/var/lib/pgsql/%s/%%s/pg_ident.conf" version)}}))) - -(defmethod default-settings :arch - [session os-family package-source settings] - (let [version (:version settings)] - (merge-settings - (base-settings session) - {:components [] - :default-cluster-name "data" - :initdb-via :initdb - :wal_directory "/var/lib/postgres/%%s/archive/" - :postgresql_file "/var/lib/postgres/%%s/postgresql.conf" - :options - {:data_directory "/var/lib/postgres/%%s/" - :hba_file "/var/lib/postgres/%%s/pg_hba.conf" - :ident_file "/var/lib/postgres/%%s/pg_ident.conf"}}))) - -;;; 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- vector-to-map - [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)) - (throw+ - {:type :postgres-invalid-hba-record - :message - (format - "The fifth item in %s does not appear to be an IP mask or auth method." - (pr-str record))})))) - (throw+ - {:type :postgres-invalid-hba-record - :message (format - "The first item in %s is not a valid connection type." - (name record))}))) - -(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) (vector-to-map record) - :else - (throw+ - {: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] - (string/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 (string/join "\t" ordered-fields) "\n")))) - -;;; postgresql.conf - -(defn database-data-directory - "Given a settings map and a database name, return the data directory - for the database." - [settings cluster] - (format "%s/%s/recovery.conf" (-> settings :options :data_directory) cluster)) - -(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 "'" (string/join "," (map name value)) "'") - (or (= value true) (= value false)) - (str value) - :else - (throw+ - {:type :postgres-invalid-parameter - :message (format - (str - "Parameters must be numbers, strings, or vectors of such. " - "Invalid value %s") (pr-str value)) - :value value}))) - -(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- format-start - [[key value]] - (name value)) - ;;; database cluster variants (defn hot-standby-master @@ -551,61 +233,56 @@ Links: (defn settings-for-cluster "Returns the settings for the specified cluster" - [session cluster & {:keys [instance]}] + [cluster {:keys [instance-id] :as options}] (get-in - (parameter/get-target-settings session :postgresql instance) + (get-settings facility options) [:clusters (keyword cluster)])) (defn check-settings "Check that settings are valid" - [session settings cluster-settings cluster & keys] - (let [error-fn (fn [session ^String message] + [settings cluster-settings cluster & keys] + (let [error-fn (fn [^String message] (logging/error (format message cluster settings)) - (assert false) - session) + (assert false)) missing-keys (remove #(get-in cluster-settings %) keys)] - (-> - session - (thread-expr/when-> - (not settings) + (when (not settings) (error-fn "No settings found %s %s")) - (thread-expr/when-> - (not cluster-settings) + (when (not cluster-settings) (error-fn "No cluster settings found %s %s")) - (thread-expr/when-> - (seq missing-keys) - (error-fn (format "Missing keys %s %%s %%s" (vec missing-keys))))))) + (when (seq missing-keys) + (error-fn (format "Missing keys %s %%s %%s" (vec missing-keys)))))) (defn conf-file "Generates a postgresql configuration file" - [session file-keys values-kw formatter & {:keys [instance cluster]}] - (let [settings (parameter/get-target-settings session :postgresql instance) + [file-keys values-kw formatter {:keys [instance-id cluster] :as options}] + (let [settings (get-settings facility options) cluster (or cluster (:default-cluster-name settings)) - cluster-settings (settings-for-cluster - session cluster :instance instance) + cluster-settings (settings-for-cluster cluster options) conf-path (get-in cluster-settings file-keys) - hba-contents (apply str pallet-cfg-preamble - (map formatter (values-kw cluster-settings)))] - (-> - session - (check-settings settings cluster-settings cluster file-keys) - (directory/directory - (stevedore/script @(~lib/dirname ~conf-path)) - :owner (:owner settings "postgres") :mode "0700" :path true) - (remote-file/remote-file - conf-path - :content hba-contents - :literal true - :flag-on-changed postgresql-config-changed-flag - :owner (:owner settings))))) + hba-contents (str pallet-cfg-preamble + (formatter (values-kw cluster-settings)))] + (check-settings settings cluster-settings cluster file-keys) + (directory + (stevedore/script @(~lib/dirname ~conf-path)) + :owner (:owner settings "postgres") :mode "0700" :path true) + (remote-file + conf-path + :content hba-contents + :literal true + :flag-on-changed postgresql-config-changed-flag + :owner (:owner settings)) + (when-let [t (:selunix-file-t settings)] + (exec-checked-script + (str "SELinux chcon " conf-path " type " t) + (lib/selinux-file-type ~conf-path ~t))))) (defn default-cluster-name "Returns the default cluster name" - [session & {:keys [instance]}] - (let [settings (parameter/get-target-settings session :postgresql instance)] + [& {:keys [instance-id] :as options}] + (let [settings (get-settings facility options)] (:default-cluster-name settings))) -;;; Crate functions +;;; # Crate functions (defn settings-map "Build a settings map for postgresql. @@ -631,65 +308,64 @@ Links: `settings`. Options: - - instance Specify the postgres instance to use for the cluster + - instance-id Specify the postgres instance to use for the cluster - variant Specify a variant for the cluster. Current options are :hot-standby-master and :hot-standby-replica For variant :hot-standby-replica, you will need to pass :primary_conninfo in the `settings-map`" - [session cluster-name settings-map & {:keys [instance variant]}] - (let [settings (parameter/get-target-settings session :postgresql instance) + [cluster-name settings-map & {:keys [instance-id variant] :as options}] + (let [settings (get-settings facility options) settings (cluster-settings-with-defaults cluster-name settings-map settings) settings (case variant :hot-standby-master (hot-standby-master settings) :hot-standby-replica (hot-standby-replica settings) settings)] - (parameter/update-target-settings - session :postgresql instance + (logging/debugf "Postgresql cluster %s settings %s" cluster-name settings) + (update-settings + facility instance-id assoc-in [:clusters (keyword cluster-name)] settings))) -(defn postgres-settings +;; (defn base-distribution +;; "Base distribution of the target-node." +;; [] +;; (compute/base-distribution (target-node))) + +(defn settings "Add postgresql settings to the session map." - [session - {:keys [version] - :or {version (version-string (postgres-package-version session))} - :as settings} - & {:keys [instance]}] - (let [settings (assoc settings :version version) - settings (install-strategy session version settings) + [{:keys [instance-id version] + :or {version (version-string (os-map-lookup @kb/postgres-package-version))} + :as settings}] + (let [options (select-keys settings [:instance-id]) + settings (-> settings + (assoc :version version) + (dissoc :instance-id)) + settings (if (:install-strategy settings) + settings + (merge (install-strategy version settings) settings)) settings (merge-settings - (default-settings - session - (session/base-distribution session) - (:layout settings) settings) + (kb/layout-settings + (os-family) (:layout settings (os-family)) version) settings) - old-settings (parameter/get-target-settings - session :postgresql instance nil)] + old-settings (get-settings facility options)] (logging/debugf "Postgresql Settings %s" settings) - (-> - session - (parameter/assoc-target-settings - :postgresql instance - (assoc settings :clusters (:clusters old-settings))) - (thread-expr/when-let-> - [cluster-name (:default-cluster-name settings)] - (cluster-settings cluster-name {} :instance instance))))) + (assoc-settings + facility (assoc settings :clusters (:clusters old-settings)) + options) + (when-let [cluster-name (:default-cluster-name settings)] + (cluster-settings cluster-name {} :instance-id instance-id)))) -(defn install-postgres +;;; ## Install +(defn install "Install postgres." - [session & {:keys [instance]}] - (let [os-family (session/os-family session) - settings (parameter/get-target-settings session :postgresql instance) - packages (:packages settings) - package-source (:package-source settings) - version (:version settings)] - (logging/debugf - "postgresql %s from %s packages [%s]" - version (:name package-source) (string/join ", " packages)) - (install-method session settings))) + [{:keys [instance-id] :as options}] + (logging/debugf "install postgresql") + (crate-install/install facility instance-id)) + +;;; ## Configure (defn hba-conf "Generates a pg_hba.conf file from the arguments. Each record is either a @@ -705,33 +381,27 @@ Links: Options: :cluster The database cluster to use - :instance The postgres instance to use" - [session & {:keys [instance cluster]}] - (conf-file - session [:options :hba_file] :permissions format-hba-record - :instance instance :cluster cluster)) + :instance-id The postgres instance to use" + [{:keys [instance-id cluster] :as options}] + (conf-file [:options :hba_file] :permissions config/hba options)) (defn postgresql-conf "Generates a postgresql.conf file from the arguments. Options: :cluster The database cluster to use - :instance The postgres instance to use" - [session & {:keys [instance cluster]}] - (conf-file - session [:postgresql_file] :options format-parameter - :instance instance :cluster cluster)) + :instance-id The postgres instance to use" + [{:keys [instance-id cluster] :as options}] + (conf-file [:postgresql_file] :options config/conf options)) (defn recovery-conf "Generates a recovery.conf file from the arguments. Options: :cluster The database cluster to use - :instance The postgres instance to use" - [session & {:keys [instance cluster]}] - (conf-file - session [:recovery_file] :recovery format-parameter - :instance instance :cluster cluster)) + :instance-id The postgres instance to use" + [{:keys [instance-id cluster] :as options}] + (conf-file [:recovery_file] :recovery config/conf options)) (defn start-conf "Generates a start.conf file from the arguments. This is debian specific. See @@ -739,11 +409,9 @@ Links: Options: :cluster The database cluster to use - :instance The postgres instance to use" - [session & {:keys [instance cluster]}] - (conf-file - session [:start_file] :start format-start - :instance instance :cluster cluster)) + :instance-id The postgres instance to use" + [{:keys [instance-id cluster] :as options}] + (conf-file [:start_file] :start config/start options)) (defn install-service "Generates a start.conf file from the arguments. This is specific to @@ -752,65 +420,103 @@ Links: Options: :cluster The database cluster to use - :instance The postgres instance to use" - [session & {:keys [instance cluster]}] - (let [settings (parameter/get-target-settings session :postgresql instance)] - (-> - session - (thread-expr/for-> - [cluster (keys (:clusters settings))] - (thread-expr/let-> - [cluster-name (name cluster) - cluster-settings (settings-for-cluster session cluster-name)] - (thread-expr/when-> - (not= cluster-name (:default-cluster-name settings)) - (service/init-script - (:service cluster-settings) - :remote-file (service/init-script-path (:default-service settings))) - (etc-default/write - (str "pgsql/" (:service cluster-settings)) - :PGDATA (-> cluster-settings :options :data_directory) - :PGPORT (-> cluster-settings :options :port)) - (thread-expr/if-> - (= :auto (-> cluster-settings :start :start)) - (service/service (:service cluster-settings) :action :enable) - (service/service (:service cluster-settings) :action :disable)))))))) + :instance-id The postgres instance to use" + [{:keys [instance-id cluster] :as options}] + (let [settings (get-settings facility options)] + (doseq [cluster (keys (:clusters settings))] + (let [cluster-name (name cluster) + cluster-settings (settings-for-cluster cluster-name options)] + (when (not= cluster-name (:default-cluster-name settings)) + ;; TODO + ;; (service/init-script + ;; (:service cluster-settings) + ;; :remote-file (service/init-script-path (:default-service settings))) + (etc-default/write + (str "pgsql/" (:service cluster-settings)) + :PGDATA (-> cluster-settings :options :data_directory) + :PGPORT (-> cluster-settings :options :port)) + ;; TODO + ;; (if (= :auto (-> cluster-settings :start :start)) + ;; (service/service (:service cluster-settings) :action :enable) + ;; (service/service (:service cluster-settings) :action :disable)) + ))))) (defn service-config "Configure the service architecture." - [session & {:keys [instance cluster]}] - (let [settings (parameter/get-target-settings session :postgresql instance)] + [{:keys [instance-id cluster] :as options}] + (let [settings (get-settings facility options)] (if (:has-multicluster-service settings) - (start-conf session :instance instance :cluster cluster) - (install-service session :instance instance :cluster cluster)))) - -(declare service) + (start-conf options) + (install-service options)))) + +(defn- port-config* [settings] + (when-let [t (:selunix-port-t settings)] + (let [p (-> settings :options :port)] + (exec-checked-script + (str "SELinux manage port " p " type " t) + (if (&& (lib/has-command? semanage) + (&& (directory? "/etc/selinux") + (file-exists? "/selinux/enforce"))) + (if (pipe ("semanage" port -l) ("fgrep" ~p)) + ("semanage" port -m -t ~t -p tcp ~p) + ("semanage" port -a -t ~t -p tcp ~p))))))) + +(defn port-config + "Manage the SELinux port type of the configured port" + [{:keys [instance-id cluster] :as options}] + (let [settings (get-settings facility options)] + (if (:has-multicluster-service settings) + (doseq [cluster (keys (:clusters settings)) + :let [cluster-name (name cluster) + cluster-settings (settings-for-cluster + cluster-name options)]] + (port-config* cluster-settings)) + (port-config* settings)))) + +(defn dir-config* + [{:keys [owner selunix-file-t options] :as settings}] + (let [d (:unix_socket_directory options)] + (directory d :owner owner) + (when selunix-file-t + (exec-checked-script + (str "SELinux chcon " d " type " selunix-file-t) + (lib/selinux-file-type ~d ~selunix-file-t))))) + +(defn dir-config + "Ensure required directories exist" + [{:keys [instance-id cluster] :as options}] + (let [settings (get-settings facility options)] + (if (:has-multicluster-service settings) + (doseq [cluster (keys (:clusters settings)) + :let [cluster-name (name cluster) + cluster-settings (settings-for-cluster + cluster-name options)]] + (dir-config* settings)) + (dir-config* settings)))) (defn initdb "Initialise a cluster" - [session & {:keys [instance cluster]}] - (let [settings (parameter/get-target-settings session :postgresql instance) + [{:keys [instance-id cluster] :as options}] + (let [settings (get-settings facility options) cluster (or cluster (:default-cluster-name settings)) initdb-via (:initdb-via settings :initdb) - cluster-settings (settings-for-cluster - session cluster :instance instance) + cluster-settings (settings-for-cluster cluster options) data-dir (-> cluster-settings :options :data_directory)] (case initdb-via - :service (service session :action :initdb) - :initdb (-> - session - (directory/directory - data-dir - :owner (:owner settings "postgres") - :mode "0700" - :path true) - (exec-script/exec-checked-script - "initdb" - (if (not (file-exists? ~(str data-dir "/PG_VERSION"))) - (sudo - -u ~(:owner settings "postgres") - (str ~(or (:bin settings) "") initdb) - -D ~data-dir))))))) + :service (actions/service :action :initdb) + :initdb (do + (directory + data-dir + :owner (:owner settings "postgres") + :mode "0700" + :path true) + (exec-checked-script + "initdb" + (if (not (file-exists? ~(str data-dir "/PG_VERSION"))) + ("sudo" + -u ~(:owner settings "postgres") + (lib/file ~(:bin settings) initdb) + -D ~data-dir))))))) ;;; Scripts @@ -825,52 +531,51 @@ Links: user. Default: postgres :db-name database - the name of the database to connect to :cluster cluster-name - the name of the cluster to connect to - :instance instance-name - the instance (pg install) to use + :instance-id instance-name - the instance (pg install) to use :ignore-result - Ignore any error return value out of psql :title string - A title to be used in script output." - [session & {:keys [as-user instance cluster db-name ignore-result show-stdout - title] - :or {show-stdout true} - :as options}] - (let [settings (parameter/get-target-settings session :postgresql instance) + [& {:keys [as-user instance-id cluster db-name ignore-result show-stdout + title] + :or {show-stdout true} + :as options}] + (let [settings (get-settings facility options) cluster (or cluster (:default-cluster-name settings)) - cluster-settings (settings-for-cluster session cluster) + cluster-settings (settings-for-cluster cluster options) as-user (or as-user (-> settings :owner)) file (str (stevedore/script @TMPDIR:-/tmp) "/" (gensym "postgresql") ".sql")] - (-> session - (apply-map-> - remote-file/remote-file - file - :no-versioning true - :owner as-user - (select-keys options remote-file/content-options)) - (exec-script/exec-checked-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). - ;; Subshell used to isolate any cd - (str "psql script" (if title (str " - " title) "")) - ("(\n" - cd (~lib/user-home ~as-user) "&&" - sudo "-u" ~as-user - ~(if (:has-pg-wrapper settings) - "" - (format - "env PGDATA=%s PGPORT=%s" - (-> cluster-settings :options :data_directory) - (-> cluster-settings :options :port))) - psql - ~(if (:has-pg-wrapper settings) - (format "--cluster %s/%s" (:version settings) cluster) - "") - ~(if db-name (str "-d " db-name) "") - "-f" ~file - ~(if show-stdout "" ">-") - ~(if ignore-result "2>-" "") - ~(if ignore-result "|| true" "") "\n )")) - (remote-file/remote-file file :action :delete)))) + + (apply-map remote-file file + :no-versioning true + :owner as-user + (select-keys options content-options)) + (exec-checked-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). + ;; Subshell used to isolate any cd + (str "psql script" (if title (str " - " title) "")) + ("(\n" + cd (~lib/user-home ~as-user) "&&" + sudo "-u" ~as-user + ~(if (:has-pg-wrapper settings) + "" + (format + "env PGHOST=%s PGDATA=%s PGPORT=%s" + (-> cluster-settings :options :unix_socket_directory) + (-> cluster-settings :options :data_directory) + (-> cluster-settings :options :port))) + psql + ~(if (:has-pg-wrapper settings) + (format "--cluster %s/%s" (:version settings) cluster) + "") + ~(if db-name (str "-d " db-name) "") + "-f" ~file + ~(if show-stdout "" ">-") + ~(if ignore-result "2>-" "") + ~(if ignore-result "|| true" "") "\n )")) + (remote-file file :action :delete))) (defn create-database "Create a database if it does not exist. @@ -882,19 +587,19 @@ Links: Example: (create-database \"my-database\" :db-parameters [:encoding \"'LATIN1'\"])" - [session db-name & rest] - (let [{:keys [db-parameters db] :as options} rest - db-parameters-str (string/join " " (map name db-parameters))] + [db-name & {:keys [db-parameters db] :as options}] + (let [db-parameters-str (string/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 - session - :content (format "CREATE DATABASE %s %s;" db-name db-parameters-str) - :literal true - (conj (vec rest) :ignore-result true)))) + (apply-map + postgresql-script + :content (format "CREATE DATABASE %s %s;" db-name db-parameters-str) + :literal true + :ignore-result true + options))) ;; 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 @@ -933,20 +638,19 @@ END$$;" Example (create-role \"myuser\" :user-parameters [:encrypted :password \"'mypasswd'\"])" - [session username & rest] - (let [{:keys [user-parameters db instance] :as options} rest - settings (parameter/get-target-settings session :postgresql instance) + [username & {:keys [user-parameters db instance-id] :as options}] + (let [settings (get-settings facility options) user-parameters-str (string/join " " (map name user-parameters))] - (apply postgresql-script - session - :content (format - (create-role-pgsql (:version settings)) - username - (if (string/blank? user-parameters-str) - "" - (str "WITH " user-parameters-str))) - :literal true - rest))) + (apply-map + postgresql-script + :content (format + (create-role-pgsql (:version settings)) + username + (if (string/blank? user-parameters-str) + "" + (str "WITH " user-parameters-str))) + :literal true + options))) (defn service @@ -957,35 +661,29 @@ END$$;" Other options are as for `pallet.action.service/service`. The service name is looked up in the request parameters." - [session & {:keys [action if-config-changed if-flag instance] :as options}] - (let [settings (parameter/get-target-settings session :postgresql instance) - service (:service settings) + [& {:keys [action if-config-changed if-flag instance-id] :as options}] + (let [settings (get-settings facility options) + service-name (:service settings) options (if if-config-changed (assoc options :if-flag postgresql-config-changed-flag) options)] - (-> - session - (thread-expr/if-> - (:has-multicluster-service settings) - (thread-expr/apply-map-> service/service service options) - (thread-expr/for-> - [cluster (keys (:clusters settings))] - (thread-expr/let-> - [cluster-name (name cluster) - cluster-settings (settings-for-cluster session cluster-name)] - (thread-expr/if-> - (= :auto (-> cluster-settings :start :start)) - (thread-expr/apply-map-> - service/service (:service cluster-settings) options)))))))) + (if (:has-multicluster-service settings) + (apply-map actions/service service-name options) + (doseq [cluster (keys (:clusters settings))] + (let [cluster-name (name cluster) + cluster-settings (settings-for-cluster cluster-name options)] + (if (= :auto (-> cluster-settings :start :start)) + (apply-map + actions/service (:service cluster-settings) options))))))) (defn controldata-script - [session {:keys [as-user instance cluster] :as options}] - (let [settings (parameter/get-target-settings session :postgresql instance) + [{:keys [as-user instance-id cluster] :as options}] + (let [settings (get-settings facility options) cluster (or cluster (:default-cluster-name settings)) - cluster-settings (settings-for-cluster session cluster) + cluster-settings (settings-for-cluster cluster options) as-user (or as-user (-> settings :owner))] (stevedore/script - (sudo "-u" ~as-user + ("sudo" "-u" ~as-user ~(if-let [bin (:bin settings)] (str bin "/pg_controldata") "pg_controldata") @@ -993,28 +691,39 @@ END$$;" (defn controldata "Execute pg_controldata." - [session & {:keys [as-user instance cluster] :as options}] - (-> session - (exec-script/exec-checked-script - "pg_controldata" - (~controldata-script session options)))) + [session & {:keys [as-user instance-id cluster] :as options}] + (exec-checked-script + "pg_controldata" + (~controldata-script session options))) (defn log-settings "Log postgresql settings" - [session & {:keys [instance level] :or {level :info}}] - (let [settings (parameter/get-target-settings session :postgresql instance)] - (logging/log level (format "Postgresql %s %s" (or instance "") settings)) - session)) - -(defn postgres - [settings] - (server-spec - :phases {:settings (phase-fn - (postgres-settings (settings-map settings))) - :configure (phase-fn - (initdb) - (hba-conf) - (postgresql-conf) - (service-config) - (service - :action :restart :if-config-changed true))})) + [& {:keys [instance-id level] :or {level :info} :as options}] + (let [settings (get-settings facility options)] + (logging/log level + (format "Postgresql %s %s" (or instance-id "") settings)))) + +(defplan configure + [{:keys [instance-id] :as options}] + (initdb options) + (hba-conf options) + (postgresql-conf options) + (service-config options) + (port-config options) + (dir-config options)) + + +(defn server-spec + "Return a postgres server-spec using the specified `settings`." + [settings & {:keys [instance-id] :as options}] + (api/server-spec + :phases {:settings (plan-fn + (pallet.crate.postgres/settings + (settings-map settings))) + :install (plan-fn + (install options)) + :configure (plan-fn + (configure options) + (service + :action :restart :if-config-changed true))} + :default-phases [:install :configure])) diff --git a/src/pallet/crate/postgres/config.clj b/src/pallet/crate/postgres/config.clj new file mode 100644 index 0000000..48fefd1 --- /dev/null +++ b/src/pallet/crate/postgres/config.clj @@ -0,0 +1,161 @@ +(ns pallet.crate.postgres.config + "Postgres configuration" + (:require + [clojure.string :as string :refer [join]])) + +;;; 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- vector-to-map + [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)) + (throw + (ex-info + (format + "The fifth item in %s does not appear to be an IP mask or auth method." + (pr-str record)) + {:type :postgres-invalid-hba-record}))))) + (throw + (ex-info + (format + "The first item in %s is not a valid connection type." + (name record))) + {:type :postgres-invalid-hba-record}))) + +(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) (vector-to-map record) + :else + (throw + (ex-info + (format "The record %s must be a vector or map." (name record)) + {:type :postgres-invalid-hba-record})))) + +(defn- format-auth-options + "Given the auth-options map, returns a string suitable for inserting into the + file." + [auth-options] + (string/join "," (map #(str (first %) "=" (second %)) auth-options))) + +(defn format-hba + [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 (string/join "\t" ordered-fields) "\n")))) + +(defn hba + "Return content for pg_hba.conf given a sequence of hba permission + entries." + [permissions] + (join (map format-hba permissions))) + +;;; postgresql.conf, recovery.conf + +(defn database-data-directory + "Given a settings map and a database name, return the data directory + for the database." + [settings cluster] + (format "%s/%s/recovery.conf" (-> settings :options :data_directory) cluster)) + +(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 "'" (string/join "," (map name value)) "'") + (or (= value true) (= value false)) + (str value) + :else + (throw + (ex-info + (format + (str + "Parameters must be numbers, strings, or vectors of such. " + "Invalid value %s") (pr-str value)) + {:type :postgres-invalid-parameter + :value value})))) + +(defn format-conf + "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 conf + "Return content for postgresql.conf given a sequence of entries." + [entries] + (join (map format-conf entries))) + +;;; start.conf + +(defn format-start + [[key value]] + (name value)) + +(defn start + "Return content for postgresql.conf given a sequence of entries." + [entries] + (join (map format-start entries))) diff --git a/src/pallet/crate/postgres/kb.clj b/src/pallet/crate/postgres/kb.clj new file mode 100644 index 0000000..227c4aa --- /dev/null +++ b/src/pallet/crate/postgres/kb.clj @@ -0,0 +1,273 @@ +(ns pallet.crate.postgres.kb + "Knowledge base for postgres install and configuration" + (:require + [clojure.string :as string :refer [split]] + [pallet.version-dispatch :refer [os-map]] + [pallet.compute :refer [os-hierarchy]] + [pallet.utils :refer [deep-merge]] + [schema.core :as schema :refer [enum maybe validate]])) + +(def Layout + {:bin String + :default-cluster-name String + ;; :default-service String + :has-multicluster-service schema/Bool + :has-pg-wrapper schema/Bool + :initdb-via (enum :initdb :service) + :postgresql_file String + :service String + :share String + :use-port-in-pidfile schema/Bool + :owner String + :wal_directory String + :selunix-port-t (maybe String) + :selunix-file-t (maybe String) + :options {:external_pid_file String + :data_directory String + :hba_file String + :ident_file String + :unix_socket_directory String + schema/Keyword schema/Any}}) + +(defmulti layout-settings + "Determine the layout of packages for the specified os-family or layout." + (fn [os-family layout version] + {:pre [(keyword? layout) + (string? version)]} + layout) + :hierarchy #'os-hierarchy) + +(prefer-method layout-settings :amzn-linux :rh-base) + +(defn base-layout + "Base layout used as a default with common options." + [] + {:service "postgresql" + :owner "postgres" + :has-pg-wrapper false + :has-multicluster-service false + :initdb-via :initdb + :use-port-in-pidfile false + :selunix-port-t nil + :selunix-file-t nil + :options {:external_pid_file "/var/run/postgresql.pid"}}) + + +;;; # System Packages + +;;; Default Postgres package version + +(def postgres-package-version + "Default version for distros." + (atom ; allow for open extension + (os-map + {{:os :linux} [8] + {:os :centos :os-version [6]} [8 4] + {:os :amzn-linux :os-version [[2013]]} [9 2] + {:os :debian :os-version [6]} [8 4] + {:os :debian :os-version [7]} [9 1] + {:os :debian :os-version [8]} [9 3] + {:os :ubuntu :os-version [[12] [13 10]]} [9 1] + {:os :ubuntu :os-version [[14 04]]} [9 3]}))) + + +;;; ## Yum system packages +(defmulti package-names + "Return a sequence of package names for system packages." + (fn [os version components] + os) + :hierarchy #'os-hierarchy) + +(prefer-method package-names :amzn-linux :rh-base) + +(defmethod package-names :rh-base + [os version components] + {:pre [(string? version)]} + (conj + (map + #(format "postgresql-%s" (name %)) + (or components #{:server :libs})) + "postgresql")) + +(defmethod package-names :amzn-linux + [os version components] + {:pre [(string? version)]} + (map + #(format "postgresql%s-%s" (first (split version #"\.")) (name %)) + (or components #{:server :libs}))) + +;;; ## Apt system packages +(defmethod package-names :debian-base + [os version components] + {:pre [(string? version)]} + (conj + (map + #(format "postgresql-%s-%s" version (name %)) + components) + (format "postgresql-%s" version))) + +(prefer-method package-names :amzn-linux :rh-base) + +;;; # Postgres RPM Repository + +;;; See http://yum.postgresql.org/ + +(def ^{:dynamic true} *pgdg-repo-versions* + "Versions available from the postgres RPM repository." + {"9.0" "9.0-5" + "9.1" "9.1-6" + "9.2" "9.2-7" + "9.3" "9.3-1"}) + +(defn base-name + [os-family] + (if (= os-family :fedora) + "fedora" + "redhat")) + +(defn base-pkg-name + [os-family] + (if (= os-family :fedora) + "fedora" + "rhel")) + +(defn distro-name + [os-family] + (name os-family)) + +(def pkg-distro-names + {:rhel "redhat"}) + +(defn pkg-distro-name + [os-family] + (os-family pkg-distro-names (name os-family))) + +(defn pgdg-url + [version os-family os-version arch] + {:pre [(string? version) + (#{"i386" "x86_64"} arch)]} + (format + "http://yum.postgresql.org/%s/%s/%s-%s-%s/pgdg-%s%s-%s.noarch.rpm" + version + (base-name os-family) + (base-pkg-name os-family) os-version arch + (pkg-distro-name os-family) + (string/replace version "." "") + (*pgdg-repo-versions* version))) + +(defn pgdg-packages + "Return package names for Postgres RPM repository packages, for the given + postgres version and sequence of component keywords." + [version components] + (map + #(str "postgresql" (string/replace version "." "") "-" (name %)) + components)) + +;;; # Postgres APT Repository + +;;; See http://wiki.postgresql.org/wiki/Apt + +(def postgres-apt + "Repository for postgres Apt packages." + {:url "http://apt.postgresql.org/pub/repos/apt/" + :key-url "https://www.postgresql.org/media/keys/ACCC4CF8.asc"}) + +(defn postgres-apt-packages + "Return a sequence of package names for the given version." + [version] + {:pre [(string? version)]} + [(str "postgresql-" version)]) + + +(defmethod layout-settings :debian-base + [os-family layout version] + {:post [(validate Layout %)]} + (deep-merge + (base-layout) + {:default-cluster-name "main" + :bin (format "/usr/lib/postgresql/%s/bin/" version) + :share (format "/usr/lib/postgresql/%s/share/" version) + :wal_directory (format "/var/lib/postgresql/%s/%%s/archive" version) + :postgresql_file (format + "/etc/postgresql/%s/%%s/postgresql.conf" version) + :has-pg-wrapper true + :has-multicluster-service true + :options + {:data_directory (format "/var/lib/postgresql/%s/%%s" version) + :hba_file (format "/etc/postgresql/%s/%%s/pg_hba.conf" version) + :ident_file (format "/etc/postgresql/%s/%%s/pg_ident.conf" version) + :external_pid_file (format "/var/run/postgresql/%s-%%s.pid" version) + :unix_socket_directory "/var/run/postgresql"}})) + +(defmethod layout-settings :rh-base + [os-family layout version] + {:post [(validate Layout %)]} + (let [major (first (split version #"\."))] + (deep-merge + (base-layout) + {:bin "/usr/bin" + :default-cluster-name "data" + :share "/usr/share/pgsql" + :wal_directory "/var/lib/pgsql/archive" + :postgresql_file "/var/lib/pgsql/data/postgresql.conf" + :selunix-port-t "postgresql_port_t" + :selunix-file-t "postgresql_db_t" + :options + {:data_directory "/var/lib/pgsql/data" + :hba_file "/var/lib/pgsql/data/pg_hba.conf" + :ident_file "/var/lib/pgsql/data/pg_ident.conf" + :external_pid_file "/var/run/postmaster.pid" + :unix_socket_directory "/var/run/postgresql"}}))) + +(defmethod layout-settings :amzn-linux + [os-family layout version] + {:post [(validate Layout %)]} + (let [major (first (split version #"\.")) + data (format "/var/lib/pgsql%s/data/" major)] + (deep-merge + (base-layout) + {:bin "/usr/bin" + :default-cluster-name "data" + :share "/usr/share/pgsql" + :wal_directory (format "%s/%%s/archive" data) + :postgresql_file (format "%s/%%s/postgresql.conf" data) + :options + {:data_directory data + :hba_file (format "%s/pg_hba.conf" data) + :ident_file (format "%s/pg_ident.conf" data) + :external_pid_file (format "/var/run/postmaster-%s.pid" version) + :unix_socket_directory "/var/run/postgresql"}}))) + +(defmethod layout-settings :pgdg + [os-family package-source version] + {:post [(validate Layout %)]} + (deep-merge + (base-layout) + (layout-settings os-family :rh-base version) + {:bin (format "/usr/pgsql-%s/bin/" version) + :share (format "/usr/pgsql-%s/share/" version) + :default-cluster-name "data" + :service (str "postgresql-" version "-%s") + :default-service (str "postgresql-" version) + :use-port-in-pidfile true + :wal_directory (format "/var/lib/pgsql/%s/%%s/archive" version) + :postgresql_file (format "/var/lib/pgsql/%s/%%s/postgresql.conf" version) + :options + {:data_directory (format "/var/lib/pgsql/%s/%%s" version) + :hba_file (format "/var/lib/pgsql/%s/%%s/pg_hba.conf" version) + :ident_file (format "/var/lib/pgsql/%s/%%s/pg_ident.conf" version)}})) + +(defmethod layout-settings :arch + [os-family package-source version] + {:post [(validate Layout %)]} + (deep-merge + (base-layout) + {:components [] + :default-cluster-name "data" + :initdb-via :initdb + :wal_directory "/var/lib/postgres/%%s/archive/" + :postgresql_file "/var/lib/postgres/%%s/postgresql.conf" + :options + {:data_directory "/var/lib/postgres/%%s/" + :hba_file "/var/lib/postgres/%%s/pg_hba.conf" + :ident_file "/var/lib/postgres/%%s/pg_ident.conf"}})) diff --git a/test/pallet/crate/postgres/config_test.clj b/test/pallet/crate/postgres/config_test.clj new file mode 100644 index 0000000..dd01d19 --- /dev/null +++ b/test/pallet/crate/postgres/config_test.clj @@ -0,0 +1,10 @@ +(ns pallet.crate.postgres.config-test + (:require + [clojure.test :refer :all] + [pallet.crate.postgres.config :refer [conf hba]])) + +(deftest hba-test + (is (= "" (hba [])))) + +(deftest conf-test + (is (= "" (conf [])))) diff --git a/test/pallet/crate/postgres/kb_test.clj b/test/pallet/crate/postgres/kb_test.clj new file mode 100644 index 0000000..5dc21da --- /dev/null +++ b/test/pallet/crate/postgres/kb_test.clj @@ -0,0 +1,21 @@ +(ns pallet.crate.postgres.kb-test + (:require + [clojure.test :refer :all] + [pallet.crate.postgres.kb :as kb])) + +;; This is brittle +(deftest pgdg-url-test + (is (= "http://yum.postgresql.org/9.3/fedora/fedora-19-x86_64/pgdg-fedora93-9.3-1.noarch.rpm" + (kb/pgdg-url "9.3" :fedora "19" "x86_64"))) + (is (= "http://yum.postgresql.org/9.3/fedora/fedora-20-i386/pgdg-fedora93-9.3-1.noarch.rpm" + (kb/pgdg-url "9.3" :fedora "20" "i386"))) + (is (= "http://yum.postgresql.org/9.3/redhat/rhel-6-i386/pgdg-redhat93-9.3-1.noarch.rpm" + (kb/pgdg-url "9.3" :rhel "6" "i386"))) + (is (= "http://yum.postgresql.org/9.3/redhat/rhel-6-x86_64/pgdg-centos93-9.3-1.noarch.rpm" + (kb/pgdg-url "9.3" :centos "6" "x86_64")))) + +(deftest layout-settings-test + (is + (-> + (kb/layout-settings :ubuntu :debian-base "9.1") + :options :data_directory))) diff --git a/test/pallet/crate/postgres/support_test.clj b/test/pallet/crate/postgres/support_test.clj new file mode 100644 index 0000000..acc26ac --- /dev/null +++ b/test/pallet/crate/postgres/support_test.clj @@ -0,0 +1,102 @@ +(ns pallet.crate.postgres.support-test + (:require + [clojure.test :refer :all] + [pallet.actions + :refer [exec-checked-script package package-manager minimal-packages]] + [pallet.api :refer [lift plan-fn group-spec]] + [pallet.core.api :refer [phase-errors]] + [pallet.crate.automated-admin-user :as automated-admin-user] + [pallet.crate.network-service :refer [wait-for-port-listen]] + [pallet.crate.postgres :as postgres] + [pallet.crates.test-nodes :as test-nodes] + [pallet.repl :refer [explain-session]] + [pallet.test-env + :refer [*compute-service* *node-spec-meta* + with-group-spec test-env unique-name]] + [pallet.test-env.project :as project])) + +(test-env test-nodes/node-specs project/project) + +(deftest ^:support port-listen-test + (let [spec (group-spec (unique-name) + :node-spec (:node-spec *node-spec-meta*) + :extends [automated-admin-user/with-automated-admin-user + (postgres/server-spec + (postgres/settings-map + {:options {:listen_addresses "*"} + :permissions + [{:connection-type "host" + :database "all" + :user "all" + :address "10.0.2.2/24" + :auth-method "md5"} + {:connection-type "host" + :database "all" + :user "all" + :address "192.168.56.1/24" + :auth-method "md5"}]}))] + :phases {:settings (plan-fn + ;; (postgres/cluster-settings + ;; "db1" {:options {:port 5432}}) + ) + :init (plan-fn + (postgres/create-database "db") + (postgres/create-role + "u3" + :user-parameters [:login :encrypted + :password ""'mypasswd'""])) + :test (plan-fn + (wait-for-port-listen 5432))})] + (with-group-spec spec + (let [session (lift spec + :phase [:install :configure :init :test] + :compute *compute-service*)] + (testing "configure postgres" + (is session) + (is (not (phase-errors session))) + (when (phase-errors session) + (explain-session session))))))) + +(deftest postgres + (let [spec (group-spec (unique-name) + :phases + {:bootstrap (plan-fn + (minimal-packages) + (package-manager :update) + (automated-admin-user/automated-admin-user)) + :settings (plan-fn + (postgres/settings (postgres/settings-map {})) + (postgres/cluster-settings "db1" {:options {:port 5433}})) + :configure (plan-fn (postgres/install)) + :verify (plan-fn + (postgres/log-settings) + (postgres/initdb) + (postgres/initdb :cluster "db1") + (postgres/hba-conf) + (postgres/hba-conf :cluster "db1") + (postgres/postgresql-conf) + (postgres/postgresql-conf :cluster "db1") + (postgres/service-config) + (postgres/service :action :restart :if-config-changed false) + (postgres/create-database "db") + (postgres/postgresql-script + :content "create temporary table table1 ();" + :show-stdout true) + (postgres/create-role "user1") + (postgres/create-database "db" :cluster "db1") + (postgres/create-role "user1" :cluster "db1") + (postgres/postgresql-script + :content "create temporary table table2 ();" + :show-stdout true :cluster "db1") + (wait-for-port-listen 5432) + (wait-for-port-listen 5433))} + :count 1 + :node-spec (:node-spec *node-spec-meta*))] + (let [session (lift spec + :phase [:settings :install :configure :init :verify] + :compute *compute-service*)] + (testing "configure postgres" + (is session) + (is (not (phase-errors session))) + (when (phase-errors session) + (explain-session session)))))) diff --git a/test/pallet/crate/postgres_test.clj b/test/pallet/crate/postgres_test.clj index f996016..f7d8d23 100644 --- a/test/pallet/crate/postgres_test.clj +++ b/test/pallet/crate/postgres_test.clj @@ -1,14 +1,15 @@ (ns pallet.crate.postgres-test (:require - [pallet.action.exec-script :as exec-script] - [pallet.action.package :as package] - [pallet.build-actions :as build-actions] - [pallet.core :as core] + [pallet.actions + :refer [exec-checked-script package package-manager minimal-packages]] + [pallet.build-actions :refer [build-actions]] + [pallet.api :refer [lift node-spec plan-fn server-spec] :as api] [pallet.crate.automated-admin-user :as automated-admin-user] - [pallet.crate.network-service :as network-service] + [pallet.crate.network-service :refer [wait-for-port-listen]] [pallet.crate.postgres :as postgres] [pallet.live-test :as live-test] - [pallet.phase :as phase] + [pallet.script :refer [with-script-context]] + [pallet.stevedore :refer [with-script-language]] [pallet.test-utils :as test-utils] [clojure.tools.logging :as logging]) (:use clojure.test)) @@ -28,113 +29,46 @@ :recovery {:bb 2} :start {:start :disable}})))) -(deftest default-settings-test - (is - (-> - (pallet.stevedore/with-script-language - :pallet.stevedore.bash/bash - (pallet.script/with-script-context - [:ubuntu :aptitude] - (postgres/default-settings - {:server {:image {:os-family :ubuntu} :node-id :id}} - :debian :debian-base (postgres/settings-map {})))) - :options :data_directory))) (deftest settings-test - (let [settings (pallet.stevedore/with-script-language - :pallet.stevedore.bash/bash - (pallet.script/with-script-context - [:ubuntu :aptitude] - (pallet.crate.postgres/postgres-settings - {:server {:image {:os-family :ubuntu} :node-id :id}} - (pallet.crate.postgres/settings-map - {:layout :debian-base}))))] - (is - (-> - settings - :parameters :host :id :postgresql :default :options :data_directory)))) + (build-actions {} + (let [settings (postgres/settings + (postgres/settings-map + {:layout :debian-base}))] + (is + (get-in + settings + [:plan-state :host :id postgres/facility nil :options :data_directory]))))) (deftest postgres-test - (is ; just check for compile errors for now - (build-actions/build-actions - {} - (postgres/postgres-settings (postgres/settings-map {:version "8.0"})) - (postgres/install-postgres) - (postgres/postgres-settings (postgres/settings-map {:version "9.0"})) - (postgres/cluster-settings "db1" {}) - (postgres/install-postgres) - (postgres/hba-conf) - (postgres/postgresql-script :content "some script") - (postgres/create-database "db") - (postgres/create-role "user")))) + (is ; just check for compile errors for now + (build-actions {} + (postgres/settings (postgres/settings-map {:version "8.0"})) + (postgres/install {}) + (postgres/settings (postgres/settings-map {:version "9.0"})) + (postgres/cluster-settings "db1" {}) + (postgres/install {}) + (postgres/hba-conf {}) + (postgres/postgresql-script :content "some script") + (postgres/create-database "db") + (postgres/create-role "user")))) (deftest cluster-settings-test (let [settings - (second (build-actions/build-actions - {} - (postgres/postgres-settings - (postgres/settings-map - {:version "9.0" - :wal_directory "/var/lib/postgres/%s/archive/"})) - (postgres/cluster-settings "db1" {}) - (postgres/cluster-settings "db2" {}) - (postgres/postgres-settings - (postgres/settings-map {:version "9.0"})))) - pg-settings (-> settings :parameters :host :id :postgresql :default)] + (second (build-actions {} + (postgres/settings + (postgres/settings-map + {:version "9.0" + :wal_directory "/var/lib/postgres/%s/archive/"})) + (postgres/cluster-settings "db1" {}) + (postgres/cluster-settings "db2" {}) + (postgres/settings + (postgres/settings-map {:version "9.0"})))) + pg-settings (get-in settings + [:plan-state :host :id postgres/facility nil])] (is (-> pg-settings :clusters :db1)) (is (-> pg-settings :clusters :db2)) (is (re-find #"db1/archive" (-> pg-settings :clusters :db1 :wal_directory))) (is (re-find #"db2/archive" (-> pg-settings :clusters :db2 :wal_directory))))) - -(def pgsql-9-unsupported - [{:os-family :debian :os-version-matches "5.0.7"} - {:os-family :debian :os-version-matches "5.0"}]) - -(deftest live-test - (live-test/test-for - [image (live-test/exclude-images (live-test/images) pgsql-9-unsupported)] - (logging/tracef "postgres live test: image %s" (pr-str image)) - (live-test/test-nodes - [compute node-map node-types] - {:pgtest - (-> - (core/server-spec - :phases - {:bootstrap (phase/phase-fn - (package/minimal-packages) - (package/package-manager :update) - (automated-admin-user/automated-admin-user)) - :settings (phase/phase-fn - (postgres/postgres-settings (postgres/settings-map {})) - (postgres/cluster-settings "db1" {:options {:port 5433}})) - :configure (phase/phase-fn - (postgres/install-postgres)) - :verify (phase/phase-fn - (postgres/log-settings) - (postgres/initdb) - (postgres/initdb :cluster "db1") - (postgres/hba-conf) - (postgres/hba-conf :cluster "db1") - (postgres/postgresql-conf) - (postgres/postgresql-conf :cluster "db1") - (postgres/service-config) - (postgres/service :action :restart :if-config-changed false) - (postgres/create-database "db") - (postgres/postgresql-script - :content "create temporary table table1 ();" - :show-stdout true) - (postgres/create-role "user1") - (postgres/create-database "db" :cluster "db1") - (postgres/create-role "user1" :cluster "db1") - (postgres/postgresql-script - :content "create temporary table table2 ();" - :show-stdout true :cluster "db1") - (pallet.crate.network-service/wait-for-port-listen 5432) - (pallet.crate.network-service/wait-for-port-listen 5433))} - :count 1 - :node-spec (core/node-spec :image image)))} - (is - (core/lift - (val (first node-types)) :phase [:settings :verify] :compute compute)))))