commit 9e1b438ce7afc2daa4e85bffd97aea2df96e96c8 Author: vic Date: Mon Oct 22 16:41:23 2012 +0400 Initial commit diff --git a/deployment/puppet/postgresql/.gitignore b/deployment/puppet/postgresql/.gitignore new file mode 100644 index 0000000000..8e3efd3b9b --- /dev/null +++ b/deployment/puppet/postgresql/.gitignore @@ -0,0 +1,6 @@ +*.swp +pkg/ +.DS_Store +metadata.json +coverage/ +.project diff --git a/deployment/puppet/postgresql/CHANGELOG.md b/deployment/puppet/postgresql/CHANGELOG.md new file mode 100644 index 0000000000..486d72dd4d --- /dev/null +++ b/deployment/puppet/postgresql/CHANGELOG.md @@ -0,0 +1,9 @@ +2012-09-17 - Version 0.3.0 released + +2012-09-14 - Chris Price + * Add a type for validating a postgres connection (ce4a049) + +2012-08-25 - Jari Bakken + * Remove trailing commas. (e6af5e5) + +2012-08-16 - Version 0.2.0 released diff --git a/deployment/puppet/postgresql/LICENSE b/deployment/puppet/postgresql/LICENSE new file mode 100644 index 0000000000..d645695673 --- /dev/null +++ b/deployment/puppet/postgresql/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/deployment/puppet/postgresql/Modulefile b/deployment/puppet/postgresql/Modulefile new file mode 100644 index 0000000000..7cc77f9c0b --- /dev/null +++ b/deployment/puppet/postgresql/Modulefile @@ -0,0 +1,11 @@ +name 'inkling-postgresql' +version '0.3.0' + +author 'Inkling' +license 'Apache' +project_page 'https://github.com/inkling/puppet-postgresql' +source 'git://github.com/inkling/puppet-postgresql.git' +summary 'PostgreSQL defined resource types' +description 'PostgreSQL defined resource types' +dependency 'puppetlabs/stdlib', '>=2.4.0' +dependency 'puppetlabs/firewall', '>=0.0.4' diff --git a/deployment/puppet/postgresql/README.md b/deployment/puppet/postgresql/README.md new file mode 100644 index 0000000000..c8493f6d2c --- /dev/null +++ b/deployment/puppet/postgresql/README.md @@ -0,0 +1,82 @@ +Puppet module for PostgreSQL resources +====================================== + +This module provides the following defined resource types for managing postgres: + + * `postgresql::initdb` + * `postgresql::db` + * `postgresql::role` + * `postgresql::user` (just for clarity; users are roles in postgres) + * `postgresql::grant` + +And the fallback, analogous to exec resources, only for SQL statements: + + * `postgresql::psql` + +Basic usage +----------- + + postgresql::user{'marmot': + password => 'foo', + } + + postgresql::grant{'grant select to marmot': + grantee => 'marmot', + on_object => 'my_table', + perm => 'select', + require => Postgresql::User['marmot'], + } + +etc, etc. + + +Automated testing +----------------- + +Install and setup an [RVM](http://beginrescueend.com/) with +[vagrant](http://vagrantup.com/), +[sahara](https://github.com/jedi4ever/sahara), and +[rspec](http://rspec.info/) + + $ curl -L get.rvm.io | bash -s stable + $ rvm install 1.9.3 + $ rvm use --create 1.9.3@puppet-postgresql + $ gem install vagrant sahara rspec + +Run the tests like so: + + $ (cd spec; vagrant up) + $ rspec -f -d -c + +The test suite will snapshot the VM and rollback between each test. + +Next, take a look at the manifests used for the automated tests. + + spec/ + manifests/ + test_*.pp + + +Contributors +------------ + + * Andrew Moon + * [Kenn Knowles](https://github.com/kennknowles) ([@kennknowles](https://twitter.com/KennKnowles)) + + +Copyright and License +--------------------- + +Copyright 2012 Inkling Systems, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/deployment/puppet/postgresql/lib/facter/postgres_default_version.rb b/deployment/puppet/postgresql/lib/facter/postgres_default_version.rb new file mode 100644 index 0000000000..44ea35e538 --- /dev/null +++ b/deployment/puppet/postgresql/lib/facter/postgres_default_version.rb @@ -0,0 +1,30 @@ +def get_debian_postgres_version + depends = Facter::Util::Resolution.exec('apt-cache show postgresql |grep "^Depends" |head -n 1') + if match = /^Depends: postgresql-(.*)$/.match(depends) + match[1] + else + nil + end +end + +def get_redhat_postgres_version + version = Facter::Util::Resolution.exec('yum info postgresql-server |grep "^Version"') + if match = /^Version\s*:\s*(\d+\.\d+).*$/.match(version) + match[1] + else + nil + end +end + +Facter.add("postgres_default_version") do + setcode do + case Facter.value('osfamily') + when 'RedHat' + get_redhat_postgres_version() + when 'Debian' + get_debian_postgres_version() + else + nil + end + end +end \ No newline at end of file diff --git a/deployment/puppet/postgresql/lib/puppet/parser/functions/postgresql_password.rb b/deployment/puppet/postgresql/lib/puppet/parser/functions/postgresql_password.rb new file mode 100644 index 0000000000..0689e0e5b0 --- /dev/null +++ b/deployment/puppet/postgresql/lib/puppet/parser/functions/postgresql_password.rb @@ -0,0 +1,18 @@ +# hash a string as mysql's "PASSWORD()" function would do it +require 'digest/md5' + +module Puppet::Parser::Functions + newfunction(:postgresql_password, :type => :rvalue, :doc => <<-EOS + Returns the postgresql password hash from the clear text username / password. + EOS + ) do |args| + + raise(Puppet::ParseError, "postgresql_password(): Wrong number of arguments " + + "given (#{args.size} for 2)") if args.size != 2 + + username = args[0] + password = args[1] + + 'md5' + Digest::MD5.hexdigest(password + username) + end +end diff --git a/deployment/puppet/postgresql/manifests/config.pp b/deployment/puppet/postgresql/manifests/config.pp new file mode 100644 index 0000000000..e0f828f782 --- /dev/null +++ b/deployment/puppet/postgresql/manifests/config.pp @@ -0,0 +1,64 @@ +# Class: postgresql::config +# +# Parameters: +# +# [*postgres_password*] - postgres db user password. +# [*ip_mask_deny_postgres_user*] - ip mask for denying remote access for postgres user; defaults to '0.0.0.0/0', +# meaning that all TCP access for postgres user is denied. +# [*ip_mask_allow_all_users*] - ip mask for allowing remote access for other users (besides postgres); +# defaults to '127.0.0.1/32', meaning only allow connections from localhost +# [*listen_addresses*] - what IP address(es) to listen on; comma-separated list of addresses; defaults to +# 'localhost', '*' = all +# [*pg_hba_conf_path*] - path to pg_hba.conf file +# [*postgresql_conf_path*] - path to postgresql.conf file +# [*manage_redhat_firewall*] - boolean indicating whether or not the module should open a port in the firewall on +# redhat-based systems; this parameter is likely to change in future versions. Possible +# changes include support for non-RedHat systems and finer-grained control over the +# firewall rule (currently, it simply opens up the postgres port to all TCP connections). +# +# +# Actions: +# +# Requires: +# +# Usage: +# +# class { 'postgresql::config': +# postgres_password => 'postgres', +# ip_mask_allow_all_users => '0.0.0.0/0', +# } +# +class postgresql::config( + $postgres_password = undef, + $ip_mask_deny_postgres_user = $postgresql::params::ip_mask_postgres_user, + $ip_mask_allow_all_users = $postgresql::params::ip_mask_all_users, + $listen_addresses = $postgresql::params::listen_addresses, + $pg_hba_conf_path = $postgresql::params::pg_hba_conf_path, + $postgresql_conf_path = $postgresql::params::postgresql_conf_path, + $manage_redhat_firewall = $postgresql::params::manage_redhat_firewall +) inherits postgresql::params { + + # Basically, all this class needs to handle is passing parameters on + # to the "beforeservice" and "afterservice" classes, and ensure + # the proper ordering. + + class { "postgresql::config::beforeservice": + ip_mask_deny_postgres_user => $ip_mask_deny_postgres_user, + ip_mask_allow_all_users => $ip_mask_allow_all_users, + listen_addresses => $listen_addresses, + pg_hba_conf_path => $pg_hba_conf_path, + postgresql_conf_path => $postgresql_conf_path, + manage_redhat_firewall => $manage_redhat_firewall, + } + + class { "postgresql::config::afterservice": + postgres_password => $postgres_password, + } + + Class['postgresql::config'] -> + Class['postgresql::config::beforeservice'] -> + Service['postgresqld'] -> + Class['postgresql::config::afterservice'] + + +} diff --git a/deployment/puppet/postgresql/manifests/config/afterservice.pp b/deployment/puppet/postgresql/manifests/config/afterservice.pp new file mode 100644 index 0000000000..ab95b9d8a3 --- /dev/null +++ b/deployment/puppet/postgresql/manifests/config/afterservice.pp @@ -0,0 +1,44 @@ +# Class: postgresql::config::afterservice +# +# Parameters: +# +# [*postgres_password*] - postgres db user password. +# +# Actions: +# +# Requires: +# +# Usage: +# This class is not intended to be used directly; it is +# managed by postgresl::config. It contains resources +# that should be handled *after* the postgres service +# has been started up. +# +# class { 'postgresql::config::afterservice': +# postgres_password => 'postgres' +# } +# +class postgresql::config::afterservice( + $postgres_password = undef +) inherits postgresql::params { + if ($postgres_password != undef) { + # NOTE: this password-setting logic relies on the pg_hba.conf being configured + # to allow the postgres system user to connect via psql without specifying + # a password ('ident', 'peer', or 'trust' security). This is the default + # for pg_hba.conf. + exec { 'set_postgres_postgrespw': + # This command works w/no password because we run it as postgres system user + command => "psql -c \"ALTER ROLE postgres PASSWORD '$postgres_password'\"", + user => $postgresql::params::user, + group => $postgresql::params::group, + logoutput => true, + cwd => '/tmp', + # With this command we're passing -h to force TCP authentication, which does require + # a password. We specify the password via the PGPASSWORD environment variable. If + # the password is correct (current), this command will exit with an exit code of 0, + # which will prevent the main command from running. + unless => "env PGPASSWORD=\"$postgres_password\" psql -h localhost -c 'select 1' > /dev/null", + path => '/usr/bin:/usr/local/bin', + } + } +} diff --git a/deployment/puppet/postgresql/manifests/config/beforeservice.pp b/deployment/puppet/postgresql/manifests/config/beforeservice.pp new file mode 100644 index 0000000000..647ca032b5 --- /dev/null +++ b/deployment/puppet/postgresql/manifests/config/beforeservice.pp @@ -0,0 +1,85 @@ +# Class: postgresql::config::beforeservice +# +# Parameters: +# +# [*ip_mask_deny_postgres_user*] - ip mask for denying remote access for postgres user; defaults to '0.0.0.0/0', +# meaning that all TCP access for postgres user is denied. +# [*ip_mask_allow_all_users*] - ip mask for allowing remote access for other users (besides postgres); +# defaults to '127.0.0.1/32', meaning only allow connections from localhost +# [*listen_addresses*] - what IP address(es) to listen on; comma-separated list of addresses; defaults to +# 'localhost', '*' = all +# [*pg_hba_conf_path*] - path to pg_hba.conf file +# [*postgresql_conf_path*] - path to postgresql.conf file +# [*manage_redhat_firewall*] - boolean indicating whether or not the module should open a port in the firewall on +# redhat-based systems; this parameter is likely to change in future versions. Possible +# changes include support for non-RedHat systems and finer-grained control over the +# firewall rule (currently, it simply opens up the postgres port to all TCP connections). +# +# Actions: +# +# Requires: +# +# Usage: +# This class is not intended to be used directly; it is +# managed by postgresl::config. It contains resources +# that should be handled *before* the postgres service +# has been started up. +# +# class { 'postgresql::config::before_service': +# ip_mask_allow_all_users => '0.0.0.0/0', +# } +# +class postgresql::config::beforeservice( + $ip_mask_deny_postgres_user = $postgresql::params::ip_mask_deny_postgres_user, + $ip_mask_allow_all_users = $postgresql::params::ip_mask_allow_all_users, + $listen_addresses = $postgresql::params::listen_addresses, + $pg_hba_conf_path = $postgresql::params::pg_hba_conf_path, + $postgresql_conf_path = $postgresql::params::postgresql_conf_path, + $manage_redhat_firewall = $postgresql::params::manage_redhat_firewall +) inherits postgresql::params { + + File { + owner => $postgresql::params::user, + group => $postgresql::params::group, + } + + # We use a templated version of pg_hba.conf. Our main needs are to + # make sure that md5 authentication can be made available for + # remote hosts. + file { 'pg_hba.conf': + ensure => file, + path => $pg_hba_conf_path, + content => template("postgresql/pg_hba.conf.erb"), + notify => Service['postgresqld'], + } + + # We must set a "listen_addresses" line in the postgresql.conf if we + # want to allow any connections from remote hosts. + file_line { 'postgresql.conf': + path => $postgresql_conf_path, + match => '^listen_addresses\s*=.*$', + line => "listen_addresses = '${listen_addresses}'", + notify => Service['postgresqld'], + } + + # TODO: is this a reasonable place for this firewall stuff? + # TODO: figure out a way to make this not platform-specific; debian and ubuntu have + # an out-of-the-box firewall configuration that seems trickier to manage + # TODO: get rid of hard-coded port + if ($manage_redhat_firewall and $firewall_supported) { + exec { "persist-firewall": + command => $persist_firewall_command, + refreshonly => true, + } + + Firewall { + notify => Exec["persist-firewall"] + } + + firewall { '5432 accept - postgres': + port => '5432', + proto => 'tcp', + action => 'accept', + } + } +} diff --git a/deployment/puppet/postgresql/manifests/database.pp b/deployment/puppet/postgresql/manifests/database.pp new file mode 100644 index 0000000000..e254736e8b --- /dev/null +++ b/deployment/puppet/postgresql/manifests/database.pp @@ -0,0 +1,49 @@ +# puppet-postgresql +# For all details and documentation: +# http://github.com/inkling/puppet-postgresql +# +# Copyright 2012- Inkling Systems, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# TODO: in order to match up more closely with the mysql module, this probably +# needs to be moved over to ruby, and add support for ensurable. + +define postgresql::database( + $dbname = $title, + $charset = 'UTF8') +{ + require postgresql::params + + if ($::postgres_default_version != "8.1") { + $locale_option = "--locale=C" + } + + $createdb_command = "${postgresql::params::createdb_path} --template=template0 --encoding '$charset' $locale_option '$dbname'" + + exec { $createdb_command : + unless => "${postgresql::params::psql_path} --command=\"SELECT datname FROM pg_database WHERE datname=\'$dbname\' \" --pset=tuples_only | grep -q $dbname", + user => 'postgres', + } + + # This will prevent users from connecting to the database unless they've been + # granted privileges. + postgresql::psql {"REVOKE CONNECT ON DATABASE $dbname FROM public": + db => 'postgres', + user => 'postgres', + unless => 'SELECT 1 where 1 = 0', + refreshonly => true, + subscribe => Exec[$createdb_command], + } + +} diff --git a/deployment/puppet/postgresql/manifests/database_grant.pp b/deployment/puppet/postgresql/manifests/database_grant.pp new file mode 100644 index 0000000000..9dbd02ba40 --- /dev/null +++ b/deployment/puppet/postgresql/manifests/database_grant.pp @@ -0,0 +1,58 @@ +# puppet-postgresql +# For all details and documentation: +# http://github.com/inkling/puppet-postgresql +# +# Copyright 2012- Inkling Systems, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# TODO: in mysql module, the grant resource name might look like this: 'user@host/dbname'; +# I think that the API for the resource type should split these up, because it's +# easier / safer to recombine them for mysql than it is to parse them for other +# databases. Also, in the mysql module, the hostname portion of that string +# affects the user's ability to connect from remote hosts. In postgres this is +# managed via pg_hba.conf; not sure if we want to try to reconcile that difference +# in the modules or not. + +define postgresql::database_grant( + # TODO: mysql supports an array of privileges here. We should do that if we + # port this to ruby. + $privilege, + $db, + $role, + $psql_db = 'postgres', + $psql_user='postgres' +) { + + # TODO: FIXME: only works on databases, due to using has_database_privilege + + # TODO: this is a terrible hack; if they pass "ALL" as the desired privilege, + # we need a way to test for it--and has_database_privilege does not recognize + # 'ALL' as a valid privilege name. So we probably need to hard-code a mapping + # between 'ALL' and the list of actual privileges that it entails, and loop + # over them to check them. That sort of thing will probably need to wait until + # we port this over to ruby, so, for now, we're just going to assume that if + # they have "CREATE" privileges on a database, then they have "ALL". (I told + # you that it was terrible!) + $unless_privilege = $privilege ? { + 'ALL' => 'CREATE', + default => $privilege, + } + + postgresql::psql {"GRANT $privilege ON database $db TO $role": + db => $psql_db, + user => $psql_user, + unless => "SELECT 1 WHERE has_database_privilege('$role', '$db', '$unless_privilege')", + } +} + diff --git a/deployment/puppet/postgresql/manifests/database_user.pp b/deployment/puppet/postgresql/manifests/database_user.pp new file mode 100644 index 0000000000..abac698cb6 --- /dev/null +++ b/deployment/puppet/postgresql/manifests/database_user.pp @@ -0,0 +1,56 @@ +# puppet-postgresql +# For all details and documentation: +# http://github.com/inkling/puppet-postgresql +# +# Copyright 2012- Inkling Systems, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Define: postgresql::database_user +# +# This type creates a postgres database user. +# +# Parameters: +# [*user*] - username to create. +# [*password_hash*] - user's password; this may be clear text, or an md5 hash as returned by the +# "postgresql_password" function in this module. +# +# Actions: +# +# Requires: +# +# +# Sample Usage: +# +# postgresql::database_user { 'frank': +# password_hash => postgresql_password('frank', 'password'), +# } +# + +define postgresql::database_user( + $user=$title, + $password_hash, + $db = 'postgres', + $createdb=false, + $superuser=false, + $createrole=false +) { + postgresql::role {$user: + db => $db, + password_hash => $password_hash, + login => true, + createdb => $createdb, + superuser => $superuser, + createrole => $createrole, + } +} diff --git a/deployment/puppet/postgresql/manifests/db.pp b/deployment/puppet/postgresql/manifests/db.pp new file mode 100644 index 0000000000..cbf0097c14 --- /dev/null +++ b/deployment/puppet/postgresql/manifests/db.pp @@ -0,0 +1,65 @@ +# Define: postgresql::db +# +# This module creates database instances, a user, and grants that user +# privileges to the database. +# +# Since it requires class postgresql::server, we assume to run all commands as the +# postgresql user against the local postgresql server. +# +# TODO: support an array of privileges for "grant"; currently only supports a single +# privilege, which is pretty useless unless that privilege is "ALL" +# +# Parameters: +# [*title*] - postgresql database name. +# [*user*] - username to create and grant access. +# [*password*] - user's password. may be md5-encoded, in the format returned by the "postgresql_password" +# function in this module +# [*charset*] - database charset. +# [*grant*] - privilege to grant user. +# +# Actions: +# +# Requires: +# +# class postgresql::server +# +# Sample Usage: +# +# postgresql::db { 'mydb': +# user => 'my_user', +# password => 'password', +# grant => 'all' +# } +# +define postgresql::db ( + $user, + $password, + $charset = 'utf8', + $grant = 'ALL' +) { + + postgresql::database { $name: + # TODO: ensure is not yet supported + #ensure => present, + charset => $charset, + #provider => 'postgresql', + require => Class['postgresql::server'], + } + + postgresql::database_user { "${user}": + # TODO: ensure is not yet supported + #ensure => present, + password_hash => $password, + #provider => 'postgresql', + require => Postgresql::Database[$name], + } + + postgresql::database_grant { "GRANT ${user} - ${grant} - ${name}": + privilege => $grant, + db => $name, + role => $user, + #provider => 'postgresql', + require => Postgresql::Database_user["${user}"], + } + +} diff --git a/deployment/puppet/postgresql/manifests/init.pp b/deployment/puppet/postgresql/manifests/init.pp new file mode 100644 index 0000000000..780dded068 --- /dev/null +++ b/deployment/puppet/postgresql/manifests/init.pp @@ -0,0 +1,24 @@ +# Class: postgresql +# +# This class installs postgresql client software. +# +# Parameters: +# [*client_package_name*] - The name of the postgresql client package. +# +# Actions: +# +# Requires: +# +# Sample Usage: +# +class postgresql ( + $package_name = $postgresql::params::client_package_name, + $package_ensure = 'present' +) inherits postgresql::params { + + package { 'postgresql_client': + name => $package_name, + ensure => $package_ensure, + } + +} diff --git a/deployment/puppet/postgresql/manifests/initdb.pp b/deployment/puppet/postgresql/manifests/initdb.pp new file mode 100644 index 0000000000..92e4e39d86 --- /dev/null +++ b/deployment/puppet/postgresql/manifests/initdb.pp @@ -0,0 +1,33 @@ +# puppet-postgresql +# For all details and documentation: +# http://github.com/inkling/puppet-postgresql +# +# Copyright 2012- Inkling Systems, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +class postgresql::initdb( + $datadir = $postgresql::params::datadir, + $initdb_path = $postgresql::params::initdb_path, + $user = 'postgres', + $group = 'postgres', + $encoding = 'UTF8', + $options='' +) inherits postgresql::params { + + exec {"${initdb_path} --encoding '$encoding' --pgdata '$datadir'": + creates => "${datadir}/PG_VERSION", + user => "$user", + group => "$group", + } +} diff --git a/deployment/puppet/postgresql/manifests/params.pp b/deployment/puppet/postgresql/manifests/params.pp new file mode 100644 index 0000000000..8310fe93c7 --- /dev/null +++ b/deployment/puppet/postgresql/manifests/params.pp @@ -0,0 +1,90 @@ +# Class: postgresql::params +# +# The postgresql configuration settings. +# +# Parameters: +# +# Actions: +# +# Requires: +# +# Sample Usage: +# +class postgresql::params { + $user = 'postgres' + $group = 'postgres' + $ip_mask_deny_postgres_user = '0.0.0.0/0' + $ip_mask_allow_all_users = '127.0.0.1/32' + $listen_addresses = 'localhost' + # TODO: figure out a way to make this not platform-specific + $manage_redhat_firewall = false + + # This is a bit hacky, but if the puppet nodes don't have pluginsync enabled, + # they will fail with a not-so-helpful error message. Here we are explicitly + # verifying that the custom fact exists (which implies that pluginsync is + # enabled and succeeded). If not, we fail with a hint that tells the user + # that pluginsync might not be enabled. Ideally this would be handled directly + # in puppet. + if ($::postgres_default_version == undef) { + fail "No value for postgres_default_version facter fact; it's possible that you don't have pluginsync enabled." + } + + case $::operatingsystem { + default: { + $service_provider = undef + } + } + + case $::osfamily { + 'RedHat': { + $service_name = 'postgresql' + $client_package_name = 'postgresql' + $server_package_name = 'postgresql-server' + $needs_initdb = true + $initdb_path = '/usr/bin/initdb' + $createdb_path = '/usr/bin/createdb' + $psql_path = '/usr/bin/psql' + $datadir = '/var/lib/pgsql/data/' + $pg_hba_conf_path = '/var/lib/pgsql/data/pg_hba.conf' + $postgresql_conf_path = '/var/lib/pgsql/data/postgresql.conf' + $firewall_supported = true + $persist_firewall_command = '/sbin/iptables-save > /etc/sysconfig/iptables' + } + + 'Debian': { + case $::operatingsystem { + 'Debian': { + $service_name = "postgresql" + } + + 'Ubuntu': { + case $::lsbmajdistrelease { + # thanks, ubuntu + '10': { $service_name = "postgresql-${::postgres_default_version}" } + default: { $service_name = "postgresql" } + } + } + } + + $client_package_name = 'postgresql-client' + $server_package_name = 'postgresql' + $needs_initdb = false + $initdb_path = "/usr/lib/postgresql/${::postgres_default_version}/bin/initdb" + $createdb_path = "/usr/lib/postgresql/${::postgres_default_version}/bin/createdb" + $psql_path = "/usr/lib/postgresql/${::postgres_default_version}/bin/psql" + $datadir = "/var/lib/postgresql/${::postgres_default_version}/main" + $pg_hba_conf_path = "/etc/postgresql/${::postgres_default_version}/main/pg_hba.conf" + $postgresql_conf_path = "/etc/postgresql/${::postgres_default_version}/main/postgresql.conf" + $firewall_supported = false + # TODO: not exactly sure yet what the right thing to do for Debian/Ubuntu is. + #$persist_firewall_command = '/sbin/iptables-save > /etc/iptables/rules.v4' + + } + + + default: { + fail("Unsupported osfamily: ${::osfamily} operatingsystem: ${::operatingsystem}, module ${module_name} currently only supports osfamily RedHat and Debian") + } + } + +} diff --git a/deployment/puppet/postgresql/manifests/psql.pp b/deployment/puppet/postgresql/manifests/psql.pp new file mode 100644 index 0000000000..fecd635900 --- /dev/null +++ b/deployment/puppet/postgresql/manifests/psql.pp @@ -0,0 +1,49 @@ +# puppet-postgresql +# For all details and documentation: +# http://github.com/inkling/puppet-postgresql +# +# Copyright 2012- Inkling Systems, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define postgresql::psql( + $command = $title, + $unless, + $db, + $user = 'postgres', + $refreshonly = false +) { + + require postgresql::params + + # TODO: FIXME: shellquote does not work, and this regex works for trivial things but not nested escaping. + # Need a lexer, preferably a ruby SQL parser to catch errors at catalog time + # Possibly https://github.com/omghax/sql ? + + if ($::postgres_default_version != "8.1") { + $no_password_option = "--no-password" + } + + $psql = "${postgresql::params::psql_path} $no_password_option --tuples-only --quiet --dbname $db" + $quoted_command = regsubst($command, '"', '\\"') + $quoted_unless = regsubst($unless, '"', '\\"') + + exec {"/bin/echo \"$quoted_command\" | $psql |egrep -v -q '^$'": + cwd => '/tmp', + user => $user, + returns => 1, + unless => "/bin/echo \"$quoted_$unless\" | $psql | egrep -v -q '^$'", + refreshonly => $refreshonly, + } +} + diff --git a/deployment/puppet/postgresql/manifests/role.pp b/deployment/puppet/postgresql/manifests/role.pp new file mode 100644 index 0000000000..be063f028d --- /dev/null +++ b/deployment/puppet/postgresql/manifests/role.pp @@ -0,0 +1,40 @@ +# puppet-postgresql +# For all details and documentation: +# http://github.com/inkling/puppet-postgresql +# +# Copyright 2012- Inkling Systems, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define postgresql::role( + $username=$title, + $password_hash, + $db='postgres', + $login=false, + $createrole=false, + $createdb=false, + $superuser=false +) { + + $login_sql = $login ? { true => 'LOGIN' , false => 'NOLOGIN' } + $createrole_sql = $createrole ? { true => 'CREATEROLE', false => 'NOCREATEROLE' } + $createdb_sql = $createdb ? { true => 'CREATEDB' , false => 'NOCREATEDB' } + $superuser_sql = $superuser ? { true => 'SUPERUSER' , false => 'NOSUPERUSER' } + + # TODO: FIXME: Will not correct the superuser / createdb / createrole / login status of a role that already exists + postgresql::psql {"CREATE ROLE ${username} ENCRYPTED PASSWORD '${password_hash}' $login_sql $createrole_sql $createdb_sql $superuser_sql": + db => $db, + user => 'postgres', + unless => "SELECT rolname FROM pg_roles WHERE rolname='$username'", + } +} diff --git a/deployment/puppet/postgresql/manifests/server.pp b/deployment/puppet/postgresql/manifests/server.pp new file mode 100644 index 0000000000..67c7c59320 --- /dev/null +++ b/deployment/puppet/postgresql/manifests/server.pp @@ -0,0 +1,51 @@ +# Class: postgresql::server +# +# manages the installation of the postgresql server. manages the package and service +# +# Parameters: +# [*package_name*] - name of package +# [*service_name*] - name of service +# +# Actions: +# +# Requires: +# +# Sample Usage: +# +class postgresql::server ( + $package_name = $postgresql::params::server_package_name, + $package_ensure = 'present', + $service_name = $postgresql::params::service_name, + $service_provider = $postgresql::params::service_provider, + $config_hash = {} +) inherits postgresql::params { + + package { 'postgresql-server': + name => $package_name, + ensure => $package_ensure, + } + + $config_class = {} + $config_class['postgresql::config'] = $config_hash + + create_resources( 'class', $config_class ) + + Package['postgresql-server'] -> Class['postgresql::config'] + + if ($needs_initdb) { + include postgresql::initdb + + Class['postgresql::initdb'] -> Class['postgresql::config'] + Class['postgresql::initdb'] -> Service['postgresqld'] + } + + service { 'postgresqld': + name => $service_name, + ensure => running, + enable => true, + require => Package['postgresql-server'], + provider => $service_provider, + } + + +} diff --git a/deployment/puppet/postgresql/manifests/validate_db_connection.pp b/deployment/puppet/postgresql/manifests/validate_db_connection.pp new file mode 100644 index 0000000000..f514c76af5 --- /dev/null +++ b/deployment/puppet/postgresql/manifests/validate_db_connection.pp @@ -0,0 +1,95 @@ +# Define: postgresql::validate_db_connection +# +# This type validates that a successful postgres connection can be established +# between the node on which this resource is run and a specified postgres +# instance (host/port/user/password/database name). +# +# Parameters: +# [*database_host*] - the hostname or IP address of the machine where the +# postgres server should be running. +# [*database_port*] - the port on which postgres server should be +# listening (defaults to 5432). +# [*database_username*] - the postgres username +# [*database_password*] - the postgres user's password +# [*database_name*] - the database name that the connection should be +# established against +# [*client_package_name*] - (optional) the name of the postgres client package +# that provides the psql tool, if you aren't using +# the default system package. +# +# NOTE: to some degree this type assumes that you've created the corresponding +# postgres database instance that you are validating by using the +# `postgresql::db` or `postgresql::database` type provided by this module +# elsewhere in your manifests. +# +# Actions: +# +# Attempts to establish a connection to the specified postgres database. If +# a connection cannot be established, the resource will fail; this allows you +# to use it as a dependency for other resources that would be negatively +# impacted if they were applied without the postgres connection being available. +# +# Requires: +# +# `psql` commandline tool (will automatically install the system's postgres +# client package if it is not already installed.) +# +# Sample Usage: +# +# postgresql::validate_db_connection { 'validate my postgres connection': +# database_host => 'my.postgres.host', +# database_username => 'mydbuser', +# database_password => 'mydbpassword', +# database_name => 'mydbname', +# } +# + +define postgresql::validate_db_connection( + $database_host, + $database_port = 5432, + $database_username, + $database_password, + $database_name, + $client_package_name = "", +) { + include postgresql::params + + # This is a bit messy, but we need to use the correct client package name + # from the params class if the user did not explicitly provide one. + if (! $client_package_name) { + $package_name = $postgresql::params::client_package_name + } else { + $package_name = $client_package_name + } + + # Make sure the postgres client package is installed; we need it for + # `psql`. + package { 'postgresql-client': + name => $package_name, + ensure => present, + } + + # TODO: port to ruby + $psql = "${postgresql::params::psql_path} --tuples-only --quiet -h $database_host -U $database_username -p $database_port --dbname $database_name" + + $exec_name = "validate postgres connection for $database_host/$database_name" + exec {$exec_name: + command => "/bin/echo \"SELECT 1\" | $psql", + cwd => '/tmp', + environment => "PGPASSWORD=$database_password", + logoutput => 'on_failure', + require => Package['postgresql-client'], + } + + # This is a little bit of puppet magic. What we want to do here is make + # sure that if the validation and the database instance creation are being + # applied on the same machine, then the database resource is applied *before* + # the validation resource. Otherwise, the validation is guaranteed to fail + # on the first run. + # + # We accomplish this by using Puppet's resource collection syntax to search + # for the Database resource in our current catalog; if it exists, the + # appropriate relationship is created here. + Database<|title == $database_name|> -> Exec[$exec_name] +} + diff --git a/deployment/puppet/postgresql/spec/Vagrantfile b/deployment/puppet/postgresql/spec/Vagrantfile new file mode 100644 index 0000000000..0a1dc477c6 --- /dev/null +++ b/deployment/puppet/postgresql/spec/Vagrantfile @@ -0,0 +1,46 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +# puppet-postgresql +# For all details and documentation: +# http://github.com/inkling/puppet-postgresql +# +# Copyright 2012- Inkling Systems, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +Vagrant::Config.run do |config| + + # Test on 64 bit lucid + config.vm.box = "lucid64" + config.vm.box_url = "http://files.vagrantup.com/lucid64.box" + + # Resolve DNS via NAT + config.vm.customize ["modifyvm", :id, "--natdnshostresolver1", "on"] + + # Share the stdlib module + # TODO: it would be better to install this via the puppet module tool + config.vm.share_folder "puppetlabs-stdlib-module", "/usr/share/puppet/modules/stdlib", "../../puppetlabs-stdlib" + + # Share the postgressql module + config.vm.share_folder "puppet-postgresql-module", "/usr/share/puppet/modules/postgresql", ".." + + # Share the module of test classes + config.vm.share_folder "puppet-postgresql-tests", "/usr/share/puppet/modules/postgresql_tests", "." + + # Provision with a base puppet config just so we don't have to repeat the puppet user/group + config.vm.provision :puppet do |puppet| + puppet.manifests_path = "." + puppet.manifest_file = "base.pp" + end +end diff --git a/deployment/puppet/postgresql/spec/base.pp b/deployment/puppet/postgresql/spec/base.pp new file mode 100644 index 0000000000..d27ad18797 --- /dev/null +++ b/deployment/puppet/postgresql/spec/base.pp @@ -0,0 +1,34 @@ +# puppet-postgresql +# For all details and documentation: +# http://github.com/inkling/puppet-postgresql +# +# Copyright 2012- Inkling Systems, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +group {'puppet': + ensure => present, +} + +user {'puppet': + ensure => present, + gid => 'puppet', +} + +file {'/etc/sudoers.d/puppet_postgresql_tests': + ensure => file, + content => 'vagrant ALL=(ALL) ALL', + mode => 0440, + owner => root, + group => root, +} diff --git a/deployment/puppet/postgresql/spec/manifests/test_db.pp b/deployment/puppet/postgresql/spec/manifests/test_db.pp new file mode 100644 index 0000000000..e764d900eb --- /dev/null +++ b/deployment/puppet/postgresql/spec/manifests/test_db.pp @@ -0,0 +1,27 @@ +# puppet-postgresql +# For all details and documentation: +# http://github.com/inkling/puppet-postgresql +# +# Copyright 2012- Inkling Systems, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +class postgresql_tests::test_db($db) { + + include postgresql::server + + postgresql::db { $db: + user => $db, + password => $db, + } +} diff --git a/deployment/puppet/postgresql/spec/manifests/test_grant_create.pp b/deployment/puppet/postgresql/spec/manifests/test_grant_create.pp new file mode 100644 index 0000000000..4d8a7f5e45 --- /dev/null +++ b/deployment/puppet/postgresql/spec/manifests/test_grant_create.pp @@ -0,0 +1,45 @@ +# puppet-postgresql +# For all details and documentation: +# http://github.com/inkling/puppet-postgresql +# +# Copyright 2012- Inkling Systems, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +class postgresql_tests::test_grant_create($user, $password, $db) { + + include postgresql::server + + # Since we are not testing pg_hba or any of that, make a local user for ident auth + user { $user: + ensure => present, + } + + postgresql::database_user { $user: + password_hash => postgresql_password($user, $password), + require => [ Class['postgresql::server'], + User[$user] ], + } + + postgresql::database { $db: + require => Class['postgresql::server'], + } + + postgresql::database_grant { "grant create test": + privilege => 'CREATE', + db => $db, + role => $user, + require => [ Postgresql::Database[$db], + Postgresql::Database_user[$user] ], + } +} diff --git a/deployment/puppet/postgresql/spec/manifests/test_initdb.pp b/deployment/puppet/postgresql/spec/manifests/test_initdb.pp new file mode 100644 index 0000000000..36616e217a --- /dev/null +++ b/deployment/puppet/postgresql/spec/manifests/test_initdb.pp @@ -0,0 +1,26 @@ +# puppet-postgresql +# For all details and documentation: +# http://github.com/inkling/puppet-postgresql +# +# Copyright 2012- Inkling Systems, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +class postgresql_tests::test_initdb { + + include postgresql::server + + class { "postgresql::initdb": + require => Class['postgresql::server'] + } +} diff --git a/deployment/puppet/postgresql/spec/manifests/test_psql.pp b/deployment/puppet/postgresql/spec/manifests/test_psql.pp new file mode 100644 index 0000000000..d5206ba6a9 --- /dev/null +++ b/deployment/puppet/postgresql/spec/manifests/test_psql.pp @@ -0,0 +1,30 @@ +# puppet-postgresql +# For all details and documentation: +# http://github.com/inkling/puppet-postgresql +# +# Copyright 2012- Inkling Systems, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +class postgresql_tests::test_psql($command = $title, $unless) { + + include postgresql::server + + postgresql::psql { $title: + db => 'postgres', + user => 'postgres', + command => $command, + unless => $unless, + require => Class['postgresql::server'], + } +} diff --git a/deployment/puppet/postgresql/spec/manifests/test_user.pp b/deployment/puppet/postgresql/spec/manifests/test_user.pp new file mode 100644 index 0000000000..10a5476cd9 --- /dev/null +++ b/deployment/puppet/postgresql/spec/manifests/test_user.pp @@ -0,0 +1,33 @@ +# puppet-postgresql +# For all details and documentation: +# http://github.com/inkling/puppet-postgresql +# +# Copyright 2012- Inkling Systems, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +class postgresql_tests::test_user($user, $password) { + + include postgresql::server + + # Since we are not testing pg_hba or any of that, make a local user for ident auth + user { $user: + ensure => present, + } + + postgresql::database_user { $user: + password_hash => postgresql_password($user, $password), + require => [ Class['postgresql::server'], + User[$user] ], + } +} diff --git a/deployment/puppet/postgresql/spec/postgresql_spec.rb b/deployment/puppet/postgresql/spec/postgresql_spec.rb new file mode 100644 index 0000000000..aaf0d99ce8 --- /dev/null +++ b/deployment/puppet/postgresql/spec/postgresql_spec.rb @@ -0,0 +1,152 @@ +# puppet-postgresql +# For all details and documentation: +# http://github.com/inkling/puppet-postgresql +# +# Copyright 2012- Inkling Systems, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'logger' +require 'vagrant' + +# VM-based tests for postgresql::* defined resource types. +# +# The general structure is: +# +# - Roll back the VM +# - Apply a minimal puppet config for the type and fail only on crashes +# - Apply it again and fail if there were any new changes reported +# - Check the state of the system + +describe "postgresql" do + + def sudo_and_log(*args) + @logger.debug("Running command: '#{args[0]}'") + @env.primary_vm.channel.sudo(args[0]) do |ch, data| + @logger.debug(data) + end + end + + before(:all) do + @logger = Logger.new(STDOUT) + @logger.level = Logger::DEBUG # TODO: get from environment or rspec? + + vagrant_dir = File.dirname(__FILE__) + @env = Vagrant::Environment::new(:cwd => vagrant_dir) + + # Sahara ignores :cwd so we have to chdir for now, see https://github.com/jedi4ever/sahara/issues/9 + Dir.chdir(vagrant_dir) + + # @env.cli("destroy") # Takes too long + @env.cli("up") + + # We are not testing the "package" resource type, so pull stuff in in advance + sudo_and_log('apt-get update') + sudo_and_log('apt-get install --yes --download-only postgresql-8.4') + @env.cli("sandbox", "on") + end + + after(:each) do + @env.cli("sandbox", "rollback") + end + + describe 'postgresql::initdb' do + it "should idempotently create a working --pgdata directory so postgres can run" do + @logger.info("starting") + + # A bare-minimum class to initdb the specified dir + test_class = 'class {"postgresql_tests::test_initdb": }' + + # Run once to check for crashes + sudo_and_log("puppet apply -e '#{test_class}'") + + # Run again to check for idempotence via --detailed-exitcodes + sudo_and_log("puppet apply --detailed-exitcodes -e '#{test_class}'") + + sudo_and_log("service postgresql-8.4 restart") + + # Connect to it and list the databases + sudo_and_log('sudo -n -u postgres /usr/lib/postgresql/8.4/bin/psql --list --tuples-only') + end + end + + describe 'postgresql::db' do + it 'should idempotently create a db that we can connect to' do + + # A bare-minimum class to add a DB to postgres, which will be running due to ubuntu + test_class = 'class {"postgresql_tests::test_db": db => "postgresql_test_db" }' + + # Run once to check for crashes + sudo_and_log("puppet apply -e '#{test_class}'") + + # Run again to check for idempotence + sudo_and_log("puppet apply --detailed-exitcodes -e '#{test_class}'") + + # Check that the database name is present + sudo_and_log('sudo -u postgres psql postgresql_test_db --command="select datname from pg_database limit 1"') + end + end + + describe 'postgresql::psql' do + it 'should run some SQL when the unless query returns no rows' do + test_class = 'class {"postgresql_tests::test_psql": command => "SELECT \'foo\'", unless => "SELECT 1 WHERE 1=2" }' + + # Run once to get all packages set up + sudo_and_log("puppet apply -e '#{test_class}'") + + # Check for exit code 2 + sudo_and_log("puppet apply --detailed-exitcodes -e '#{test_class}' ; [ $? == 2 ]") + end + + it 'should not run SQL when the unless query returns rows' do + test_class = 'class {"postgresql_tests::test_psql": command => "SELECT * FROM pg_datbase limit 1", unless => "SELECT 1 WHERE 1=1" }' + + # Run once to get all packages set up + sudo_and_log("puppet apply -e '#{test_class}'") + + # Check for exit code 0 + sudo_and_log("puppet apply --detailed-exitcodes -e '#{test_class}'") + end + end + + describe 'postgresql::user' do + it 'should idempotently create a user who can log in' do + test_class = 'class {"postgresql_tests::test_user": user => "postgresql_test_user", password => "postgresql_test_password" }' + + # Run once to check for crashes + sudo_and_log("puppet apply -e '#{test_class}'") + + # Run again to check for idempotence + sudo_and_log("puppet apply --detailed-exitcodes -e '#{test_class}'") + + # Check that the user can log in + sudo_and_log('sudo -u postgresql_test_user psql --command="select datname from pg_database limit 1" postgres') + end + end + + describe 'postgresql::grant' do + it 'should grant access so a user can create in a database' do + test_class = 'class {"postgresql_tests::test_grant_create": db => "postgres", user => "psql_grant_tester", password => "psql_grant_pw" }' + + # Run once to check for crashes + sudo_and_log("puppet apply -e '#{test_class}'") + + # Run again to check for idempotence + sudo_and_log("puppet apply --detailed-exitcodes -e '#{test_class}'") + + # Check that the user can select from the table in + sudo_and_log('sudo -u psql_grant_tester psql --command="create table foo (foo int)" postgres') + end + end +end + diff --git a/deployment/puppet/postgresql/templates/pg_hba.conf.erb b/deployment/puppet/postgresql/templates/pg_hba.conf.erb new file mode 100644 index 0000000000..003be66866 --- /dev/null +++ b/deployment/puppet/postgresql/templates/pg_hba.conf.erb @@ -0,0 +1,88 @@ +# PostgreSQL Client Authentication Configuration File +# =================================================== +# +# Refer to the "Client Authentication" section in the +# PostgreSQL documentation for a complete description +# of this file. A short synopsis follows. +# +# This file controls: which hosts are allowed to connect, how clients +# are authenticated, which PostgreSQL user names they can use, which +# databases they can access. Records take one of these forms: +# +# local DATABASE USER METHOD [OPTIONS] +# host DATABASE USER CIDR-ADDRESS METHOD [OPTIONS] +# hostssl DATABASE USER CIDR-ADDRESS METHOD [OPTIONS] +# hostnossl DATABASE USER CIDR-ADDRESS METHOD [OPTIONS] +# +# (The uppercase items must be replaced by actual values.) +# +# The first field is the connection type: "local" is a Unix-domain socket, +# "host" is either a plain or SSL-encrypted TCP/IP socket, "hostssl" is an +# SSL-encrypted TCP/IP socket, and "hostnossl" is a plain TCP/IP socket. +# +# DATABASE can be "all", "sameuser", "samerole", a database name, or +# a comma-separated list thereof. +# +# USER can be "all", a user name, a group name prefixed with "+", or +# a comma-separated list thereof. In both the DATABASE and USER fields +# you can also write a file name prefixed with "@" to include names from +# a separate file. +# +# CIDR-ADDRESS specifies the set of hosts the record matches. +# It is made up of an IP address and a CIDR mask that is an integer +# (between 0 and 32 (IPv4) or 128 (IPv6) inclusive) that specifies +# the number of significant bits in the mask. Alternatively, you can write +# an IP address and netmask in separate columns to specify the set of hosts. +# +# METHOD can be "trust", "reject", "md5", "password", "gss", "sspi", "krb5", +# "ident", "pam", "ldap" or "cert". Note that "password" sends passwords +# in clear text; "md5" is preferred since it sends encrypted passwords. +# +# OPTIONS are a set of options for the authentication in the format +# NAME=VALUE. The available options depend on the different authentication +# methods - refer to the "Client Authentication" section in the documentation +# for a list of which options are available for which authentication methods. +# +# Database and user names containing spaces, commas, quotes and other special +# characters must be quoted. Quoting one of the keywords "all", "sameuser" or +# "samerole" makes the name lose its special character, and just match a +# database or username with that name. +# +# This file is read on server startup and when the postmaster receives +# a SIGHUP signal. If you edit the file on a running system, you have +# to SIGHUP the postmaster for the changes to take effect. You can use +# "pg_ctl reload" to do that. + +# Put your actual configuration here +# ---------------------------------- +# +# If you want to allow non-local connections, you need to add more +# "host" records. In that case you will also need to make PostgreSQL listen +# on a non-local interface via the listen_addresses configuration parameter, +# or via the -i or -h command line switches. +# + + + + +# DO NOT DISABLE! +# If you change this first entry you will need to make sure that the +# database +# super user can access the database using some other method. +# Noninteractive +# access to all databases is required during automatic maintenance +# (custom daily cronjobs, replication, and similar tasks). +# +# Database administrative login by UNIX sockets +local all postgres ident <%= "sameuser" if @postgres_default_version == "8.1" %> + +# TYPE DATABASE USER CIDR-ADDRESS METHOD + +# "local" is for Unix domain socket connections only +local all all ident <%= "sameuser" if @postgres_default_version == "8.1" %> +# IPv4 local connections: +host all postgres <%= @ip_mask_deny_postgres_user + "\t" %> reject +host all all <%= @ip_mask_allow_all_users + "\t" %> md5 +# IPv6 local connections: +host all all ::1/128 md5 + diff --git a/deployment/puppet/postgresql/tests/init.pp b/deployment/puppet/postgresql/tests/init.pp new file mode 100644 index 0000000000..51de05af9e --- /dev/null +++ b/deployment/puppet/postgresql/tests/init.pp @@ -0,0 +1 @@ +include postgresql diff --git a/deployment/puppet/postgresql/tests/postgresql_database.pp b/deployment/puppet/postgresql/tests/postgresql_database.pp new file mode 100644 index 0000000000..31bcbfd88c --- /dev/null +++ b/deployment/puppet/postgresql/tests/postgresql_database.pp @@ -0,0 +1,22 @@ +class { 'postgresql::server': + config_hash => { + 'ip_mask_deny_postgres_user' => '0.0.0.0/32', + 'ip_mask_allow_all_users' => '0.0.0.0/0', + 'listen_addresses' => '*', + 'manage_redhat_firewall' => true, + 'postgres_password' => 'postgres', + }, +} + +postgresql::database{ ['test1', 'test2', 'test3']: + # TODO: ensure not yet supported + #ensure => present, + charset => 'utf8', + require => Class['postgresql::server'], +} +postgresql::database{ 'test4': + # TODO: ensure not yet supported + #ensure => present, + charset => 'latin1', + require => Class['postgresql::server'], +} diff --git a/deployment/puppet/postgresql/tests/postgresql_db.pp b/deployment/puppet/postgresql/tests/postgresql_db.pp new file mode 100644 index 0000000000..26748fba69 --- /dev/null +++ b/deployment/puppet/postgresql/tests/postgresql_db.pp @@ -0,0 +1,30 @@ +class { 'postgresql::server': + config_hash => { + 'ip_mask_allow_all_users' => '0.0.0.0/0', + 'listen_addresses' => '*', + 'manage_redhat_firewall' => true, + + #'ip_mask_deny_postgres_user' => '0.0.0.0/32', + #'postgres_password' => 'puppet', + }, +} + +postgresql::db{ 'test1': + user => 'test1', + password => 'test1', + grant => 'all', +} + +postgresql::db{ 'test2': + user => 'test2', + password => postgresql_password('test2', 'test2'), + grant => 'all', +} + +postgresql::db{ 'test3': + user => 'test3', + # The password here is a copy/paste of the output of the 'postgresql_password' + # function from this module, with a u/p of 'test3', 'test3'. + password => 'md5e12234d4575a12bfd61d61294f32b086', + grant => 'all', +} diff --git a/deployment/puppet/postgresql/tests/postgresql_grant.pp b/deployment/puppet/postgresql/tests/postgresql_grant.pp new file mode 100644 index 0000000000..05d87a47a8 --- /dev/null +++ b/deployment/puppet/postgresql/tests/postgresql_grant.pp @@ -0,0 +1,14 @@ +# TODO: in mysql module, the grant resource name might look like this: 'user@host/dbname'; +# I think that the API for the resource type should split these up, because it's +# easier / safer to recombine them for mysql than it is to parse them for other +# databases. Also, in the mysql module, the hostname portion of that string +# affects the user's ability to connect from remote hosts. In postgres this is +# managed via pg_hba.conf; not sure if we want to try to reconcile that difference +# in the modules or not. +postgresql::database_grant{'test1': + # TODO: mysql supports an array of privileges here. We should do that if we + # port this to ruby. + privilege => 'ALL', + db => 'test1', + role => 'dan', +} diff --git a/deployment/puppet/postgresql/tests/postgresql_user.pp b/deployment/puppet/postgresql/tests/postgresql_user.pp new file mode 100644 index 0000000000..dc8954357a --- /dev/null +++ b/deployment/puppet/postgresql/tests/postgresql_user.pp @@ -0,0 +1,28 @@ +class { 'postgresql::server': + config_hash => { + 'ip_mask_deny_postgres_user' => '0.0.0.0/32', + 'ip_mask_allow_all_users' => '0.0.0.0/0', + 'listen_addresses' => '*', + 'manage_redhat_firewall' => true, + 'postgres_password' => 'postgres', + }, +} + +# TODO: in mysql module, the username includes, e.g., '@%' or '@localhost', which +# affects the user's ability to connect from remote hosts. In postgres this is +# managed via pg_hba.conf; not sure if we want to try to reconcile that difference +# in the modules or not. +postgresql::database_user{ 'redmine': + # TODO: ensure is not yet supported + #ensure => present, + password_hash => postgresql_password('redmine', 'redmine'), + require => Class['postgresql::server'], +} + +postgresql::database_user{ 'dan': + # TODO: ensure is not yet supported + #ensure => present, + password_hash => postgresql_password('dan', 'blah'), + require => Class['postgresql::server'], +} + diff --git a/deployment/puppet/postgresql/tests/server.pp b/deployment/puppet/postgresql/tests/server.pp new file mode 100644 index 0000000000..8946f4adba --- /dev/null +++ b/deployment/puppet/postgresql/tests/server.pp @@ -0,0 +1,9 @@ +class { 'postgresql::server': + config_hash => { + 'ip_mask_deny_postgres_user' => '0.0.0.0/32', + 'ip_mask_allow_all_users' => '0.0.0.0/0', + 'listen_addresses' => '*', + 'manage_redhat_firewall' => true, + 'postgres_password' => 'postgres', + }, +}