├── .gitignore ├── .kitchen.docker.yml ├── .kitchen.yml ├── .rubocop.yml ├── .travis.yml ├── .travis └── secret_keys.tar.enc ├── Berksfile ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Gemfile ├── LICENSE ├── MAINTAINERS ├── README.md ├── TESTING.md ├── Thorfile ├── attributes ├── client.rb ├── cloud_backup_wal_e.rb ├── cloud_backup_wal_g.rb └── server.rb ├── chefignore ├── libraries ├── helpers.rb └── matchers.rb ├── metadata.rb ├── recipes ├── apt_official_repository.rb ├── cloud_backup.rb └── default.rb ├── resources ├── cloud_backup.rb ├── database.rb ├── default.rb ├── extension.rb ├── pgxn_extension.rb └── user.rb ├── spec ├── spec_helper.rb └── unit │ └── recipes │ ├── apt_official_repository_spec.rb │ └── default_spec.rb ├── templates └── default │ ├── pg_hba.conf.erb │ ├── pg_ident.conf.erb │ ├── postgresql.conf.erb │ ├── postgresql_cloud_backup_helper.sh.erb │ └── recovery.conf.erb └── test ├── fixtures └── cookbooks │ └── pgtest │ ├── metadata.rb │ └── recipes │ ├── cloud_backup.rb │ ├── create_database.rb │ ├── create_user.rb │ ├── install_ext.rb │ ├── master.rb │ ├── master_for_walg.rb │ ├── slave.rb │ ├── slave_init_nostart.rb │ └── test.rb └── integration ├── postgresql_lwrp_test └── inspec │ ├── controls │ └── default.rb │ ├── inspec.yml │ └── libraries │ ├── postgres_cluster.rb │ ├── postgres_database.rb │ ├── postgres_extension.rb │ └── postgres_user.rb └── spec_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *# 3 | .#* 4 | \#*# 5 | .*.sw[a-z] 6 | *.un~ 7 | pkg/ 8 | 9 | # Berkshelf 10 | .vagrant 11 | /cookbooks 12 | Berksfile.lock 13 | 14 | # Bundler 15 | Gemfile.lock 16 | bin/* 17 | .bundle/* 18 | 19 | .kitchen/ 20 | .kitchen.local.yml 21 | .idea 22 | -------------------------------------------------------------------------------- /.kitchen.docker.yml: -------------------------------------------------------------------------------- 1 | --- 2 | driver: 3 | name: docker 4 | use_sudo: false 5 | privileged: true 6 | 7 | provisioner: 8 | product_name: chef 9 | chef_license: accept 10 | 11 | verifier: 12 | name: inspec 13 | inspec_tests: 14 | - path: test/integration/postgresql_lwrp_test 15 | 16 | platforms: 17 | - name: debian-8-docker 18 | driver_config: 19 | image: debian:8 20 | disable_upstart: false 21 | run_command: '/sbin/init' 22 | provision_command: 23 | # For postgreqsl 24 | - echo "en_US.UTF-8 UTF-8" > /etc/locale.gen 25 | - apt-get install -y locales && locale-gen en_US.UTF-8 26 | # Netstat for Serverspec 27 | - apt-get install -y net-tools 28 | 29 | - name: debian-9-docker 30 | driver_config: 31 | image: debian:9 32 | disable_upstart: false 33 | run_command: '/lib/systemd/systemd' 34 | provision_command: 35 | # For postgreqsl 36 | - echo "en_US.UTF-8 UTF-8" > /etc/locale.gen 37 | - apt-get install -y locales && locale-gen en_US.UTF-8 38 | # Netstat for Serverspec 39 | - apt-get install -y net-tools 40 | # gnupg for Chef 41 | - apt-get install -y gnupg 42 | 43 | - name: ubuntu-14.04-docker 44 | driver_config: 45 | image: ubuntu-upstart:14.04 46 | disable_upstart: false 47 | run_command: '/sbin/init' 48 | provision_command: 49 | # Netstat for Serverspec 50 | - apt-get install -y net-tools 51 | 52 | 53 | - name: ubuntu-16.04-docker 54 | driver_config: 55 | image: ubuntu:16.04 56 | run_command: '/sbin/init' 57 | provision_command: 58 | # For systemd 59 | - systemctl set-default multi-user.target 60 | # For postgreqsl 61 | - apt-get install -y locales && locale-gen en_US.UTF-8 62 | # Netstat for Serverspec 63 | - apt-get install -y net-tools 64 | 65 | - name: ubuntu-18.04-docker 66 | driver_config: 67 | image: ubuntu:18.04 68 | run_command: '/sbin/init' 69 | provision_command: 70 | - apt-get install -y locales && locale-gen en_US.UTF-8 71 | # For systemd 72 | - systemctl set-default multi-user.target 73 | # Netstat for Inspec 74 | - apt-get install -y net-tools 75 | # gnupg for Chef 76 | - apt-get install -y gnupg 77 | 78 | suites: 79 | - name: pg-96-chef13 80 | provisioner: 81 | product_name: chef 82 | product_version: 13 83 | run_list: 84 | - recipe[apt] 85 | - recipe[pgtest::test] 86 | attributes: 87 | pgtest: 88 | version: '9.6' 89 | verifier: 90 | attributes: 91 | pg_version: 9.6 92 | 93 | - name: pg-10-chef13 94 | provisioner: 95 | product_name: chef 96 | product_version: 13 97 | run_list: 98 | - recipe[apt] 99 | - recipe[pgtest::test] 100 | attributes: 101 | pgtest: 102 | version: '10' 103 | verifier: 104 | attributes: 105 | pg_version: 10 106 | 107 | - name: pg-11-chef13 108 | provisioner: 109 | product_name: chef 110 | product_version: 13 111 | run_list: 112 | - recipe[apt] 113 | - recipe[pgtest::test] 114 | attributes: 115 | pgtest: 116 | version: '11' 117 | verifier: 118 | attributes: 119 | pg_version: 11 120 | 121 | - name: pg-91-chef14 122 | provisioner: 123 | product_name: chef 124 | product_version: 14 125 | run_list: 126 | - recipe[apt] 127 | - recipe[pgtest::test] 128 | attributes: 129 | pgtest: 130 | version: '9.1' 131 | verifier: 132 | attributes: 133 | pg_version: 9.1 134 | excludes: 135 | # There is no official postgresql-9.1 package for Debian 9 & Ubuntu 18.04 136 | - debian-9-docker 137 | - ubuntu-18.04-docker 138 | 139 | - name: pg-92-chef14 140 | provisioner: 141 | product_name: chef 142 | product_version: 14 143 | run_list: 144 | - recipe[apt] 145 | - recipe[pgtest::test] 146 | attributes: 147 | pgtest: 148 | version: '9.2' 149 | verifier: 150 | attributes: 151 | pg_version: 9.2 152 | excludes: 153 | # There is no official postgresql-9.2 package for Ubuntu 18.04 154 | - ubuntu-18.04-docker 155 | 156 | - name: pg-93-chef14 157 | provisioner: 158 | product_name: chef 159 | product_version: 14 160 | run_list: 161 | - recipe[apt] 162 | - recipe[pgtest::test] 163 | attributes: 164 | pgtest: 165 | version: '9.3' 166 | verifier: 167 | attributes: 168 | pg_version: 9.3 169 | 170 | - name: pg-94-chef14 171 | provisioner: 172 | product_name: chef 173 | product_version: 14 174 | run_list: 175 | - recipe[apt] 176 | - recipe[pgtest::test] 177 | attributes: 178 | pgtest: 179 | version: '9.4' 180 | verifier: 181 | attributes: 182 | pg_version: 9.4 183 | 184 | - name: pg-95-chef14 185 | provisioner: 186 | product_name: chef 187 | product_version: 14 188 | run_list: 189 | - recipe[apt] 190 | - recipe[pgtest::test] 191 | attributes: 192 | pgtest: 193 | version: '9.5' 194 | verifier: 195 | attributes: 196 | pg_version: 9.5 197 | 198 | - name: pg-96-chef14 199 | provisioner: 200 | product_name: chef 201 | product_version: 14 202 | run_list: 203 | - recipe[apt] 204 | - recipe[pgtest::test] 205 | attributes: 206 | pgtest: 207 | version: '9.6' 208 | verifier: 209 | attributes: 210 | pg_version: 9.6 211 | 212 | - name: pg-10-chef14 213 | provisioner: 214 | product_name: chef 215 | product_version: 14 216 | run_list: 217 | - recipe[apt] 218 | - recipe[pgtest::test] 219 | attributes: 220 | pgtest: 221 | version: '10' 222 | verifier: 223 | attributes: 224 | pg_version: 10 225 | 226 | - name: pg-11-chef14 227 | provisioner: 228 | product_name: chef 229 | product_version: 14 230 | run_list: 231 | - recipe[apt] 232 | - recipe[pgtest::test] 233 | attributes: 234 | pgtest: 235 | version: '11' 236 | verifier: 237 | attributes: 238 | pg_version: 11 239 | 240 | - name: pg-91-chef15 241 | provisioner: 242 | product_name: chef 243 | product_version: 15 244 | run_list: 245 | - recipe[apt] 246 | - recipe[pgtest::test] 247 | attributes: 248 | pgtest: 249 | version: '9.1' 250 | verifier: 251 | attributes: 252 | pg_version: 9.1 253 | excludes: 254 | # There is no official postgresql-9.1 package for Debian 9 & Ubuntu 18.04 255 | - debian-9-docker 256 | - ubuntu-18.04-docker 257 | 258 | - name: pg-92-chef15 259 | provisioner: 260 | product_name: chef 261 | product_version: 15 262 | run_list: 263 | - recipe[apt] 264 | - recipe[pgtest::test] 265 | attributes: 266 | pgtest: 267 | version: '9.2' 268 | verifier: 269 | attributes: 270 | pg_version: 9.2 271 | excludes: 272 | # There is no official postgresql-9.2 package for Ubuntu 18.04 273 | - ubuntu-18.04-docker 274 | 275 | - name: pg-93-chef15 276 | provisioner: 277 | product_name: chef 278 | product_version: 15 279 | run_list: 280 | - recipe[apt] 281 | - recipe[pgtest::test] 282 | attributes: 283 | pgtest: 284 | version: '9.3' 285 | verifier: 286 | attributes: 287 | pg_version: 9.3 288 | 289 | - name: pg-94-chef15 290 | provisioner: 291 | product_name: chef 292 | product_version: 15 293 | run_list: 294 | - recipe[apt] 295 | - recipe[pgtest::test] 296 | attributes: 297 | pgtest: 298 | version: '9.4' 299 | verifier: 300 | attributes: 301 | pg_version: 9.4 302 | 303 | - name: pg-95-chef15 304 | provisioner: 305 | product_name: chef 306 | product_version: 15 307 | run_list: 308 | - recipe[apt] 309 | - recipe[pgtest::test] 310 | attributes: 311 | pgtest: 312 | version: '9.5' 313 | verifier: 314 | attributes: 315 | pg_version: 9.5 316 | 317 | - name: pg-96-chef15 318 | provisioner: 319 | product_name: chef 320 | product_version: 15 321 | run_list: 322 | - recipe[apt] 323 | - recipe[pgtest::test] 324 | attributes: 325 | pgtest: 326 | version: '9.6' 327 | verifier: 328 | attributes: 329 | pg_version: 9.6 330 | 331 | - name: pg-10-chef15 332 | provisioner: 333 | product_name: chef 334 | product_version: 15 335 | run_list: 336 | - recipe[apt] 337 | - recipe[pgtest::test] 338 | attributes: 339 | pgtest: 340 | version: '10' 341 | verifier: 342 | attributes: 343 | pg_version: 10 344 | 345 | - name: pg-11-chef15 346 | provisioner: 347 | product_name: chef 348 | product_version: 15 349 | run_list: 350 | - recipe[apt] 351 | - recipe[pgtest::test] 352 | attributes: 353 | pgtest: 354 | version: '11' 355 | verifier: 356 | attributes: 357 | pg_version: 11 358 | -------------------------------------------------------------------------------- /.kitchen.yml: -------------------------------------------------------------------------------- 1 | --- 2 | driver_plugin: vagrant 3 | 4 | provisioner: 5 | name: chef_zero 6 | chef_license: accept 7 | 8 | verifier: 9 | name: inspec 10 | inspec_tests: 11 | - path: test/integration/postgresql_lwrp_test 12 | 13 | platforms: 14 | - name: ubuntu-14.04 15 | driver: 16 | box: bento/ubuntu-14.04 17 | - name: ubuntu-16.04 18 | driver: 19 | box: bento/ubuntu-16.04 20 | - name: ubuntu-18.04 21 | driver: 22 | box: bento/ubuntu-18.04 23 | - name: debian-9 24 | driver: 25 | box: bento/debian-9 26 | 27 | suites: 28 | - name: pg-96-chef13 29 | provisioner: 30 | product_name: chef 31 | product_version: 13 32 | run_list: 33 | - recipe[apt] 34 | - recipe[pgtest::test] 35 | attributes: 36 | pgtest: 37 | version: '9.6' 38 | verifier: 39 | attributes: 40 | pg_version: 9.6 41 | 42 | - name: pg-10-chef13 43 | provisioner: 44 | product_name: chef 45 | product_version: 13 46 | run_list: 47 | - recipe[apt] 48 | - recipe[pgtest::test] 49 | attributes: 50 | pgtest: 51 | version: '10' 52 | verifier: 53 | attributes: 54 | pg_version: 10 55 | 56 | - name: pg-11-chef13 57 | provisioner: 58 | product_name: chef 59 | product_version: 13 60 | run_list: 61 | - recipe[apt] 62 | - recipe[pgtest::test] 63 | attributes: 64 | pgtest: 65 | version: '11' 66 | verifier: 67 | attributes: 68 | pg_version: 11 69 | 70 | - name: pg-91-chef14 71 | provisioner: 72 | product_name: chef 73 | product_version: 14 74 | run_list: 75 | - recipe[apt] 76 | - recipe[pgtest::test] 77 | attributes: 78 | pgtest: 79 | version: '9.1' 80 | verifier: 81 | attributes: 82 | pg_version: 9.1 83 | excludes: 84 | # There is no official postgresql-9.1 package for Debian 9 & Ubuntu 18.04 85 | - debian-9 86 | - ubuntu-18.04 87 | 88 | - name: pg-92-chef14 89 | provisioner: 90 | product_name: chef 91 | product_version: 14 92 | run_list: 93 | - recipe[apt] 94 | - recipe[pgtest::test] 95 | attributes: 96 | pgtest: 97 | version: '9.2' 98 | verifier: 99 | attributes: 100 | pg_version: 9.2 101 | excludes: 102 | # There is no official postgresql-9.2 package for Ubuntu 18.04 103 | - ubuntu-18.04 104 | 105 | - name: pg-93-chef14 106 | provisioner: 107 | product_name: chef 108 | product_version: 14 109 | run_list: 110 | - recipe[apt] 111 | - recipe[pgtest::test] 112 | attributes: 113 | pgtest: 114 | version: '9.3' 115 | verifier: 116 | attributes: 117 | pg_version: 9.3 118 | 119 | - name: pg-94-chef14 120 | provisioner: 121 | product_name: chef 122 | product_version: 14 123 | run_list: 124 | - recipe[apt] 125 | - recipe[pgtest::test] 126 | attributes: 127 | pgtest: 128 | version: '9.4' 129 | verifier: 130 | attributes: 131 | pg_version: 9.4 132 | 133 | - name: pg-95-chef14 134 | provisioner: 135 | product_name: chef 136 | product_version: 14 137 | run_list: 138 | - recipe[apt] 139 | - recipe[pgtest::test] 140 | attributes: 141 | pgtest: 142 | version: '9.5' 143 | verifier: 144 | attributes: 145 | pg_version: 9.5 146 | 147 | - name: pg-96-chef14 148 | provisioner: 149 | product_name: chef 150 | product_version: 14 151 | run_list: 152 | - recipe[apt] 153 | - recipe[pgtest::test] 154 | attributes: 155 | pgtest: 156 | version: '9.6' 157 | verifier: 158 | attributes: 159 | pg_version: 9.6 160 | 161 | - name: pg-10-chef14 162 | provisioner: 163 | product_name: chef 164 | product_version: 14 165 | run_list: 166 | - recipe[apt] 167 | - recipe[pgtest::test] 168 | attributes: 169 | pgtest: 170 | version: '10' 171 | verifier: 172 | attributes: 173 | pg_version: 10 174 | 175 | - name: pg-11-chef14 176 | provisioner: 177 | product_name: chef 178 | product_version: 14 179 | run_list: 180 | - recipe[apt] 181 | - recipe[pgtest::test] 182 | attributes: 183 | pgtest: 184 | version: '11' 185 | verifier: 186 | attributes: 187 | pg_version: 11 188 | 189 | - name: pg-91-chef15 190 | provisioner: 191 | product_name: chef 192 | product_version: 15 193 | run_list: 194 | - recipe[apt] 195 | - recipe[pgtest::test] 196 | attributes: 197 | pgtest: 198 | version: '9.1' 199 | verifier: 200 | attributes: 201 | pg_version: 9.1 202 | excludes: 203 | # There is no official postgresql-9.1 package for Debian 9 & Ubuntu 18.04 204 | - debian-9 205 | - ubuntu-18.04 206 | 207 | - name: pg-92-chef15 208 | provisioner: 209 | product_name: chef 210 | product_version: 15 211 | run_list: 212 | - recipe[apt] 213 | - recipe[pgtest::test] 214 | attributes: 215 | pgtest: 216 | version: '9.2' 217 | verifier: 218 | attributes: 219 | pg_version: 9.2 220 | excludes: 221 | # There is no official postgresql-9.2 package for Ubuntu 18.04 222 | - ubuntu-18.04 223 | 224 | - name: pg-93-chef15 225 | provisioner: 226 | product_name: chef 227 | product_version: 15 228 | run_list: 229 | - recipe[apt] 230 | - recipe[pgtest::test] 231 | attributes: 232 | pgtest: 233 | version: '9.3' 234 | verifier: 235 | attributes: 236 | pg_version: 9.3 237 | 238 | - name: pg-94-chef15 239 | provisioner: 240 | product_name: chef 241 | product_version: 15 242 | run_list: 243 | - recipe[apt] 244 | - recipe[pgtest::test] 245 | attributes: 246 | pgtest: 247 | version: '9.4' 248 | verifier: 249 | attributes: 250 | pg_version: 9.4 251 | 252 | - name: pg-95-chef15 253 | provisioner: 254 | product_name: chef 255 | product_version: 15 256 | run_list: 257 | - recipe[apt] 258 | - recipe[pgtest::test] 259 | attributes: 260 | pgtest: 261 | version: '9.5' 262 | verifier: 263 | attributes: 264 | pg_version: 9.5 265 | 266 | - name: pg-96-chef15 267 | provisioner: 268 | product_name: chef 269 | product_version: 15 270 | run_list: 271 | - recipe[apt] 272 | - recipe[pgtest::test] 273 | attributes: 274 | pgtest: 275 | version: '9.6' 276 | verifier: 277 | attributes: 278 | pg_version: 9.6 279 | 280 | - name: pg-10-chef15 281 | provisioner: 282 | product_name: chef 283 | product_version: 15 284 | run_list: 285 | - recipe[apt] 286 | - recipe[pgtest::test] 287 | attributes: 288 | pgtest: 289 | version: '10' 290 | verifier: 291 | attributes: 292 | pg_version: 10 293 | 294 | - name: pg-11-chef15 295 | provisioner: 296 | product_name: chef 297 | product_version: 15 298 | run_list: 299 | - recipe[apt] 300 | - recipe[pgtest::test] 301 | attributes: 302 | pgtest: 303 | version: '11' 304 | verifier: 305 | attributes: 306 | pg_version: 11 307 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | --- 2 | Naming/PredicateName: 3 | Enabled: false 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | services: 2 | - docker 3 | 4 | language: ruby 5 | cache: bundler 6 | sudo: false 7 | rvm: 8 | - 2.6.3 9 | bundler_args: "--without kitchen_vagrant kitchen_cloud aws" 10 | 11 | before_install: 12 | - gem update --system 13 | - gem install bundler 14 | 15 | script: "bundle exec kitchen test ${INSTANCE} --destroy=always" 16 | 17 | env: 18 | global: 19 | - KITCHEN_YAML=.kitchen.docker.yml 20 | - secure: Ag8Zn5lQxHrj+Xi79FAKiyq1mJxos+eQe6rL3xrk0aDL10YuAbbYz32jOce1MlIvTsEl4uaVQXQXIJmHjbCqzf5m61FYqRMqVhQ8B+Sh3AYiGe1MYpl9KJSYleyJPqJBDBZ/xfLpDKX7PoUCgXpHm2ZION2YFBHdRx3tN/lejoU= 21 | - secure: FAO//JQqC+rY3ce19GjA/KWpNskY2nT+8i83WyueHZ20zIc6FMdkWOs+WcEcnfcXDG8GBCqiIG+wF1GajUWirWo+aEIjIguADJhZwuLqD4g6KDmRW9p1MB2b3XDvAbvzze7yy6uG9GcuhiKo+J3S33YugFD59UHGa7hCGeEukAo= 22 | matrix: 23 | - INSTANCE=pg-11-chef15-ubuntu-1604 24 | - INSTANCE=pg-11-chef15-debian-9 25 | - INSTANCE=pg-11-chef15-debian-8 26 | - INSTANCE=pg-10-chef15-ubuntu-1604 27 | - INSTANCE=pg-10-chef15-debian-9 28 | - INSTANCE=pg-10-chef15-debian-8 29 | - INSTANCE=pg-96-chef15-ubuntu-1604 30 | - INSTANCE=pg-96-chef15-ubuntu-1404 31 | - INSTANCE=pg-96-chef15-debian-9 32 | - INSTANCE=pg-96-chef15-debian-8 33 | - INSTANCE=pg-93-chef14-ubuntu-1404 34 | - INSTANCE=pg-93-chef14-ubuntu-1604 35 | - INSTANCE=pg-93-chef14-debian-8 36 | - INSTANCE=pg-93-chef14-debian-9 37 | - INSTANCE=pg-94-chef14-ubuntu-1604 38 | - INSTANCE=pg-94-chef14-debian-8 39 | - INSTANCE=pg-94-chef14-debian-9 40 | - INSTANCE=pg-95-chef14-ubuntu-1604 41 | - INSTANCE=pg-95-chef14-debian-8 42 | - INSTANCE=pg-95-chef14-debian-9 43 | - INSTANCE=pg-96-chef14-ubuntu-1404 44 | - INSTANCE=pg-96-chef14-ubuntu-1604 45 | - INSTANCE=pg-96-chef14-debian-8 46 | - INSTANCE=pg-96-chef14-debian-9 47 | - INSTANCE=pg-10-chef14-ubuntu-1604 48 | - INSTANCE=pg-10-chef14-debian-8 49 | - INSTANCE=pg-10-chef14-debian-9 50 | - INSTANCE=pg-11-chef14-ubuntu-1604 51 | - INSTANCE=pg-11-chef14-ubuntu-1604 52 | - INSTANCE=pg-11-chef14-debian-8 53 | - INSTANCE=pg-11-chef14-debian-9 54 | - INSTANCE=pg-96-chef13-ubuntu-1404 55 | - INSTANCE=pg-96-chef13-ubuntu-1604 56 | - INSTANCE=pg-96-chef13-debian-8 57 | - INSTANCE=pg-96-chef13-debian-9 58 | - INSTANCE=pg-10-chef13-ubuntu-1604 59 | - INSTANCE=pg-10-chef13-debian-8 60 | - INSTANCE=pg-10-chef13-debian-9 61 | - INSTANCE=pg-11-chef13-ubuntu-1404 62 | - INSTANCE=pg-11-chef13-ubuntu-1604 63 | - INSTANCE=pg-11-chef13-debian-8 64 | - INSTANCE=pg-11-chef13-debian-9 65 | 66 | jobs: 67 | include: 68 | - stage: lint 69 | script: 70 | - bundle exec cookstyle --version 71 | - bundle exec cookstyle 72 | - bundle exec foodcritic --version 73 | - bundle exec foodcritic --contex --epic-fail any . 74 | 75 | - stage: deploy 76 | if: tag =~ ^[0-9]\.[0-9]\.[0-9]$ 77 | script: 78 | - openssl aes-256-cbc -K $encrypted_b45d6f20bf91_key -iv $encrypted_b45d6f20bf91_iv 79 | -in .travis/secret_keys.tar.enc -out .travis/secret_keys.tar -d 80 | - tar xf .travis/secret_keys.tar -C .travis 81 | - bundle exec stove login --username express42 --key .travis/express42.pem 82 | - bundle exec stove --no-git 83 | 84 | notifications: 85 | slack: 86 | secure: M7REbNB5Wf6eMBxQijnquWw4KlBbrw5zLM6fWqB1CjA3IF7DbYFUgvBTeUAxTFE9P2KEahYydPRkhzDtQ49PpE31g93yfy1yqQ/1rBHuzmZztR1hpwzYAhzGg4cak/47LQPaHx6Y9Pa83N8BPwpE4t9936wB3gXBlQERQdGdmoE= 87 | -------------------------------------------------------------------------------- /.travis/secret_keys.tar.enc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/express42/postgresql_lwrp/1b1a55e75247fc21850c9e72a8e5fcf0aa71178d/.travis/secret_keys.tar.enc -------------------------------------------------------------------------------- /Berksfile: -------------------------------------------------------------------------------- 1 | source 'https://supermarket.chef.io' 2 | 3 | metadata 4 | 5 | group :integration do 6 | cookbook 'sysctl' 7 | cookbook 'pgtest', path: 'test/fixtures/cookbooks/pgtest' 8 | end 9 | 10 | group :compat do 11 | cookbook 'build-essential' 12 | cookbook 'apt' 13 | cookbook 'ohai' 14 | end 15 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.6.3 (Aug 19, 2019) 2 | 3 | * (New) wal-g version 0.2.11 4 | * (New) bundle update 5 | 6 | ## 1.6.2 (Aug 05, 2019) 7 | 8 | * (Fix) more typos in attributes 9 | 10 | ## 1.6.1 (Jul 30, 2019) 11 | 12 | * (Fix) typos in attributes. 13 | 14 | ## 1.6.0 (Jul 03, 2019) 15 | 16 | * (Deprecation) `postgresql_cloud_backup` attribute `protocol` was removed. It was used only for a partial env vars validation. 17 | 18 | ## 1.5.4 (Jun 28, 2019) 19 | 20 | * Travis config: tests for Ubuntu 18.04 were removed again. Run local ones. 21 | 22 | ## 1.5.3 (Jun 27, 2019) 23 | 24 | * (New) WAL-G version 0.2.9 25 | * (New) `wal-g wal-push` fix by injecting the `PATH` variable into the envdir 26 | * (New) ruby 2.6.3 for TravisCI 27 | * (New) Test Kitchen suites for Chef 15 28 | 29 | ## 1.5.2 (Feb 25, 2019) 30 | 31 | * (New) tests for PostgreSQL 9.1 & 9.2 were dropped from TravisCI configuration 32 | * (New) WAL-G version 0.2.6 which has new GPG support implementation 33 | 34 | ## 1.5.1 (Feb 17, 2019) 35 | 36 | * (Fix) envdir's files should be marked as sensitive. 37 | * (Fix) Set PGHOST environment variable to `/var/run/postgresql` for wal-g if unset. 38 | 39 | ## 1.5.0 (Feb 16, 2019) 40 | 41 | * (New) PostgreSQL 11 tests 42 | * (New) wal-g support for cloud backup 43 | * (New) Ruby 2.6.0 for TravisCI tests 44 | * (New) Inspec configuration to test running PostgreSQL configuration 45 | * (Fix) pip version 18.0 was pinned as the newer fails to install to sandbox 46 | * (Fix) Some minor ruby style fixes 47 | * (Fix) Other pg extension should be installed during integration tests 48 | 49 | ## 1.4.2 (May 22, 2018) 50 | 51 | * (New) Ubuntu 18.04 tests 52 | * (New) Test Kitchen configuration for AWS cloud was removed 53 | * (Fix) cookbook `poise-python` version was restricted again `>= 1.7.0` 54 | * (Fix) Some minor Test Kitchen configuration improvements 55 | 56 | ## 1.4.1 (May 15, 2018) 57 | 58 | * (Fix) Unpin `poise-python` cookbook version 59 | 60 | ## 1.4.0 (Apr 10, 2018) 61 | 62 | * (New) Chef 14 support & tests 63 | * (New) LWRP-defined resources were rewritten using the new custom resource style 64 | * (Fix) TravisCI: all Chef 13 test were enabled 65 | 66 | ## 1.3.1 (Apr 03, 2018) 67 | * (Fix) TravisCI build method. Now using `sethvargo/stove` gem instead of `dpl` 68 | 69 | ## 1.3.0 (Apr 03, 2018) 70 | * (New) Chef 12 test were dropped. (Chef 12 reaches EOL at the and of April 2018) 71 | * (New) Chef 13 full support; 72 | * (New) WAL-E version 1.1.0; 73 | * (New) `poise-python` cookbook is used instead of outdated `python`; 74 | * (Fix) TravisCI configuration was updated to test against more OSes and to use Chef 13; 75 | * (Fix) Berksfile version pins were removed; 76 | 77 | ## 1.2.4 (Mar 12, 2018) 78 | * (Fix) Rename `params` method in `postgresql_cloud_backup` for compatibility with Chef 13 79 | 80 | ## 1.2.3 (Jan 31, 2018) 81 | * (Fix) Resource `postgresql` PostgreSQL version validation. 82 | * (Fix) Use resource attributes to set PostgreSQL version for test purposes. 83 | 84 | ## 1.2.2 (Jan 16, 2018) 85 | * (New) PostgreSQL 10 support. 86 | * (New) Integration tests were migrated to InSpec. 87 | * (New) InSpec resources: postgres_database 88 | * (Fix) InSpec resources: `postgres_cluster`, `postgres_extension` & `postgres_user` were refactored. 89 | * (Fix) Test Kitchen: use one test recipe instead of one-recipe-per-pg-version. 90 | * (Fix) Test Kitchen: use only official images. 91 | * (Fix) Test Kitchen: tests for Chef 11 support were removed, as outdated. 92 | * (Fix) Test Kitchen: Test for Postgresql 9.0 were removed; there is PostgreSQL 9.0 package on modern systems. 93 | * (Fix) [postgresql] fix ruby_block notifications. 94 | * (Fix) [pgtest] user creation should be invoked using `encrypted_password` attribute for better compatibility. 95 | 96 | ## 1.2.1 (Dec 15, 2016) 97 | * (New) Autoremove checkpoint_segments from configuration if pg > 9.4 98 | 99 | ## 1.2.0 (Jul 22, 2016) 100 | * (New) Add extension lwrp to install extensions from postgresql-contrib subpackage, which comes installed 101 | * (New) Add pgxn extension lwrp to install extensions from pgxn.org website, using pgxn client 102 | * (New) Add test recipes for installing extensions with newly introduced resources 103 | 104 | ## 1.1.15 (Sep 24, 2015) 105 | * (Fix) [postgresql] Fix initial slave creation on 9.1 106 | 107 | ## 1.1.14 (Aug 11, 2015) 108 | * (Fix) [postgresql_database] Fix database existence 109 | 110 | ## 1.1.13 (Jul 17, 2015) 111 | * (Fix) [common] Fix run under Chef 11 112 | 113 | ## 1.1.12 (Jun 27, 2015) 114 | * (Fix) [common] Fix compatibility with Chef 12.4.0 115 | * (New) [postgresql_user] Use inline resources in user provider 116 | 117 | ## 1.1.11 (Apr 7, 2015) 118 | * (New) [common] Return to LR for all resources 119 | 120 | ## 1.1.10 (Apr 5, 2015) 121 | * (New) [cloud_backup] Add backup retention 122 | * (New) [cloud_backup] Add postgresql_cloud_backup_helper.sh (See README) 123 | * (Fix) [cloud_backup] install libffi-dev package for cffi 124 | * (Fix) [common] Fix reload on Chef 12 125 | 126 | ## 1.1.9 (Mar 5, 2015) 127 | * (Fix) [metadata] Fix recipes name 128 | 129 | ## 1.1.8 (Mar 3, 2015) 130 | * (New) [Replication] Add primary_slot_name param support in recovery.conf 131 | * (New) [cloud_backup] Add add prefix to crontab command 132 | * (Fix) [packages] Install dev package only for actual cluster version 133 | 134 | ## 1.1.7 (Jan 22, 2015) 135 | * (Fix) Remove wal-e pip attribute 136 | * (New) Add serverspec tests for cloud backup 137 | 138 | ## 1.1.6 (Jan 21, 2015) 139 | * (New) Use virtualenv for wal-e 140 | * (New) Add test recipe for cloud backup 141 | 142 | ## 1.1.5 (Dec 28, 2014) 143 | * (Fix) Fix cloud_backup cron script name 144 | 145 | ## 1.1.4 (Dec 22, 2014) 146 | * (Fix) Fix pg version checks 147 | 148 | ## 1.1.3 (Dec 22, 2014) 149 | * (New) ssl key and cert linkage for pg < 9.2 150 | 151 | ## 1.1.2 (Dec 22, 2014) 152 | * (Fix) Fix full_backup_time param 153 | 154 | ## 1.1.1 (Dec 18, 2014) 155 | * (Fix) Fix Test Kitchen boxes 156 | * (Fix) Fix postgresql start after reboot 157 | 158 | ## 1.1.0 (Dec 10, 2014) 159 | * (New) Add cloud backup lwrp, using wal-e for cloud backup 160 | 161 | ## 1.0.1 (Oct 31, 2014) 162 | * (Fix) Fix broken allow_restart_cluster option 163 | 164 | ## 1.0.0 (Aug 25, 2014) 165 | 166 | * (New) Flat configuration file 167 | * (New) Initial replicaton can be started automatically 168 | * (New) Option allow_restart_cluster allows do restart instead reload (Only first time or always) 169 | * (New) Resources/providers for database and user creation 170 | * (New) Recipe apt_official_repository with official postgresql repository 171 | * (New) Severspec tests added 172 | * (Removed) Removed databags for users and databases. You should use appropriate providers 173 | * (Fix) pg_ident template fixed 174 | 175 | ## 0.2.3 (Jun 18, 2013) 176 | 177 | ### Minor fixes 178 | 179 | * Cluster create options were defined as Hash and accessed as Mash. 180 | * pg_hba.conf became faulty on long db/user names or other line fields. 181 | * Examples in readme was badly formatted and contained small syntax issues. 182 | * ssl was hardcoded to postgresql.conf. 183 | 184 | ## 0.2.2 (May 8, 2013) 185 | 186 | ### Minor fixes 187 | 188 | * Check cluster_create_options hash for key before accessing it. 189 | 190 | ## 0.2.1 (Apr 14, 2013) 191 | 192 | ### Minor fixes 193 | 194 | * Style fixes to satisfy foodcritic wishes 195 | 196 | ## 0.2.0 (Apr 14, 2013) 197 | 198 | ### Improvements 199 | 200 | * Set LANG from cluster_create for postgresql package install(used in pg_clustercreate in debian scripts) 201 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | If you wish to contribute, please follow these steps: 2 | 3 | 1. Fork this repository on GitHub. 4 | 2. Create a new branch. 5 | 3. Write your code. 6 | 4. Write new tests. 7 | 5. Run tests from [TESTING.md](https://github.com/express42/postgresql_lwrp/blob/master/TESTING.md). 8 | 6. If everythig is Ok, commit and create a pull request. 9 | 10 | Thank you! -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | group :berks do 3 | gem 'berkshelf' 4 | end 5 | group :unit do 6 | gem 'chefspec' 7 | end 8 | group :inspec do 9 | gem 'inspec', '~>4.7.24' 10 | gem 'kitchen-inspec' 11 | end 12 | group :lint do 13 | gem 'foodcritic' 14 | gem 'cookstyle' 15 | end 16 | group :kitchen_docker do 17 | gem 'kitchen-docker' 18 | end 19 | group :kitchen_common do 20 | gem 'test-kitchen' 21 | end 22 | group :kitchen_vagrant do 23 | gem 'kitchen-vagrant' 24 | end 25 | group :deploy do 26 | gem 'stove' 27 | end 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Express 42 public cookbooks 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /MAINTAINERS: -------------------------------------------------------------------------------- 1 | sample 2 | whitew1nd 3 | ikurochkin 4 | serjs 5 | osminog 6 | evtuhovich 7 | dragonsmith 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Chef cookbook](https://img.shields.io/cookbook/v/postgresql_lwrp.svg)](https://github.com/express42/postgresql_lwrp) 2 | [![Code Climate](https://codeclimate.com/github/express42/postgresql_lwrp/badges/gpa.svg)](https://codeclimate.com/github/express42/postgresql_lwrp) 3 | [![Build Status](https://travis-ci.org/express42/postgresql_lwrp.svg)](https://travis-ci.org/express42/postgresql_lwrp) 4 | 5 | Description 6 | =========== 7 | This cookbook includes recipes and providers to install and configure postgresql database. This cookbook was tested with Postgresql 9.1, 9.2, 9.3, 9.4, 9.5, 9.6 & 10. 8 | 9 | Supported platforms: 10 | 11 | * Debian 8 12 | * Debian 9 13 | * Ubuntu 14.04 14 | * Ubuntu 16.04 15 | * Ubuntu 18.04 16 | 17 | *Note: TravisCI tests for Ubuntu 18.04 are omitted now because they somehow hang. Local Vagrant & Docker-based tests are succesfull. This will be investigated further.* 18 | 19 | Changelog 20 | ========= 21 | See [CHANGELOG.md](https://github.com/express42/postgresql_lwrp/blob/master/CHANGELOG.md) 22 | 23 | Requirements 24 | ============ 25 | 26 | The minimal recommended version of chef-client is `13.0.113`. It may still work on version `12.5.1` and older, but no tests are made starting from version `1.3.0` of this cookbook as Chef 12 is reaching its EOL in the April, 2018 27 | 28 | Dependencies 29 | ============ 30 | 31 | * apt 32 | * cron 33 | * poise-python 34 | 35 | Attributes 36 | ========== 37 | This cookbook have server and client attribute files. 38 | 39 | With client attributes(["postgresql"]["client"]) you can set only postgresql client and library version. 40 | 41 | Server attributes are starting from ["postgresql"]["defaults"] and used as default attributes for postgresql provider. You should not override this defaults, you can pass your settings to provider instead. 42 | 43 | Resources/Providers 44 | =================== 45 | 46 | ### Resource: default 47 | 48 | #### Actions 49 | 50 | - :create: creates postgresql cluster 51 | 52 | #### Resource parameters 53 | 54 | - cluster_name: name attribute. Cluster name (e.g. main). Be aware, systemd (in Ubuntu 16.04 and Debian Jessie) not working with cluster names that containing dashes ('-'). 55 | - cluster_version: set cluster version 56 | - cookbook: cookbook for templates. Skip this for default templates. 57 | - cluster_create_options: options for pg_createcluster (only locale related options) 58 | - configuration: Hash with configuration options for postgresql, see examples. 59 | - hba_configuration: Array with hba configuration, see examples. 60 | - ident_configuration: Array with ident configuration, see examples. 61 | - replication: Hash with replication configuration. See replication example. 62 | - replication_initial_copy: Boolean. If `true` pg_basebackup will be exec to make initial replication copy. Default is `false`. 63 | - replication_start_slave: Boolean. If `true` slave cluster will be started after creation. Should be used with replication_initial_copy option. Default `false`. 64 | - allow_restart_cluster: Can be `first`, `always` or `none`. Specifies when cluster must restart instead of reload. `first` – only first time after installation. `always` – always restart, even if changes doesn't require restart. `none` - never, use reload every time. Default is `none`. 65 | 66 | 67 | Other 68 | ===== 69 | ### Cloud backup helper: 70 | 71 | `postgresql_cloud_backup_helper.sh` helper can be found at `/opt/wal-e/bin/`. 72 | 73 | #### Usage: 74 | 75 | `postgresql_cloud_backup_helper.sh last|count` 76 | 77 | - `cluster_name` – postgresql cluster name (ex. *main*) 78 | - `cluser_version` – postgresql cluser version (ex. 9.3) 79 | - `last` – shows last backup time 80 | - `count` – shows total number of backups. 81 | 82 | Examples 83 | ======== 84 | Example master database setup: 85 | 86 | ```ruby 87 | postgresql 'main' do 88 | cluster_version '9.3' 89 | cluster_create_options( locale: 'ru_RU.UTF-8' ) 90 | configuration( 91 | listen_addresses: '192.168.0.2', 92 | max_connections: 300, 93 | ssl_renegotiation_limit: 0, 94 | shared_buffers: '512MB', 95 | maintenance_work_mem: '64MB', 96 | work_mem: '8MB', 97 | log_min_duration_statement: 200 98 | ) 99 | hba_configuration( 100 | [ 101 | { type: 'host', database: 'all', user: 'all', address: '192.168.0.0/24', method: 'md5' }, 102 | { type: 'host', database: 'replication', user: 'postgres', address: '192.168.0.3/32', method: 'trust' } 103 | ] 104 | ) 105 | end 106 | ``` 107 | 108 | Example slave database setup: 109 | 110 | ```ruby 111 | postgresql 'main' do 112 | cluster_version '9.3' 113 | cluster_create_options( locale: 'ru_RU.UTF-8' ) 114 | configuration( 115 | listen_addresses: '192.168.0.3', 116 | max_connections: 300, 117 | ssl_renegotiation_limit: 0, 118 | shared_buffers: '512MB', 119 | maintenance_work_mem: '64MB', 120 | work_mem: '8MB', 121 | log_min_duration_statement: 200 122 | ) 123 | hba_configuration( 124 | [ 125 | { type: 'host', database: 'all', user: 'all', address: '192.168.0.0/24', method: 'md5' }, 126 | { type: 'host', database: 'replication', user: 'postgres', address: '192.168.0.2/32', method: 'trust' } 127 | ] 128 | ) 129 | replication( 130 | standby_mode: 'on', 131 | primary_conninfo: 'host=192.168.0.1', 132 | trigger_file: '/tmp/pgtrigger' 133 | ) 134 | replication_initial_copy true 135 | replication_start_slave true 136 | end 137 | ``` 138 | 139 | Example slave configuration with replication slots (PostgreSQL >= 9.4) 140 | 141 | ```ruby 142 | replication( 143 | standby_mode: 'on', 144 | primary_conninfo: 'host=192.168.0.1', 145 | trigger_file: '/tmp/pgtrigger' 146 | primary_slot_name: 'some_slot_on_master' 147 | ) 148 | ``` 149 | Don't forget to create slot on master server before: 150 | 151 | ```sql 152 | # SELECT pg_create_physical_replication_slot('some_slot_on_master'); 153 | ``` 154 | 155 | Example users and databases setup 156 | 157 | ```ruby 158 | postgresql_user 'user01' do 159 | in_version '9.3' 160 | in_cluster 'main' 161 | unencrypted_password 'user01password' 162 | end 163 | 164 | postgresql_database 'database01' do 165 | in_version '9.3' 166 | in_cluster 'main' 167 | owner 'user01' 168 | end 169 | ``` 170 | 171 | Example full daily database backup 172 | 173 | ```ruby 174 | postgresql_cloud_backup 'main' do 175 | utility 'wal-g' 176 | in_version '9.3' 177 | in_cluster 'main' 178 | full_backup_time weekday: '*', month: '*', day: '*', hour: '3', minute: '0' 179 | # Data bag item should contain following keys for S3 protocol: 180 | # aws_access_key_id, aws_secret_access_key, wale_s3_prefix 181 | parameters Chef::EncryptedDataBagItem.load('s3', 'secrets').to_hash.select {|i| i != "id"} 182 | # Or just a hash, if you don't use data bags: 183 | parameters { aws_access_key_id: 'access_key', aws_secret_access_key: 'secret_key', walg_s3_prefix: 's3_prefix' } 184 | # In case you need to prepend wal-e with, for example, traffic limiter 185 | # you can use following method: 186 | command_prefix 'trickle -s -u 1024' 187 | # It will be prepended to resulting wal-e execution in cron task 188 | end 189 | ``` 190 | 191 | Example usage of cloud backup helper usage 192 | 193 | ```bash 194 | $ /opt/wal-e/bin/postgresql_cloud_backup_helper.sh main 9.3 last 195 | 1428192159 196 | $ /opt/wal-e/bin/postgresql_cloud_backup_helper.sh main 9.3 count 197 | 31 198 | ``` 199 | 200 | Example of how to install extensions from postgresql-contrib 201 | NOTE: schema and version are optional parameters, but others are required 202 | 203 | ```ruby 204 | postgresql_extension 'cube' do 205 | in_version '9.4' 206 | in_cluster 'main' 207 | db 'test01' 208 | schema 'public' 209 | end 210 | ``` 211 | Example of how to install extensions from http://pgxn.org/ 212 | NOTE: schema is an optional parameter, but others are required 213 | 214 | ```ruby 215 | pgxn_extension 'pg_lambda' do 216 | in_version '9.4' 217 | in_cluster 'main' 218 | db 'test01' 219 | version '1.0.2' 220 | stage 'stable' 221 | end 222 | ``` 223 | 224 | 225 | # License and Maintainer 226 | 227 | Maintainer:: LLC Express 42 () 228 | Source:: https://github.com/express42/postgresql_lwrp 229 | Issues:: https://github.com/express42/postgresql_lwrp/issues 230 | 231 | License:: MIT 232 | -------------------------------------------------------------------------------- /TESTING.md: -------------------------------------------------------------------------------- 1 | ## Testing 2 | 3 | To run all tests execute: 4 | 5 | ```shell 6 | bundle install 7 | 8 | bundle exec cookstyle && \ 9 | bundle exec foodcritic --contex --epic-fail any . && \ 10 | bundle exec kitchen test 11 | ``` 12 | -------------------------------------------------------------------------------- /Thorfile: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'bundler' 4 | require 'bundler/setup' 5 | require 'berkshelf/thor' 6 | 7 | begin 8 | require 'kitchen/thor_tasks' 9 | Kitchen::ThorTasks.new 10 | rescue LoadError 11 | puts '>>>>> Kitchen gem not loaded, omitting tasks' unless ENV['CI'] 12 | end 13 | -------------------------------------------------------------------------------- /attributes/client.rb: -------------------------------------------------------------------------------- 1 | default['postgresql']['client']['version'] = '9.2' 2 | -------------------------------------------------------------------------------- /attributes/cloud_backup_wal_e.rb: -------------------------------------------------------------------------------- 1 | # https://github.com/poise/poise-python/issues/133 2 | default['poise-python']['options']['pip_version'] = '18.0' 3 | 4 | default['postgresql']['cloud_backup']['wal_e']['packages'] = %w( 5 | gcc 6 | lzop 7 | mbuffer 8 | pv 9 | python3-dev 10 | libffi-dev 11 | libssl-dev 12 | ) 13 | 14 | default['postgresql']['cloud_backup']['wal_e']['pypi_packages'] = %w( 15 | boto 16 | ) 17 | 18 | default['postgresql']['cloud_backup']['wal_e']['install_source'] = 'pypi' 19 | default['postgresql']['cloud_backup']['wal_e']['version'] = '1.1.0' 20 | default['postgresql']['cloud_backup']['wal_e']['bin'] = '/opt/wal-e/bin/wal-e' 21 | default['postgresql']['cloud_backup']['wal_e']['path'] = '/opt/wal-e' 22 | default['postgresql']['cloud_backup']['wal_e']['github_repo'] = 'https://github.com/wal-e/wal-e' 23 | -------------------------------------------------------------------------------- /attributes/cloud_backup_wal_g.rb: -------------------------------------------------------------------------------- 1 | default['postgresql']['cloud_backup']['wal_g']['packages'] = %w( 2 | ) 3 | 4 | default['postgresql']['cloud_backup']['wal_g']['version'] = 'v0.2.11' 5 | default['postgresql']['cloud_backup']['wal_g']['url'] = "https://github.com/wal-g/wal-g/releases/download/#{node['postgresql']['cloud_backup']['wal_g']['version']}/wal-g.linux-amd64.tar.gz" 6 | default['postgresql']['cloud_backup']['wal_g']['checksum'] = '313a617311ad58005c407c2e9b06b3556785559b051e5fae5d147c17ba36f2bc' 7 | default['postgresql']['cloud_backup']['wal_g']['bin'] = '/usr/local/bin/wal-g' 8 | -------------------------------------------------------------------------------- /attributes/server.rb: -------------------------------------------------------------------------------- 1 | default['postgresql']['defaults']['server']['version'] = '9.2' 2 | 3 | default['postgresql']['defaults']['server']['configuration'] = { 4 | listen_addresses: 'localhost', 5 | port: 5432, 6 | max_connections: 100, 7 | shared_buffers: '100MB', 8 | temp_buffers: '8MB', 9 | max_prepared_transactions: 0, 10 | work_mem: '2MB', 11 | maintenance_work_mem: '64MB', 12 | max_stack_depth: '2MB', 13 | max_files_per_process: '1000', 14 | vacuum_cost_delay: 0, 15 | wal_level: 'hot_standby', 16 | fsync: 'on', 17 | synchronous_commit: 'on', 18 | checkpoint_segments: '64', 19 | wal_sync_method: 'fsync', 20 | checkpoint_completion_target: '0.9', 21 | effective_cache_size: "#{(node['memory']['total'].to_i / (2 * 1024))}MB", 22 | log_destination: 'stderr', 23 | syslog_ident: 'postgres', 24 | log_min_duration_statement: 200, 25 | log_truncate_on_rotation: 'on', 26 | log_rotation_age: '1d', 27 | log_rotation_size: 0, 28 | log_line_prefix: '%t [%p]: [%l-1]', 29 | track_activities: 'on', 30 | track_counts: 'on', 31 | autovacuum: 'on', 32 | autovacuum_naptime: '1min', 33 | archive_mode: 'off', 34 | max_wal_senders: 5, 35 | wal_keep_segments: 32, 36 | hot_standby: 'off', 37 | max_standby_archive_delay: '30s', 38 | max_standby_streaming_delay: '30s', 39 | wal_receiver_status_interval: '10s', 40 | hot_standby_feedback: 'on', 41 | extra_float_digits: 0, 42 | client_encoding: 'UTF8', 43 | ssl: true, 44 | ssl_cert_file: '/etc/ssl/certs/ssl-cert-snakeoil.pem', 45 | ssl_key_file: '/etc/ssl/private/ssl-cert-snakeoil.key', 46 | ssl_renegotiation_limit: 0, 47 | lc_messages: 'en_US.UTF-8', 48 | lc_monetary: 'en_US.UTF-8', 49 | lc_numeric: 'en_US.UTF-8', 50 | lc_time: 'en_US.UTF-8', 51 | default_text_search_config: 'pg_catalog.russian', 52 | } 53 | default['postgresql']['defaults']['server']['ident_configuration'] = [] 54 | 55 | default['postgresql']['defaults']['server']['hba_configuration'] = [ 56 | { type: 'local', database: 'all', user: 'postgres', address: '', method: 'peer' }, 57 | { type: 'local', database: 'all', user: 'all', address: '', method: 'peer' }, 58 | { type: 'host', database: 'all', user: 'all', address: '127.0.0.1/32', method: 'md5' }, 59 | { type: 'host', database: 'all', user: 'all', address: '::1/128', method: 'md5' }, 60 | ] 61 | default['postgresql']['defaults']['server']['replication'] = {} 62 | -------------------------------------------------------------------------------- /chefignore: -------------------------------------------------------------------------------- 1 | # Put files/directories that should be ignored in this file when uploading 2 | # or sharing to the community site. 3 | # Lines that start with '# ' are comments. 4 | 5 | # OS generated files # 6 | ###################### 7 | .DS_Store 8 | Icon? 9 | nohup.out 10 | ehthumbs.db 11 | Thumbs.db 12 | 13 | # SASS # 14 | ######## 15 | .sass-cache 16 | 17 | # EDITORS # 18 | ########### 19 | \#* 20 | .#* 21 | *~ 22 | *.sw[a-z] 23 | *.bak 24 | REVISION 25 | TAGS* 26 | tmtags 27 | *_flymake.* 28 | *_flymake 29 | *.tmproj 30 | .project 31 | .settings 32 | mkmf.log 33 | 34 | ## COMPILED ## 35 | ############## 36 | a.out 37 | *.o 38 | *.pyc 39 | *.so 40 | *.com 41 | *.class 42 | *.dll 43 | *.exe 44 | */rdoc/ 45 | 46 | # Testing # 47 | ########### 48 | .watchr 49 | .rspec 50 | spec/* 51 | spec/fixtures/* 52 | test/* 53 | features/* 54 | Guardfile 55 | Procfile 56 | .kitchen.yml 57 | .kitchen/* 58 | .rubocop.yml 59 | Thorfile 60 | 61 | # SCM # 62 | ####### 63 | .git 64 | */.git 65 | .gitignore 66 | .gitmodules 67 | .gitconfig 68 | .gitattributes 69 | .svn 70 | */.bzr/* 71 | */.hg/* 72 | */.svn/* 73 | 74 | # Berkshelf # 75 | ############# 76 | Berksfile 77 | Berksfile.lock 78 | cookbooks/* 79 | tmp 80 | 81 | # Cookbooks # 82 | ############# 83 | CONTRIBUTING 84 | CHANGELOG* 85 | 86 | # Strainer # 87 | ############ 88 | Colanderfile 89 | Strainerfile 90 | .colander 91 | .strainer 92 | 93 | # Vagrant # 94 | ########### 95 | .vagrant 96 | Vagrantfile 97 | 98 | # Travis # 99 | ########## 100 | .travis.yml 101 | .travis 102 | -------------------------------------------------------------------------------- /libraries/helpers.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook Name:: postgresql 3 | # Library:: helpers 4 | # 5 | # Author:: LLC Express 42 (info@express42.com) 6 | # 7 | # Copyright (C) 2012-2014 LLC Express 42 8 | # 9 | # Permission is hereby granted, free of charge, to any person obtaining a copy of 10 | # this software and associated documentation files (the "Software"), to deal in 11 | # the Software without restriction, including without limitation the rights to 12 | # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 13 | # of the Software, and to permit persons to whom the Software is furnished to do 14 | # so, subject to the following conditions: 15 | # 16 | # The above copyright notice and this permission notice shall be included in all 17 | # copies or substantial portions of the Software. 18 | # 19 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | # SOFTWARE. 26 | # 27 | 28 | class Chef 29 | # Postgresql modules 30 | module Postgresql 31 | # Helpers module 32 | module Helpers 33 | def pg_installed?(pkg_name) 34 | dpkg_status = Mixlib::ShellOut.new("dpkg-query -W -f='${Status}\n' #{pkg_name} 2>/dev/null | grep -c -q 'ok installed'") 35 | dpkg_status.run_command 36 | return false if dpkg_status.exitstatus == 0 37 | true 38 | end 39 | 40 | def systemd_used? 41 | systemd_checker = Mixlib::ShellOut.new('file /sbin/init') 42 | systemd_checker.run_command 43 | return true if systemd_checker.stdout =~ /systemd/ 44 | false 45 | end 46 | 47 | def exec_in_pg_cluster(cluster_version, cluster_name, *cluster_database, sql) 48 | return [nil, "PostgreSQL cluster #{cluster_name} not running!"] unless pg_running?(cluster_version, cluster_name) 49 | pg_port = get_pg_port(cluster_version, cluster_name) 50 | psql_status = Mixlib::ShellOut.new("echo -n \"#{sql};\" | su -c 'psql -t -p #{pg_port} #{cluster_database.first}' postgres") 51 | psql_status.run_command 52 | [psql_status.stdout, psql_status.stderr] 53 | end 54 | 55 | def get_pg_port(cluster_version, cluster_name) 56 | return [nil, nil] unless pg_running?(cluster_version, cluster_name) 57 | postmaster_content = ::File.open("/var/lib/postgresql/#{cluster_version}/#{cluster_name}/postmaster.pid").readlines 58 | postmaster_content[3].to_i 59 | end 60 | 61 | def need_to_restart?(allow_restart_cluster, first_time) 62 | return first_time if allow_restart_cluster == :first 63 | return true if allow_restart_cluster == :always 64 | false 65 | end 66 | 67 | def pg_running?(cluster_version, cluster_name) 68 | pg_status = Mixlib::ShellOut.new("su -c '/usr/lib/postgresql/#{cluster_version}/bin/pg_ctl \ 69 | -D /var/lib/postgresql/#{cluster_version}/#{cluster_name} status' postgres") 70 | pg_status.run_command 71 | return true if pg_status.stdout =~ /server\ is\ running/ 72 | false 73 | end 74 | 75 | def create_user(cluster_version, cluster_name, cluster_user, options) 76 | stdout, stderr = exec_in_pg_cluster(cluster_version, cluster_name, 'SELECT usename FROM pg_user') 77 | raise "postgresql create_user: can't get users list\nSTDOUT: #{stdout}\nSTDERR: #{stderr}" unless stderr.empty? 78 | 79 | if stdout.include? cluster_user 80 | Chef::Log.info("postgresql create_user: user '#{cluster_user}' already exists, skiping") 81 | return nil 82 | else 83 | stdout, stderr = exec_in_pg_cluster(cluster_version, cluster_name, "CREATE USER \\\"#{cluster_user}\\\" #{options.map { |t| t.join(' ') }.join(' ')}") 84 | raise "postgresql create_user: can't create user #{cluster_user}\nSTDOUT: #{stdout}\nSTDERR: #{stderr}" unless stdout.include?("CREATE ROLE\n") 85 | Chef::Log.info("postgresql create_user: user '#{cluster_user}' created") 86 | end 87 | end 88 | 89 | def create_database(cluster_version, cluster_name, cluster_database, options) 90 | stdout, stderr = exec_in_pg_cluster(cluster_version, cluster_name, 'SELECT datname FROM pg_database') 91 | raise "postgresql create_database: can't get database list\nSTDOUT: #{stdout}\nSTDERR: #{stderr}" unless stderr.empty? 92 | 93 | if stdout.gsub(/\s+/, ' ').split(' ').include? cluster_database 94 | Chef::Log.info("postgresql create_database: database '#{cluster_database}' already exists, skiping") 95 | return nil 96 | 97 | else 98 | stdout, stderr = exec_in_pg_cluster(cluster_version, cluster_name, "CREATE DATABASE \\\"#{cluster_database}\\\" #{options.map { |t| t.join(' ') }.join(' ')}") 99 | raise "postgresql create_database: can't create database #{cluster_database}\nSTDOUT: #{stdout}\nSTDERR: #{stderr}" unless stdout.include?("CREATE DATABASE\n") 100 | Chef::Log.info("postgresql create_database: database '#{cluster_database}' created") 101 | end 102 | end 103 | 104 | def extension_available?(cluster_version, cluster_name, extension) 105 | stdout, _stderr = exec_in_pg_cluster(cluster_version, cluster_name, 'SELECT name FROM pg_available_extensions') 106 | return true if stdout.include? extension 107 | false 108 | end 109 | 110 | def install_extension(cluster_version, cluster_name, cluster_database, extension, options) 111 | raise "extension '#{extension}' is not available, please use \'pgxn_extension\' resource to install the extention" unless extension_available?(cluster_version, cluster_name, extension) 112 | 113 | stdout, stderr = exec_in_pg_cluster(cluster_version, cluster_name, cluster_database, 'SELECT extname FROM pg_extension') 114 | raise "postgresql install_extension: can't get extensions list\nSTDOUT: #{stdout}\nSTDERR: #{stderr}" unless stderr.empty? 115 | 116 | if stdout.include? extension 117 | Chef::Log.info("postgresql install: extension '#{extension}' already installed, skiping") 118 | return nil 119 | else 120 | stdout, stderr = exec_in_pg_cluster(cluster_version, cluster_name, cluster_database, "CREATE EXTENSION \\\"#{extension}\\\" #{options.map { |t| t.join(' ') }.join(' ')}") 121 | raise "postgresql install_extension: can't install extension #{extension}\nSTDOUT: #{stdout}\nSTDERR: #{stderr}" unless stdout.include?("CREATE EXTENSION\n") 122 | Chef::Log.info("postgresql install_extension: extension '#{extension}' installed") 123 | end 124 | end 125 | 126 | def pgxn_install_extension(cluster_version, cluster_name, params, options) 127 | pgxn_status = Mixlib::ShellOut.new("pgxn install '#{params[:name]}'='#{params[:version]}' --sudo --#{params[:stage]}") 128 | pgxn_status.run_command 129 | 130 | stdout, stderr = exec_in_pg_cluster(cluster_version, cluster_name, params[:db], 'SELECT extname FROM pg_extension') 131 | raise "postgresql install_extension: can't get extensions list\nSTDOUT: #{stdout}\nSTDERR: #{stderr}" unless stderr.empty? 132 | 133 | if stdout.include? params[:name].downcase 134 | Chef::Log.info("postgresql install: extension '#{params[:name]}' already installed, skipping") 135 | return nil 136 | else 137 | pgxn_status = Mixlib::ShellOut.new("sudo -u postgres pgxn load '#{params[:name]}'='#{params[:version]}' -d #{params[:db]} --#{params[:stage]} #{options.map { |t| t.join(' ') }.join(' ')}") 138 | pgxn_status.run_command 139 | raise "postgresql install_extension: can't install extension #{params[:name]}\nSTDOUT: #{pgxn_status.stdout}\nSTDERR: #{pgxn_status.stderr}" unless pgxn_status.stdout.include?('CREATE EXTENSION') 140 | Chef::Log.info("postgresql install_extension: extension '#{params[:name]}' installed") 141 | end 142 | end 143 | 144 | def configuration_hacks(configuration, cluster_version) 145 | configuration['unix_socket_directory'] ||= '/var/run/postgresql' if cluster_version.to_f < 9.3 146 | configuration['unix_socket_directories'] ||= '/var/run/postgresql' if cluster_version.to_f >= 9.3 147 | configuration.delete('wal_receiver_status_interval') if cluster_version.to_f < 9.1 148 | configuration.delete('hot_standby_feedback') if cluster_version.to_f < 9.1 149 | end 150 | end 151 | end 152 | end 153 | -------------------------------------------------------------------------------- /libraries/matchers.rb: -------------------------------------------------------------------------------- 1 | if defined?(ChefSpec) 2 | def create_postgresql(resource_name) 3 | ChefSpec::Matchers::ResourceMatcher.new(:postgresql, :create, resource_name) 4 | end 5 | 6 | def create_postgresql_user(resource_name) 7 | ChefSpec::Matchers::ResourceMatcher.new(:postgresql_user, :create, resource_name) 8 | end 9 | 10 | def create_postgresql_database(resource_name) 11 | ChefSpec::Matchers::ResourceMatcher.new(:postgresql_database, :create, resource_name) 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /metadata.rb: -------------------------------------------------------------------------------- 1 | name 'postgresql_lwrp' 2 | maintainer 'LLC Express 42' 3 | maintainer_email 'cookbooks@express42.com' 4 | license 'MIT' 5 | description 'Installs and configures postgresql for clients or servers' 6 | long_description IO.read(File.join(File.dirname(__FILE__), 'README.md')) 7 | version '1.6.3' 8 | chef_version '>= 12.5' if respond_to?(:chef_version) 9 | source_url 'https://github.com/express42/postgresql_lwrp' if respond_to?(:source_url) 10 | issues_url 'https://github.com/express42/postgresql_lwrp/issues' if respond_to?(:issues_url) 11 | 12 | recipe 'postgresql_lwrp::default', 'Installs postgresql client packages' 13 | recipe 'postgresql_lwrp::apt_official_repository', 'Setup official apt repository' 14 | 15 | depends 'apt' 16 | depends 'poise-python', '>= 1.7.0' 17 | depends 'cron' 18 | 19 | supports 'debian' 20 | supports 'ubuntu' 21 | -------------------------------------------------------------------------------- /recipes/apt_official_repository.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook Name:: postgresql_lwrp 3 | # Recipe:: apt_official_repository 4 | # 5 | # Author:: LLC Express 42 (info@express42.com) 6 | # 7 | # Copyright (C) 2014 LLC Express 42 8 | # 9 | # Permission is hereby granted, free of charge, to any person obtaining a copy of 10 | # this software and associated documentation files (the "Software"), to deal in 11 | # the Software without restriction, including without limitation the rights to 12 | # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 13 | # of the Software, and to permit persons to whom the Software is furnished to do 14 | # so, subject to the following conditions: 15 | # 16 | # The above copyright notice and this permission notice shall be included in all 17 | # copies or substantial portions of the Software. 18 | # 19 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | # SOFTWARE. 26 | # 27 | 28 | apt_repository 'pg-repo' do 29 | uri 'http://apt.postgresql.org/pub/repos/apt/' 30 | distribution node['lsb']['codename'] + '-pgdg' 31 | components ['main'] 32 | key 'https://www.postgresql.org/media/keys/ACCC4CF8.asc' 33 | end 34 | -------------------------------------------------------------------------------- /recipes/cloud_backup.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook Name:: postgresql_lwrp 3 | # Recipe:: cloud_backup 4 | # 5 | # Author:: LLC Express 42 (info@express42.com) 6 | # 7 | # Copyright (C) 2012-2014 LLC Express 42 8 | # 9 | # Permission is hereby granted, free of charge, to any person obtaining a copy of 10 | # this software and associated documentation files (the "Software"), to deal in 11 | # the Software without restriction, including without limitation the rights to 12 | # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 13 | # of the Software, and to permit persons to whom the Software is furnished to do 14 | # so, subject to the following conditions: 15 | # 16 | # The above copyright notice and this permission notice shall be included in all 17 | # copies or substantial portions of the Software. 18 | # 19 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | # SOFTWARE. 26 | # 27 | 28 | # Install packages 29 | 30 | Chef::Log.warn('recipe[postgresql_lwrp::cloud_backup] is no longer necessary to use the postgresql_cloud_backup resource. Please update your cookbooks or remove this recipe from your runlist.') 31 | -------------------------------------------------------------------------------- /recipes/default.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook Name:: postgresql_lwrp 3 | # Recipe:: client 4 | # 5 | # Author:: LLC Express 42 (info@express42.com) 6 | # 7 | # Copyright (C) 2012-2014 LLC Express 42 8 | # 9 | # Permission is hereby granted, free of charge, to any person obtaining a copy of 10 | # this software and associated documentation files (the "Software"), to deal in 11 | # the Software without restriction, including without limitation the rights to 12 | # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 13 | # of the Software, and to permit persons to whom the Software is furnished to do 14 | # so, subject to the following conditions: 15 | # 16 | # The above copyright notice and this permission notice shall be included in all 17 | # copies or substantial portions of the Software. 18 | # 19 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | # SOFTWARE. 26 | # 27 | 28 | %W(postgresql-client-#{node['postgresql']['client']['version']} libpq-dev).each do |pkg| 29 | package pkg 30 | end 31 | -------------------------------------------------------------------------------- /resources/cloud_backup.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook Name:: postgresql_lwrp 3 | # Resource:: cloud_backup 4 | # 5 | # Author:: Kirill Kouznetsov (agon.smith@gmail.com) 6 | # 7 | # Copyright (C) 2014 LLC Express 42 8 | # 9 | # Permission is hereby granted, free of charge, to any person obtaining a copy of 10 | # this software and associated documentation files (the "Software"), to deal in 11 | # the Software without restriction, including without limitation the rights to 12 | # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 13 | # of the Software, and to permit persons to whom the Software is furnished to do 14 | # so, subject to the following conditions: 15 | # 16 | # The above copyright notice and this permission notice shall be included in all 17 | # copies or substantial portions of the Software. 18 | # 19 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | # SOFTWARE. 26 | # 27 | 28 | provides :postgresql_cloud_backup 29 | resource_name :postgresql_cloud_backup 30 | 31 | default_action :schedule 32 | 33 | property :utility, String, 34 | default: 'wal-e', 35 | equal_to: %w(wal-e wal_e wal-g wal_g) 36 | property :in_version, String, required: true 37 | property :in_cluster, String, required: true 38 | property :parameters, Hash, required: true 39 | 40 | # Crontab command prefix to use with wal-e, e.g. for speed limit by trickle 41 | # like `trickle -s -u 1024 envdir /etc/wal-e.d/...` 42 | property :command_prefix, String, required: false 43 | property :full_backup_time, Hash, 44 | default: { 45 | minute: '0', 46 | hour: '3', 47 | day: '*', 48 | month: '*', 49 | weekday: '*', 50 | } 51 | property :retain, Integer, required: false 52 | 53 | action_class do 54 | include Chef::Postgresql::Helpers 55 | 56 | def install_wal_e 57 | wal_e_attributes = node['postgresql']['cloud_backup']['wal_e'] 58 | 59 | python_runtime '3' do 60 | provider :system 61 | options package_name: 'python3' 62 | end 63 | 64 | python_virtualenv wal_e_attributes['path'] do 65 | python '3' 66 | end 67 | 68 | python_package wal_e_attributes['pypi_packages'] do 69 | virtualenv wal_e_attributes['path'] 70 | end 71 | 72 | wal_e_repo = wal_e_attributes['github_repo'] 73 | wal_e_version = wal_e_attributes['version'] 74 | case wal_e_attributes['install_source'] 75 | when 'github' 76 | archive_url = "#{wal_e_repo}/archive/#{wal_e_version}.zip" 77 | python_package 'wal-e' do 78 | package_name archive_url 79 | virtualenv wal_e_attributes['path'] 80 | end 81 | when 'pypi' 82 | python_package 'wal-e' do 83 | version wal_e_version 84 | virtualenv wal_e_attributes['path'] 85 | end 86 | end 87 | 88 | template 'postgresql cloud backup' do 89 | path "#{wal_e_attributes['path']}/bin/postgresql_cloud_backup_helper.sh" 90 | source 'postgresql_cloud_backup_helper.sh.erb' 91 | cookbook 'postgresql_lwrp' 92 | mode '0755' 93 | variables( 94 | wal_e_bin: wal_e_attributes['bin'] 95 | ) 96 | end 97 | end 98 | 99 | def install_wal_g 100 | remote_file 'wal-g' do 101 | path "#{Chef::Config[:file_cache_path]}/wal-g.linux-amd64.tar.gz" 102 | owner 'root' 103 | group 'root' 104 | mode '0644' 105 | source node['postgresql']['cloud_backup']['wal_g']['url'] 106 | checksum node['postgresql']['cloud_backup']['wal_g']['checksum'] 107 | end 108 | 109 | dirname = ::File.dirname node['postgresql']['cloud_backup']['wal_g']['bin'] 110 | bash 'untar wal-g' do 111 | code "tar -xzf #{Chef::Config[:file_cache_path]}/wal-g.linux-amd64.tar.gz -C #{dirname}" 112 | action :nothing 113 | subscribes :run, 'remote_file[wal-g]', :immediately 114 | end 115 | end 116 | end 117 | 118 | action :install do 119 | backup_utility = new_resource.utility.sub('_', '-') 120 | 121 | package node['postgresql']['cloud_backup'][backup_utility.sub('-', '_')]['packages'] + 122 | ['daemontools'] 123 | 124 | case backup_utility 125 | when 'wal-e' 126 | install_wal_e 127 | when 'wal-g' 128 | install_wal_g 129 | end 130 | end 131 | 132 | action :schedule do 133 | action_install 134 | 135 | postgresql_version = new_resource.in_version 136 | postgresql_instance_name = new_resource.in_cluster 137 | postgresql_name_version = "#{postgresql_instance_name}-#{postgresql_version}" 138 | postgresql_path = "/var/lib/postgresql/#{postgresql_version}/#{postgresql_instance_name}" 139 | 140 | backup_utility = new_resource.utility.sub('_', '-') 141 | backup_utility_bin = node['postgresql']['cloud_backup'][backup_utility.sub('-', '_')]['bin'] 142 | command_prefix = new_resource.command_prefix 143 | envdir_params = new_resource.parameters 144 | full_backup_time = new_resource.full_backup_time 145 | 146 | # Add libpq PGPORT variable to envdir_params 147 | envdir_params['PGPORT'] = get_pg_port(postgresql_version, postgresql_instance_name).to_s 148 | 149 | # wal-g needs a default PGHOST variable to connect via UNIX socket 150 | if backup_utility == 'wal-g' && 151 | !envdir_params.key?('PGHOST') 152 | envdir_params['PGHOST'] = '/var/run/postgresql' 153 | envdir_params['PATH'] = '/bin:/sbin:/usr/bin:/usr/sbin' 154 | end 155 | 156 | # Create environment directory 157 | directory "/etc/#{backup_utility}.d/#{postgresql_name_version}/env" do 158 | recursive true 159 | mode '0750' 160 | owner 'root' 161 | group 'postgres' 162 | end 163 | 164 | if envdir_params.key? 'tmpdir' 165 | # We may use custom temp dir 166 | directory "#{backup_utility.upcase} temp directory" do 167 | path envdir_params['tmpdir'] 168 | recursive true 169 | mode '0750' 170 | owner 'postgres' 171 | group 'postgres' 172 | end 173 | end 174 | 175 | # Create all param files in directory 176 | envdir_params.each do |key, val| 177 | file "/etc/#{backup_utility}.d/#{postgresql_name_version}/env/#{key.upcase}" do 178 | mode '0640' 179 | owner 'root' 180 | group 'postgres' 181 | content val 182 | sensitive true 183 | backup false 184 | end 185 | end 186 | 187 | # Remove unused 188 | ruby_block 'Remove unused variables' do 189 | block do 190 | unused_variables = ::Dir["/etc/#{backup_utility}.d/#{postgresql_name_version}/env/*"] - envdir_params.keys.map { |key| "/etc/#{backup_utility}.d/#{postgresql_name_version}/env/#{key.upcase}" } 191 | unused_variables.each { |var| ::File.delete var } 192 | end 193 | end 194 | 195 | # Create crontask via cron cookbook 196 | cron_d "backup_postgresql_cluster_#{postgresql_name_version.sub('.', '-')}" do 197 | command "#{command_prefix} envdir /etc/#{backup_utility}.d/#{postgresql_name_version}/env #{backup_utility_bin} backup-push #{postgresql_path}" 198 | user 'postgres' 199 | minute full_backup_time[:minute] 200 | hour full_backup_time[:hour] 201 | day full_backup_time[:day] 202 | month full_backup_time[:month] 203 | weekday full_backup_time[:weekday] 204 | end 205 | 206 | if new_resource.retain 207 | num_to_retain = new_resource.retain 208 | cron_d "remove_old_backups_postgresql_cluster_#{postgresql_name_version.sub('.', '-')}" do 209 | command "#{command_prefix} envdir /etc/#{backup_utility}.d/#{postgresql_name_version}/env #{backup_utility_bin} delete --confirm retain #{num_to_retain}" 210 | user 'postgres' 211 | minute full_backup_time[:minute] 212 | hour full_backup_time[:hour] 213 | day full_backup_time[:day] 214 | month full_backup_time[:month] 215 | weekday full_backup_time[:weekday] 216 | end 217 | else 218 | cron_d "remove_old_backups_postgresql_cluster_#{postgresql_name_version.sub('.', '-')}" do 219 | action :delete 220 | end 221 | end 222 | end 223 | -------------------------------------------------------------------------------- /resources/database.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook Name:: postgresql_lwrp 3 | # Resource:: database 4 | # 5 | # Author:: Kirill Kouznetsov (agon.smith@gmail.com) 6 | # 7 | # Copyright (C) 2014 LLC Express 42 8 | # 9 | # Permission is hereby granted, free of charge, to any person obtaining a copy of 10 | # this software and associated documentation files (the "Software"), to deal in 11 | # the Software without restriction, including without limitation the rights to 12 | # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 13 | # of the Software, and to permit persons to whom the Software is furnished to do 14 | # so, subject to the following conditions: 15 | # 16 | # The above copyright notice and this permission notice shall be included in all 17 | # copies or substantial portions of the Software. 18 | # 19 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | # SOFTWARE. 26 | # 27 | 28 | include Chef::Postgresql::Helpers 29 | 30 | provides :postgresql_database 31 | resource_name :postgresql_database 32 | 33 | default_action :create 34 | 35 | property :in_version, String, required: true 36 | property :in_cluster, String, required: true 37 | property :owner, String 38 | property :tablespace, String 39 | property :template, String 40 | property :encoding, String 41 | property :connection_limit, Integer 42 | 43 | action :create do 44 | options = {} 45 | 46 | options['OWNER'] = "\\\"#{new_resource.owner}\\\"" if new_resource.owner 47 | options['TABLESPACE'] = "'#{new_resource.tablespace}'" if new_resource.tablespace 48 | options['TEMPLATE'] = "'#{new_resource.template}'" if new_resource.template 49 | options['ENCODING'] = "'#{new_resource.encoding}'" if new_resource.encoding 50 | options['CONNECTION LIMIT'] = new_resource.connection_limit if new_resource.connection_limit 51 | converge_by "create database #{new_resource.name}" do 52 | create_database(new_resource.in_version, new_resource.in_cluster, new_resource.name, options) 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /resources/default.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook Name:: postgresql_lwrp 3 | # Resource:: default 4 | # 5 | # Author:: Kirill Kouznetsov (agon.smith@gmail.com) 6 | # 7 | # Copyright (C) 2014 LLC Express 42 8 | # 9 | # Permission is hereby granted, free of charge, to any person obtaining a copy of 10 | # this software and associated documentation files (the "Software"), to deal in 11 | # the Software without restriction, including without limitation the rights to 12 | # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 13 | # of the Software, and to permit persons to whom the Software is furnished to do 14 | # so, subject to the following conditions: 15 | # 16 | # The above copyright notice and this permission notice shall be included in all 17 | # copies or substantial portions of the Software. 18 | # 19 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | # SOFTWARE. 26 | # 27 | 28 | include Chef::Postgresql::Helpers 29 | 30 | provides :postgresql 31 | resource_name :postgresql 32 | 33 | default_action :create 34 | 35 | property :cluster_name, String, required: true 36 | property :cluster_version, String, regex: [/\A(1\d|9\.[1-6])\Z\z/] 37 | property :cookbook, String, default: 'postgresql_lwrp' 38 | property :cluster_create_options, Hash, default: {} 39 | property :configuration, Hash, default: {} 40 | property :hba_configuration, Array, default: [] 41 | property :ident_configuration, Array, default: [] 42 | property :replication, Hash, default: {} 43 | property :replication_initial_copy, [TrueClass, FalseClass], default: false 44 | property :replication_start_slave, [TrueClass, FalseClass], default: false 45 | property :allow_restart_cluster, default: :none, callbacks: { 46 | 'is not allowed! Allowed params for allow_restart_cluster: first, always or none' => proc do |value| 47 | !value.to_sym.match(/^(first|always|none)$/).nil? 48 | end, 49 | } 50 | 51 | action :create do 52 | configuration = Chef::Mixin::DeepMerge.merge(node['postgresql']['defaults']['server']['configuration'].to_hash, new_resource.configuration) 53 | hba_configuration = node['postgresql']['defaults']['server']['hba_configuration'] | new_resource.hba_configuration 54 | ident_configuration = node['postgresql']['defaults']['server']['ident_configuration'] | new_resource.ident_configuration 55 | 56 | cluster_name = new_resource.name 57 | cluster_version = (!new_resource.cluster_version.empty? && new_resource.cluster_version) || node['postgresql']['defaults']['server']['version'] 58 | service_name = "postgresql_#{cluster_version}_#{cluster_name}" 59 | 60 | allow_restart_cluster = new_resource.allow_restart_cluster 61 | 62 | replication = Chef::Mixin::DeepMerge.merge(node['postgresql']['defaults']['server']['replication'].to_hash, new_resource.replication) 63 | replication_file = "/var/lib/postgresql/#{cluster_version}/#{cluster_name}/recovery.conf" 64 | replication_start_slave = new_resource.replication_start_slave 65 | replication_initial_copy = new_resource.replication_initial_copy 66 | 67 | cluster_options = Mash.new(new_resource.cluster_create_options) 68 | parsed_cluster_options = [] 69 | 70 | first_time = pg_installed?("postgresql-#{cluster_version}") 71 | 72 | %w(locale lc-collate lc-ctype lc-messages lc-monetary lc-numeric lc-time).each do |option| 73 | parsed_cluster_options << "--#{option} #{cluster_options[:locale]}" if cluster_options[option] 74 | end 75 | 76 | # Locale hack 77 | if new_resource.cluster_create_options.key?('locale') && !new_resource.cluster_create_options['locale'].empty? 78 | system_lang = ENV['LANG'] 79 | ENV['LANG'] = new_resource.cluster_create_options['locale'] 80 | end 81 | 82 | # Configuration hacks 83 | configuration_hacks(configuration, cluster_version) 84 | 85 | # Install postgresql-common package 86 | package 'postgresql-common' 87 | 88 | file '/etc/postgresql-common/createcluster.conf' do 89 | content "create_main_cluster = false\n" 90 | only_if { cluster_version.to_f >= 9.2 } 91 | end 92 | 93 | # Install postgresql server packages 94 | %W(postgresql-#{cluster_version} postgresql-contrib-#{cluster_version} postgresql-server-dev-#{cluster_version}).each do |pkg| 95 | package pkg 96 | end 97 | 98 | # Install pgxn client to download custom extensions 99 | package 'pgxnclient' 100 | package 'build-essential' 101 | 102 | # Return locale 103 | if new_resource.cluster_create_options.key?('locale') && !new_resource.cluster_create_options['locale'].empty? 104 | ENV['LANG'] = system_lang 105 | end 106 | 107 | # Systemd not working with cluster names with dashes 108 | # see http://comments.gmane.org/gmane.comp.db.postgresql.debian/346 109 | if systemd_used? && cluster_name.include?('-') 110 | raise "Sorry, systemd not support cluster names with dashes ('-'), use underscore ('_') instead" 111 | end 112 | 113 | # Create postgresql cluster directories 114 | %W( 115 | /etc/postgresql 116 | /etc/postgresql/#{cluster_version} 117 | /etc/postgresql/#{cluster_version}/#{cluster_name} 118 | /var/lib/postgresql 119 | /var/lib/postgresql/#{cluster_version} 120 | ).each do |dir| 121 | directory dir do 122 | owner 'postgres' 123 | group 'postgres' 124 | end 125 | end 126 | 127 | directory "/var/lib/postgresql/#{cluster_version}/#{cluster_name}" do 128 | mode '0700' 129 | owner 'postgres' 130 | group 'postgres' 131 | end 132 | 133 | # Exec pg_cluster create 134 | execute 'Exec pg_createcluster' do 135 | command "pg_createcluster #{parsed_cluster_options.join(' ')} #{cluster_version} #{cluster_name}" 136 | not_if do 137 | ::File.exist?("/etc/postgresql/#{cluster_version}/#{cluster_name}/postgresql.conf") || 138 | !replication.empty? 139 | end 140 | end 141 | 142 | # Define postgresql service 143 | postgresql_service = service service_name do 144 | action :nothing 145 | provider Chef::Provider::Service::Simple 146 | start_command "pg_ctlcluster #{cluster_version} #{cluster_name} start" 147 | stop_command "pg_ctlcluster #{cluster_version} #{cluster_name} stop" 148 | reload_command "pg_ctlcluster #{cluster_version} #{cluster_name} reload" 149 | restart_command "pg_ctlcluster #{cluster_version} #{cluster_name} restart" 150 | status_command "su -c '/usr/lib/postgresql/#{cluster_version}/bin/pg_ctl \ 151 | -D /var/lib/postgresql/#{cluster_version}/#{cluster_name} status' postgres" 152 | supports status: true, restart: true, reload: true 153 | end 154 | 155 | # Ruby block for postgresql smart restart 156 | ruby_block "restart_service_#{service_name}" do 157 | action :nothing 158 | block do 159 | if !replication.empty? && !replication_start_slave 160 | run_context.notifies_delayed(Chef::Resource::Notification.new(postgresql_service, :reload, self)) 161 | elsif need_to_restart?(allow_restart_cluster.to_sym, first_time) 162 | run_context.notifies_delayed(Chef::Resource::Notification.new(postgresql_service, :restart, self)) 163 | else 164 | run_context.notifies_delayed(Chef::Resource::Notification.new(postgresql_service, :reload, self)) 165 | end 166 | end 167 | end 168 | 169 | # Configuration templates 170 | 171 | main_configuration = configuration.dup 172 | main_configuration.delete('ssl_cert_file') if cluster_version.to_f < 9.2 173 | main_configuration.delete('ssl_key_file') if cluster_version.to_f < 9.2 174 | main_configuration.delete('checkpoint_segments') if cluster_version.to_f > 9.4 175 | 176 | # Backu hack: 177 | # Set `archive_command` option automatically according to the tool of the 178 | # choise in `postgresql_cloud_backup` resource assosiated with this PG 179 | # instance if `archive command` config option is set to `:cloud_auto` 180 | with_run_context :root do 181 | t = run_context.resource_collection.select do |res| 182 | res.resource_name == :postgresql_cloud_backup && 183 | res.in_cluster == cluster_name && 184 | res.in_version == cluster_version 185 | end 186 | 187 | if !t.empty? && 188 | main_configuration['archive_command'] == :cloud_auto 189 | backup_utility = t.first.utility 190 | backup_utility_bin = node['postgresql']['cloud_backup'][backup_utility.sub('-', '_')]['bin'] 191 | main_configuration['archive_command'] = "envdir /etc/#{backup_utility}.d/#{cluster_name}-#{cluster_version}/env/ #{backup_utility_bin} wal-push %p" 192 | end 193 | end 194 | 195 | template "/etc/postgresql/#{cluster_version}/#{cluster_name}/postgresql.conf" do 196 | source 'postgresql.conf.erb' 197 | owner 'postgres' 198 | group 'postgres' 199 | mode '0644' 200 | variables( 201 | configuration: main_configuration, 202 | cluster_name: cluster_name, 203 | cluster_version: cluster_version 204 | ) 205 | cookbook new_resource.cookbook 206 | notifies :run, "ruby_block[restart_service_#{service_name}]", :delayed 207 | end 208 | 209 | template "/etc/postgresql/#{cluster_version}/#{cluster_name}/pg_hba.conf" do 210 | source 'pg_hba.conf.erb' 211 | owner 'postgres' 212 | group 'postgres' 213 | mode '0644' 214 | variables configuration: hba_configuration 215 | cookbook new_resource.cookbook 216 | notifies :run, "ruby_block[restart_service_#{service_name}]", :delayed 217 | end 218 | 219 | template "/etc/postgresql/#{cluster_version}/#{cluster_name}/pg_ident.conf" do 220 | source 'pg_ident.conf.erb' 221 | owner 'postgres' 222 | group 'postgres' 223 | mode '0644' 224 | variables configuration: ident_configuration 225 | cookbook new_resource.cookbook 226 | notifies :run, "ruby_block[restart_service_#{service_name}]", :delayed 227 | end 228 | 229 | file "/etc/postgresql/#{cluster_version}/#{cluster_name}/start.conf" do 230 | content "auto\n" 231 | end 232 | 233 | # Replication 234 | if !replication.empty? 235 | 236 | if replication_initial_copy 237 | pg_basebackup_path = "/usr/lib/postgresql/#{cluster_version}/bin/pg_basebackup" 238 | pg_data_directory = "/var/lib/postgresql/#{cluster_version}/#{cluster_name}" 239 | 240 | BASEBACKUP_PARAMS = { 241 | 'host' => '-h', 242 | 'port' => '-p', 243 | 'user' => '-U', 244 | 'password' => '-W', 245 | }.freeze 246 | 247 | conninfo_hash = Hash[*replication[:primary_conninfo].scan(/\w+=[^\s]+/).map { |x| x.split('=', 2) }.flatten] 248 | 249 | basebackup_conninfo_hash = conninfo_hash.map do |key, val| 250 | "#{BASEBACKUP_PARAMS[key.to_s]} #{val}" if BASEBACKUP_PARAMS[key.to_s] 251 | end.compact 252 | 253 | fetch_wal_logs = if cluster_version.to_i >= 10 254 | '-X fetch' 255 | else 256 | '-x' 257 | end 258 | 259 | execute 'Make basebackup' do 260 | command "#{pg_basebackup_path} -D #{pg_data_directory} -F p #{fetch_wal_logs} -c fast #{basebackup_conninfo_hash.join(' ')}" 261 | user 'postgres' 262 | not_if { ::File.exist?("/var/lib/postgresql/#{cluster_version}/#{cluster_name}/base") } 263 | timeout 604_800 264 | end 265 | end 266 | 267 | link "/var/lib/postgresql/#{cluster_version}/#{cluster_name}/server.key" do 268 | to configuration['ssl_key_file'] 269 | not_if { cluster_version.to_f > 9.1 && ::File.exist?("/var/lib/postgresql/#{cluster_version}/#{cluster_name}/server.key") } 270 | end 271 | 272 | link "/var/lib/postgresql/#{cluster_version}/#{cluster_name}/server.crt" do 273 | to configuration['ssl_cert_file'] 274 | not_if { cluster_version.to_f > 9.1 && ::File.exist?("/var/lib/postgresql/#{cluster_version}/#{cluster_name}/server.crt") } 275 | end 276 | 277 | template "/var/lib/postgresql/#{cluster_version}/#{cluster_name}/recovery.conf" do 278 | source 'recovery.conf.erb' 279 | owner 'postgres' 280 | group 'postgres' 281 | mode '0644' 282 | variables replication: replication 283 | cookbook new_resource.cookbook 284 | notifies :run, "ruby_block[restart_service_#{service_name}]", :delayed 285 | end 286 | 287 | else 288 | 289 | file replication_file do 290 | action :delete 291 | notifies :run, "ruby_block[restart_service_#{service_name}]", :delayed 292 | end 293 | end 294 | 295 | # Start postgresql 296 | ruby_block "start_service_#{service_name}]" do 297 | block do 298 | run_context.notifies_delayed(Chef::Resource::Notification.new(postgresql_service, :start, self)) 299 | end 300 | not_if { pg_running?(cluster_version, cluster_name) || (!replication.empty? && !replication_start_slave) } 301 | end 302 | end 303 | -------------------------------------------------------------------------------- /resources/extension.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook Name:: postgresql_lwrp 3 | # Resource:: extension 4 | # 5 | # Author:: LLC Express 42 (info@express42.com) 6 | # 7 | # Copyright (C) 2016 LLC Express 42 8 | # 9 | # Permission is hereby granted, free of charge, to any person obtaining a copy of 10 | # this software and associated documentation files (the "Software"), to deal in 11 | # the Software without restriction, including without limitation the rights to 12 | # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 13 | # of the Software, and to permit persons to whom the Software is furnished to do 14 | # so, subject to the following conditions: 15 | # 16 | # The above copyright notice and this permission notice shall be included in all 17 | # copies or substantial portions of the Software. 18 | # 19 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | # SOFTWARE. 26 | # 27 | 28 | include Chef::Postgresql::Helpers 29 | 30 | provides :postgresql_extension 31 | resource_name :postgresql_extension 32 | 33 | default_action :install 34 | 35 | property :in_version, String, required: true 36 | property :in_cluster, String, required: true 37 | property :db, String, required: true 38 | property :schema, String 39 | property :version, String 40 | 41 | action :install do 42 | options = {} 43 | options['SCHEMA'] = new_resource.schema.to_s if new_resource.schema 44 | options['VERSION'] = "'#{new_resource.version}'" if new_resource.version 45 | 46 | converge_by 'install extension new_resource.name' do 47 | install_extension(new_resource.in_version, new_resource.in_cluster, new_resource.db, new_resource.name, options) 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /resources/pgxn_extension.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook Name:: postgresql_lwrp 3 | # Resource:: pgxn_extension 4 | # 5 | # Author::LLC Express 42 (info@express42.com) 6 | # 7 | # Copyright (C) 2016 LLC Express 42 8 | # 9 | # Permission is hereby granted, free of charge, to any person obtaining a copy of 10 | # this software and associated documentation files (the "Software"), to deal in 11 | # the Software without restriction, including without limitation the rights to 12 | # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 13 | # of the Software, and to permit persons to whom the Software is furnished to do 14 | # so, subject to the following conditions: 15 | # 16 | # The above copyright notice and this permission notice shall be included in all 17 | # copies or substantial portions of the Software. 18 | # 19 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | # SOFTWARE. 26 | # 27 | 28 | include Chef::Postgresql::Helpers 29 | 30 | provides :pgxn_extension 31 | resource_name :pgxn_extension 32 | 33 | default_action :install 34 | 35 | property :in_version, String, required: true 36 | property :in_cluster, String, required: true 37 | property :db, String, required: true 38 | property :schema, String 39 | property :version, String, required: true 40 | property :stage, String, required: true 41 | 42 | action :install do 43 | options = {} 44 | options['--schema'] = new_resource.schema.to_s if new_resource.schema 45 | params = {} 46 | params[:name] = new_resource.name.to_s if new_resource.name 47 | params[:stage] = new_resource.stage.to_s if new_resource.stage 48 | params[:version] = new_resource.version.to_s if new_resource.version 49 | params[:db] = new_resource.db.to_s if new_resource.db 50 | converge_by 'install pgxn extension' do 51 | pgxn_install_extension(new_resource.in_version, new_resource.in_cluster, params, options) 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /resources/user.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook Name:: postgresql_lwrp 3 | # Resource:: user 4 | # 5 | # Author:: Kirill Kouznetsov (agon.smith@gmail.com) 6 | # 7 | # Copyright (C) 2014 LLC Express 42 8 | # 9 | # Permission is hereby granted, free of charge, to any person obtaining a copy of 10 | # this software and associated documentation files (the "Software"), to deal in 11 | # the Software without restriction, including without limitation the rights to 12 | # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 13 | # of the Software, and to permit persons to whom the Software is furnished to do 14 | # so, subject to the following conditions: 15 | # 16 | # The above copyright notice and this permission notice shall be included in all 17 | # copies or substantial portions of the Software. 18 | # 19 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | # SOFTWARE. 26 | # 27 | 28 | include Chef::Postgresql::Helpers 29 | 30 | provides :postgresql_user 31 | resource_name :postgresql_user 32 | 33 | default_action :create 34 | 35 | property :in_version, String, required: true 36 | property :in_cluster, String, required: true 37 | property :unencrypted_password, String 38 | property :encrypted_password, String 39 | property :replication, [TrueClass, FalseClass] 40 | property :superuser, [TrueClass, FalseClass] 41 | property :advanced_options, Hash, default: {} 42 | 43 | action :create do 44 | options = new_resource.advanced_options.dup 45 | 46 | if new_resource.replication == true 47 | options['REPLICATION'] = nil 48 | elsif new_resource.replication == false 49 | options['NOREPLICATION'] = nil 50 | end 51 | 52 | if new_resource.superuser == true 53 | options['SUPERUSER'] = nil 54 | elsif new_resource.superuser == false 55 | options['NOSUPERUSER'] = nil 56 | end 57 | 58 | options['ENCRYPTED PASSWORD'] = "'#{new_resource.encrypted_password}'" if new_resource.encrypted_password 59 | 60 | options['UNENCRYPTED PASSWORD'] = "'#{new_resource.unencrypted_password}'" if new_resource.unencrypted_password 61 | converge_by "create user #{new_resource.name}" do 62 | create_user(new_resource.in_version, new_resource.in_cluster, new_resource.name, options) 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'chefspec' 2 | require 'chefspec/berkshelf' 3 | 4 | at_exit { ChefSpec::Coverage.report! } 5 | -------------------------------------------------------------------------------- /spec/unit/recipes/apt_official_repository_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'postgresql_lwrp::apt_official_repository' do 4 | let(:chef_run) { ChefSpec::SoloRunner.new(platform: 'ubuntu', version: '12.04').converge(described_recipe) } 5 | 6 | it 'test' do 7 | expect(chef_run).to add_apt_repository('pg-repo') 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/unit/recipes/default_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'postgresql_lwrp::default' do 4 | let(:chef_run) do 5 | ChefSpec::SoloRunner.new do |node| 6 | node.automatic['memory']['total'] = '1GB' 7 | end.converge(described_recipe) 8 | end 9 | 10 | it 'install postgresql-client-9.2 package' do 11 | expect(chef_run).to install_package('postgresql-client-9.2') 12 | end 13 | 14 | it 'install libpq-dev package' do 15 | expect(chef_run).to install_package('libpq-dev') 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /templates/default/pg_hba.conf.erb: -------------------------------------------------------------------------------- 1 | # 2 | # Generated by Chef for <%= node['fqdn'] %>. 3 | # Local modifications will be overwritten. 4 | # 5 | 6 | # TYPE DATABASE USER ADDRESS METHOD 7 | 8 | <% @configuration.each do |config| %> 9 | <%= "#{config[:type].ljust(7)} #{config[:database].ljust(15)} #{config[:user].ljust(15)} #{config[:address].ljust(23)} #{config[:method]}"%> 10 | <% end %> 11 | -------------------------------------------------------------------------------- /templates/default/pg_ident.conf.erb: -------------------------------------------------------------------------------- 1 | # 2 | # Generated by Chef for <%= node['fqdn'] %>. 3 | # Local modifications will be overwritten. 4 | # 5 | 6 | <% @configuration.each do |config| %> 7 | <%= "#{config[:mapname].ljust(10)}#{config[:systemname].ljust(10)}#{config[:pgusername]}"%> 8 | <% end %> 9 | -------------------------------------------------------------------------------- /templates/default/postgresql.conf.erb: -------------------------------------------------------------------------------- 1 | # 2 | ## Generated by Chef for <%= node['fqdn'] %>. 3 | ## Local modifications will be overwritten. 4 | # 5 | 6 | data_directory = '/var/lib/postgresql/<%= @cluster_version %>/<%= @cluster_name %>' 7 | hba_file = '/etc/postgresql/<%= @cluster_version %>/<%= @cluster_name %>/pg_hba.conf' 8 | ident_file = '/etc/postgresql/<%= @cluster_version %>/<%= @cluster_name %>/pg_ident.conf' 9 | external_pid_file = '/var/run/postgresql/<%= @cluster_version %>-<%= @cluster_name %>.pid' 10 | 11 | <% @configuration.sort.each do |parameter, value| %> 12 | <% if value.to_s !~ /^[a-zA-Z0-9]+$/ -%> 13 | <%= parameter %> = '<%= value %>' 14 | <% else -%> 15 | <%= parameter %> = <%= value %> 16 | <% end -%> 17 | <% end %> 18 | -------------------------------------------------------------------------------- /templates/default/postgresql_cloud_backup_helper.sh.erb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cluster_name=${1:?ERROR, missing cluster name} 4 | cluster_version=${2:?ERROR, missing cluster version} 5 | 6 | if [ ! -x "<%= @wal_e_bin %>" ]; then 7 | echo "ERROR, WAL-E not deployed, no wal-e in /opt/wal-e/bin" 8 | exit 1 9 | fi 10 | 11 | if [ ! -d "/etc/wal-e.d/${cluster_name}-${cluster_version}/env" ]; then 12 | echo "ERROR, WAL-E not deployed for postgresql ${cluster_name}-${cluster_version}" 13 | exit 1 14 | fi 15 | 16 | case "$3" in 17 | "last"): 18 | last_backup_date=(`envdir /etc/wal-e.d/${cluster_name}-${cluster_version}/env <%= @wal_e_bin %> backup-list LATEST 2>/dev/null | awk 'NR>1 {print $2}'`) 19 | if [ -z $last_backup_date ]; then 20 | echo "ERROR, can't get last backup time" 21 | exit 1 22 | fi 23 | last_backup_date_unixtime=`date -d $last_backup_date +%s` 24 | echo $last_backup_date_unixtime 25 | ;; 26 | "count"): 27 | backup_count=`envdir /etc/wal-e.d/${cluster_name}-${cluster_version}/env <%= @wal_e_bin %> backup-list 2>/dev/null | awk 'NR>1' | wc -l` 28 | echo $backup_count 29 | ;; 30 | *) 31 | echo "ERROR, wrong command. Valid commands: last, count" 32 | ;; 33 | esac 34 | -------------------------------------------------------------------------------- /templates/default/recovery.conf.erb: -------------------------------------------------------------------------------- 1 | # 2 | ## Generated by Chef for <%= node['fqdn'] %>. 3 | ## Local modifications will be overwritten. 4 | # 5 | 6 | <% @replication.each do |parameter, value| %> 7 | <%= parameter %> = '<%= value %>' 8 | <% end %> 9 | -------------------------------------------------------------------------------- /test/fixtures/cookbooks/pgtest/metadata.rb: -------------------------------------------------------------------------------- 1 | name 'pgtest' 2 | maintainer 'LLC Express 42' 3 | maintainer_email 'cookbooks@express42.com' 4 | license 'MIT' 5 | description 'Tests for postgresql cookbook' 6 | version '1.0.0' 7 | 8 | depends 'postgresql_lwrp' 9 | depends 'apt' 10 | depends 'sysctl' 11 | supports 'debian' 12 | supports 'ubuntu' 13 | -------------------------------------------------------------------------------- /test/fixtures/cookbooks/pgtest/recipes/cloud_backup.rb: -------------------------------------------------------------------------------- 1 | postgresql_cloud_backup 'test-wal-e' do 2 | in_version node['pgtest']['version'] 3 | in_cluster 'main' 4 | parameters( 5 | 'PGPORT' => '5432', 6 | 'AWS_ACCESS_KEY_ID' => 'example', 7 | 'AWS_SECRET_ACCESS_KEY' => 'example', 8 | 'WALE_S3_PREFIX' => 'example' 9 | ) 10 | end 11 | 12 | postgresql_cloud_backup 'test-wal-g' do 13 | utility 'wal-g' 14 | in_version node['pgtest']['version'] 15 | in_cluster 'walg' 16 | parameters( 17 | 'PGPORT' => '5432', 18 | 'AWS_ACCESS_KEY_ID' => 'example', 19 | 'AWS_SECRET_ACCESS_KEY' => 'example', 20 | 'WALG_S3_PREFIX' => 'example' 21 | ) 22 | end 23 | -------------------------------------------------------------------------------- /test/fixtures/cookbooks/pgtest/recipes/create_database.rb: -------------------------------------------------------------------------------- 1 | include_recipe 'postgresql_lwrp::default' 2 | 3 | postgresql_database 'test01' do 4 | in_version node['pgtest']['version'] 5 | in_cluster 'main' 6 | owner 'test01' 7 | end 8 | 9 | postgresql_database 'test-02' do 10 | in_version node['pgtest']['version'] 11 | in_cluster 'main' 12 | owner 'test-02' 13 | end 14 | -------------------------------------------------------------------------------- /test/fixtures/cookbooks/pgtest/recipes/create_user.rb: -------------------------------------------------------------------------------- 1 | include_recipe 'postgresql_lwrp::default' 2 | 3 | postgresql_user 'test01' do 4 | in_version node['pgtest']['version'] 5 | in_cluster 'main' 6 | encrypted_password 'test01' 7 | replication true 8 | end 9 | 10 | postgresql_user 'test-02' do 11 | in_version node['pgtest']['version'] 12 | in_cluster 'main' 13 | encrypted_password 'test-02' 14 | superuser true 15 | end 16 | -------------------------------------------------------------------------------- /test/fixtures/cookbooks/pgtest/recipes/install_ext.rb: -------------------------------------------------------------------------------- 1 | include_recipe 'postgresql_lwrp::default' 2 | 3 | postgresql_extension 'cube' do 4 | in_version node['pgtest']['version'] 5 | in_cluster 'main' 6 | db 'test01' 7 | end 8 | 9 | pgxn_extension 'semver' do 10 | in_version node['pgtest']['version'] 11 | in_cluster 'main' 12 | db 'test01' 13 | version '0.20.3' 14 | stage 'stable' 15 | only_if { node['pgtest']['version'].to_f > 9.1 } 16 | end 17 | -------------------------------------------------------------------------------- /test/fixtures/cookbooks/pgtest/recipes/master.rb: -------------------------------------------------------------------------------- 1 | include_recipe 'postgresql_lwrp::apt_official_repository' 2 | include_recipe 'postgresql_lwrp::default' 3 | include_recipe 'sysctl::default' 4 | 5 | sysctl_param 'kernel.shmmax' do 6 | value 68_719_476_736 7 | end 8 | 9 | postgresql 'main' do 10 | cluster_create_options('locale' => 'en_US.UTF-8') 11 | cluster_version node['pgtest']['version'] 12 | configuration( 13 | archive_command: :cloud_auto, 14 | listen_addresses: '*', 15 | max_connections: 300, 16 | ssl_renegotiation_limit: 0, 17 | archive_mode: 'on', 18 | shared_buffers: '64MB', 19 | maintenance_work_mem: '8MB', 20 | work_mem: '2MB', 21 | effective_cache_size: '200MB' 22 | ) 23 | hba_configuration( 24 | [ 25 | { type: 'host', database: 'all', user: 'all', address: '0.0.0.0/0', method: 'md5' }, 26 | { type: 'host', database: 'replication', user: 'postgres', address: '127.0.0.1/32', method: 'trust' }, 27 | ] 28 | ) 29 | end 30 | -------------------------------------------------------------------------------- /test/fixtures/cookbooks/pgtest/recipes/master_for_walg.rb: -------------------------------------------------------------------------------- 1 | include_recipe 'postgresql_lwrp::apt_official_repository' 2 | include_recipe 'postgresql_lwrp::default' 3 | include_recipe 'sysctl::default' 4 | 5 | sysctl_param 'kernel.shmmax' do 6 | value 68_719_476_736 7 | end 8 | 9 | postgresql 'walg' do 10 | cluster_create_options('locale' => 'en_US.UTF-8') 11 | cluster_version node['pgtest']['version'] 12 | configuration( 13 | archive_command: :cloud_auto, 14 | port: 5435, 15 | listen_addresses: '*', 16 | max_connections: 100, 17 | ssl_renegotiation_limit: 0, 18 | archive_mode: 'on', 19 | shared_buffers: '64MB', 20 | maintenance_work_mem: '8MB', 21 | work_mem: '2MB', 22 | effective_cache_size: '200MB' 23 | ) 24 | hba_configuration( 25 | [ 26 | { type: 'host', database: 'all', user: 'all', address: '0.0.0.0/0', method: 'md5' }, 27 | { type: 'host', database: 'replication', user: 'postgres', address: '127.0.0.1/32', method: 'trust' }, 28 | ] 29 | ) 30 | end 31 | -------------------------------------------------------------------------------- /test/fixtures/cookbooks/pgtest/recipes/slave.rb: -------------------------------------------------------------------------------- 1 | include_recipe 'postgresql_lwrp::apt_official_repository' 2 | include_recipe 'postgresql_lwrp::default' 3 | 4 | service 'postgresql' do 5 | action :restart 6 | end 7 | 8 | sysctl_param 'kernel.shmmax' do 9 | value 68_719_476_736 10 | end 11 | 12 | postgresql 'slave' do 13 | cluster_create_options('locale' => 'en_US.UTF-8') 14 | cluster_version node['pgtest']['version'] 15 | configuration( 16 | port: '5433', 17 | hot_standby: 'on', 18 | listen_addresses: '*', 19 | max_connections: 300, 20 | ssl_renegotiation_limit: 0, 21 | shared_buffers: '64MB', 22 | maintenance_work_mem: '8MB', 23 | work_mem: '2MB', 24 | effective_cache_size: '200MB' 25 | ) 26 | hba_configuration( 27 | [ 28 | { type: 'host', database: 'all', user: 'all', address: '0.0.0.0/0', method: 'md5' }, 29 | ] 30 | ) 31 | replication( 32 | standby_mode: 'on', 33 | primary_conninfo: 'host=127.0.0.1 port=5432' 34 | ) 35 | replication_initial_copy true 36 | replication_start_slave true 37 | end 38 | -------------------------------------------------------------------------------- /test/fixtures/cookbooks/pgtest/recipes/slave_init_nostart.rb: -------------------------------------------------------------------------------- 1 | include_recipe 'postgresql_lwrp::apt_official_repository' 2 | include_recipe 'postgresql_lwrp::default' 3 | 4 | sysctl_param 'kernel.shmmax' do 5 | value 68_719_476_736 6 | end 7 | 8 | postgresql 'slave2' do 9 | cluster_create_options('locale' => 'ru_RU.UTF-8') 10 | cluster_version node['pgtest']['version'] 11 | configuration( 12 | port: '5434', 13 | listen_addresses: '*', 14 | max_connections: 300, 15 | ssl_renegotiation_limit: 0, 16 | shared_buffers: '64MB', 17 | maintenance_work_mem: '8MB', 18 | work_mem: '2MB', 19 | effective_cache_size: '200MB' 20 | ) 21 | hba_configuration( 22 | [ 23 | { type: 'host', database: 'all', user: 'all', address: '0.0.0.0/0', method: 'md5' }, 24 | ] 25 | ) 26 | replication( 27 | standby_mode: 'on', 28 | primary_conninfo: 'host=127.0.0.1 port=5432' 29 | ) 30 | replication_initial_copy true 31 | replication_start_slave false 32 | end 33 | -------------------------------------------------------------------------------- /test/fixtures/cookbooks/pgtest/recipes/test.rb: -------------------------------------------------------------------------------- 1 | node.default['postgresql']['client']['version'] = node['pgtest']['version'] 2 | 3 | include_recipe 'pgtest::master' 4 | include_recipe 'pgtest::master_for_walg' 5 | include_recipe 'pgtest::create_user' 6 | include_recipe 'pgtest::create_database' 7 | include_recipe 'pgtest::install_ext' 8 | include_recipe 'pgtest::slave' 9 | include_recipe 'pgtest::slave_init_nostart' 10 | include_recipe 'pgtest::cloud_backup' 11 | -------------------------------------------------------------------------------- /test/integration/postgresql_lwrp_test/inspec/controls/default.rb: -------------------------------------------------------------------------------- 1 | # # encoding: utf-8 2 | 3 | # Inspec default test 4 | 5 | # The Inspec reference, with examples and extensive documentation, can be 6 | # found at http://inspec.io/docs/reference/resources/ 7 | 8 | pg_version = attribute('pg_version', default: nil, description: 'Set postgresql version') 9 | 10 | control 'postgres users' do 11 | title 'Users should be configured' 12 | 13 | describe postgres_user(pg_version, 'main', 'test01', 'test01') do 14 | it { should have_login } 15 | it { should have_privilege('rolreplication') } 16 | it { should_not have_privilege('rolsuper') } 17 | end 18 | 19 | describe postgres_user(pg_version, 'main', 'test-02', 'test-02') do 20 | it { should have_login } 21 | it { should have_privilege('rolsuper') } 22 | end 23 | end 24 | 25 | control 'postgres master' do 26 | title 'Postgres cluster' 27 | 28 | describe package("postgresql-#{pg_version}") do 29 | it { should be_installed } 30 | end 31 | 32 | # Chef 14 resource service is broken on a first run on Ubuntu 14. 33 | if os.name == 'ubuntu' && os.release.to_f > 14.04 34 | describe service('postgresql') do 35 | it { should be_enabled } 36 | end 37 | end 38 | 39 | describe postgres_cluster(pg_version, 'main') do 40 | it { should be_running } 41 | end 42 | 43 | describe port(5432) do 44 | it { should be_listening } 45 | end 46 | end 47 | 48 | control 'postgres databases' do 49 | title 'Check postgres databases' 50 | 51 | describe postgres_database(pg_version, 'main', 'test01') do 52 | it { should be_created } 53 | it { should have_owner('test01') } 54 | end 55 | 56 | describe postgres_database(pg_version, 'main', 'test-02') do 57 | it { should be_created } 58 | it { should have_owner('test-02') } 59 | it { should_not have_owner('test01') } 60 | end 61 | 62 | describe postgres_database(pg_version, 'main', 'test-03') do 63 | it { should_not be_created } 64 | end 65 | end 66 | 67 | control 'postgres extensions' do 68 | title 'Check postgres extensions' 69 | describe postgres_extension(pg_version, 'main', 'cube', 'test01') do 70 | it { should be_installed } 71 | end 72 | 73 | if pg_version.to_f > 9.1 74 | describe postgres_extension(pg_version, 'main', 'semver', 'test01') do 75 | it { should be_installed } 76 | end 77 | end 78 | end 79 | 80 | control 'postgres slave' do 81 | title 'Postgres cluster' 82 | # Chef 14 resource service is broken on a first run on Ubuntu 14. 83 | if os.name == 'ubuntu' && os.release.to_f > 14.04 84 | describe service('postgresql') do 85 | it { should be_enabled } 86 | end 87 | end 88 | 89 | describe postgres_cluster(pg_version, 'slave') do 90 | it { should be_running } 91 | end 92 | 93 | describe postgres_cluster(pg_version, 'slave2') do 94 | it { should be_stopped } 95 | end 96 | 97 | describe port(5433) do 98 | it { should be_listening } 99 | end 100 | 101 | describe port(5434) do 102 | it { should_not be_listening } 103 | end 104 | end 105 | 106 | control 'WAL-E cloud backup' do 107 | title 'Check WAL-E cloud backup installation' 108 | 109 | %w( 110 | daemontools 111 | lzop 112 | mbuffer 113 | pv 114 | python3-dev 115 | ).each do |pkg| 116 | describe package(pkg) do 117 | it { should be_installed } 118 | end 119 | end 120 | 121 | %w( 122 | wal-e 123 | boto 124 | ).each do |pip_package| 125 | describe pip(pip_package, '/opt/wal-e/bin/pip') do 126 | it { should be_installed } 127 | end 128 | end 129 | 130 | describe postgres_cluster(pg_version, 'main') do 131 | its('archive_command') do 132 | should match(%r{envdir /etc/wal-e.d/main-#{pg_version}/env/ /opt/wal-e/bin/wal-e wal-push %p}s) 133 | end 134 | end 135 | end 136 | 137 | control 'WAL-G cloud backup' do 138 | title 'Check WAL-G cloud backup installation' 139 | describe file('/usr/local/bin/wal-g') do 140 | it { should exist } 141 | it { should be_executable } 142 | end 143 | 144 | describe file("/etc/wal-g.d/walg-#{pg_version}/env/PGHOST") do 145 | it { should exist } 146 | its('content') { should match(%r{/var/run/postgresql}) } 147 | end 148 | 149 | describe postgres_cluster(pg_version, 'walg') do 150 | its('archive_command') do 151 | should match(%r{envdir /etc/wal-g.d/walg-#{pg_version}/env/ /usr/local/bin/wal-g wal-push %p}s) 152 | end 153 | end 154 | end 155 | -------------------------------------------------------------------------------- /test/integration/postgresql_lwrp_test/inspec/inspec.yml: -------------------------------------------------------------------------------- 1 | name: inspec-test-fixtures 2 | title: InSpec Profile 3 | maintainer: LLC Express 42 4 | license: MIT 5 | version: 0.1.0 6 | -------------------------------------------------------------------------------- /test/integration/postgresql_lwrp_test/inspec/libraries/postgres_cluster.rb: -------------------------------------------------------------------------------- 1 | # author: Dmitry Mischenko 2 | 3 | class PostgresCluster < Inspec.resource(1) 4 | name 'postgres_cluster' 5 | desc 'Use the postgres_cluster InSpec audit resource to test PostgreSQL database cluster status' 6 | example " 7 | describe postgres_cluster('9.6', 'slave') do 8 | it { should be_running } 9 | end 10 | 11 | describe postgres_cluster('9.6', 'slave2') do 12 | it { should be_stopped } 13 | end 14 | " 15 | 16 | def initialize(version, name = 'main') 17 | @name = name 18 | @version = version 19 | @port = get_port(version, name) 20 | @running = 0 21 | @stopping = define_stopping_code(version) 22 | 23 | @params = SimpleConfig.new( 24 | running_configuration, 25 | assignment_regex: /^\s*([^=\|]*?)\s*\|\s*(.*?)\s*$/ 26 | ) 27 | end 28 | 29 | def running? 30 | cmd = "/usr/lib/postgresql/#{@version}/bin/pg_ctl" 31 | cmd += " -D /var/lib/postgresql/#{@version}/#{@name} status" 32 | full_cmd = su_wrapper(cmd) 33 | return true if inspec.command(full_cmd).exit_status == @running 34 | false 35 | end 36 | 37 | def stopped? 38 | cmd = "/usr/lib/postgresql/#{@version}/bin/pg_ctl" 39 | cmd += " -D /var/lib/postgresql/#{@version}/#{@name} status" 40 | full_cmd = su_wrapper(cmd) 41 | return true if inspec.command(full_cmd).exit_status == @stopping 42 | false 43 | end 44 | 45 | def to_s 46 | "Cluster #{@name}" 47 | end 48 | 49 | # Expose all parameters of the configuration file. 50 | def method_missing(name) 51 | @params.params[name.to_s] || super 52 | end 53 | 54 | def respond_to_missing?(method_name, include_private = false) 55 | @params.params.include?(method_name.to_s) || super 56 | end 57 | 58 | private 59 | 60 | def running_configuration 61 | c = query('SELECT name, setting FROM pg_settings') 62 | c 63 | end 64 | 65 | def query(query) 66 | psql_cmd = create_psql_cmd(query, 'postgres') 67 | cmd = inspec.command(psql_cmd) 68 | out = cmd.stdout + "\n" + cmd.stderr 69 | 70 | if cmd.exit_status != 0 || 71 | out =~ /could not connect to .*/ || 72 | out.downcase =~ /^error:.*/ 73 | nil 74 | else 75 | cmd.stdout.strip 76 | end 77 | end 78 | 79 | def escaped_query(query) 80 | Shellwords.escape(query) 81 | end 82 | 83 | def su_wrapper(psql_cmd) 84 | "su postgres -c \"#{psql_cmd}\"" 85 | end 86 | 87 | def create_psql_cmd(query, database) 88 | cmd = '/usr/bin/psql' 89 | cmd += " -d #{database}" 90 | cmd += " -p #{@port}" 91 | cmd += ' -q -A -t' 92 | cmd += " -c #{escaped_query(query)}" 93 | su_wrapper(cmd) 94 | end 95 | 96 | def get_port(version, cluster) 97 | pid_file_name = "/var/lib/postgresql/#{version}/#{cluster}/postmaster.pid" 98 | postmaster_content = inspec.command("cat #{pid_file_name}").stdout.split 99 | postmaster_content[3].to_i 100 | end 101 | 102 | def define_stopping_code(version) 103 | case version.to_s 104 | when '9.1' 105 | 1 106 | else 107 | 3 108 | end 109 | end 110 | end 111 | -------------------------------------------------------------------------------- /test/integration/postgresql_lwrp_test/inspec/libraries/postgres_database.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # author: Kirill Kuznetsov 3 | 4 | class PostgresDatabase < Inspec.resource(1) 5 | name 'postgres_database' 6 | desc 'Use the postgres_database InSpec audit resource to test PostgreSQL cluster databases.' 7 | example " 8 | describe postgres_database('9.6', 'main', 'test01') do 9 | it { should be_created } 10 | it { should have_owner('test01')} 11 | end 12 | " 13 | 14 | def initialize(version, cluster, name) 15 | @name = name 16 | @version = version 17 | @cluster = cluster 18 | @port = get_port(version, cluster) 19 | end 20 | 21 | def created? 22 | return true if query("SELECT datname FROM pg_database where datname='#{@name}'") == @name 23 | false 24 | end 25 | 26 | def has_owner?(owner) 27 | return true if query("SELECT pg_get_userbyid(datdba) FROM pg_database where datname='#{@name}'") == owner 28 | false 29 | end 30 | 31 | def to_s 32 | "Database #{@name}" 33 | end 34 | 35 | private 36 | 37 | def query(query) 38 | psql_cmd = create_psql_cmd(query, 'postgres') 39 | cmd = inspec.command(psql_cmd) 40 | out = cmd.stdout + "\n" + cmd.stderr 41 | if cmd.exit_status != 0 || out =~ /could not connect to .*/ || out.downcase =~ /^error:.*/ 42 | false 43 | else 44 | cmd.stdout.strip 45 | end 46 | end 47 | 48 | def escaped_query(query) 49 | Shellwords.escape(query) 50 | end 51 | 52 | def create_psql_cmd(query, database) 53 | "su postgres -c \"psql -d #{database} -p #{@port} -q -t -c #{escaped_query(query)}\"" 54 | end 55 | 56 | def get_port(version, cluster) 57 | pid_file_name = "/var/lib/postgresql/#{version}/#{cluster}/postmaster.pid" 58 | postmaster_content = inspec.command("cat #{pid_file_name}").stdout.split 59 | postmaster_content[3].to_i 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /test/integration/postgresql_lwrp_test/inspec/libraries/postgres_extension.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # author: Dmitry Mischenko 3 | 4 | class PostgresExtension < Inspec.resource(1) 5 | name 'postgres_extension' 6 | desc 'Use the postgres_extension InSpec audit resource to test installation of PostgreSQL database extensions' 7 | example " 8 | describe postgres_extension('9.6', 'main', 'cube', 'test01') do 9 | it { should be_installed } 10 | end 11 | " 12 | 13 | def initialize(version, cluster, name, db = 'postgres') 14 | @name = name 15 | @db = db 16 | @port = get_port(version, cluster) 17 | end 18 | 19 | def installed? 20 | return true if query('SELECT extname FROM pg_extension').include? @name 21 | false 22 | end 23 | 24 | def to_s 25 | "Extension #{@name}" 26 | end 27 | 28 | private 29 | 30 | def query(query) 31 | psql_cmd = create_psql_cmd(query, @db) 32 | cmd = inspec.command(psql_cmd) 33 | out = cmd.stdout + "\n" + cmd.stderr 34 | if cmd.exit_status != 0 || out =~ /could not connect to .*/ || out.downcase =~ /^error:.*/ 35 | false 36 | else 37 | cmd.stdout.strip 38 | end 39 | end 40 | 41 | def escaped_query(query) 42 | Shellwords.escape(query) 43 | end 44 | 45 | # TODO: You cannot specify multiple DBs 46 | def create_psql_cmd(query, db) 47 | "su postgres -c \"psql -d #{db} -p #{@port} -q -t -c #{escaped_query(query)}\"" 48 | end 49 | 50 | def get_port(version, cluster) 51 | postmaster_content = inspec.command("cat /var/lib/postgresql/#{version}/#{cluster}/postmaster.pid").stdout.split 52 | postmaster_content[3].to_i 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /test/integration/postgresql_lwrp_test/inspec/libraries/postgres_user.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # author: Dmitry Mischenko 3 | 4 | class PostgresUser < Inspec.resource(1) 5 | name 'postgres_user' 6 | desc 'Use the postgres_user InSpec audit resource to test PostgreSQL database user options' 7 | example " 8 | describe postgres_user('9.6', 'main', 'test-02', 'test-02') do 9 | it { should have_login } 10 | it { should have_privilege('rolsuper') } 11 | end 12 | " 13 | 14 | def initialize(version, cluster, user, pass, db = 'postgres') 15 | @user = user || 'postgres' 16 | @pass = pass 17 | @host = 'localhost' 18 | @db = db 19 | @port = get_port(version, cluster) 20 | end 21 | 22 | def has_privilege?(priv) 23 | return true if query("SELECT #{priv} FROM pg_roles where rolname='#{@user}'") == 't' 24 | false 25 | end 26 | 27 | def has_login? 28 | return true if query('SELECT 1') 29 | false 30 | end 31 | 32 | def to_s 33 | "User #{@user}" 34 | end 35 | 36 | private 37 | 38 | def query(query) 39 | psql_cmd = create_psql_cmd(query, @db) 40 | cmd = inspec.command(psql_cmd) 41 | out = cmd.stdout + "\n" + cmd.stderr 42 | if cmd.exit_status != 0 || out =~ /could not connect to .*/ || out.downcase =~ /^error:.*/ 43 | false 44 | else 45 | cmd.stdout.strip 46 | end 47 | end 48 | 49 | def escaped_query(query) 50 | Shellwords.escape(query) 51 | end 52 | 53 | def create_psql_cmd(query, db) 54 | "PGPASSWORD='#{@pass}' psql -U #{@user} -d #{db} -h #{@host} -p #{@port} -A -t -c #{escaped_query(query)}" 55 | end 56 | 57 | def get_port(version, cluster) 58 | postmaster_content = inspec.command("cat /var/lib/postgresql/#{version}/#{cluster}/postmaster.pid").stdout.split 59 | postmaster_content[3].to_i 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /test/integration/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'serverspec' 2 | 3 | set :backend, :exec 4 | set :path, '/sbin:/usr/sbin:$PATH' 5 | 6 | RUNNING = 0 7 | STOPPED = 3 8 | 9 | def get_port(version, name) 10 | postmaster_content = command("cat /var/lib/postgresql/#{version}/#{name}/postmaster.pid").stdout.split 11 | postmaster_content[3].to_i 12 | end 13 | 14 | def postgresql_cluster(version, name) 15 | command("su postgres -c \"/usr/lib/postgresql/#{version}/bin/pg_ctl -D /var/lib/postgresql/#{version}/#{name} status\"").exit_status 16 | end 17 | 18 | def postgresql_check_owner(version, name, database, user) 19 | pg_port = get_port(version, name) 20 | psql_out = command("su postgres -c \"psql -p #{pg_port} -tql 2>/dev/null\"").stdout.strip.split("\n") 21 | psql_out.each do |t| 22 | return true if t.split('|')[0].strip == database && t.split('|')[1].strip == user 23 | end 24 | false 25 | end 26 | 27 | def postgresql_check_priv(version, name, user, priv) 28 | pg_port = get_port(version, name) 29 | psql_out = command("su postgres -c \"psql -qt -p #{pg_port} -c \\\"SELECT #{priv} FROM pg_roles where rolname='#{user}'\\\" 2>/dev/null\"").stdout.strip 30 | psql_out == 't' 31 | end 32 | 33 | def postgresql_check_login(version, name, user, password) 34 | pg_port = get_port(version, name) 35 | psql_out = command("PGPASSWORD='#{password}' su postgres -c \"psql -h 127.0.0.1 -p #{pg_port} -U #{user} -d postgres -c \\\"SELECT 1\\\" 2>/dev/null\"").exit_status 36 | psql_out == 0 37 | end 38 | 39 | def postgresql_extension_installed?(version, name, database, extension) 40 | pg_port = get_port(version, name) 41 | psql_out = command("echo -n \"SELECT extname FROM pg_extension\"| sudo -u postgres psql -t -p \"#{pg_port}\" \"#{database}\" 2>/dev/null") 42 | return true if psql_out.stdout.include? extension 43 | false 44 | end 45 | 46 | def master_tests(pg_version) 47 | describe package("postgresql-#{pg_version}") do 48 | it { should be_installed } 49 | end 50 | 51 | describe service('postgresql') do 52 | it { should be_enabled } 53 | end 54 | 55 | describe 'master service postgresql' do 56 | it 'should be running' do 57 | expect(postgresql_cluster(pg_version, 'main')).to eq(RUNNING) 58 | end 59 | end 60 | 61 | describe port(5432) do 62 | it { should be_listening } 63 | end 64 | end 65 | 66 | def create_database_tests(pg_version) 67 | describe 'database test01' do 68 | it 'should be created and have owner test01' do 69 | expect(postgresql_check_owner(pg_version, 'main', 'test01', 'test01')).to eq(true) 70 | end 71 | end 72 | 73 | describe 'database test-02' do 74 | it 'should be created and have owner test-02' do 75 | expect(postgresql_check_owner(pg_version, 'main', 'test-02', 'test-02')).to eq(true) 76 | end 77 | end 78 | end 79 | 80 | def create_users_tests(pg_version) 81 | describe 'user test01' do 82 | it 'should be able to login with password' do 83 | expect(postgresql_check_login(pg_version, 'main', 'test01', 'test01')).to eq(true) 84 | end 85 | it 'should have replication privileges' do 86 | expect(postgresql_check_priv(pg_version, 'main', 'test01', 'rolreplication')).to eq(true) 87 | end 88 | it 'should not have replication privileges' do 89 | expect(postgresql_check_priv(pg_version, 'main', 'test01', 'rolsuper')).to eq(false) 90 | end 91 | end 92 | 93 | describe 'user test-02' do 94 | it 'should be able to login with password' do 95 | expect(postgresql_check_login(pg_version, 'main', 'test-02', 'test-02')).to eq(true) 96 | end 97 | it 'should have replication privileges' do 98 | expect(postgresql_check_priv(pg_version, 'main', 'test-02', 'rolsuper')).to eq(true) 99 | end 100 | end 101 | end 102 | 103 | def install_extension_tests(pg_version) 104 | describe 'extension should be installed for test01 database' do 105 | it 'extension from postgresql-contrib should be installed' do 106 | expect(postgresql_extension_installed?(pg_version, 'main', 'test01', 'cube')).to eq(true) 107 | end 108 | it 'extension from pgxn should be installed' do 109 | expect(postgresql_extension_installed?(pg_version, 'main', 'test01', 'count_distinct')).to eq(true) 110 | end 111 | end 112 | end 113 | 114 | def slave_tests(pg_version) 115 | describe package("postgresql-#{pg_version}") do 116 | it { should be_installed } 117 | end 118 | 119 | describe service('postgresql') do 120 | it { should be_enabled } 121 | end 122 | 123 | describe 'slave service postgresql' do 124 | it 'should be running' do 125 | expect(postgresql_cluster(pg_version, 'slave')).to eq(RUNNING) 126 | end 127 | end 128 | 129 | describe 'another slave service postgresql' do 130 | it 'should be not running' do 131 | expect(postgresql_cluster(pg_version, 'slave2')).to eq(STOPPED) 132 | end 133 | end 134 | 135 | describe port(5433) do 136 | it { should be_listening } 137 | end 138 | 139 | describe port(5434) do 140 | it { should_not be_listening } 141 | end 142 | end 143 | 144 | def cloud_backup_tests 145 | %w(daemontools lzop mbuffer pv python-dev).each do |pkg| 146 | describe package(pkg) do 147 | it { should be_installed } 148 | end 149 | end 150 | describe package('virtualenv') do 151 | it { should be_installed.by('pip') } 152 | end 153 | describe package('wal-e') do 154 | let(:path) { '/opt/wal-e/bin:$PATH' } 155 | it { should be_installed.by('pip') } 156 | end 157 | describe command('/opt/wal-e/bin/wal-e version') do 158 | its(:exit_status) { should eq 0 } 159 | end 160 | end 161 | --------------------------------------------------------------------------------