From eeab194fb2334a6e1f9f084cba107e9cf40aa8f7 Mon Sep 17 00:00:00 2001 From: Martin Zobel-Helas Date: Sun, 31 May 2015 17:00:18 +0000 Subject: [PATCH] move to puppetlabs rabbitmq module Signed-off-by: Martin Zobel-Helas --- 3rdparty/Puppetfile | 2 +- 3rdparty/modules/rabbitmq/CHANGELOG.md | 261 ++++ 3rdparty/modules/rabbitmq/CONTRIBUTING.md | 220 ++++ 3rdparty/modules/rabbitmq/Gemfile | 47 + 3rdparty/modules/rabbitmq/LICENSE | 201 +++ 3rdparty/modules/rabbitmq/README.md | 609 +++++++++ 3rdparty/modules/rabbitmq/Rakefile | 10 + 3rdparty/modules/rabbitmq/TODO | 10 + 3rdparty/modules/rabbitmq/checksums.json | 105 ++ .../modules/rabbitmq/files/README.markdown | 22 + .../files/plugins/amqp_client-2.3.1.ez | Bin 0 -> 151912 bytes .../files/plugins/rabbit_stomp-2.3.1.ez | Bin 0 -> 55937 bytes .../rabbitmq_binding/rabbitmqadmin.rb | 112 ++ .../provider/rabbitmq_erlang_cookie/ruby.rb | 38 + .../rabbitmq_exchange/rabbitmqadmin.rb | 112 ++ .../rabbitmq_plugin/rabbitmqplugins.rb | 52 + .../provider/rabbitmq_policy/rabbitmqctl.rb | 119 ++ .../provider/rabbitmq_queue/rabbitmqadmin.rb | 107 ++ .../provider/rabbitmq_user/rabbitmqctl.rb | 126 ++ .../rabbitmq_user_permissions/rabbitmqctl.rb | 42 +- .../provider/rabbitmq_vhost/rabbitmqctl.rb | 40 + .../lib/puppet/provider/rabbitmqctl.rb | 33 + .../lib/puppet/type/rabbitmq_binding.rb | 96 ++ .../lib/puppet/type/rabbitmq_erlang_cookie.rb | 34 + .../lib/puppet/type/rabbitmq_exchange.rb | 74 ++ .../lib/puppet/type/rabbitmq_plugin.rb | 0 .../lib/puppet/type/rabbitmq_policy.rb | 101 ++ .../lib/puppet/type/rabbitmq_queue.rb | 68 + .../rabbitmq/lib/puppet/type/rabbitmq_user.rb | 85 ++ .../puppet/type/rabbitmq_user_permissions.rb | 4 +- .../lib/puppet/type/rabbitmq_vhost.rb | 2 + .../2015-05-22_18_55_23/sut.log | 2 + .../2015-05-22_18_57_07/sut.log | 2 + .../2015-05-22_18_35_45/sut.log | 2 + .../2015-05-22_18_49_58/sut.log | 2 + .../2015-05-22_19_10_48/sut.log | 2 + .../2015-05-22_19_11_31/sut.log | 2 + .../log/default/2015-05-22_18_35_28/sut.log | 0 3rdparty/modules/rabbitmq/manifests/config.pp | 172 +++ 3rdparty/modules/rabbitmq/manifests/init.pp | 238 ++++ .../modules/rabbitmq/manifests/install.pp | 23 + .../manifests/install/rabbitmqadmin.pp | 35 + .../modules/rabbitmq/manifests/management.pp | 13 + 3rdparty/modules/rabbitmq/manifests/params.pp | 121 ++ .../modules/rabbitmq/manifests/repo/apt.pp | 41 + .../modules/rabbitmq/manifests/repo/rhel.pp | 16 + 3rdparty/modules/rabbitmq/manifests/server.pp | 82 ++ .../modules/rabbitmq/manifests/service.pp | 40 + 3rdparty/modules/rabbitmq/metadata.json | 55 + .../modules/rabbitmq/spec/README.markdown | 7 + .../rabbitmq/spec/acceptance/class_spec.rb | 95 ++ .../spec/acceptance/clustering_spec.rb | 60 + .../spec/acceptance/delete_guest_user_spec.rb | 26 + .../acceptance/nodesets/centos-59-x64.yml | 10 + .../nodesets/centos-6-x64-vcloud.yml | 16 + .../acceptance/nodesets/centos-64-x64-pe.yml | 12 + .../acceptance/nodesets/centos-65-x64.yml | 10 + .../nodesets/debian-7-x64-vcloud.yml | 16 + .../spec/acceptance/nodesets/default.yml | 11 + .../nodesets/ubuntu-server-10044-x64.yml | 10 + .../nodesets/ubuntu-server-12042-x64.yml | 10 + .../nodesets/ubuntu-server-1404-x64.yml | 11 + .../rabbitmq/spec/acceptance/policy_spec.rb | 47 + .../rabbitmq/spec/acceptance/queue_spec.rb | 157 +++ .../spec/acceptance/rabbitmqadmin_spec.rb | 85 ++ .../rabbitmq/spec/acceptance/server_spec.rb | 96 ++ .../rabbitmq/spec/acceptance/user_spec.rb | 38 + .../rabbitmq/spec/acceptance/vhost_spec.rb | 37 + .../rabbitmq/spec/acceptance/zz281_spec.rb | 213 ++++ .../rabbitmq/spec/classes/rabbitmq_spec.rb | 1099 +++++++++++++++++ 3rdparty/modules/rabbitmq/spec/spec.opts | 6 + 3rdparty/modules/rabbitmq/spec/spec_helper.rb | 1 + .../rabbitmq/spec/spec_helper_acceptance.rb | 38 + .../rabbitmq_binding/rabbitmqadmin_spec.rb | 59 + .../rabbitmq_exchange/rabbitmqadmin_spec.rb | 75 ++ .../rabbitmq_plugin/rabbitmqctl_spec.rb | 26 + .../rabbitmq_policy/rabbitmqctl_spec.rb | 114 ++ .../rabbitmq_queue/rabbitmqadmin_spec.rb | 60 + .../rabbitmq_user/rabbitmqctl_spec.rb | 215 ++++ .../rabbitmqctl_spec.rb | 93 ++ .../rabbitmq_vhost/rabbitmqctl_spec.rb | 45 + .../unit/puppet/type/rabbitmq_binding_spec.rb | 50 + .../puppet/type/rabbitmq_exchange_spec.rb | 57 + .../unit/puppet/type/rabbitmq_policy_spec.rb | 119 ++ .../unit/puppet/type/rabbitmq_queue_spec.rb | 60 + .../type/rabbitmq_user_permissions_spec.rb | 55 + .../unit/puppet/type/rabbitmq_user_spec.rb | 52 + .../unit/puppet/type/rabbitmq_vhost_spec.rb | 21 + .../rabbitmq/templates/README.markdown | 23 + .../modules/rabbitmq/templates/default.erb | 10 + .../modules/rabbitmq/templates/limits.conf | 2 + .../rabbitmq/templates/rabbitmq-env.conf.erb | 5 + .../rabbitmq-server.service.d/limits.conf | 2 + .../rabbitmq/templates/rabbitmq.config.erb | 110 ++ .../rabbitmq/templates/rabbitmqadmin.conf.erb | 7 + .../modules/rabbitmq/tests/erlang_deps.pp | 5 + 3rdparty/modules/rabbitmq/tests/full.pp | 21 + .../modules/rabbitmq/tests/permissions/add.pp | 9 + 3rdparty/modules/rabbitmq/tests/plugin.pp | 11 + 3rdparty/modules/rabbitmq/tests/repo/apt.pp | 2 + 3rdparty/modules/rabbitmq/tests/server.pp | 5 + 3rdparty/modules/rabbitmq/tests/service.pp | 1 + 3rdparty/modules/rabbitmq/tests/site.pp | 16 + 3rdparty/modules/rabbitmq/tests/user/add.pp | 4 + 3rdparty/modules/rabbitmq/tests/vhosts/add.pp | 1 + .../provider/rabbitmq_plugin/default.rb | 22 - .../rabbitmq_plugin/rabbitmqplugins.rb | 30 - .../provider/rabbitmq_policy/default.rb | 23 - .../provider/rabbitmq_policy/rabbitmqctl.rb | 39 - .../puppet/provider/rabbitmq_user/default.rb | 22 - .../provider/rabbitmq_user/rabbitmqctl.rb | 59 - .../rabbitmq_user_permissions/default.rb | 18 - .../puppet/provider/rabbitmq_vhost/default.rb | 22 - .../provider/rabbitmq_vhost/rabbitmqctl.rb | 30 - .../lib/puppet/type/rabbitmq_policy.rb | 39 - .../rabbitmq/lib/puppet/type/rabbitmq_user.rb | 40 - modules/rabbitmq/manifests/autouser.pp | 32 - modules/rabbitmq/manifests/config.pp | 14 - modules/rabbitmq/manifests/init.pp | 97 -- modules/rabbitmq/templates/rabbitmq.conf.erb | 12 - .../rabbitmq/templates/rabbitmq.ulimit.erb | 1 - .../roles/files/pubsub/rabbitmq-mgmt.config | 10 - modules/roles/files/pubsub/rabbitmq.config | 7 - modules/roles/manifests/pubsub.pp | 27 +- modules/roles/manifests/pubsub/entities.pp | 132 +- 125 files changed, 7052 insertions(+), 650 deletions(-) create mode 100644 3rdparty/modules/rabbitmq/CHANGELOG.md create mode 100644 3rdparty/modules/rabbitmq/CONTRIBUTING.md create mode 100644 3rdparty/modules/rabbitmq/Gemfile create mode 100644 3rdparty/modules/rabbitmq/LICENSE create mode 100644 3rdparty/modules/rabbitmq/README.md create mode 100644 3rdparty/modules/rabbitmq/Rakefile create mode 100644 3rdparty/modules/rabbitmq/TODO create mode 100644 3rdparty/modules/rabbitmq/checksums.json create mode 100644 3rdparty/modules/rabbitmq/files/README.markdown create mode 100644 3rdparty/modules/rabbitmq/files/plugins/amqp_client-2.3.1.ez create mode 100644 3rdparty/modules/rabbitmq/files/plugins/rabbit_stomp-2.3.1.ez create mode 100644 3rdparty/modules/rabbitmq/lib/puppet/provider/rabbitmq_binding/rabbitmqadmin.rb create mode 100644 3rdparty/modules/rabbitmq/lib/puppet/provider/rabbitmq_erlang_cookie/ruby.rb create mode 100644 3rdparty/modules/rabbitmq/lib/puppet/provider/rabbitmq_exchange/rabbitmqadmin.rb create mode 100644 3rdparty/modules/rabbitmq/lib/puppet/provider/rabbitmq_plugin/rabbitmqplugins.rb create mode 100644 3rdparty/modules/rabbitmq/lib/puppet/provider/rabbitmq_policy/rabbitmqctl.rb create mode 100644 3rdparty/modules/rabbitmq/lib/puppet/provider/rabbitmq_queue/rabbitmqadmin.rb create mode 100644 3rdparty/modules/rabbitmq/lib/puppet/provider/rabbitmq_user/rabbitmqctl.rb rename {modules => 3rdparty/modules}/rabbitmq/lib/puppet/provider/rabbitmq_user_permissions/rabbitmqctl.rb (63%) create mode 100644 3rdparty/modules/rabbitmq/lib/puppet/provider/rabbitmq_vhost/rabbitmqctl.rb create mode 100644 3rdparty/modules/rabbitmq/lib/puppet/provider/rabbitmqctl.rb create mode 100644 3rdparty/modules/rabbitmq/lib/puppet/type/rabbitmq_binding.rb create mode 100644 3rdparty/modules/rabbitmq/lib/puppet/type/rabbitmq_erlang_cookie.rb create mode 100644 3rdparty/modules/rabbitmq/lib/puppet/type/rabbitmq_exchange.rb rename {modules => 3rdparty/modules}/rabbitmq/lib/puppet/type/rabbitmq_plugin.rb (100%) create mode 100644 3rdparty/modules/rabbitmq/lib/puppet/type/rabbitmq_policy.rb create mode 100644 3rdparty/modules/rabbitmq/lib/puppet/type/rabbitmq_queue.rb create mode 100644 3rdparty/modules/rabbitmq/lib/puppet/type/rabbitmq_user.rb rename {modules => 3rdparty/modules}/rabbitmq/lib/puppet/type/rabbitmq_user_permissions.rb (91%) rename {modules => 3rdparty/modules}/rabbitmq/lib/puppet/type/rabbitmq_vhost.rb (87%) create mode 100644 3rdparty/modules/rabbitmq/log/centos-6-x64-vcloud/2015-05-22_18_55_23/sut.log create mode 100644 3rdparty/modules/rabbitmq/log/centos-6-x64-vcloud/2015-05-22_18_57_07/sut.log create mode 100644 3rdparty/modules/rabbitmq/log/debian-7-x64-vcloud/2015-05-22_18_35_45/sut.log create mode 100644 3rdparty/modules/rabbitmq/log/debian-7-x64-vcloud/2015-05-22_18_49_58/sut.log create mode 100644 3rdparty/modules/rabbitmq/log/debian-7-x64-vcloud/2015-05-22_19_10_48/sut.log create mode 100644 3rdparty/modules/rabbitmq/log/debian-7-x64-vcloud/2015-05-22_19_11_31/sut.log create mode 100644 3rdparty/modules/rabbitmq/log/default/2015-05-22_18_35_28/sut.log create mode 100644 3rdparty/modules/rabbitmq/manifests/config.pp create mode 100644 3rdparty/modules/rabbitmq/manifests/init.pp create mode 100644 3rdparty/modules/rabbitmq/manifests/install.pp create mode 100644 3rdparty/modules/rabbitmq/manifests/install/rabbitmqadmin.pp create mode 100644 3rdparty/modules/rabbitmq/manifests/management.pp create mode 100644 3rdparty/modules/rabbitmq/manifests/params.pp create mode 100644 3rdparty/modules/rabbitmq/manifests/repo/apt.pp create mode 100644 3rdparty/modules/rabbitmq/manifests/repo/rhel.pp create mode 100644 3rdparty/modules/rabbitmq/manifests/server.pp create mode 100644 3rdparty/modules/rabbitmq/manifests/service.pp create mode 100644 3rdparty/modules/rabbitmq/metadata.json create mode 100644 3rdparty/modules/rabbitmq/spec/README.markdown create mode 100644 3rdparty/modules/rabbitmq/spec/acceptance/class_spec.rb create mode 100644 3rdparty/modules/rabbitmq/spec/acceptance/clustering_spec.rb create mode 100644 3rdparty/modules/rabbitmq/spec/acceptance/delete_guest_user_spec.rb create mode 100644 3rdparty/modules/rabbitmq/spec/acceptance/nodesets/centos-59-x64.yml create mode 100644 3rdparty/modules/rabbitmq/spec/acceptance/nodesets/centos-6-x64-vcloud.yml create mode 100644 3rdparty/modules/rabbitmq/spec/acceptance/nodesets/centos-64-x64-pe.yml create mode 100644 3rdparty/modules/rabbitmq/spec/acceptance/nodesets/centos-65-x64.yml create mode 100644 3rdparty/modules/rabbitmq/spec/acceptance/nodesets/debian-7-x64-vcloud.yml create mode 100644 3rdparty/modules/rabbitmq/spec/acceptance/nodesets/default.yml create mode 100644 3rdparty/modules/rabbitmq/spec/acceptance/nodesets/ubuntu-server-10044-x64.yml create mode 100644 3rdparty/modules/rabbitmq/spec/acceptance/nodesets/ubuntu-server-12042-x64.yml create mode 100644 3rdparty/modules/rabbitmq/spec/acceptance/nodesets/ubuntu-server-1404-x64.yml create mode 100644 3rdparty/modules/rabbitmq/spec/acceptance/policy_spec.rb create mode 100644 3rdparty/modules/rabbitmq/spec/acceptance/queue_spec.rb create mode 100644 3rdparty/modules/rabbitmq/spec/acceptance/rabbitmqadmin_spec.rb create mode 100644 3rdparty/modules/rabbitmq/spec/acceptance/server_spec.rb create mode 100644 3rdparty/modules/rabbitmq/spec/acceptance/user_spec.rb create mode 100644 3rdparty/modules/rabbitmq/spec/acceptance/vhost_spec.rb create mode 100644 3rdparty/modules/rabbitmq/spec/acceptance/zz281_spec.rb create mode 100644 3rdparty/modules/rabbitmq/spec/classes/rabbitmq_spec.rb create mode 100644 3rdparty/modules/rabbitmq/spec/spec.opts create mode 100644 3rdparty/modules/rabbitmq/spec/spec_helper.rb create mode 100644 3rdparty/modules/rabbitmq/spec/spec_helper_acceptance.rb create mode 100644 3rdparty/modules/rabbitmq/spec/unit/puppet/provider/rabbitmq_binding/rabbitmqadmin_spec.rb create mode 100644 3rdparty/modules/rabbitmq/spec/unit/puppet/provider/rabbitmq_exchange/rabbitmqadmin_spec.rb create mode 100644 3rdparty/modules/rabbitmq/spec/unit/puppet/provider/rabbitmq_plugin/rabbitmqctl_spec.rb create mode 100644 3rdparty/modules/rabbitmq/spec/unit/puppet/provider/rabbitmq_policy/rabbitmqctl_spec.rb create mode 100644 3rdparty/modules/rabbitmq/spec/unit/puppet/provider/rabbitmq_queue/rabbitmqadmin_spec.rb create mode 100644 3rdparty/modules/rabbitmq/spec/unit/puppet/provider/rabbitmq_user/rabbitmqctl_spec.rb create mode 100644 3rdparty/modules/rabbitmq/spec/unit/puppet/provider/rabbitmq_user_permissions/rabbitmqctl_spec.rb create mode 100644 3rdparty/modules/rabbitmq/spec/unit/puppet/provider/rabbitmq_vhost/rabbitmqctl_spec.rb create mode 100644 3rdparty/modules/rabbitmq/spec/unit/puppet/type/rabbitmq_binding_spec.rb create mode 100644 3rdparty/modules/rabbitmq/spec/unit/puppet/type/rabbitmq_exchange_spec.rb create mode 100644 3rdparty/modules/rabbitmq/spec/unit/puppet/type/rabbitmq_policy_spec.rb create mode 100644 3rdparty/modules/rabbitmq/spec/unit/puppet/type/rabbitmq_queue_spec.rb create mode 100644 3rdparty/modules/rabbitmq/spec/unit/puppet/type/rabbitmq_user_permissions_spec.rb create mode 100644 3rdparty/modules/rabbitmq/spec/unit/puppet/type/rabbitmq_user_spec.rb create mode 100644 3rdparty/modules/rabbitmq/spec/unit/puppet/type/rabbitmq_vhost_spec.rb create mode 100644 3rdparty/modules/rabbitmq/templates/README.markdown create mode 100644 3rdparty/modules/rabbitmq/templates/default.erb create mode 100644 3rdparty/modules/rabbitmq/templates/limits.conf create mode 100644 3rdparty/modules/rabbitmq/templates/rabbitmq-env.conf.erb create mode 100644 3rdparty/modules/rabbitmq/templates/rabbitmq-server.service.d/limits.conf create mode 100644 3rdparty/modules/rabbitmq/templates/rabbitmq.config.erb create mode 100644 3rdparty/modules/rabbitmq/templates/rabbitmqadmin.conf.erb create mode 100644 3rdparty/modules/rabbitmq/tests/erlang_deps.pp create mode 100644 3rdparty/modules/rabbitmq/tests/full.pp create mode 100644 3rdparty/modules/rabbitmq/tests/permissions/add.pp create mode 100644 3rdparty/modules/rabbitmq/tests/plugin.pp create mode 100644 3rdparty/modules/rabbitmq/tests/repo/apt.pp create mode 100644 3rdparty/modules/rabbitmq/tests/server.pp create mode 100644 3rdparty/modules/rabbitmq/tests/service.pp create mode 100644 3rdparty/modules/rabbitmq/tests/site.pp create mode 100644 3rdparty/modules/rabbitmq/tests/user/add.pp create mode 100644 3rdparty/modules/rabbitmq/tests/vhosts/add.pp delete mode 100644 modules/rabbitmq/lib/puppet/provider/rabbitmq_plugin/default.rb delete mode 100644 modules/rabbitmq/lib/puppet/provider/rabbitmq_plugin/rabbitmqplugins.rb delete mode 100644 modules/rabbitmq/lib/puppet/provider/rabbitmq_policy/default.rb delete mode 100644 modules/rabbitmq/lib/puppet/provider/rabbitmq_policy/rabbitmqctl.rb delete mode 100644 modules/rabbitmq/lib/puppet/provider/rabbitmq_user/default.rb delete mode 100644 modules/rabbitmq/lib/puppet/provider/rabbitmq_user/rabbitmqctl.rb delete mode 100644 modules/rabbitmq/lib/puppet/provider/rabbitmq_user_permissions/default.rb delete mode 100644 modules/rabbitmq/lib/puppet/provider/rabbitmq_vhost/default.rb delete mode 100644 modules/rabbitmq/lib/puppet/provider/rabbitmq_vhost/rabbitmqctl.rb delete mode 100644 modules/rabbitmq/lib/puppet/type/rabbitmq_policy.rb delete mode 100644 modules/rabbitmq/lib/puppet/type/rabbitmq_user.rb delete mode 100644 modules/rabbitmq/manifests/autouser.pp delete mode 100644 modules/rabbitmq/manifests/config.pp delete mode 100644 modules/rabbitmq/manifests/init.pp delete mode 100644 modules/rabbitmq/templates/rabbitmq.conf.erb delete mode 100644 modules/rabbitmq/templates/rabbitmq.ulimit.erb delete mode 100644 modules/roles/files/pubsub/rabbitmq-mgmt.config delete mode 100644 modules/roles/files/pubsub/rabbitmq.config diff --git a/3rdparty/Puppetfile b/3rdparty/Puppetfile index 5b1d0e43..8357f92d 100644 --- a/3rdparty/Puppetfile +++ b/3rdparty/Puppetfile @@ -1,9 +1,9 @@ forge "http://forge.puppetlabs.com" -#mod 'puppetlabs/rabbitmq', '5.1.0' #mod 'puppetlabs/xinetd', '1.5.0' #mod 'puppetlabs/stunnel', '0.1.0' mod 'puppetlabs/stdlib', '4.6.0' mod 'puppetlabs/concat', '1.2.2' +mod 'puppetlabs/rabbitmq', '5.2.1' mod 'elasticsearch/elasticsearch', '0.9.5' diff --git a/3rdparty/modules/rabbitmq/CHANGELOG.md b/3rdparty/modules/rabbitmq/CHANGELOG.md new file mode 100644 index 00000000..9800626d --- /dev/null +++ b/3rdparty/modules/rabbitmq/CHANGELOG.md @@ -0,0 +1,261 @@ +## 2015-05-26 - Version 5.2.1 +###Summary +This release includes a fix for idempotency between puppet runs, as well as Readme updates + +####Features +- Readme updates +- Testing updates + +####Bugfixes +- Ensure idempotency between Puppet runs + +## 2015-04-28 - Version 5.2.0 +###Summary +This release adds several new features for expanded configuration, support for SSL Ciphers, several bugfixes, and improved tests. + +####Features +- New parameters to class `rabbitmq` + - `ssl_ciphers` +- New parameters to class `rabbitmq::config` + - `interface` + - `ssl_interface` +- New parameters to type `rabbitmq_exchange` + - `internal` + - `auto_delete` + - `durable` +- Adds syncing with Modulesync +- Adds support for SSL Ciphers +- Adds `file_limit` support for RedHat platforms + +####Bugfixes +- Will not create `rabbitmqadmin.conf` if admin is disabled +- Fixes `check_password` +- Fix to allow bindings and queues to be created when non-default management port is being used by rabbitmq. (MODULES-1856) +- `rabbitmq_policy` converts known parameters to integers +- Updates apt key for full fingerprint compliance. +- Adds a missing `routing_key` param to rabbitmqadmin absent binding call. + +## 2015-03-10 - Version 5.1.0 +###Summary +This release adds several features for greater flexibility in configuration of rabbitmq, includes a number of bug fixes, and bumps the minimum required version of puppetlabs-stdlib to 3.0.0. + +####Changes to defaults +- The default environment variables in `rabbitmq::config` have been renamed from `RABBITMQ_NODE_PORT` and `RABBITMQ_NODE_IP_ADDRESS` to `NODE_PORT` and `NODE_IP_ADDRESS` (MODULES-1673) + +####Features +- New parameters to class `rabbitmq` + - `file_limit` + - `interface` + - `ldap_other_bind` + - `ldap_config_variables` + - `ssl_interface` + - `ssl_versions` + - `rabbitmq_group` + - `rabbitmq_home` + - `rabbitmq_user` +- Add `rabbitmq_queue` and `rabbitmq_binding` types +- Update the providers to be able to retry commands + +####Bugfixes +- Cleans up the formatting for rabbitmq.conf for readability +- Update tag splitting in the `rabbitmqctl` provider for `rabbitmq_user` to work with comma or space separated tags +- Do not enforce the source value for the yum provider (MODULES-1631) +- Fix conditional around `$pin` +- Remove broken SSL option in rabbitmqadmin.conf (MODULES-1691) +- Fix issues in `rabbitmq_user` with admin and no tags +- Fix issues in `rabbitmq_user` with tags not being sorted +- Fix broken check for existing exchanges in `rabbitmq_exchange` + +## 2014-12-22 - Version 5.0.0 +### Summary + +This release fixes a longstanding security issue where the rabbitmq +erlang cookie was exposed as a fact by managing the cookie with a +provider. It also drops support for Puppet 2.7, adds many features +and fixes several bugs. + +#### Backwards-incompatible Changes + +- Removed the rabbitmq_erlang_cookie fact and replaced the logic to + manage that cookie with a provider. +- Dropped official support for Puppet 2.7 (EOL 9/30/2014 + https://groups.google.com/forum/#!topic/puppet-users/QLguMcLraLE ) +- Changed the default value of $rabbitmq::params::ldap_user_dn_pattern + to not contain a variable +- Removed deprecated parameters: $rabbitmq::cluster_disk_nodes, + $rabbitmq::server::manage_service, and + $rabbitmq::server::config_mirrored_queues + +#### Features + +- Add tcp_keepalive parameter to enable TCP keepalive +- Use https to download rabbitmqadmin tool when $rabbitmq::ssl is true +- Add key_content parameter for offline Debian package installations +- Use 16 character apt key to avoid potential collisions +- Add rabbitmq_policy type, including support for rabbitmq <3.2.0 +- Add rabbitmq::ensure_repo parameter +- Add ability to change rabbitmq_user password +- Allow disk as a valid cluster node type + +#### Bugfixes + +- Avoid attempting to install rabbitmqadmin via a proxy (since it is + downloaded from localhost) +- Optimize check for RHEL GPG key +- Configure ssl_listener in stomp only if using ssl +- Use rpm as default package provider for RedHat, bringing the module in + line with the documented instructions to manage erlang separately and allowing + the default version and source parameters to become meaningful +- Configure cacertfile only if verify_none is not set +- Use -q flag for rabbitmqctl commands to avoid parsing inconsistent + debug output +- Use the -m flag for rabbitmqplugins commands, again to avoid parsing + inconsistent debug output +- Strip backslashes from the rabbitmqctl output to avoid parsing issues +- Fix limitation where version parameter was ignored +- Add /etc/rabbitmq/rabbitmqadmin.conf to fix rabbitmqadmin port usage + when ssl is on +- Fix linter errors and warnings +- Add, update, and fix tests +- Update docs + +## 2014-08-20 - Version 4.1.0 +### Summary + +This release adds several new features, fixes bugs, and improves tests and +documentation. + +#### Features +- Autorequire the rabbitmq-server service in the rabbitmq_vhost type +- Add credentials to rabbitmqadmin URL +- Added $ssl_only parameter to rabbitmq, rabbitmq::params, and +rabbitmq::config +- Added property tags to rabbitmq_user provider + +#### Bugfixes +- Fix erroneous commas in rabbitmq::config +- Use correct ensure value for the rabbitmq_stomp rabbitmq_plugin +- Set HOME env variable to nil when leveraging rabbitmq to remove type error +from Python script +- Fix location for rabbitmq-plugins for RHEL +- Remove validation for package_source to allow it to be set to false +- Allow LDAP auth configuration without configuring stomp +- Added missing $ssl_verify and $ssl_fail_if_no_peer_cert to rabbitmq::config + +## 2014-05-16 - Version 4.0.0 +### Summary + +This release includes many new features and bug fixes. With the exception of +erlang management this should be backwards compatible with 3.1.0. + +#### Backwards-incompatible Changes +- erlang_manage was removed. You will need to manage erlang separately. See +the README for more information on how to configure this. + +#### Features +- Improved SSL support +- Add LDAP support +- Add ability to manage RabbitMQ repositories +- Add ability to manage Erlang kernel configuration options +- Improved handling of user tags +- Use nanliu-staging module instead of hardcoded 'curl' +- Switch to yum or zypper provider instead of rpm +- Add ability to manage STOMP plugin installation. +- Allow empty permission fields +- Convert existing system tests to beaker acceptance tests. + +#### Bugfixes +- exchanges no longer recreated on each puppet run if non-default vhost is used +- Allow port to be UNSET +- Re-added rabbitmq::server class +- Deprecated previously unused manage_service variable in favor of + service_manage +- Use correct key for rabbitmq apt::source +- config_mirrored_queues variable removed + - It previously did nothing, will now at least throw a warning if you try to + use it +- Remove unnecessary dependency on Class['rabbitmq::repo::rhel'] in + rabbitmq::install + + +## 2013-09-14 - Version 3.1.0 +### Summary + +This release focuses on a few small (but critical) bugfixes as well as extends +the amount of custom RabbitMQ configuration you can do with the module. + +#### Features +- You can now change RabbitMQ 'Config Variables' via the parameter `config_variables`. +- You can now change RabbitMQ 'Environment Variables' via the parameter `environment_variables`. +- ArchLinux support added. + +#### Fixes +- Make use of the user/password parameters in rabbitmq_exchange{} +- Correct the read/write parameter order on set_permissions/list_permissions as + they were reversed. +- Make the module pull down 3.1.5 by default. + +## 2013-07-18 3.0.0 +### Summary + +This release heavily refactors the RabbitMQ and changes functionality in +several key ways. Please pay attention to the new README.md file for +details of how to interact with the class now. Puppet 3 and RHEL are +now fully supported. The default version of RabbitMQ has changed to +a 3.x release. + +#### Bugfixes + +- Improve travis testing options. +- Stop reimporting the GPG key on every run on RHEL and Debian. +- Fix documentation to make it clear you don't have to set provider => each time. +- Reference the standard rabbitmq port in the documentation instead of a custom port. +- Fixes to the README formatting. + +#### Features +- Refactor the module to fix RHEL support. All interaction with the module +is now done through the main rabbitmq class. +- Add support for mirrored queues (Only on Debian family distributions currently) +- Add rabbitmq_exchange provider (using rabbitmqadmin) +- Add new `rabbitmq` class parameters: + - `manage_service`: Boolean to choose if Puppet should manage the service. (For pacemaker/HA setups) +- Add SuSE support. + +#### Incompatible Changes + +- Rabbitmq::server has been removed and is now rabbitmq::config. You should +not use this class directly, only via the main rabbitmq class. + +## 2013-04-11 2.1.0 + +- remove puppetversion from rabbitmq.config template +- add cluster support +- escape resource names in regexp + +## 2012-07-31 Jeff McCune 2.0.2 +- Re-release 2.0.1 with $EDITOR droppings cleaned up + +## 2012-05-03 2.0.0 +- added support for new-style admin users +- added support for rabbitmq 2.7.1 + +## 2011-06-14 Dan Bode 2.0.0rc1 +- Massive refactor: +- added native types for user/vhost/user_permissions +- added apt support for vendor packages +- added smoke tests + +## 2011-04-08 Jeff McCune 1.0.4 +- Update module for RabbitMQ 2.4.1 and rabbitmq-plugin-stomp package. + +## 2011-03-24 1.0.3 +- Initial release to the forge. Reviewed by Cody. Whitespace is good. + +## 2011-03-22 1.0.2 +- Whitespace only fix again... ack '\t' is my friend... + +## 2011-03-22 1.0.1 +- Whitespace only fix. + +## 2011-03-22 1.0.0 +- Initial Release. Manage the package, file and service. diff --git a/3rdparty/modules/rabbitmq/CONTRIBUTING.md b/3rdparty/modules/rabbitmq/CONTRIBUTING.md new file mode 100644 index 00000000..f1cbde4b --- /dev/null +++ b/3rdparty/modules/rabbitmq/CONTRIBUTING.md @@ -0,0 +1,220 @@ +Checklist (and a short version for the impatient) +================================================= + + * Commits: + + - Make commits of logical units. + + - Check for unnecessary whitespace with "git diff --check" before + committing. + + - Commit using Unix line endings (check the settings around "crlf" in + git-config(1)). + + - Do not check in commented out code or unneeded files. + + - The first line of the commit message should be a short + description (50 characters is the soft limit, excluding ticket + number(s)), and should skip the full stop. + + - Associate the issue in the message. The first line should include + the issue number in the form "(#XXXX) Rest of message". + + - The body should provide a meaningful commit message, which: + + - uses the imperative, present tense: "change", not "changed" or + "changes". + + - includes motivation for the change, and contrasts its + implementation with the previous behavior. + + - Make sure that you have tests for the bug you are fixing, or + feature you are adding. + + - Make sure the test suites passes after your commit: + `bundle exec rspec spec/acceptance` More information on [testing](#Testing) below + + - When introducing a new feature, make sure it is properly + documented in the README.md + + * Submission: + + * Pre-requisites: + + - Make sure you have a [GitHub account](https://github.com/join) + + - [Create a ticket](https://tickets.puppetlabs.com/secure/CreateIssue!default.jspa), or [watch the ticket](https://tickets.puppetlabs.com/browse/) you are patching for. + + * Preferred method: + + - Fork the repository on GitHub. + + - Push your changes to a topic branch in your fork of the + repository. (the format ticket/1234-short_description_of_change is + usually preferred for this project). + + - Submit a pull request to the repository in the puppetlabs + organization. + +The long version +================ + + 1. Make separate commits for logically separate changes. + + Please break your commits down into logically consistent units + which include new or changed tests relevant to the rest of the + change. The goal of doing this is to make the diff easier to + read for whoever is reviewing your code. In general, the easier + your diff is to read, the more likely someone will be happy to + review it and get it into the code base. + + If you are going to refactor a piece of code, please do so as a + separate commit from your feature or bug fix changes. + + We also really appreciate changes that include tests to make + sure the bug is not re-introduced, and that the feature is not + accidentally broken. + + Describe the technical detail of the change(s). If your + description starts to get too long, that is a good sign that you + probably need to split up your commit into more finely grained + pieces. + + Commits which plainly describe the things which help + reviewers check the patch and future developers understand the + code are much more likely to be merged in with a minimum of + bike-shedding or requested changes. Ideally, the commit message + would include information, and be in a form suitable for + inclusion in the release notes for the version of Puppet that + includes them. + + Please also check that you are not introducing any trailing + whitespace or other "whitespace errors". You can do this by + running "git diff --check" on your changes before you commit. + + 2. Sending your patches + + To submit your changes via a GitHub pull request, we _highly_ + recommend that you have them on a topic branch, instead of + directly on "master". + It makes things much easier to keep track of, especially if + you decide to work on another thing before your first change + is merged in. + + GitHub has some pretty good + [general documentation](http://help.github.com/) on using + their site. They also have documentation on + [creating pull requests](http://help.github.com/send-pull-requests/). + + In general, after pushing your topic branch up to your + repository on GitHub, you can switch to the branch in the + GitHub UI and click "Pull Request" towards the top of the page + in order to open a pull request. + + + 3. Update the related GitHub issue. + + If there is a GitHub issue associated with the change you + submitted, then you should update the ticket to include the + location of your branch, along with any other commentary you + may wish to make. + +Testing +======= + +Getting Started +--------------- + +Our puppet modules provide [`Gemfile`](./Gemfile)s which can tell a ruby +package manager such as [bundler](http://bundler.io/) what Ruby packages, +or Gems, are required to build, develop, and test this software. + +Please make sure you have [bundler installed](http://bundler.io/#getting-started) +on your system, then use it to install all dependencies needed for this project, +by running + +```shell +% bundle install +Fetching gem metadata from https://rubygems.org/........ +Fetching gem metadata from https://rubygems.org/.. +Using rake (10.1.0) +Using builder (3.2.2) +-- 8><-- many more --><8 -- +Using rspec-system-puppet (2.2.0) +Using serverspec (0.6.3) +Using rspec-system-serverspec (1.0.0) +Using bundler (1.3.5) +Your bundle is complete! +Use `bundle show [gemname]` to see where a bundled gem is installed. +``` + +NOTE some systems may require you to run this command with sudo. + +If you already have those gems installed, make sure they are up-to-date: + +```shell +% bundle update +``` + +With all dependencies in place and up-to-date we can now run the tests: + +```shell +% rake spec +``` + +This will execute all the [rspec tests](http://rspec-puppet.com/) tests +under [spec/defines](./spec/defines), [spec/classes](./spec/classes), +and so on. rspec tests may have the same kind of dependencies as the +module they are testing. While the module defines in its [Modulefile](./Modulefile), +rspec tests define them in [.fixtures.yml](./fixtures.yml). + +Some puppet modules also come with [beaker](https://github.com/puppetlabs/beaker) +tests. These tests spin up a virtual machine under +[VirtualBox](https://www.virtualbox.org/)) with, controlling it with +[Vagrant](http://www.vagrantup.com/) to actually simulate scripted test +scenarios. In order to run these, you will need both of those tools +installed on your system. + +You can run them by issuing the following command + +```shell +% rake spec_clean +% rspec spec/acceptance +``` + +This will now download a pre-fabricated image configured in the [default node-set](./spec/acceptance/nodesets/default.yml), +install puppet, copy this module and install its dependencies per [spec/spec_helper_acceptance.rb](./spec/spec_helper_acceptance.rb) +and then run all the tests under [spec/acceptance](./spec/acceptance). + +Writing Tests +------------- + +XXX getting started writing tests. + +If you have commit access to the repository +=========================================== + +Even if you have commit access to the repository, you will still need to +go through the process above, and have someone else review and merge +in your changes. The rule is that all changes must be reviewed by a +developer on the project (that did not write the code) to ensure that +all changes go through a code review process. + +Having someone other than the author of the topic branch recorded as +performing the merge is the record that they performed the code +review. + + +Additional Resources +==================== + +* [Getting additional help](http://puppetlabs.com/community/get-help) + +* [Writing tests](http://projects.puppetlabs.com/projects/puppet/wiki/Development_Writing_Tests) + +* [Patchwork](https://patchwork.puppetlabs.com) + +* [General GitHub documentation](http://help.github.com/) + +* [GitHub pull request documentation](http://help.github.com/send-pull-requests/) + diff --git a/3rdparty/modules/rabbitmq/Gemfile b/3rdparty/modules/rabbitmq/Gemfile new file mode 100644 index 00000000..2b1b7cd8 --- /dev/null +++ b/3rdparty/modules/rabbitmq/Gemfile @@ -0,0 +1,47 @@ +source ENV['GEM_SOURCE'] || "https://rubygems.org" + +def location_for(place, fake_version = nil) + if place =~ /^(git:[^#]*)#(.*)/ + [fake_version, { :git => $1, :branch => $2, :require => false }].compact + elsif place =~ /^file:\/\/(.*)/ + ['>= 0', { :path => File.expand_path($1), :require => false }] + else + [place, { :require => false }] + end +end + +group :development, :unit_tests do + gem 'rspec-core', '3.1.7', :require => false + gem 'puppetlabs_spec_helper', :require => false + gem 'simplecov', :require => false + gem 'puppet_facts', :require => false + gem 'json', :require => false +end + +group :system_tests do + if beaker_version = ENV['BEAKER_VERSION'] + gem 'beaker', *location_for(beaker_version) + end + if beaker_rspec_version = ENV['BEAKER_RSPEC_VERSION'] + gem 'beaker-rspec', *location_for(beaker_rspec_version) + else + gem 'beaker-rspec', :require => false + end + gem 'serverspec', :require => false +end + + + +if facterversion = ENV['FACTER_GEM_VERSION'] + gem 'facter', facterversion, :require => false +else + gem 'facter', :require => false +end + +if puppetversion = ENV['PUPPET_GEM_VERSION'] + gem 'puppet', puppetversion, :require => false +else + gem 'puppet', :require => false +end + +# vim:ft=ruby diff --git a/3rdparty/modules/rabbitmq/LICENSE b/3rdparty/modules/rabbitmq/LICENSE new file mode 100644 index 00000000..297f85cf --- /dev/null +++ b/3rdparty/modules/rabbitmq/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2013 Puppet Labs + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/3rdparty/modules/rabbitmq/README.md b/3rdparty/modules/rabbitmq/README.md new file mode 100644 index 00000000..72c1ea03 --- /dev/null +++ b/3rdparty/modules/rabbitmq/README.md @@ -0,0 +1,609 @@ +#rabbitmq + +####Table of Contents + +1. [Overview](#overview) +2. [Module Description - What the module does and why it is useful](#module-description) +3. [Setup - The basics of getting started with rabbitmq](#setup) + * [What rabbitmq affects](#what-rabbitmq-affects) + * [Setup requirements](#setup-requirements) + * [Beginning with rabbitmq](#beginning-with-rabbitmq) +4. [Usage - Configuration options and additional functionality](#usage) +5. [Reference - An under-the-hood peek at what the module is doing and how](#reference) +5. [Limitations - OS compatibility, etc.](#limitations) + * [RedHat module dependencies](#redhat-module-dependecies) +6. [Development - Guide for contributing to the module](#development) + +##Overview + +This module manages RabbitMQ (www.rabbitmq.com) + +##Module Description +The rabbitmq module sets up rabbitmq and has a number of providers to manage +everything from vhosts to exchanges after setup. + +This module has been tested against 2.7.1 and is known to not support +all features against earlier versions. + +##Setup + +###What rabbitmq affects + +* rabbitmq repository files. +* rabbitmq package. +* rabbitmq configuration file. +* rabbitmq service. + +###Beginning with rabbitmq + + +```puppet +include '::rabbitmq' +``` + +##Usage + +All options and configuration can be done through interacting with the parameters +on the main rabbitmq class. These are documented below. + +##rabbitmq class + +To begin with the rabbitmq class controls the installation of rabbitmq. In here +you can control many parameters relating to the package and service, such as +disabling puppet support of the service: + +```puppet +class { '::rabbitmq': + service_manage => false, + port => '5672', + delete_guest_user => true, +} +``` + +Or such as offline installation from intranet or local mirrors: + +```puppet +class { '::rabbitmq': + key_content => template('openstack/rabbit.pub.key'), + package_gpg_key => '/tmp/rabbit.pub.key', +} +``` + +And this one will use external package key source for any (apt/rpm) package provider: + +```puppet +class { '::rabbitmq': + package_gpg_key => 'http://www.some_site.some_domain/some_key.pub.key', +} +``` + +### Environment Variables +To use RabbitMQ Environment Variables, use the parameters `environment_variables` e.g.: + +```puppet +class { 'rabbitmq': + port => '5672', + environment_variables => { + 'NODENAME' => 'node01', + 'SERVICENAME' => 'RabbitMQ' + } +} +``` + +### Variables Configurable in rabbitmq.config +To change RabbitMQ Config Variables in rabbitmq.config, use the parameters `config_variables` e.g.: + +```puppet +class { 'rabbitmq': + port => '5672', + config_variables => { + 'hipe_compile' => true, + 'frame_max' => 131072, + 'log_levels' => "[{connection, info}]" + } +} +``` + +To change Erlang Kernel Config Variables in rabbitmq.config, use the parameters +`config_kernel_variables` e.g.: + +```puppet +class { 'rabbitmq': + port => '5672', + config_kernel_variables => { + 'inet_dist_listen_min' => 9100, + 'inet_dist_listen_max' => 9105, + } +} +``` + +### Clustering +To use RabbitMQ clustering facilities, use the rabbitmq parameters +`config_cluster`, `cluster_nodes`, and `cluster_node_type`, e.g.: + +```puppet +class { 'rabbitmq': + config_cluster => true, + cluster_nodes => ['rabbit1', 'rabbit2'], + cluster_node_type => 'ram', + erlang_cookie => 'A_SECRET_COOKIE_STRING', + wipe_db_on_cookie_change => true, +} +``` + +##Reference + +##Classes + +* rabbitmq: Main class for installation and service management. +* rabbitmq::config: Main class for rabbitmq configuration/management. +* rabbitmq::install: Handles package installation. +* rabbitmq::params: Different configuration data for different systems. +* rabbitmq::service: Handles the rabbitmq service. +* rabbitmq::repo::apt: Handles apt repo for Debian systems. +* rabbitmq::repo::rhel: Handles rpm repo for Redhat systems. + +###Parameters + +####`admin_enable` + +Boolean, if enabled sets up the management interface/plugin for RabbitMQ. + +####`cluster_node_type` + +Choose between disk and ram nodes. + +####`cluster_nodes` + +An array of nodes for clustering. + +####`cluster_partition_handling` + +Value to set for `cluster_partition_handling` RabbitMQ configuration variable. + +####`config` + +The file to use as the rabbitmq.config template. + +####`config_cluster` + +Boolean to enable or disable clustering support. + +####`config_kernel_variables` + +Hash of Erlang kernel configuration variables to set (see [Variables Configurable in rabbitmq.config](#variables-configurable-in-rabbitmq.config)). + +####`config_mirrored_queues` + +DEPRECATED + +Configuring queue mirroring should be done by setting the according policy for +the queue. You can read more about it +[here](http://www.rabbitmq.com/ha.html#genesis) + +####`config_path` + +The path to write the RabbitMQ configuration file to. + +####`config_stomp` + +Boolean to enable or disable stomp. + +####`config_variables` + +To set config variables in rabbitmq.config + +####`default_user` + +Username to set for the `default_user` in rabbitmq.config. + +####`default_pass` + +Password to set for the `default_user` in rabbitmq.config. + +####`delete_guest_user` + +Boolean to decide if we should delete the default guest user. + +####`env_config` + +The template file to use for rabbitmq_env.config. + +####`env_config_path` + +The path to write the rabbitmq_env.config file to. + +####`environment_variables` + +RabbitMQ Environment Variables in rabbitmq_env.config + +####`erlang_cookie` + +The erlang cookie to use for clustering - must be the same between all nodes. +This value has no default and must be set explicitly if using clustering. + +####`file_limit` + +Set rabbitmq file ulimit. Defaults to 16384. Only available on systems with +`$::osfamily == 'Debian'` or `$::osfamily == 'RedHat'`. + +####`key_content` + +Uses content method for Debian OS family. Should be a template for apt::source +class. Overrides `package_gpg_key` behavior, if enabled. Undefined by default. + +####`ldap_auth` + +Boolean, set to true to enable LDAP auth. + +####`ldap_server` + +LDAP server to use for auth. + +####`ldap_user_dn_pattern` + +User DN pattern for LDAP auth. + +####`ldap_other_bind` + +How to bind to the LDAP server. Defaults to 'anon'. + +####`ldap_config_variables` + +Hash of other LDAP config variables. + +####`ldap_use_ssl` + +Boolean, set to true to use SSL for the LDAP server. + +####`ldap_port` + +Numeric port for LDAP server. + +####`ldap_log` + +Boolean, set to true to log LDAP auth. + +####`manage_repos` + +Boolean, whether or not to manage package repositories. + +####`management_port` + +The port for the RabbitMQ management interface. + +####`node_ip_address` + +The value of NODE_IP_ADDRESS in rabbitmq_env.config + +####`package_ensure` + +Determines the ensure state of the package. Set to installed by default, but could +be changed to latest. + +####`package_gpg_key` + +RPM package GPG key to import. Uses source method. Should be a URL for Debian/RedHat +OS family, or a file name for RedHat OS family. +Set to http://www.rabbitmq.com/rabbitmq-signing-key-public.asc by default. +Note, that `key_content`, if specified, would override this parameter for Debian OS family. + +####`package_name` + +The name of the package to install. + +####`package_provider` + +What provider to use to install the package. + +####`package_source` + +Where should the package be installed from? + +On Debian- and Arch-based systems using the default package provider, +this parameter is ignored and the package is installed from the +rabbitmq repository, if enabled with manage_repo => true, or from the +system repository otherwise. If you want to use dpkg as the +package_provider, you must specify a local package_source. + +####`plugin_dir` + +Location of RabbitMQ plugins. + +####`port` + +The RabbitMQ port. + +####`service_ensure` + +The state of the service. + +####`service_manage` + +Determines if the service is managed. + +####`service_name` + +The name of the service to manage. + +####`ssl` + +Configures the service for using SSL. + +####`ssl_only` + +Configures the service to only use SSL. No cleartext TCP listeners will be created. +Requires that ssl => true and port => UNSET also + +####`ssl_cacert` + +CA cert path to use for SSL. + +####`ssl_cert` + +Cert to use for SSL. + +####`ssl_key` + +Key to use for SSL. + +####`ssl_management_port` + +SSL management port. + +####`ssl_stomp_port` + +SSL stomp port. + +####`ssl_verify` + +rabbitmq.config SSL verify setting. + +####`ssl_fail_if_no_peer_cert` + +rabbitmq.config `fail_if_no_peer_cert` setting. + +####`ssl_versions` + +Choose which SSL versions to enable. Example: `['tlsv1.2', 'tlsv1.1']`. + +Note that it is recommended to disable `sslv3` and `tlsv1` to prevent against POODLE and BEAST attacks. Please see the [RabbitMQ SSL](https://www.rabbitmq.com/ssl.html) documentation for more information. + +####`ssl_ciphers` + +Support only a given list of SSL ciphers. Example: `['dhe_rsa,aes_256_cbc,sha','dhe_dss,aes_256_cbc,sha','ecdhe_rsa,aes_256_cbc,sha']`. + +Supported ciphers in your install can be listed with: + rabbitmqctl eval 'ssl:cipher_suites().' +Functionality can be tested with cipherscan or similar tool: https://github.com/jvehent/cipherscan.git + +####`stomp_port` + +The port to use for Stomp. + +####`stomp_ensure` + +Boolean to install the stomp plugin. + +####`tcp_keepalive` + +Boolean to enable TCP connection keepalive for RabbitMQ service. + +####`version` + +Sets the version to install. + +On Debian- and Arch-based operating systems, the version parameter is +ignored and the latest version is installed from the rabbitmq +repository, if enabled with manage_repo => true, or from the system +repository otherwise. + +####`wipe_db_on_cookie_change` + +Boolean to determine if we should DESTROY AND DELETE the RabbitMQ database. + +####`rabbitmq_user` + +String: OS dependent, default defined in param.pp. The system user the rabbitmq daemon runs as. + +####`rabbitmq_group` + +String: OS dependent, default defined in param.pp. The system group the rabbitmq daemon runs as. + +####`rabbitmq_home` + +String: OS dependent. default defined in param.pp. The home directory of the rabbitmq deamon. + +##Native Types + +### rabbitmq\_user + +query all current users: `$ puppet resource rabbitmq_user` + +``` +rabbitmq_user { 'dan': + admin => true, + password => 'bar', +} +``` +Optional parameter tags will set further rabbitmq tags like monitoring, policymaker, etc. +To set the administrator tag use admin-flag. +```puppet +rabbitmq_user { 'dan': + admin => true, + password => 'bar', + tags => ['monitoring', 'tag1'], +} +``` + + +### rabbitmq\_vhost + +query all current vhosts: `$ puppet resource rabbitmq_vhost` + +```puppet +rabbitmq_vhost { 'myvhost': + ensure => present, +} +``` + +### rabbitmq\_exchange + +```puppet +rabbitmq_exchange { 'myexchange@myvhost': + user => 'dan', + password => 'bar', + type => 'topic', + ensure => present, + internal => false, + auto_delete => false, + durable => true, + arguments => { + hash-header => 'message-distribution-hash' + } +} +``` + +### rabbitmq\_queue + +```puppet +rabbitmq_queue { 'myqueue@myvhost': + user => 'dan', + password => 'bar', + durable => true, + auto_delete => false, + arguments => { + x-message-ttl => 123, + x-dead-letter-exchange => 'other' + }, + ensure => present, +} +``` + +### rabbitmq\_binding + +```puppet +rabbitmq_binding { 'myexchange@myqueue@myvhost': + user => 'dan', + password => 'bar', + destination_type => 'queue', + routing_key => '#', + arguments => {}, + ensure => present, +} +``` + +### rabbitmq\_user\_permissions + +```puppet +rabbitmq_user_permissions { 'dan@myvhost': + configure_permission => '.*', + read_permission => '.*', + write_permission => '.*', +} +``` + +### rabbitmq\_policy + +```puppet +rabbitmq_policy { 'ha-all@myvhost': + pattern => '.*', + priority => 0, + applyto => 'all', + definition => { + 'ha-mode' => 'all', + 'ha-sync-mode' => 'automatic', + }, +} +``` + +### rabbitmq\_plugin + +query all currently enabled plugins `$ puppet resource rabbitmq_plugin` + +```puppet +rabbitmq_plugin {'rabbitmq_stomp': + ensure => present, +} +``` + +### rabbitmq\_erlang\_cookie + +This is essentially a private type used by the rabbitmq::config class +to manage the erlang cookie. It replaces the rabbitmq_erlang_cookie fact +from earlier versions of this module. It manages the content of the cookie +usually located at "${rabbitmq_home}/.erlang.cookie", which includes +stopping the rabbitmq service and wiping out the database at +"${rabbitmq_home}/mnesia" if the user agrees to it. We don't recommend using +this type directly. + +##Limitations + +This module has been built on and tested against Puppet 3.x. + +The module has been tested on: + +* RedHat Enterprise Linux 5/6 +* Debian 6/7 +* CentOS 5/6 +* Ubuntu 12.04/14.04 + +Testing on other platforms has been light and cannot be guaranteed. + +### Module dependencies + +If running CentOS/RHEL, and using the yum provider, ensure the epel repo is present. + +To have a suitable erlang version installed on RedHat and Debian systems, +you have to install another puppet module from http://forge.puppetlabs.com/garethr/erlang with: + + puppet module install garethr-erlang + +This module handles the packages for erlang. +To use the module, add the following snippet to your site.pp or an appropriate profile class: + +For RedHat systems: + + include 'erlang' + class { 'erlang': epel_enable => true} + +For Debian systems: + + include 'erlang' + package { 'erlang-base': + ensure => 'latest', + } + +This module also depends on the excellent nanliu/staging module on the Forge: + + puppet module install nanliu-staging + +### Downgrade Issues + +Be advised that there were configuration file syntax and other changes made between RabbitMQ +versions 2 and 3. In order to downgrade from 3 to 2 (not that this is a terribly good idea) +you will need to manually remove all RabbitMQ configuration files (``/etc/rabbitmq``) and +the mnesia directory (usually ``/var/lib/rabbitmq/mnesia``). The latter action will delete +any and all messages stored to disk. + +Failure to do this will result in RabbitMQ failing to start with a cryptic error message about +"init terminating in do_boot", containing "rabbit_upgrade,maybe_upgrade_mnesia". + +##Development + +Puppet Labs modules on the Puppet Forge are open projects, and community +contributions are essential for keeping them great. We can’t access the +huge number of platforms and myriad of hardware, software, and deployment +configurations that Puppet is intended to serve. + +We want to keep it as easy as possible to contribute changes so that our +modules work in your environment. There are a few guidelines that we need +contributors to follow so that we can have a chance of keeping on top of things. + +You can read the complete module contribution guide [on the Puppet Labs wiki.](http://projects.puppetlabs.com/projects/module-site/wiki/Module_contributing) + +### Authors +* Jeff McCune +* Dan Bode +* RPM/RHEL packages by Vincent Janelle +* Puppetlabs Module Team diff --git a/3rdparty/modules/rabbitmq/Rakefile b/3rdparty/modules/rabbitmq/Rakefile new file mode 100644 index 00000000..181157e6 --- /dev/null +++ b/3rdparty/modules/rabbitmq/Rakefile @@ -0,0 +1,10 @@ +require 'puppetlabs_spec_helper/rake_tasks' +require 'puppet-lint/tasks/puppet-lint' + +PuppetLint.configuration.fail_on_warnings = true +PuppetLint.configuration.send('relative') +PuppetLint.configuration.send('disable_80chars') +PuppetLint.configuration.send('disable_class_inherits_from_params_class') +PuppetLint.configuration.send('disable_documentation') +PuppetLint.configuration.send('disable_single_quote_string_with_variables') +PuppetLint.configuration.ignore_paths = ["spec/**/*.pp", "pkg/**/*.pp"] diff --git a/3rdparty/modules/rabbitmq/TODO b/3rdparty/modules/rabbitmq/TODO new file mode 100644 index 00000000..8ae578e9 --- /dev/null +++ b/3rdparty/modules/rabbitmq/TODO @@ -0,0 +1,10 @@ +provider TODO - + - password should be a property and not a param + - what if we tried to log in as that user? + - can permissions from list_user_permissions contain whitespace? + - what about defaultfor :true? + - prefetching for performance + - rabbit plugin should require rabbitmq class + - rabbitmq class should be renamed server?? + - service name should default to -server + - cannot find stomp package diff --git a/3rdparty/modules/rabbitmq/checksums.json b/3rdparty/modules/rabbitmq/checksums.json new file mode 100644 index 00000000..f228740c --- /dev/null +++ b/3rdparty/modules/rabbitmq/checksums.json @@ -0,0 +1,105 @@ +{ + "CHANGELOG.md": "43cb464088dc5a6558ce1f2f119a0f48", + "CONTRIBUTING.md": "e2b8e8e433fc76b3798b7fe435f49375", + "Gemfile": "e62c96457cdaab2a09f1a37479ea6351", + "LICENSE": "6089b6bd1f0d807edb8bdfd76da0b038", + "README.md": "1babcf19c9f1f10e3a594b6b89620b49", + "Rakefile": "d953eb985f82600dc3b9ac6e1f2cfe64", + "TODO": "53cf21155ec1e83e3e167f711fd3ff9f", + "files/README.markdown": "3d44458cc68d8513b51e3b56c604eec4", + "files/plugins/amqp_client-2.3.1.ez": "543ec53b7208fdc2dc4eba3684868011", + "files/plugins/rabbit_stomp-2.3.1.ez": "f552a986409a6d407a080b1aceb80d20", + "lib/puppet/provider/rabbitmq_binding/rabbitmqadmin.rb": "1b7bd0bd9ce3e0303f52178487170f42", + "lib/puppet/provider/rabbitmq_erlang_cookie/ruby.rb": "80c99bb254471ed32e6ee25a9dcb6f73", + "lib/puppet/provider/rabbitmq_exchange/rabbitmqadmin.rb": "f79a7aab47e00a6d29910e09550f03d9", + "lib/puppet/provider/rabbitmq_plugin/rabbitmqplugins.rb": "393eac704e7f906052f4d1c285de364e", + "lib/puppet/provider/rabbitmq_policy/rabbitmqctl.rb": "ea39ffa45a616fcd755c8121338fcbcd", + "lib/puppet/provider/rabbitmq_queue/rabbitmqadmin.rb": "b971f7ec8f5454623d82b35a6c178432", + "lib/puppet/provider/rabbitmq_user/rabbitmqctl.rb": "eeddc61ef6c57e7225c818e494fc6e47", + "lib/puppet/provider/rabbitmq_user_permissions/rabbitmqctl.rb": "35c1224ef9f1191c61cc577d7b496395", + "lib/puppet/provider/rabbitmq_vhost/rabbitmqctl.rb": "3e4754621f03062a4620950134cb2042", + "lib/puppet/provider/rabbitmqctl.rb": "59d97156cab9dba9008426e650df17bc", + "lib/puppet/type/rabbitmq_binding.rb": "5c19f5f98247d4ee5286e16242a53441", + "lib/puppet/type/rabbitmq_erlang_cookie.rb": "ad082a425169634391888fcd9a2131bb", + "lib/puppet/type/rabbitmq_exchange.rb": "3ed3e62c8f85cc4e41a81cd07915727c", + "lib/puppet/type/rabbitmq_plugin.rb": "6a707d089d0e50a949ecf8fae114eab0", + "lib/puppet/type/rabbitmq_policy.rb": "6e421d709978675d36bd6e1e361be498", + "lib/puppet/type/rabbitmq_queue.rb": "653a5e3eaa69c6d0d29f477c40362c13", + "lib/puppet/type/rabbitmq_user.rb": "4cac856b77ccebff4e9fd63e71ccf611", + "lib/puppet/type/rabbitmq_user_permissions.rb": "2d12cd7d9c8bd2afd2e4a4b24a35b58b", + "lib/puppet/type/rabbitmq_vhost.rb": "ff6fc35bb9c22b1c493b84adf6bb3167", + "log/centos-6-x64-vcloud/2015-05-22_18_55_23/sut.log": "062a9bc8d4ae0dd97e5ee74784a26c9d", + "log/centos-6-x64-vcloud/2015-05-22_18_57_07/sut.log": "4b5085af02e0fba4e6ae0addee258ce1", + "log/debian-7-x64-vcloud/2015-05-22_18_35_45/sut.log": "b6c8b56197861c0e6c3a46fb730f3334", + "log/debian-7-x64-vcloud/2015-05-22_18_49_58/sut.log": "a4d4b5f2278e5cba66026ad9f0156de4", + "log/debian-7-x64-vcloud/2015-05-22_19_10_48/sut.log": "27d3e61d3a9d9da3b87501a49f49aece", + "log/debian-7-x64-vcloud/2015-05-22_19_11_31/sut.log": "ad6cbb9cbd128e0848c5f5bcfe3d494a", + "log/default/2015-05-22_18_35_28/sut.log": "d41d8cd98f00b204e9800998ecf8427e", + "manifests/config.pp": "df0d180fd482f63eb8b26ca2ca83febf", + "manifests/init.pp": "52b0931a4d2083e90e36c331d6450084", + "manifests/install/rabbitmqadmin.pp": "64a81e06996b5ecc147795e64f3c6e7b", + "manifests/install.pp": "0f700b158484bfdacb2ce3ec65b939c9", + "manifests/management.pp": "93c41d238f2734fa7c5944b8f32f6cc4", + "manifests/params.pp": "260d40f1ece0f76e4a86079455e12c87", + "manifests/repo/apt.pp": "a5b1a06ac77526e8764bc754bc0718c4", + "manifests/repo/rhel.pp": "1c4cfcba993e0667b65ec3c5deafbac7", + "manifests/server.pp": "3bc67c2006c1144c50c3fc04394cd800", + "manifests/service.pp": "ec365148eec9e739ca06e24c972cd5de", + "metadata.json": "cfe09daeacab44586c336f6ea55d6aff", + "spec/README.markdown": "32a1fc0121c28aff554ef5d422b8b51e", + "spec/acceptance/class_spec.rb": "26e9ed9f9391692419f110624c091390", + "spec/acceptance/clustering_spec.rb": "1aa359697551e1ddf2a030cac5b8f2f5", + "spec/acceptance/delete_guest_user_spec.rb": "7c5da810cf2a3a124943eb56c46cac83", + "spec/acceptance/nodesets/centos-59-x64.yml": "57eb3e471b9042a8ea40978c467f8151", + "spec/acceptance/nodesets/centos-6-x64-vcloud.yml": "aaa7352c8a5f5244a6f9008112918074", + "spec/acceptance/nodesets/centos-64-x64-pe.yml": "ec075d95760df3d4702abea1ce0a829b", + "spec/acceptance/nodesets/centos-65-x64.yml": "3e5c36e6aa5a690229e720f4048bb8af", + "spec/acceptance/nodesets/debian-7-x64-vcloud.yml": "83c1c189f46fced0a8d5ec27a3e12877", + "spec/acceptance/nodesets/default.yml": "d65958bdf25fb31eb4838fd984b555df", + "spec/acceptance/nodesets/ubuntu-server-10044-x64.yml": "75e86400b7889888dc0781c0ae1a1297", + "spec/acceptance/nodesets/ubuntu-server-12042-x64.yml": "d30d73e34cd50b043c7d14e305955269", + "spec/acceptance/nodesets/ubuntu-server-1404-x64.yml": "5f0aed10098ac5b78e4217bb27c7aaf0", + "spec/acceptance/policy_spec.rb": "28211810baf46a25e0e9dda0097ce2db", + "spec/acceptance/queue_spec.rb": "b7d54bd218982f191b8a909652d65d09", + "spec/acceptance/rabbitmqadmin_spec.rb": "7844ca3109ed35147ee8bcd2c8a045c6", + "spec/acceptance/server_spec.rb": "f88e85559c71afcf5d80b01ffd78877d", + "spec/acceptance/user_spec.rb": "1f560f25a45e249bafdae3964efabaf2", + "spec/acceptance/vhost_spec.rb": "75c867b618eae881b9f13272cffdc777", + "spec/acceptance/zz281_spec.rb": "56051cd811c1f2546bcdce2590860306", + "spec/classes/rabbitmq_spec.rb": "45725cd9e08e33a4ea809a7ec48ee2f8", + "spec/spec.opts": "a600ded995d948e393fbe2320ba8e51c", + "spec/spec_helper.rb": "0db89c9a486df193c0e40095422e19dc", + "spec/spec_helper_acceptance.rb": "9aa45d83b91ef209b17abeace32781bf", + "spec/unit/puppet/provider/rabbitmq_binding/rabbitmqadmin_spec.rb": "f855af3424e3573b37f015f460b3f3f5", + "spec/unit/puppet/provider/rabbitmq_exchange/rabbitmqadmin_spec.rb": "3a909fddbe1019b6bf68d43408c5fa70", + "spec/unit/puppet/provider/rabbitmq_plugin/rabbitmqctl_spec.rb": "430f9d204f9a0135772b90f10fb36c76", + "spec/unit/puppet/provider/rabbitmq_policy/rabbitmqctl_spec.rb": "9b9213cc615b7e164a5b2c754c57f2d0", + "spec/unit/puppet/provider/rabbitmq_queue/rabbitmqadmin_spec.rb": "6f7664ce99673b73114ec17afa20febe", + "spec/unit/puppet/provider/rabbitmq_user/rabbitmqctl_spec.rb": "b6835fdfed6719217325e36667e9b416", + "spec/unit/puppet/provider/rabbitmq_user_permissions/rabbitmqctl_spec.rb": "7a16aee31dbf747d0226ded6ce8a3b45", + "spec/unit/puppet/provider/rabbitmq_vhost/rabbitmqctl_spec.rb": "1f8d85c70d5f288a2ac803c99dc3eb72", + "spec/unit/puppet/type/rabbitmq_binding_spec.rb": "60bde85ca7075b0584bfd57bd86ebfd3", + "spec/unit/puppet/type/rabbitmq_exchange_spec.rb": "797b8a550f0620731efc2280a0f30a53", + "spec/unit/puppet/type/rabbitmq_policy_spec.rb": "387079c6a0235602c4ef18b456c2b282", + "spec/unit/puppet/type/rabbitmq_queue_spec.rb": "568f66963bd4392b91fe347b3e1baa45", + "spec/unit/puppet/type/rabbitmq_user_permissions_spec.rb": "eb5b390edb76be3a2b8899e9b996d6e1", + "spec/unit/puppet/type/rabbitmq_user_spec.rb": "80e042c92f64e3a5f97a83bbbde85d42", + "spec/unit/puppet/type/rabbitmq_vhost_spec.rb": "162e29065eb5ce664842b66bcfa0ac34", + "templates/README.markdown": "aada0a1952329e46b98695349dba6203", + "templates/default.erb": "800642a1015e3eaa37f18100d1d63f41", + "templates/limits.conf": "c5f991430be0bcb7446eb7291cf34bf8", + "templates/rabbitmq-env.conf.erb": "174bf40d6f7fed0cf29604e858cc96c4", + "templates/rabbitmq-server.service.d/limits.conf": "80655f98baca4c7bc673359c5f846690", + "templates/rabbitmq.config.erb": "1d0ed42cd32aa466b4f3c4d3efb252cb", + "templates/rabbitmqadmin.conf.erb": "df2a15c7ee621cced815916cb0c56a5f", + "tests/erlang_deps.pp": "4a2ac78d56802dee3a66e3246633b603", + "tests/full.pp": "fb1e9f59fe63846c60b402202152eeb0", + "tests/permissions/add.pp": "b53b627a4d5521af8cdcfd83d99d3824", + "tests/plugin.pp": "5fc1271d5684dd51fa94b67876179e63", + "tests/repo/apt.pp": "4ea43b4f8dcaf474ec11d796efef66a3", + "tests/server.pp": "56dba93d20d5b716b66df2e0f4f693d6", + "tests/service.pp": "f06296b103daf449f9e7644fd9eee58b", + "tests/site.pp": "653334bf690768a8af42cd13e8e53ef2", + "tests/user/add.pp": "d9f051f1edc91114097b54f818729ea8", + "tests/vhosts/add.pp": "f054d84ac87dc206f586d779fc312fa6" +} \ No newline at end of file diff --git a/3rdparty/modules/rabbitmq/files/README.markdown b/3rdparty/modules/rabbitmq/files/README.markdown new file mode 100644 index 00000000..be52188c --- /dev/null +++ b/3rdparty/modules/rabbitmq/files/README.markdown @@ -0,0 +1,22 @@ +Files +===== + +Puppet comes with both a client and server for copying files around. The file +serving function is provided as part of the central Puppet daemon, +puppetmasterd, and the client function is used through the source attribute of +file objects. Learn more at +http://projects.puppetlabs.com/projects/puppet/wiki/File_Serving_Configuration + +You can use managed files like this: + + class myclass { + package { mypackage: ensure => latest } + service { myservice: ensure => running } + file { "/etc/myfile": + source => "puppet://$servername/modules/mymodule/myfile" + } + } + +The files are searched for in: + + $modulepath/mymodule/files/myfile diff --git a/3rdparty/modules/rabbitmq/files/plugins/amqp_client-2.3.1.ez b/3rdparty/modules/rabbitmq/files/plugins/amqp_client-2.3.1.ez new file mode 100644 index 0000000000000000000000000000000000000000..6ef6d4ff148f4bbba71800a8941c26f25bd393d6 GIT binary patch literal 151912 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 z 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
  • +ISg5XS(jGV>i@`2(w{j$SS3sol8uZa>t(Oa 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 literal 0 HcmV?d00001 diff --git a/3rdparty/modules/rabbitmq/files/plugins/rabbit_stomp-2.3.1.ez b/3rdparty/modules/rabbitmq/files/plugins/rabbit_stomp-2.3.1.ez new file mode 100644 index 0000000000000000000000000000000000000000..4a28b72f832b0b738a5ea30cad7f8897e05845d0 GIT binary patch literal 55937 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 Y|CdQ&qHG=jz(+kqsL=NH*zdjn0iQ?}8vp 'rabbitmqctl' + commands :rabbitmqadmin => '/usr/local/bin/rabbitmqadmin' + else + has_command(:rabbitmqctl, 'rabbitmqctl') do + environment :HOME => "/tmp" + end + has_command(:rabbitmqadmin, '/usr/local/bin/rabbitmqadmin') do + environment :HOME => "/tmp" + end + end + defaultfor :feature => :posix + + def should_vhost + if @should_vhost + @should_vhost + else + @should_vhost = resource[:name].split('@').last + end + end + + def self.all_vhosts + vhosts = [] + rabbitmqctl('list_vhosts', '-q').split(/\n/).collect do |vhost| + vhosts.push(vhost) + end + vhosts + end + + def self.all_bindings(vhost) + rabbitmqctl('list_bindings', '-q', '-p', vhost, 'source_name', 'destination_name', 'destination_kind', 'routing_key', 'arguments').split(/\n/) + end + + def self.instances + resources = [] + all_vhosts.each do |vhost| + all_bindings(vhost).collect do |line| + source_name, destination_name, destination_type, routing_key, arguments = line.split(/\t/) + # Convert output of arguments from the rabbitmqctl command to a json string. + if !arguments.nil? + arguments = arguments.gsub(/^\[(.*)\]$/, "").gsub(/\{("(?:.|\\")*?"),/, '{\1:').gsub(/\},\{/, ",") + if arguments == "" + arguments = '{}' + end + else + arguments = '{}' + end + if (source_name != '') + binding = { + :destination_type => destination_type, + :routing_key => routing_key, + :arguments => JSON.parse(arguments), + :ensure => :present, + :name => "%s@%s@%s" % [source_name, destination_name, vhost], + } + resources << new(binding) if binding[:name] + end + end + end + resources + end + + def self.prefetch(resources) + packages = instances + resources.keys.each do |name| + if provider = packages.find{ |pkg| pkg.name == name } + resources[name].provider = provider + end + end + end + + def exists? + @property_hash[:ensure] == :present + end + + def create + vhost_opt = should_vhost ? "--vhost=#{should_vhost}" : '' + name = resource[:name].split('@').first + destination = resource[:name].split('@')[1] + arguments = resource[:arguments] + if arguments.nil? + arguments = {} + end + rabbitmqadmin('declare', + 'binding', + vhost_opt, + "--user=#{resource[:user]}", + "--password=#{resource[:password]}", + '-c', + '/etc/rabbitmq/rabbitmqadmin.conf', + "source=#{name}", + "destination=#{destination}", + "arguments=#{arguments.to_json}", + "routing_key=#{resource[:routing_key]}", + "destination_type=#{resource[:destination_type]}" + ) + @property_hash[:ensure] = :present + end + + def destroy + vhost_opt = should_vhost ? "--vhost=#{should_vhost}" : '' + name = resource[:name].split('@').first + destination = resource[:name].split('@')[1] + rabbitmqadmin('delete', 'binding', vhost_opt, "--user=#{resource[:user]}", "--password=#{resource[:password]}", '-c', '/etc/rabbitmq/rabbitmqadmin.conf', "source=#{name}", "destination_type=#{resource[:destination_type]}", "destination=#{destination}", "properties_key=#{resource[:routing_key]}") + @property_hash[:ensure] = :absent + end + +end diff --git a/3rdparty/modules/rabbitmq/lib/puppet/provider/rabbitmq_erlang_cookie/ruby.rb b/3rdparty/modules/rabbitmq/lib/puppet/provider/rabbitmq_erlang_cookie/ruby.rb new file mode 100644 index 00000000..58c8b3c9 --- /dev/null +++ b/3rdparty/modules/rabbitmq/lib/puppet/provider/rabbitmq_erlang_cookie/ruby.rb @@ -0,0 +1,38 @@ +require 'puppet' +require 'set' +Puppet::Type.type(:rabbitmq_erlang_cookie).provide(:ruby) do + + defaultfor :feature => :posix + has_command(:puppet, 'puppet') do + environment :PATH => '/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin' + end + + def exists? + # Hack to prevent the create method from being called. + # We never need to create or destroy this resource, only change its value + true + end + + def content=(value) + if resource[:force] == :true # Danger! + puppet('resource', 'service', resource[:service_name], 'ensure=stopped') + FileUtils.rm_rf(resource[:rabbitmq_home] + File::PATH_SEPARATOR + 'mnesia') + File.open(resource[:path], 'w') do |cookie| + cookie.chmod(0400) + cookie.write(value) + end + FileUtils.chown(resource[:rabbitmq_user], resource[:rabbitmq_group], resource[:path]) + else + fail("The current erlang cookie needs to change. In order to do this the RabbitMQ database needs to be wiped. Please set force => true to allow this to happen automatically.") + end + end + + def content + if File.exists?(resource[:path]) + File.read(resource[:path]) + else + '' + end + end + +end diff --git a/3rdparty/modules/rabbitmq/lib/puppet/provider/rabbitmq_exchange/rabbitmqadmin.rb b/3rdparty/modules/rabbitmq/lib/puppet/provider/rabbitmq_exchange/rabbitmqadmin.rb new file mode 100644 index 00000000..c1cff095 --- /dev/null +++ b/3rdparty/modules/rabbitmq/lib/puppet/provider/rabbitmq_exchange/rabbitmqadmin.rb @@ -0,0 +1,112 @@ +require 'puppet' +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'rabbitmqctl')) +Puppet::Type.type(:rabbitmq_exchange).provide(:rabbitmqadmin, :parent => Puppet::Provider::Rabbitmqctl) do + + if Puppet::PUPPETVERSION.to_f < 3 + commands :rabbitmqctl => 'rabbitmqctl' + commands :rabbitmqadmin => '/usr/local/bin/rabbitmqadmin' + else + has_command(:rabbitmqctl, 'rabbitmqctl') do + environment :HOME => "/tmp" + end + has_command(:rabbitmqadmin, '/usr/local/bin/rabbitmqadmin') do + environment :HOME => "/tmp" + end + end + defaultfor :feature => :posix + + def should_vhost + if @should_vhost + @should_vhost + else + @should_vhost = resource[:name].split('@')[1] + end + end + + def self.all_vhosts + vhosts = [] + self.run_with_retries { + rabbitmqctl('-q', 'list_vhosts') + }.split(/\n/).each do |vhost| + vhosts.push(vhost) + end + vhosts + end + + def self.all_exchanges(vhost) + exchanges = [] + self.run_with_retries { + rabbitmqctl('-q', 'list_exchanges', '-p', vhost, 'name', 'type', 'internal', 'durable', 'auto_delete', 'arguments') + }.split(/\n/).each do |exchange| + exchanges.push(exchange) + end + exchanges + end + + def self.instances + resources = [] + all_vhosts.each do |vhost| + all_exchanges(vhost).each do |line| + name, type, internal, durable, auto_delete, arguments = line.split() + if type.nil? + # if name is empty, it will wrongly get the type's value. + # This way type will get the correct value + type = name + name = '' + end + # Convert output of arguments from the rabbitmqctl command to a json string. + if !arguments.nil? + arguments = arguments.gsub(/^\[(.*)\]$/, "").gsub(/\{("(?:.|\\")*?"),/, '{\1:').gsub(/\},\{/, ",") + if arguments == "" + arguments = '{}' + end + else + arguments = '{}' + end + exchange = { + :type => type, + :ensure => :present, + :internal => internal, + :durable => durable, + :auto_delete => auto_delete, + :name => "%s@%s" % [name, vhost], + :arguments => JSON.parse(arguments), + } + resources << new(exchange) if exchange[:type] + end + end + resources + end + + def self.prefetch(resources) + packages = instances + resources.keys.each do |name| + if provider = packages.find{ |pkg| pkg.name == name } + resources[name].provider = provider + end + end + end + + def exists? + @property_hash[:ensure] == :present + end + + def create + vhost_opt = should_vhost ? "--vhost=#{should_vhost}" : '' + name = resource[:name].split('@')[0] + arguments = resource[:arguments] + if arguments.nil? + arguments = {} + end + rabbitmqadmin('declare', 'exchange', vhost_opt, "--user=#{resource[:user]}", "--password=#{resource[:password]}", "name=#{name}", "type=#{resource[:type]}", "internal=#{resource[:internal]}", "durable=#{resource[:durable]}", "auto_delete=#{resource[:auto_delete]}", "arguments=#{arguments.to_json}", '-c', '/etc/rabbitmq/rabbitmqadmin.conf') + @property_hash[:ensure] = :present + end + + def destroy + vhost_opt = should_vhost ? "--vhost=#{should_vhost}" : '' + name = resource[:name].split('@')[0] + rabbitmqadmin('delete', 'exchange', vhost_opt, "--user=#{resource[:user]}", "--password=#{resource[:password]}", "name=#{name}", '-c', '/etc/rabbitmq/rabbitmqadmin.conf') + @property_hash[:ensure] = :absent + end + +end diff --git a/3rdparty/modules/rabbitmq/lib/puppet/provider/rabbitmq_plugin/rabbitmqplugins.rb b/3rdparty/modules/rabbitmq/lib/puppet/provider/rabbitmq_plugin/rabbitmqplugins.rb new file mode 100644 index 00000000..7ab5420c --- /dev/null +++ b/3rdparty/modules/rabbitmq/lib/puppet/provider/rabbitmq_plugin/rabbitmqplugins.rb @@ -0,0 +1,52 @@ +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'rabbitmqctl')) +Puppet::Type.type(:rabbitmq_plugin).provide(:rabbitmqplugins, :parent => Puppet::Provider::Rabbitmqctl) do + + if Puppet::PUPPETVERSION.to_f < 3 + if Facter.value(:osfamily) == 'RedHat' + commands :rabbitmqplugins => '/usr/lib/rabbitmq/bin/rabbitmq-plugins' + else + commands :rabbitmqplugins => 'rabbitmq-plugins' + end + else + if Facter.value(:osfamily) == 'RedHat' + has_command(:rabbitmqplugins, '/usr/lib/rabbitmq/bin/rabbitmq-plugins') do + environment :HOME => "/tmp" + end + else + has_command(:rabbitmqplugins, 'rabbitmq-plugins') do + environment :HOME => "/tmp" + end + end + end + + defaultfor :feature => :posix + + def self.instances + self.run_with_retries { + rabbitmqplugins('list', '-E', '-m') + }.split(/\n/).map do |line| + if line =~ /^(\S+)$/ + new(:name => $1) + else + raise Puppet::Error, "Cannot parse invalid plugins line: #{line}" + end + end + end + + def create + rabbitmqplugins('enable', resource[:name]) + end + + def destroy + rabbitmqplugins('disable', resource[:name]) + end + + def exists? + self.class.run_with_retries { + rabbitmqplugins('list', '-E', '-m') + }.split(/\n/).detect do |line| + line.match(/^#{resource[:name]}$/) + end + end + +end diff --git a/3rdparty/modules/rabbitmq/lib/puppet/provider/rabbitmq_policy/rabbitmqctl.rb b/3rdparty/modules/rabbitmq/lib/puppet/provider/rabbitmq_policy/rabbitmqctl.rb new file mode 100644 index 00000000..7e732958 --- /dev/null +++ b/3rdparty/modules/rabbitmq/lib/puppet/provider/rabbitmq_policy/rabbitmqctl.rb @@ -0,0 +1,119 @@ +require 'json' +require 'puppet/util/package' + +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'rabbitmqctl')) +Puppet::Type.type(:rabbitmq_policy).provide(:rabbitmqctl, :parent => Puppet::Provider::Rabbitmqctl) do + + defaultfor :feature => :posix + + # cache policies + def self.policies(name, vhost) + @policies = {} unless @policies + unless @policies[vhost] + @policies[vhost] = {} + self.run_with_retries { + rabbitmqctl('list_policies', '-q', '-p', vhost) + }.split(/\n/).each do |line| + # rabbitmq<3.2 does not support the applyto field + # 1 2 3? 4 5 6 + # / ha-all all .* {"ha-mode":"all","ha-sync-mode":"automatic"} 0 + if line =~ /^(\S+)\s+(\S+)\s+(all|exchanges|queues)?\s*(\S+)\s+(\S+)\s+(\d+)$/ + applyto = $3 || 'all' + @policies[vhost][$2] = { + :applyto => applyto, + :pattern => $4, + :definition => JSON.parse($5), + :priority => $6} + else + raise Puppet::Error, "cannot parse line from list_policies:#{line}" + end + end + end + @policies[vhost][name] + end + + def policies(name, vhost) + self.class.policies(vhost, name) + end + + def should_policy + @should_policy ||= resource[:name].rpartition('@').first + end + + def should_vhost + @should_vhost ||= resource[:name].rpartition('@').last + end + + def create + set_policy + end + + def destroy + rabbitmqctl('clear_policy', '-p', should_vhost, should_policy) + end + + def exists? + policies(should_vhost, should_policy) + end + + def pattern + policies(should_vhost, should_policy)[:pattern] + end + + def pattern=(pattern) + set_policy + end + + def applyto + policies(should_vhost, should_policy)[:applyto] + end + + def applyto=(applyto) + set_policy + end + + def definition + policies(should_vhost, should_policy)[:definition] + end + + def definition=(definition) + set_policy + end + + def priority + policies(should_vhost, should_policy)[:priority] + end + + def priority=(priority) + set_policy + end + + def set_policy + unless @set_policy + @set_policy = true + resource[:applyto] ||= applyto + resource[:definition] ||= definition + resource[:pattern] ||= pattern + resource[:priority] ||= priority + # rabbitmq>=3.2.0 + if Puppet::Util::Package.versioncmp(self.class.rabbitmq_version, '3.2.0') >= 0 + rabbitmqctl('set_policy', + '-p', should_vhost, + '--priority', resource[:priority], + '--apply-to', resource[:applyto].to_s, + should_policy, + resource[:pattern], + resource[:definition].to_json + ) + else + rabbitmqctl('set_policy', + '-p', should_vhost, + should_policy, + resource[:pattern], + resource[:definition].to_json, + resource[:priority] + ) + end + end + end +end diff --git a/3rdparty/modules/rabbitmq/lib/puppet/provider/rabbitmq_queue/rabbitmqadmin.rb b/3rdparty/modules/rabbitmq/lib/puppet/provider/rabbitmq_queue/rabbitmqadmin.rb new file mode 100644 index 00000000..eeffa953 --- /dev/null +++ b/3rdparty/modules/rabbitmq/lib/puppet/provider/rabbitmq_queue/rabbitmqadmin.rb @@ -0,0 +1,107 @@ +require 'json' +require 'puppet' +Puppet::Type.type(:rabbitmq_queue).provide(:rabbitmqadmin) do + + if Puppet::PUPPETVERSION.to_f < 3 + commands :rabbitmqctl => 'rabbitmqctl' + commands :rabbitmqadmin => '/usr/local/bin/rabbitmqadmin' + else + has_command(:rabbitmqctl, 'rabbitmqctl') do + environment :HOME => "/tmp" + end + has_command(:rabbitmqadmin, '/usr/local/bin/rabbitmqadmin') do + environment :HOME => "/tmp" + end + end + defaultfor :feature => :posix + + def should_vhost + if @should_vhost + @should_vhost + else + @should_vhost = resource[:name].rpartition('@').last + end + end + + def self.all_vhosts + vhosts = [] + rabbitmqctl('list_vhosts', '-q').split(/\n/).collect do |vhost| + vhosts.push(vhost) + end + vhosts + end + + def self.all_queues(vhost) + rabbitmqctl('list_queues', '-q', '-p', vhost, 'name', 'durable', 'auto_delete', 'arguments').split(/\n/) + end + + def self.instances + resources = [] + all_vhosts.each do |vhost| + all_queues(vhost).collect do |line| + name, durable, auto_delete, arguments = line.split() + # Convert output of arguments from the rabbitmqctl command to a json string. + if !arguments.nil? + arguments = arguments.gsub(/^\[(.*)\]$/, "").gsub(/\{("(?:.|\\")*?"),/, '{\1:').gsub(/\},\{/, ",") + if arguments == "" + arguments = '{}' + end + else + arguments = '{}' + end + queue = { + :durable => durable, + :auto_delete => auto_delete, + :arguments => JSON.parse(arguments), + :ensure => :present, + :name => "%s@%s" % [name, vhost], + } + resources << new(queue) if queue[:name] + end + end + resources + end + + def self.prefetch(resources) + packages = instances + resources.keys.each do |name| + if provider = packages.find{ |pkg| pkg.name == name } + resources[name].provider = provider + end + end + end + + def exists? + @property_hash[:ensure] == :present + end + + def create + vhost_opt = should_vhost ? "--vhost=#{should_vhost}" : '' + name = resource[:name].rpartition('@').first + arguments = resource[:arguments] + if arguments.nil? + arguments = {} + end + rabbitmqadmin('declare', + 'queue', + vhost_opt, + "--user=#{resource[:user]}", + "--password=#{resource[:password]}", + '-c', + '/etc/rabbitmq/rabbitmqadmin.conf', + "name=#{name}", + "durable=#{resource[:durable]}", + "auto_delete=#{resource[:auto_delete]}", + "arguments=#{arguments.to_json}" + ) + @property_hash[:ensure] = :present + end + + def destroy + vhost_opt = should_vhost ? "--vhost=#{should_vhost}" : '' + name = resource[:name].rpartition('@').first + rabbitmqadmin('delete', 'queue', vhost_opt, "--user=#{resource[:user]}", "--password=#{resource[:password]}", '-c', '/etc/rabbitmq/rabbitmqadmin.conf', "name=#{name}") + @property_hash[:ensure] = :absent + end + +end diff --git a/3rdparty/modules/rabbitmq/lib/puppet/provider/rabbitmq_user/rabbitmqctl.rb b/3rdparty/modules/rabbitmq/lib/puppet/provider/rabbitmq_user/rabbitmqctl.rb new file mode 100644 index 00000000..da37886c --- /dev/null +++ b/3rdparty/modules/rabbitmq/lib/puppet/provider/rabbitmq_user/rabbitmqctl.rb @@ -0,0 +1,126 @@ +require 'puppet' +require 'set' +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'rabbitmqctl')) +Puppet::Type.type(:rabbitmq_user).provide(:rabbitmqctl, :parent => Puppet::Provider::Rabbitmqctl) do + + if Puppet::PUPPETVERSION.to_f < 3 + commands :rabbitmqctl => 'rabbitmqctl' + else + has_command(:rabbitmqctl, 'rabbitmqctl') do + environment :HOME => "/tmp" + end + end + + defaultfor :feature => :posix + + def self.instances + self.run_with_retries { + rabbitmqctl('-q', 'list_users') + }.split(/\n/).collect do |line| + if line =~ /^(\S+)(\s+\[.*?\]|)$/ + new(:name => $1) + else + raise Puppet::Error, "Cannot parse invalid user line: #{line}" + end + end + end + + def create + rabbitmqctl('add_user', resource[:name], resource[:password]) + if resource[:admin] == :true + make_user_admin() + end + if ! resource[:tags].empty? + set_user_tags(resource[:tags]) + end + end + + def change_password + rabbitmqctl('change_password', resource[:name], resource[:password]) + end + + def password + nil + end + + + def check_password + response = rabbitmqctl('eval', 'rabbit_access_control:check_user_pass_login(list_to_binary("' + resource[:name] + '"), list_to_binary("' + resource[:password] +'")).') + if response.include? 'refused' + false + else + true + end + end + + def destroy + rabbitmqctl('delete_user', resource[:name]) + end + + def exists? + self.class.run_with_retries { + rabbitmqctl('-q', 'list_users') + }.split(/\n/).detect do |line| + line.match(/^#{Regexp.escape(resource[:name])}(\s+(\[.*?\]|\S+)|)$/) + end + end + + + def tags + tags = get_user_tags + # do not expose the administrator tag for admins + if resource[:admin] == :true + tags.delete('administrator') + end + tags.entries.sort + end + + + def tags=(tags) + if ! tags.nil? + set_user_tags(tags) + end + end + + def admin + if usertags = get_user_tags + (:true if usertags.include?('administrator')) || :false + else + raise Puppet::Error, "Could not match line '#{resource[:name]} (true|false)' from list_users (perhaps you are running on an older version of rabbitmq that does not support admin users?)" + end + end + + def admin=(state) + if state == :true + make_user_admin() + else + usertags = get_user_tags + usertags.delete('administrator') + rabbitmqctl('set_user_tags', resource[:name], usertags.entries.sort) + end + end + + def set_user_tags(tags) + is_admin = get_user_tags().member?("administrator") \ + || resource[:admin] == :true + usertags = Set.new(tags) + if is_admin + usertags.add("administrator") + end + rabbitmqctl('set_user_tags', resource[:name], usertags.entries.sort) + end + + def make_user_admin + usertags = get_user_tags + usertags.add('administrator') + rabbitmqctl('set_user_tags', resource[:name], usertags.entries.sort) + end + + private + def get_user_tags + match = rabbitmqctl('-q', 'list_users').split(/\n/).collect do |line| + line.match(/^#{Regexp.escape(resource[:name])}\s+\[(.*?)\]/) + end.compact.first + Set.new(match[1].split(' ').map{|x| x.gsub(/,$/, '')}) if match + end +end diff --git a/modules/rabbitmq/lib/puppet/provider/rabbitmq_user_permissions/rabbitmqctl.rb b/3rdparty/modules/rabbitmq/lib/puppet/provider/rabbitmq_user_permissions/rabbitmqctl.rb similarity index 63% rename from modules/rabbitmq/lib/puppet/provider/rabbitmq_user_permissions/rabbitmqctl.rb rename to 3rdparty/modules/rabbitmq/lib/puppet/provider/rabbitmq_user_permissions/rabbitmqctl.rb index 4e3c9dd0..a0b8b5a1 100644 --- a/modules/rabbitmq/lib/puppet/provider/rabbitmq_user_permissions/rabbitmqctl.rb +++ b/3rdparty/modules/rabbitmq/lib/puppet/provider/rabbitmq_user_permissions/rabbitmqctl.rb @@ -1,21 +1,28 @@ -Puppet::Type.type(:rabbitmq_user_permissions).provide(:rabbitmqctl) do +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'rabbitmqctl')) +Puppet::Type.type(:rabbitmq_user_permissions).provide(:rabbitmqctl, :parent => Puppet::Provider::Rabbitmqctl) do + + if Puppet::PUPPETVERSION.to_f < 3 + commands :rabbitmqctl => 'rabbitmqctl' + else + has_command(:rabbitmqctl, 'rabbitmqctl') do + environment :HOME => "/tmp" + end + end - commands :rabbitmqctl => 'rabbitmqctl' defaultfor :feature=> :posix - #def self.instances - # - #end - # cache users permissions def self.users(name, vhost) @users = {} unless @users unless @users[name] @users[name] = {} - out = rabbitmqctl('list_user_permissions', name).split(/\n/)[1..-2].each do |line| - if line =~ /^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)$/ + self.run_with_retries { + rabbitmqctl('-q', 'list_user_permissions', name) + }.split(/\n/).each do |line| + line = self::strip_backslashes(line) + if line =~ /^(\S+)\s+(\S*)\s+(\S*)\s+(\S*)$/ @users[name][$1] = - {:configure => $2, :read => $3, :write => $4} + {:configure => $2, :read => $4, :write => $3} else raise Puppet::Error, "cannot parse line from list_user_permissions:#{line}" end @@ -45,10 +52,10 @@ Puppet::Type.type(:rabbitmq_user_permissions).provide(:rabbitmqctl) do end def create - resource[:configure_permission] ||= "'^$'" - resource[:read_permission] ||= "'^$'" - resource[:write_permission] ||= "'^$'" - rabbitmqctl('set_permissions', '-p', should_vhost, should_user, resource[:configure_permission], resource[:read_permission], resource[:write_permission]) + resource[:configure_permission] ||= "''" + resource[:read_permission] ||= "''" + resource[:write_permission] ||= "''" + rabbitmqctl('set_permissions', '-p', should_vhost, should_user, resource[:configure_permission], resource[:write_permission], resource[:read_permission]) end def destroy @@ -93,10 +100,15 @@ Puppet::Type.type(:rabbitmq_user_permissions).provide(:rabbitmqctl) do resource[:read_permission] ||= read_permission resource[:write_permission] ||= write_permission rabbitmqctl('set_permissions', '-p', should_vhost, should_user, - resource[:configure_permission], resource[:read_permission], - resource[:write_permission] + resource[:configure_permission], resource[:write_permission], + resource[:read_permission] ) end end + def self.strip_backslashes(string) + # See: https://github.com/rabbitmq/rabbitmq-server/blob/v1_7/docs/rabbitmqctl.1.pod#output-escaping + string.gsub(/\\\\/, '\\') + end + end diff --git a/3rdparty/modules/rabbitmq/lib/puppet/provider/rabbitmq_vhost/rabbitmqctl.rb b/3rdparty/modules/rabbitmq/lib/puppet/provider/rabbitmq_vhost/rabbitmqctl.rb new file mode 100644 index 00000000..fbd389d8 --- /dev/null +++ b/3rdparty/modules/rabbitmq/lib/puppet/provider/rabbitmq_vhost/rabbitmqctl.rb @@ -0,0 +1,40 @@ +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'rabbitmqctl')) +Puppet::Type.type(:rabbitmq_vhost).provide(:rabbitmqctl, :parent => Puppet::Provider::Rabbitmqctl) do + + if Puppet::PUPPETVERSION.to_f < 3 + commands :rabbitmqctl => 'rabbitmqctl' + else + has_command(:rabbitmqctl, 'rabbitmqctl') do + environment :HOME => "/tmp" + end + end + + def self.instances + self.run_with_retries { + rabbitmqctl('-q', 'list_vhosts') + }.split(/\n/).map do |line| + if line =~ /^(\S+)$/ + new(:name => $1) + else + raise Puppet::Error, "Cannot parse invalid user line: #{line}" + end + end + end + + def create + rabbitmqctl('add_vhost', resource[:name]) + end + + def destroy + rabbitmqctl('delete_vhost', resource[:name]) + end + + def exists? + out = self.class.run_with_retries { + rabbitmqctl('-q', 'list_vhosts') + }.split(/\n/).detect do |line| + line.match(/^#{Regexp.escape(resource[:name])}$/) + end + end + +end diff --git a/3rdparty/modules/rabbitmq/lib/puppet/provider/rabbitmqctl.rb b/3rdparty/modules/rabbitmq/lib/puppet/provider/rabbitmqctl.rb new file mode 100644 index 00000000..d2366456 --- /dev/null +++ b/3rdparty/modules/rabbitmq/lib/puppet/provider/rabbitmqctl.rb @@ -0,0 +1,33 @@ +class Puppet::Provider::Rabbitmqctl < Puppet::Provider + initvars + commands :rabbitmqctl => 'rabbitmqctl' + + def self.rabbitmq_version + output = rabbitmqctl('-q', 'status') + version = output.match(/\{rabbit,"RabbitMQ","([\d\.]+)"\}/) + version[1] if version + end + + # Retry the given code block 'count' retries or until the + # command suceeeds. Use 'step' delay between retries. + # Limit each query time by 'timeout'. + # For example: + # users = self.class.run_with_retries { rabbitmqctl 'list_users' } + def self.run_with_retries(count=30, step=6, timeout=10) + count.times do |n| + begin + output = Timeout::timeout(timeout) do + yield + end + rescue Puppet::ExecutionFailure, Timeout + Puppet.debug 'Command failed, retrying' + sleep step + else + Puppet.debug 'Command succeeded' + return output + end + end + raise Puppet::Error, "Command is still failing after #{count * step} seconds expired!" + end + +end diff --git a/3rdparty/modules/rabbitmq/lib/puppet/type/rabbitmq_binding.rb b/3rdparty/modules/rabbitmq/lib/puppet/type/rabbitmq_binding.rb new file mode 100644 index 00000000..13094800 --- /dev/null +++ b/3rdparty/modules/rabbitmq/lib/puppet/type/rabbitmq_binding.rb @@ -0,0 +1,96 @@ +Puppet::Type.newtype(:rabbitmq_binding) do + desc 'Native type for managing rabbitmq bindings' + + ensurable do + defaultto(:present) + newvalue(:present) do + provider.create + end + newvalue(:absent) do + provider.destroy + end + end + + newparam(:name, :namevar => true) do + desc 'source and destination of bind' + newvalues(/^\S*@\S+@\S+$/) + end + + newparam(:destination_type) do + desc 'binding destination_type' + newvalues(/queue|exchange/) + defaultto('queue') + end + + newparam(:routing_key) do + desc 'binding routing_key' + newvalues(/^\S*$/) + end + + newparam(:arguments) do + desc 'binding arguments' + defaultto {} + validate do |value| + resource.validate_argument(value) + end + end + + newparam(:user) do + desc 'The user to use to connect to rabbitmq' + defaultto('guest') + newvalues(/^\S+$/) + end + + newparam(:password) do + desc 'The password to use to connect to rabbitmq' + defaultto('guest') + newvalues(/\S+/) + end + + autorequire(:rabbitmq_vhost) do + [self[:name].split('@')[2]] + end + + autorequire(:rabbitmq_exchange) do + setup_autorequire('exchange') + end + + autorequire(:rabbitmq_queue) do + setup_autorequire('queue') + end + + autorequire(:rabbitmq_user) do + [self[:user]] + end + + autorequire(:rabbitmq_user_permissions) do + [ + "#{self[:user]}@#{self[:name].split('@')[1]}", + "#{self[:user]}@#{self[:name].split('@')[0]}" + ] + end + + def setup_autorequire(type) + destination_type = value(:destination_type) + if type == 'exchange' + rval = ["#{self[:name].split('@')[0]}@#{self[:name].split('@')[2]}"] + if destination_type == type + rval.push("#{self[:name].split('@')[1]}@#{self[:name].split('@')[2]}") + end + else + if destination_type == type + rval = ["#{self[:name].split('@')[1]}@#{self[:name].split('@')[2]}"] + else + rval = [] + end + end + rval + end + + def validate_argument(argument) + unless [Hash].include?(argument.class) + raise ArgumentError, "Invalid argument" + end + end + +end diff --git a/3rdparty/modules/rabbitmq/lib/puppet/type/rabbitmq_erlang_cookie.rb b/3rdparty/modules/rabbitmq/lib/puppet/type/rabbitmq_erlang_cookie.rb new file mode 100644 index 00000000..c2e5898d --- /dev/null +++ b/3rdparty/modules/rabbitmq/lib/puppet/type/rabbitmq_erlang_cookie.rb @@ -0,0 +1,34 @@ +Puppet::Type.newtype(:rabbitmq_erlang_cookie) do + desc 'Type to manage the rabbitmq erlang cookie securely' + + newparam(:path, :namevar => true) + + newproperty(:content) do + desc 'Content of cookie' + newvalues(/^\S+$/) + def change_to_s(current, desired) + "The rabbitmq erlang cookie was changed" + end + end + + newparam(:force) do + defaultto(:false) + newvalues(:true, :false) + end + + newparam(:rabbitmq_user) do + defaultto('rabbitmq') + end + + newparam(:rabbitmq_group) do + defaultto('rabbitmq') + end + + newparam(:rabbitmq_home) do + defaultto('/var/lib/rabbitmq') + end + + newparam(:service_name) do + newvalues(/^\S+$/) + end +end diff --git a/3rdparty/modules/rabbitmq/lib/puppet/type/rabbitmq_exchange.rb b/3rdparty/modules/rabbitmq/lib/puppet/type/rabbitmq_exchange.rb new file mode 100644 index 00000000..b2e88a8d --- /dev/null +++ b/3rdparty/modules/rabbitmq/lib/puppet/type/rabbitmq_exchange.rb @@ -0,0 +1,74 @@ +Puppet::Type.newtype(:rabbitmq_exchange) do + desc 'Native type for managing rabbitmq exchanges' + + ensurable do + defaultto(:present) + newvalue(:present) do + provider.create + end + newvalue(:absent) do + provider.destroy + end + end + + newparam(:name, :namevar => true) do + desc 'Name of exchange' + newvalues(/^\S*@\S+$/) + end + + newparam(:type) do + desc 'Exchange type to be set *on creation*' + newvalues(/^\S+$/) + end + + newparam(:durable) do + desc 'Exchange durability to be set *on creation*' + newvalues(/^\S+$/) + end + + newparam(:auto_delete) do + desc 'Exchange auto delete option to be set *on creation*' + newvalues(/^\S+$/) + end + + newparam(:internal) do + desc 'Exchange internal option to be set *on creation*' + newvalues(/^\S+$/) + end + + newparam(:arguments) do + desc 'Exchange arguments example: {"hash-header": "message-distribution-hash"}' + defaultto {} + end + + newparam(:user) do + desc 'The user to use to connect to rabbitmq' + defaultto('guest') + newvalues(/^\S+$/) + end + + newparam(:password) do + desc 'The password to use to connect to rabbitmq' + defaultto('guest') + newvalues(/\S+/) + end + + validate do + if self[:ensure] == :present and self[:type].nil? + raise ArgumentError, "must set type when creating exchange for #{self[:name]} whose type is #{self[:type]}" + end + end + + autorequire(:rabbitmq_vhost) do + [self[:name].split('@')[1]] + end + + autorequire(:rabbitmq_user) do + [self[:user]] + end + + autorequire(:rabbitmq_user_permissions) do + ["#{self[:user]}@#{self[:name].split('@')[1]}"] + end + +end diff --git a/modules/rabbitmq/lib/puppet/type/rabbitmq_plugin.rb b/3rdparty/modules/rabbitmq/lib/puppet/type/rabbitmq_plugin.rb similarity index 100% rename from modules/rabbitmq/lib/puppet/type/rabbitmq_plugin.rb rename to 3rdparty/modules/rabbitmq/lib/puppet/type/rabbitmq_plugin.rb diff --git a/3rdparty/modules/rabbitmq/lib/puppet/type/rabbitmq_policy.rb b/3rdparty/modules/rabbitmq/lib/puppet/type/rabbitmq_policy.rb new file mode 100644 index 00000000..259a1d66 --- /dev/null +++ b/3rdparty/modules/rabbitmq/lib/puppet/type/rabbitmq_policy.rb @@ -0,0 +1,101 @@ +Puppet::Type.newtype(:rabbitmq_policy) do + desc 'Type for managing rabbitmq policies' + + ensurable do + defaultto(:present) + newvalue(:present) do + provider.create + end + newvalue(:absent) do + provider.destroy + end + end + + autorequire(:service) { 'rabbitmq-server' } + + validate do + fail('pattern parameter is required.') if self[:ensure] == :present and self[:pattern].nil? + fail('definition parameter is required.') if self[:ensure] == :present and self[:definition].nil? + end + + newparam(:name, :namevar => true) do + desc 'combination of policy@vhost to create policy for' + newvalues(/^\S+@\S+$/) + end + + newproperty(:pattern) do + desc 'policy pattern' + validate do |value| + resource.validate_pattern(value) + end + end + + newproperty(:applyto) do + desc 'policy apply to' + newvalue(:all) + newvalue(:exchanges) + newvalue(:queues) + defaultto :all + end + + newproperty(:definition) do + desc 'policy definition' + validate do |value| + resource.validate_definition(value) + end + munge do |value| + resource.munge_definition(value) + end + end + + newproperty(:priority) do + desc 'policy priority' + newvalues(/^\d+$/) + defaultto 0 + end + + autorequire(:rabbitmq_vhost) do + [self[:name].split('@')[1]] + end + + def validate_pattern(value) + begin + Regexp.new(value) + rescue RegexpError + raise ArgumentError, "Invalid regexp #{value}" + end + end + + def validate_definition(definition) + unless [Hash].include?(definition.class) + raise ArgumentError, "Invalid definition" + end + definition.each do |k,v| + unless [String].include?(v.class) + raise ArgumentError, "Invalid definition" + end + end + if definition['ha-mode'] == 'exactly' + ha_params = definition['ha-params'] + unless ha_params.to_i.to_s == ha_params + raise ArgumentError, "Invalid ha-params '#{ha_params}' for ha-mode 'exactly'" + end + end + if definition.key? 'expires' + expires_val = definition['expires'] + unless expires_val.to_i.to_s == expires_val + raise ArgumentError, "Invalid expires value '#{expires_val}'" + end + end + end + + def munge_definition(definition) + if definition['ha-mode'] == 'exactly' + definition['ha-params'] = definition['ha-params'].to_i + end + if definition.key? 'expires' + definition['expires'] = definition['expires'].to_i + end + definition + end +end diff --git a/3rdparty/modules/rabbitmq/lib/puppet/type/rabbitmq_queue.rb b/3rdparty/modules/rabbitmq/lib/puppet/type/rabbitmq_queue.rb new file mode 100644 index 00000000..464a2ca6 --- /dev/null +++ b/3rdparty/modules/rabbitmq/lib/puppet/type/rabbitmq_queue.rb @@ -0,0 +1,68 @@ +Puppet::Type.newtype(:rabbitmq_queue) do + desc 'Native type for managing rabbitmq queue' + + ensurable do + defaultto(:present) + newvalue(:present) do + provider.create + end + newvalue(:absent) do + provider.destroy + end + end + + newparam(:name, :namevar => true) do + desc 'Name of queue' + newvalues(/^\S*@\S+$/) + end + + newparam(:durable) do + desc 'Queue is durable' + newvalues(/true|false/) + defaultto('true') + end + + newparam(:auto_delete) do + desc 'Queue will be auto deleted' + newvalues(/true|false/) + defaultto('false') + end + + newparam(:arguments) do + desc 'Queue arguments example: {x-message-ttl => 60, x-expires => 10}' + defaultto {} + validate do |value| + resource.validate_argument(value) + end + end + + newparam(:user) do + desc 'The user to use to connect to rabbitmq' + defaultto('guest') + newvalues(/^\S+$/) + end + + newparam(:password) do + desc 'The password to use to connect to rabbitmq' + defaultto('guest') + newvalues(/\S+/) + end + + autorequire(:rabbitmq_vhost) do + [self[:name].split('@')[1]] + end + + autorequire(:rabbitmq_user) do + [self[:user]] + end + + autorequire(:rabbitmq_user_permissions) do + ["#{self[:user]}@#{self[:name].split('@')[1]}"] + end + + def validate_argument(argument) + unless [Hash].include?(argument.class) + raise ArgumentError, "Invalid argument" + end + end +end diff --git a/3rdparty/modules/rabbitmq/lib/puppet/type/rabbitmq_user.rb b/3rdparty/modules/rabbitmq/lib/puppet/type/rabbitmq_user.rb new file mode 100644 index 00000000..baf47958 --- /dev/null +++ b/3rdparty/modules/rabbitmq/lib/puppet/type/rabbitmq_user.rb @@ -0,0 +1,85 @@ +Puppet::Type.newtype(:rabbitmq_user) do + desc 'Native type for managing rabbitmq users' + + ensurable do + defaultto(:present) + newvalue(:present) do + provider.create + end + newvalue(:absent) do + provider.destroy + end + end + + autorequire(:service) { 'rabbitmq-server' } + + newparam(:name, :namevar => true) do + desc 'Name of user' + newvalues(/^\S+$/) + end + + newproperty(:password) do + desc 'User password to be set *on creation* and validated each run' + def insync?(is) + provider.check_password + end + def set(value) + provider.change_password + end + def change_to_s(current, desired) + "password has been changed" + end + end + + newproperty(:admin) do + desc 'whether or not user should be an admin' + newvalues(/true|false/) + munge do |value| + # converting to_s in case its a boolean + value.to_s.to_sym + end + defaultto :false + end + + newproperty(:tags, :array_matching => :all) do + desc 'additional tags for the user' + validate do |value| + unless value =~ /^\S+$/ + raise ArgumentError, "Invalid tag: #{value.inspect}" + end + + if value == "administrator" + raise ArgumentError, "must use admin property instead of administrator tag" + end + end + defaultto [] + + def insync?(is) + self.is_to_s(is) == self.should_to_s + end + + def is_to_s(currentvalue = @is) + if currentvalue + "[#{currentvalue.sort.join(', ')}]" + else + '[]' + end + end + + def should_to_s(newvalue = @should) + if newvalue + "[#{newvalue.sort.join(', ')}]" + else + '[]' + end + end + + end + + validate do + if self[:ensure] == :present and ! self[:password] + raise ArgumentError, 'must set password when creating user' unless self[:password] + end + end + +end diff --git a/modules/rabbitmq/lib/puppet/type/rabbitmq_user_permissions.rb b/3rdparty/modules/rabbitmq/lib/puppet/type/rabbitmq_user_permissions.rb similarity index 91% rename from modules/rabbitmq/lib/puppet/type/rabbitmq_user_permissions.rb rename to 3rdparty/modules/rabbitmq/lib/puppet/type/rabbitmq_user_permissions.rb index 427da6ca..493d47c8 100644 --- a/modules/rabbitmq/lib/puppet/type/rabbitmq_user_permissions.rb +++ b/3rdparty/modules/rabbitmq/lib/puppet/type/rabbitmq_user_permissions.rb @@ -11,8 +11,10 @@ Puppet::Type.newtype(:rabbitmq_user_permissions) do end end + autorequire(:service) { 'rabbitmq-server' } + newparam(:name, :namevar => true) do - 'combination of user@vhost to grant privileges to' + desc 'combination of user@vhost to grant privileges to' newvalues(/^\S+@\S+$/) end diff --git a/modules/rabbitmq/lib/puppet/type/rabbitmq_vhost.rb b/3rdparty/modules/rabbitmq/lib/puppet/type/rabbitmq_vhost.rb similarity index 87% rename from modules/rabbitmq/lib/puppet/type/rabbitmq_vhost.rb rename to 3rdparty/modules/rabbitmq/lib/puppet/type/rabbitmq_vhost.rb index 9dd0982d..1349be6e 100644 --- a/modules/rabbitmq/lib/puppet/type/rabbitmq_vhost.rb +++ b/3rdparty/modules/rabbitmq/lib/puppet/type/rabbitmq_vhost.rb @@ -11,6 +11,8 @@ Puppet::Type.newtype(:rabbitmq_vhost) do end end + autorequire(:service) { 'rabbitmq-server' } + newparam(:name, :namevar => true) do 'name of the vhost to add' newvalues(/^\S+$/) diff --git a/3rdparty/modules/rabbitmq/log/centos-6-x64-vcloud/2015-05-22_18_55_23/sut.log b/3rdparty/modules/rabbitmq/log/centos-6-x64-vcloud/2015-05-22_18_55_23/sut.log new file mode 100644 index 00000000..daba18f8 --- /dev/null +++ b/3rdparty/modules/rabbitmq/log/centos-6-x64-vcloud/2015-05-22_18_55_23/sut.log @@ -0,0 +1,2 @@ +2015-05-22 18:55:24 [+] vcloud el-6-x86_64 x7igdqecc5wczwg.delivery.puppetlabs.net (centos-6-vcloud) +2015-05-22 18:56:27 [-] vcloud el-6-x86_64 x7igdqecc5wczwg.delivery.puppetlabs.net (centos-6-vcloud) diff --git a/3rdparty/modules/rabbitmq/log/centos-6-x64-vcloud/2015-05-22_18_57_07/sut.log b/3rdparty/modules/rabbitmq/log/centos-6-x64-vcloud/2015-05-22_18_57_07/sut.log new file mode 100644 index 00000000..ee5be971 --- /dev/null +++ b/3rdparty/modules/rabbitmq/log/centos-6-x64-vcloud/2015-05-22_18_57_07/sut.log @@ -0,0 +1,2 @@ +2015-05-22 18:57:08 [+] vcloud el-6-x86_64 bnzq0zsilo3bhbv.delivery.puppetlabs.net (centos-6-vcloud) +2015-05-22 19:09:48 [-] vcloud el-6-x86_64 bnzq0zsilo3bhbv.delivery.puppetlabs.net (centos-6-vcloud) diff --git a/3rdparty/modules/rabbitmq/log/debian-7-x64-vcloud/2015-05-22_18_35_45/sut.log b/3rdparty/modules/rabbitmq/log/debian-7-x64-vcloud/2015-05-22_18_35_45/sut.log new file mode 100644 index 00000000..5f2329a2 --- /dev/null +++ b/3rdparty/modules/rabbitmq/log/debian-7-x64-vcloud/2015-05-22_18_35_45/sut.log @@ -0,0 +1,2 @@ +2015-05-22 18:35:46 [+] vcloud debian-7-x86_64 upzah8fcy4az78n.delivery.puppetlabs.net (debian-7-vcloud) +2015-05-22 18:46:28 [-] vcloud debian-7-x86_64 upzah8fcy4az78n.delivery.puppetlabs.net (debian-7-vcloud) diff --git a/3rdparty/modules/rabbitmq/log/debian-7-x64-vcloud/2015-05-22_18_49_58/sut.log b/3rdparty/modules/rabbitmq/log/debian-7-x64-vcloud/2015-05-22_18_49_58/sut.log new file mode 100644 index 00000000..322d50be --- /dev/null +++ b/3rdparty/modules/rabbitmq/log/debian-7-x64-vcloud/2015-05-22_18_49_58/sut.log @@ -0,0 +1,2 @@ +2015-05-22 18:49:59 [+] vcloud debian-7-x86_64 pwvlrfgqbbgoc21.delivery.puppetlabs.net (debian-7-vcloud) +2015-05-22 18:54:13 [-] vcloud debian-7-x86_64 pwvlrfgqbbgoc21.delivery.puppetlabs.net (debian-7-vcloud) diff --git a/3rdparty/modules/rabbitmq/log/debian-7-x64-vcloud/2015-05-22_19_10_48/sut.log b/3rdparty/modules/rabbitmq/log/debian-7-x64-vcloud/2015-05-22_19_10_48/sut.log new file mode 100644 index 00000000..5d01374f --- /dev/null +++ b/3rdparty/modules/rabbitmq/log/debian-7-x64-vcloud/2015-05-22_19_10_48/sut.log @@ -0,0 +1,2 @@ +2015-05-22 19:10:49 [+] vcloud debian-7-x86_64 tnx0tmyf2ws118o.delivery.puppetlabs.net (debian-7-vcloud) +2015-05-22 19:11:16 [-] vcloud debian-7-x86_64 tnx0tmyf2ws118o.delivery.puppetlabs.net (debian-7-vcloud) diff --git a/3rdparty/modules/rabbitmq/log/debian-7-x64-vcloud/2015-05-22_19_11_31/sut.log b/3rdparty/modules/rabbitmq/log/debian-7-x64-vcloud/2015-05-22_19_11_31/sut.log new file mode 100644 index 00000000..ff82d28a --- /dev/null +++ b/3rdparty/modules/rabbitmq/log/debian-7-x64-vcloud/2015-05-22_19_11_31/sut.log @@ -0,0 +1,2 @@ +2015-05-22 19:11:32 [+] vcloud debian-7-x86_64 inmrclcv6poi3ho.delivery.puppetlabs.net (debian-7-vcloud) +2015-05-22 19:15:48 [-] vcloud debian-7-x86_64 inmrclcv6poi3ho.delivery.puppetlabs.net (debian-7-vcloud) diff --git a/3rdparty/modules/rabbitmq/log/default/2015-05-22_18_35_28/sut.log b/3rdparty/modules/rabbitmq/log/default/2015-05-22_18_35_28/sut.log new file mode 100644 index 00000000..e69de29b diff --git a/3rdparty/modules/rabbitmq/manifests/config.pp b/3rdparty/modules/rabbitmq/manifests/config.pp new file mode 100644 index 00000000..159ae68d --- /dev/null +++ b/3rdparty/modules/rabbitmq/manifests/config.pp @@ -0,0 +1,172 @@ +# Class: rabbitmq::config +# Sets all the configuration values for RabbitMQ and creates the directories for +# config and ssl. +class rabbitmq::config { + + $admin_enable = $rabbitmq::admin_enable + $cluster_node_type = $rabbitmq::cluster_node_type + $cluster_nodes = $rabbitmq::cluster_nodes + $config = $rabbitmq::config + $config_cluster = $rabbitmq::config_cluster + $config_path = $rabbitmq::config_path + $config_stomp = $rabbitmq::config_stomp + $default_user = $rabbitmq::default_user + $default_pass = $rabbitmq::default_pass + $env_config = $rabbitmq::env_config + $env_config_path = $rabbitmq::env_config_path + $erlang_cookie = $rabbitmq::erlang_cookie + $interface = $rabbitmq::interface + $management_port = $rabbitmq::management_port + $node_ip_address = $rabbitmq::node_ip_address + $plugin_dir = $rabbitmq::plugin_dir + $rabbitmq_user = $rabbitmq::rabbitmq_user + $rabbitmq_group = $rabbitmq::rabbitmq_group + $rabbitmq_home = $rabbitmq::rabbitmq_home + $port = $rabbitmq::port + $tcp_keepalive = $rabbitmq::tcp_keepalive + $service_name = $rabbitmq::service_name + $ssl = $rabbitmq::ssl + $ssl_only = $rabbitmq::ssl_only + $ssl_cacert = $rabbitmq::ssl_cacert + $ssl_cert = $rabbitmq::ssl_cert + $ssl_key = $rabbitmq::ssl_key + $ssl_port = $rabbitmq::ssl_port + $ssl_interface = $rabbitmq::ssl_interface + $ssl_management_port = $rabbitmq::ssl_management_port + $ssl_stomp_port = $rabbitmq::ssl_stomp_port + $ssl_verify = $rabbitmq::ssl_verify + $ssl_fail_if_no_peer_cert = $rabbitmq::ssl_fail_if_no_peer_cert + $ssl_versions = $rabbitmq::ssl_versions + $ssl_ciphers = $rabbitmq::ssl_ciphers + $stomp_port = $rabbitmq::stomp_port + $ldap_auth = $rabbitmq::ldap_auth + $ldap_server = $rabbitmq::ldap_server + $ldap_user_dn_pattern = $rabbitmq::ldap_user_dn_pattern + $ldap_other_bind = $rabbitmq::ldap_other_bind + $ldap_use_ssl = $rabbitmq::ldap_use_ssl + $ldap_port = $rabbitmq::ldap_port + $ldap_log = $rabbitmq::ldap_log + $ldap_config_variables = $rabbitmq::ldap_config_variables + $wipe_db_on_cookie_change = $rabbitmq::wipe_db_on_cookie_change + $config_variables = $rabbitmq::config_variables + $config_kernel_variables = $rabbitmq::config_kernel_variables + $cluster_partition_handling = $rabbitmq::cluster_partition_handling + $file_limit = $rabbitmq::file_limit + $default_env_variables = { + 'NODE_PORT' => $port, + 'NODE_IP_ADDRESS' => $node_ip_address + } + + # Handle env variables. + $environment_variables = merge($default_env_variables, $rabbitmq::environment_variables) + + file { '/etc/rabbitmq': + ensure => directory, + owner => '0', + group => '0', + mode => '0644', + } + + file { '/etc/rabbitmq/ssl': + ensure => directory, + owner => '0', + group => '0', + mode => '0644', + } + + file { 'rabbitmq.config': + ensure => file, + path => $config_path, + content => template($config), + owner => '0', + group => '0', + mode => '0644', + notify => Class['rabbitmq::service'], + } + + file { 'rabbitmq-env.config': + ensure => file, + path => $env_config_path, + content => template($env_config), + owner => '0', + group => '0', + mode => '0644', + notify => Class['rabbitmq::service'], + } + + if $admin_enable { + file { 'rabbitmqadmin.conf': + ensure => file, + path => '/etc/rabbitmq/rabbitmqadmin.conf', + content => template('rabbitmq/rabbitmqadmin.conf.erb'), + owner => '0', + group => '0', + mode => '0644', + require => File['/etc/rabbitmq'], + } + } + + case $::osfamily { + 'Debian': { + file { '/etc/default/rabbitmq-server': + ensure => file, + content => template('rabbitmq/default.erb'), + mode => '0644', + owner => '0', + group => '0', + notify => Class['rabbitmq::service'], + } + } + 'RedHat': { + if versioncmp($::operatingsystemmajrelease, '7') >= 0 { + file { '/etc/systemd/system/rabbitmq-server.service.d': + ensure => directory, + owner => '0', + group => '0', + mode => '0755', + selinux_ignore_defaults => true, + } -> + file { '/etc/systemd/system/rabbitmq-server.service.d/limits.conf': + content => template('rabbitmq/rabbitmq-server.service.d/limits.conf'), + owner => '0', + group => '0', + mode => '0644', + notify => Exec['rabbitmq-systemd-reload'], + } + exec { 'rabbitmq-systemd-reload': + command => '/usr/bin/systemctl daemon-reload', + notify => Class['Rabbitmq::Service'], + refreshonly => true, + } + } else { + file { '/etc/security/limits.d/rabbitmq-server.conf': + content => template('rabbitmq/limits.conf'), + owner => '0', + group => '0', + mode => '0644', + notify => Class['Rabbitmq::Service'], + } + } + } + default: { + } + } + + if $config_cluster { + + if $erlang_cookie == undef { + fail('You must set the $erlang_cookie value in order to configure clustering.') + } else { + rabbitmq_erlang_cookie { "${rabbitmq_home}/.erlang.cookie": + content => $erlang_cookie, + force => $wipe_db_on_cookie_change, + rabbitmq_user => $rabbitmq_user, + rabbitmq_group => $rabbitmq_group, + rabbitmq_home => $rabbitmq_home, + service_name => $service_name, + before => File['rabbitmq.config'], + notify => Class['rabbitmq::service'], + } + } + } +} diff --git a/3rdparty/modules/rabbitmq/manifests/init.pp b/3rdparty/modules/rabbitmq/manifests/init.pp new file mode 100644 index 00000000..4e115f9b --- /dev/null +++ b/3rdparty/modules/rabbitmq/manifests/init.pp @@ -0,0 +1,238 @@ +# Main rabbitmq class +class rabbitmq( + $admin_enable = $rabbitmq::params::admin_enable, + $cluster_node_type = $rabbitmq::params::cluster_node_type, + $cluster_nodes = $rabbitmq::params::cluster_nodes, + $config = $rabbitmq::params::config, + $config_cluster = $rabbitmq::params::config_cluster, + $config_path = $rabbitmq::params::config_path, + $config_stomp = $rabbitmq::params::config_stomp, + $default_user = $rabbitmq::params::default_user, + $default_pass = $rabbitmq::params::default_pass, + $delete_guest_user = $rabbitmq::params::delete_guest_user, + $env_config = $rabbitmq::params::env_config, + $env_config_path = $rabbitmq::params::env_config_path, + $erlang_cookie = $rabbitmq::params::erlang_cookie, + $interface = $rabbitmq::params::interface, + $management_port = $rabbitmq::params::management_port, + $node_ip_address = $rabbitmq::params::node_ip_address, + $package_apt_pin = $rabbitmq::params::package_apt_pin, + $package_ensure = $rabbitmq::params::package_ensure, + $package_gpg_key = $rabbitmq::params::package_gpg_key, + $package_name = $rabbitmq::params::package_name, + $package_provider = $rabbitmq::params::package_provider, + $package_source = undef, + $repos_ensure = $rabbitmq::params::repos_ensure, + $manage_repos = $rabbitmq::params::manage_repos, + $plugin_dir = $rabbitmq::params::plugin_dir, + $rabbitmq_user = $rabbitmq::params::rabbitmq_user, + $rabbitmq_group = $rabbitmq::params::rabbitmq_group, + $rabbitmq_home = $rabbitmq::params::rabbitmq_home, + $port = $rabbitmq::params::port, + $tcp_keepalive = $rabbitmq::params::tcp_keepalive, + $service_ensure = $rabbitmq::params::service_ensure, + $service_manage = $rabbitmq::params::service_manage, + $service_name = $rabbitmq::params::service_name, + $ssl = $rabbitmq::params::ssl, + $ssl_only = $rabbitmq::params::ssl_only, + $ssl_cacert = $rabbitmq::params::ssl_cacert, + $ssl_cert = $rabbitmq::params::ssl_cert, + $ssl_key = $rabbitmq::params::ssl_key, + $ssl_port = $rabbitmq::params::ssl_port, + $ssl_interface = $rabbitmq::params::ssl_interface, + $ssl_management_port = $rabbitmq::params::ssl_management_port, + $ssl_stomp_port = $rabbitmq::params::ssl_stomp_port, + $ssl_verify = $rabbitmq::params::ssl_verify, + $ssl_fail_if_no_peer_cert = $rabbitmq::params::ssl_fail_if_no_peer_cert, + $ssl_versions = $rabbitmq::params::ssl_versions, + $ssl_ciphers = $rabbitmq::params::ssl_ciphers, + $stomp_ensure = $rabbitmq::params::stomp_ensure, + $ldap_auth = $rabbitmq::params::ldap_auth, + $ldap_server = $rabbitmq::params::ldap_server, + $ldap_user_dn_pattern = $rabbitmq::params::ldap_user_dn_pattern, + $ldap_other_bind = $rabbitmq::params::ldap_other_bind, + $ldap_use_ssl = $rabbitmq::params::ldap_use_ssl, + $ldap_port = $rabbitmq::params::ldap_port, + $ldap_log = $rabbitmq::params::ldap_log, + $ldap_config_variables = $rabbitmq::params::ldap_config_variables, + $stomp_port = $rabbitmq::params::stomp_port, + $version = $rabbitmq::params::version, + $wipe_db_on_cookie_change = $rabbitmq::params::wipe_db_on_cookie_change, + $cluster_partition_handling = $rabbitmq::params::cluster_partition_handling, + $file_limit = $rabbitmq::params::file_limit, + $environment_variables = $rabbitmq::params::environment_variables, + $config_variables = $rabbitmq::params::config_variables, + $config_kernel_variables = $rabbitmq::params::config_kernel_variables, + $key_content = undef, +) inherits rabbitmq::params { + + validate_bool($admin_enable) + # Validate install parameters. + validate_re($package_apt_pin, '^(|\d+)$') + validate_string($package_ensure) + validate_string($package_gpg_key) + validate_string($package_name) + validate_string($package_provider) + validate_bool($repos_ensure) + validate_re($version, '^\d+\.\d+\.\d+(-\d+)*$') # Allow 3 digits and optional -n postfix. + # Validate config parameters. + validate_re($cluster_node_type, '^(ram|disc|disk)$') # Both disc and disk are valid http://www.rabbitmq.com/clustering.html + validate_array($cluster_nodes) + validate_string($config) + validate_absolute_path($config_path) + validate_bool($config_cluster) + validate_bool($config_stomp) + validate_string($default_user) + validate_string($default_pass) + validate_bool($delete_guest_user) + validate_string($env_config) + validate_absolute_path($env_config_path) + validate_string($erlang_cookie) + if ! is_integer($management_port) { + validate_re($management_port, '\d+') + } + validate_string($node_ip_address) + validate_absolute_path($plugin_dir) + if ! is_integer($port) { + validate_re($port, ['\d+','UNSET']) + } + if ! is_integer($stomp_port) { + validate_re($stomp_port, '\d+') + } + validate_bool($wipe_db_on_cookie_change) + validate_bool($tcp_keepalive) + if ! is_integer($file_limit) { + validate_re($file_limit, '^(unlimited|infinity)$', '$file_limit must be an integer, \'unlimited\', or \'infinity\'.') + } + # Validate service parameters. + validate_re($service_ensure, '^(running|stopped)$') + validate_bool($service_manage) + validate_string($service_name) + validate_bool($ssl) + validate_bool($ssl_only) + validate_string($ssl_cacert) + validate_string($ssl_cert) + validate_string($ssl_key) + validate_array($ssl_ciphers) + if ! is_integer($ssl_port) { + validate_re($ssl_port, '\d+') + } + if ! is_integer($ssl_management_port) { + validate_re($ssl_management_port, '\d+') + } + if ! is_integer($ssl_stomp_port) { + validate_re($ssl_stomp_port, '\d+') + } + validate_bool($stomp_ensure) + validate_bool($ldap_auth) + validate_string($ldap_server) + validate_string($ldap_user_dn_pattern) + validate_string($ldap_other_bind) + validate_hash($ldap_config_variables) + validate_bool($ldap_use_ssl) + validate_re($ldap_port, '\d+') + validate_bool($ldap_log) + validate_hash($environment_variables) + validate_hash($config_variables) + validate_hash($config_kernel_variables) + + if $ssl_only and ! $ssl { + fail('$ssl_only => true requires that $ssl => true') + } + + if $config_stomp and $ssl_stomp_port and ! $ssl { + warning('$ssl_stomp_port requires that $ssl => true and will be ignored') + } + + if $ssl_versions { + if $ssl { + validate_array($ssl_versions) + } else { + fail('$ssl_versions requires that $ssl => true') + } + } + + # This needs to happen here instead of params.pp because + # $package_source needs to override the constructed value in params.pp + if $package_source { # $package_source was specified by user so use that one + $real_package_source = $package_source + # NOTE(bogdando) do not enforce the source value for yum provider #MODULES-1631 + } elsif $package_provider != 'yum' { + # package_source was not specified, so construct it, unless the provider is 'yum' + case $::osfamily { + 'RedHat', 'SUSE': { + $base_version = regsubst($version,'^(.*)-\d$','\1') + $real_package_source = "http://www.rabbitmq.com/releases/rabbitmq-server/v${base_version}/rabbitmq-server-${version}.noarch.rpm" + } + default: { # Archlinux and Debian + $real_package_source = '' + } + } + } else { # for yum provider, use the source as is + $real_package_source = $package_source + } + + include '::rabbitmq::install' + include '::rabbitmq::config' + include '::rabbitmq::service' + include '::rabbitmq::management' + + if $manage_repos != undef { + warning('$manage_repos is now deprecated. Please use $repos_ensure instead') + } + + if $manage_repos != false { + case $::osfamily { + 'RedHat', 'SUSE': + { include '::rabbitmq::repo::rhel' } + 'Debian': { + class { '::rabbitmq::repo::apt' : + key_source => $package_gpg_key, + key_content => $key_content, + } + } + default: + { } + } + } + + if $admin_enable and $service_manage { + include '::rabbitmq::install::rabbitmqadmin' + + rabbitmq_plugin { 'rabbitmq_management': + ensure => present, + require => Class['rabbitmq::install'], + notify => Class['rabbitmq::service'], + } + + Class['::rabbitmq::service'] -> Class['::rabbitmq::install::rabbitmqadmin'] + Class['::rabbitmq::install::rabbitmqadmin'] -> Rabbitmq_exchange<| |> + } + + if $stomp_ensure { + rabbitmq_plugin { 'rabbitmq_stomp': + ensure => present, + require => Class['rabbitmq::install'], + notify => Class['rabbitmq::service'], + } + } + + if ($ldap_auth) { + rabbitmq_plugin { 'rabbitmq_auth_backend_ldap': + ensure => present, + require => Class['rabbitmq::install'], + notify => Class['rabbitmq::service'], + } + } + + anchor { 'rabbitmq::begin': } + anchor { 'rabbitmq::end': } + + Anchor['rabbitmq::begin'] -> Class['::rabbitmq::install'] + -> Class['::rabbitmq::config'] ~> Class['::rabbitmq::service'] + -> Class['::rabbitmq::management'] -> Anchor['rabbitmq::end'] + + # Make sure the various providers have their requirements in place. + Class['::rabbitmq::install'] -> Rabbitmq_plugin<| |> + +} diff --git a/3rdparty/modules/rabbitmq/manifests/install.pp b/3rdparty/modules/rabbitmq/manifests/install.pp new file mode 100644 index 00000000..f2df83aa --- /dev/null +++ b/3rdparty/modules/rabbitmq/manifests/install.pp @@ -0,0 +1,23 @@ +# Class rabbitmq::install +# Ensures the rabbitmq-server exists +class rabbitmq::install { + + $package_ensure = $rabbitmq::package_ensure + $package_name = $rabbitmq::package_name + $package_provider = $rabbitmq::package_provider + $package_source = $rabbitmq::real_package_source + + package { 'rabbitmq-server': + ensure => $package_ensure, + name => $package_name, + provider => $package_provider, + notify => Class['rabbitmq::service'], + } + + if $package_source { + Package['rabbitmq-server'] { + source => $package_source, + } + } + +} diff --git a/3rdparty/modules/rabbitmq/manifests/install/rabbitmqadmin.pp b/3rdparty/modules/rabbitmq/manifests/install/rabbitmqadmin.pp new file mode 100644 index 00000000..bf545eea --- /dev/null +++ b/3rdparty/modules/rabbitmq/manifests/install/rabbitmqadmin.pp @@ -0,0 +1,35 @@ +# +class rabbitmq::install::rabbitmqadmin { + + if($rabbitmq::ssl) { + $management_port = $rabbitmq::ssl_management_port + } + else { + $management_port = $rabbitmq::management_port + } + + $default_user = $rabbitmq::default_user + $default_pass = $rabbitmq::default_pass + $protocol = $rabbitmq::ssl ? { false => 'http', default => 'https' } + + staging::file { 'rabbitmqadmin': + target => "${rabbitmq::rabbitmq_home}/rabbitmqadmin", + source => "${protocol}://${default_user}:${default_pass}@localhost:${management_port}/cli/rabbitmqadmin", + curl_option => '-k --noproxy localhost --retry 30 --retry-delay 6', + timeout => '180', + wget_option => '--no-proxy', + require => [ + Class['rabbitmq::service'], + Rabbitmq_plugin['rabbitmq_management'] + ], + } + + file { '/usr/local/bin/rabbitmqadmin': + owner => 'root', + group => '0', + source => "${rabbitmq::rabbitmq_home}/rabbitmqadmin", + mode => '0755', + require => Staging::File['rabbitmqadmin'], + } + +} diff --git a/3rdparty/modules/rabbitmq/manifests/management.pp b/3rdparty/modules/rabbitmq/manifests/management.pp new file mode 100644 index 00000000..481a7b4f --- /dev/null +++ b/3rdparty/modules/rabbitmq/manifests/management.pp @@ -0,0 +1,13 @@ +# +class rabbitmq::management { + + $delete_guest_user = $rabbitmq::delete_guest_user + + if $delete_guest_user { + rabbitmq_user{ 'guest': + ensure => absent, + provider => 'rabbitmqctl', + } + } + +} diff --git a/3rdparty/modules/rabbitmq/manifests/params.pp b/3rdparty/modules/rabbitmq/manifests/params.pp new file mode 100644 index 00000000..7366d957 --- /dev/null +++ b/3rdparty/modules/rabbitmq/manifests/params.pp @@ -0,0 +1,121 @@ + # Class: rabbitmq::params +# +# The RabbitMQ Module configuration settings. +# +class rabbitmq::params { + + case $::osfamily { + 'Archlinux': { + $package_ensure = 'installed' + $package_name = 'rabbitmq' + $service_name = 'rabbitmq' + $version = '3.1.3-1' + $rabbitmq_user = 'rabbitmq' + $rabbitmq_group = 'rabbitmq' + $rabbitmq_home = '/var/lib/rabbitmq' + $plugin_dir = "/usr/lib/rabbitmq/lib/rabbitmq_server-${version}/plugins" + } + 'Debian': { + $package_ensure = 'installed' + $package_name = 'rabbitmq-server' + $service_name = 'rabbitmq-server' + $package_provider = 'apt' + $version = '3.1.5' + $rabbitmq_user = 'rabbitmq' + $rabbitmq_group = 'rabbitmq' + $rabbitmq_home = '/var/lib/rabbitmq' + $plugin_dir = "/usr/lib/rabbitmq/lib/rabbitmq_server-${version}/plugins" + } + 'OpenBSD': { + $package_ensure = 'installed' + $package_name = 'rabbitmq' + $service_name = 'rabbitmq' + $version = '3.4.2' + $rabbitmq_user = '_rabbitmq' + $rabbitmq_group = '_rabbitmq' + $rabbitmq_home = '/var/rabbitmq' + $plugin_dir = '/usr/local/lib/rabbitmq/plugins' + } + 'RedHat': { + $package_ensure = 'installed' + $package_name = 'rabbitmq-server' + $service_name = 'rabbitmq-server' + $package_provider = 'rpm' + $version = '3.1.5-1' + $rabbitmq_user = 'rabbitmq' + $rabbitmq_group = 'rabbitmq' + $rabbitmq_home = '/var/lib/rabbitmq' + $plugin_dir = "/usr/lib/rabbitmq/lib/rabbitmq_server-${version}/plugins" + } + 'SUSE': { + $package_ensure = 'installed' + $package_name = 'rabbitmq-server' + $service_name = 'rabbitmq-server' + $package_provider = 'zypper' + $version = '3.1.5-1' + $rabbitmq_user = 'rabbitmq' + $rabbitmq_group = 'rabbitmq' + $rabbitmq_home = '/var/lib/rabbitmq' + $plugin_dir = "/usr/lib/rabbitmq/lib/rabbitmq_server-${version}/plugins" + } + default: { + fail("The ${module_name} module is not supported on an ${::osfamily} based system.") + } + } + + #install + $admin_enable = true + $management_port = '15672' + $package_apt_pin = '' + $package_gpg_key = 'http://www.rabbitmq.com/rabbitmq-signing-key-public.asc' + $repos_ensure = true + $manage_repos = undef + $service_ensure = 'running' + $service_manage = true + #config + $cluster_node_type = 'disc' + $cluster_nodes = [] + $config = 'rabbitmq/rabbitmq.config.erb' + $config_cluster = false + $config_path = '/etc/rabbitmq/rabbitmq.config' + $config_stomp = false + $default_user = 'guest' + $default_pass = 'guest' + $delete_guest_user = false + $env_config = 'rabbitmq/rabbitmq-env.conf.erb' + $env_config_path = '/etc/rabbitmq/rabbitmq-env.conf' + $erlang_cookie = undef + $interface = 'UNSET' + $node_ip_address = 'UNSET' + $port = '5672' + $tcp_keepalive = false + $ssl = false + $ssl_only = false + $ssl_cacert = 'UNSET' + $ssl_cert = 'UNSET' + $ssl_key = 'UNSET' + $ssl_port = '5671' + $ssl_interface = 'UNSET' + $ssl_management_port = '15671' + $ssl_stomp_port = '6164' + $ssl_verify = 'verify_none' + $ssl_fail_if_no_peer_cert = false + $ssl_versions = undef + $ssl_ciphers = [] + $stomp_ensure = false + $ldap_auth = false + $ldap_server = 'ldap' + $ldap_user_dn_pattern = 'cn=username,ou=People,dc=example,dc=com' + $ldap_other_bind = 'anon' + $ldap_use_ssl = false + $ldap_port = '389' + $ldap_log = false + $ldap_config_variables = {} + $stomp_port = '6163' + $wipe_db_on_cookie_change = false + $cluster_partition_handling = 'ignore' + $environment_variables = {} + $config_variables = {} + $config_kernel_variables = {} + $file_limit = 16384 +} diff --git a/3rdparty/modules/rabbitmq/manifests/repo/apt.pp b/3rdparty/modules/rabbitmq/manifests/repo/apt.pp new file mode 100644 index 00000000..0902e2c2 --- /dev/null +++ b/3rdparty/modules/rabbitmq/manifests/repo/apt.pp @@ -0,0 +1,41 @@ +# requires +# puppetlabs-apt +# puppetlabs-stdlib +class rabbitmq::repo::apt( + $location = 'http://www.rabbitmq.com/debian/', + $release = 'testing', + $repos = 'main', + $include_src = false, + $key = 'F78372A06FF50C80464FC1B4F7B8CEA6056E8E56', + $key_source = 'http://www.rabbitmq.com/rabbitmq-signing-key-public.asc', + $key_content = undef, + ) { + + $pin = $rabbitmq::package_apt_pin + + Class['rabbitmq::repo::apt'] -> Package<| title == 'rabbitmq-server' |> + + $ensure_source = $rabbitmq::repos_ensure ? { + false => 'absent', + default => 'present', + } + + apt::source { 'rabbitmq': + ensure => $ensure_source, + location => $location, + release => $release, + repos => $repos, + include_src => $include_src, + key => $key, + key_source => $key_source, + key_content => $key_content, + } + + if $pin != '' { + validate_re($pin, '\d\d\d') + apt::pin { 'rabbitmq': + packages => 'rabbitmq-server', + priority => $pin, + } + } +} diff --git a/3rdparty/modules/rabbitmq/manifests/repo/rhel.pp b/3rdparty/modules/rabbitmq/manifests/repo/rhel.pp new file mode 100644 index 00000000..28490994 --- /dev/null +++ b/3rdparty/modules/rabbitmq/manifests/repo/rhel.pp @@ -0,0 +1,16 @@ +# Class: rabbitmq::repo::rhel +# Imports the gpg key if it doesn't already exist. +class rabbitmq::repo::rhel { + + if $rabbitmq::repos_ensure { + + $package_gpg_key = $rabbitmq::package_gpg_key + + Class['rabbitmq::repo::rhel'] -> Package<| title == 'rabbitmq-server' |> + + exec { "rpm --import ${package_gpg_key}": + path => ['/bin','/usr/bin','/sbin','/usr/sbin'], + unless => 'rpm -q gpg-pubkey-056e8e56-468e43f2 2>/dev/null', + } + } +} diff --git a/3rdparty/modules/rabbitmq/manifests/server.pp b/3rdparty/modules/rabbitmq/manifests/server.pp new file mode 100644 index 00000000..05de184f --- /dev/null +++ b/3rdparty/modules/rabbitmq/manifests/server.pp @@ -0,0 +1,82 @@ +# Class: rabbitmq::server +# +# This module manages the installation and config of the rabbitmq server +# it has only been tested on certain version of debian-ish systems +# Parameters: +# [*port*] - port where rabbitmq server is hosted +# [*delete_guest_user*] - rather or not to delete the default user +# [*version*] - version of rabbitmq-server to install +# [*package_name*] - name of rabbitmq package +# [*service_name*] - name of rabbitmq service +# [*service_ensure*] - desired ensure state for service +# [*stomp_port*] - port stomp should be listening on +# [*node_ip_address*] - ip address for rabbitmq to bind to +# [*config*] - contents of config file +# [*env_config*] - contents of env-config file +# [*config_cluster*] - whether to configure a RabbitMQ cluster +# [*cluster_nodes*] - which nodes to cluster with (including the current one) +# [*cluster_node_type*] - Type of cluster node (disc/disk or ram) +# [*erlang_cookie*] - erlang cookie, must be the same for all nodes in a cluster +# [*wipe_db_on_cookie_change*] - whether to wipe the RabbitMQ data if the specified +# erlang_cookie differs from the current one. This is a sad parameter: actually, +# if the cookie indeed differs, then wiping the database is the *only* thing you +# can do. You're only required to set this parameter to true as a sign that you +# realise this. +# Requires: +# stdlib +# Sample Usage: +# +# This module is used as backward compability layer for modules +# which require rabbitmq::server instead of rabbitmq class. +# It's still common uasge in many modules. +# +# +# [Remember: No empty lines between comments and class definition] +class rabbitmq::server( + $port = $rabbitmq::params::port, + $delete_guest_user = $rabbitmq::params::delete_guest_user, + $package_name = $rabbitmq::params::package_name, + $version = $rabbitmq::params::version, + $service_name = $rabbitmq::params::service_name, + $service_ensure = $rabbitmq::params::service_ensure, + $service_manage = $rabbitmq::params::service_manage, + $config_stomp = $rabbitmq::params::config_stomp, + $stomp_port = $rabbitmq::params::stomp_port, + $config_cluster = $rabbitmq::params::config_cluster, + $cluster_nodes = $rabbitmq::params::cluster_nodes, + $cluster_node_type = $rabbitmq::params::cluster_node_type, + $node_ip_address = $rabbitmq::params::node_ip_address, + $config = $rabbitmq::params::config, + $env_config = $rabbitmq::params::env_config, + $erlang_cookie = $rabbitmq::params::erlang_cookie, + $wipe_db_on_cookie_change = $rabbitmq::params::wipe_db_on_cookie_change, +) inherits rabbitmq::params { + + anchor {'before::rabbimq::class': + before => Class['rabbitmq'], + } + + anchor {'after::rabbimq::class': + require => Class['rabbitmq'], + } + + class { 'rabbitmq': + port => $port, + delete_guest_user => $delete_guest_user, + package_name => $package_name, + version => $version, + service_name => $service_name, + service_ensure => $service_ensure, + service_manage => $service_manage, + config_stomp => $config_stomp, + stomp_port => $stomp_port, + config_cluster => $config_cluster, + cluster_nodes => $cluster_nodes, + cluster_node_type => $cluster_node_type, + node_ip_address => $node_ip_address, + config => $config, + env_config => $env_config, + erlang_cookie => $erlang_cookie, + wipe_db_on_cookie_change => $wipe_db_on_cookie_change, + } +} diff --git a/3rdparty/modules/rabbitmq/manifests/service.pp b/3rdparty/modules/rabbitmq/manifests/service.pp new file mode 100644 index 00000000..c01aa64a --- /dev/null +++ b/3rdparty/modules/rabbitmq/manifests/service.pp @@ -0,0 +1,40 @@ +# Class: rabbitmq::service +# +# This class manages the rabbitmq server service itself. +# +# Parameters: +# +# Actions: +# +# Requires: +# +# Sample Usage: +# +class rabbitmq::service( + $service_ensure = $rabbitmq::service_ensure, + $service_manage = $rabbitmq::service_manage, + $service_name = $rabbitmq::service_name, +) inherits rabbitmq { + + validate_re($service_ensure, '^(running|stopped)$') + validate_bool($service_manage) + + if ($service_manage) { + if $service_ensure == 'running' { + $ensure_real = 'running' + $enable_real = true + } else { + $ensure_real = 'stopped' + $enable_real = false + } + + service { 'rabbitmq-server': + ensure => $ensure_real, + enable => $enable_real, + hasstatus => true, + hasrestart => true, + name => $service_name, + } + } + +} diff --git a/3rdparty/modules/rabbitmq/metadata.json b/3rdparty/modules/rabbitmq/metadata.json new file mode 100644 index 00000000..945a26f4 --- /dev/null +++ b/3rdparty/modules/rabbitmq/metadata.json @@ -0,0 +1,55 @@ +{ + "name": "puppetlabs-rabbitmq", + "version": "5.2.1", + "author": "puppetlabs", + "summary": "Installs, configures, and manages RabbitMQ.", + "license": "Apache-2.0", + "source": "https://github.com/puppetlabs/puppetlabs-rabbitmq", + "project_page": "https://github.com/puppetlabs/puppetlabs-rabbitmq", + "issues_url": "https://tickets.puppetlabs.com/browse/MODULES", + "operatingsystem_support": [ + { + "operatingsystem": "RedHat", + "operatingsystemrelease": [ + "5", + "6" + ] + }, + { + "operatingsystem": "CentOS", + "operatingsystemrelease": [ + "5", + "6" + ] + }, + { + "operatingsystem": "Debian", + "operatingsystemrelease": [ + "6", + "7" + ] + }, + { + "operatingsystem": "Ubuntu", + "operatingsystemrelease": [ + "12.04", + "14.04" + ] + } + ], + "requirements": [ + { + "name": "pe", + "version_requirement": "3.x" + }, + { + "name": "puppet", + "version_requirement": "3.x" + } + ], + "dependencies": [ + {"name":"puppetlabs/stdlib","version_requirement":">=3.0.0 <5.0.0"}, + {"name":"puppetlabs/apt","version_requirement":">=1.8.0 <2.0.0"}, + {"name":"nanliu/staging","version_requirement":">=0.3.1 <2.0.0"} + ] +} diff --git a/3rdparty/modules/rabbitmq/spec/README.markdown b/3rdparty/modules/rabbitmq/spec/README.markdown new file mode 100644 index 00000000..286d3417 --- /dev/null +++ b/3rdparty/modules/rabbitmq/spec/README.markdown @@ -0,0 +1,7 @@ +Specs +===== + +The Puppet project uses RSpec for testing. + +For more information on RSpec, see http://rspec.info/ + diff --git a/3rdparty/modules/rabbitmq/spec/acceptance/class_spec.rb b/3rdparty/modules/rabbitmq/spec/acceptance/class_spec.rb new file mode 100644 index 00000000..b5b50cd3 --- /dev/null +++ b/3rdparty/modules/rabbitmq/spec/acceptance/class_spec.rb @@ -0,0 +1,95 @@ +require 'spec_helper_acceptance' + +describe 'rabbitmq class:' do + case fact('osfamily') + when 'RedHat' + package_name = 'rabbitmq-server' + service_name = 'rabbitmq-server' + when 'SUSE' + package_name = 'rabbitmq-server' + service_name = 'rabbitmq-server' + when 'Debian' + package_name = 'rabbitmq-server' + service_name = 'rabbitmq-server' + when 'Archlinux' + package_name = 'rabbitmq' + service_name = 'rabbitmq' + end + + context "default class inclusion" do + it 'should run successfully' do + pp = <<-EOS + class { 'rabbitmq': } + if $::osfamily == 'RedHat' { + class { 'erlang': epel_enable => true} + Class['erlang'] -> Class['rabbitmq'] + } + EOS + + # Apply twice to ensure no errors the second time. + apply_manifest(pp, :catch_failures => true) + expect(apply_manifest(pp, :catch_changes => true).exit_code).to be_zero + end + + describe package(package_name) do + it { should be_installed } + end + + describe service(service_name) do + it { should be_enabled } + it { should be_running } + end + end + + context "disable and stop service" do + it 'should run successfully' do + pp = <<-EOS + class { 'rabbitmq': + service_ensure => 'stopped', + } + if $::osfamily == 'RedHat' { + class { 'erlang': epel_enable => true} + Class['erlang'] -> Class['rabbitmq'] + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + describe service(service_name) do + it { should_not be_enabled } + it { should_not be_running } + end + end + + context "service is unmanaged" do + it 'should run successfully' do + pp_pre = <<-EOS + class { 'rabbitmq': } + if $::osfamily == 'RedHat' { + class { 'erlang': epel_enable => true} + Class['erlang'] -> Class['rabbitmq'] + } + EOS + + pp = <<-EOS + class { 'rabbitmq': + service_manage => false, + service_ensure => 'stopped', + } + if $::osfamily == 'RedHat' { + class { 'erlang': epel_enable => true} + Class['erlang'] -> Class['rabbitmq'] + } + EOS + + apply_manifest(pp_pre, :catch_failures => true) + apply_manifest(pp, :catch_failures => true) + end + + describe service(service_name) do + it { should be_enabled } + it { should be_running } + end + end +end diff --git a/3rdparty/modules/rabbitmq/spec/acceptance/clustering_spec.rb b/3rdparty/modules/rabbitmq/spec/acceptance/clustering_spec.rb new file mode 100644 index 00000000..438c65ba --- /dev/null +++ b/3rdparty/modules/rabbitmq/spec/acceptance/clustering_spec.rb @@ -0,0 +1,60 @@ +require 'spec_helper_acceptance' + +describe 'rabbitmq clustering' do + context 'rabbitmq::wipe_db_on_cookie_change => false' do + it 'should run successfully' do + pp = <<-EOS + class { 'rabbitmq': + config_cluster => true, + cluster_nodes => ['rabbit1', 'rabbit2'], + cluster_node_type => 'ram', + erlang_cookie => 'TESTCOOKIE', + wipe_db_on_cookie_change => false, + } + if $::osfamily == 'RedHat' { + class { 'erlang': epel_enable => true} + Class['erlang'] -> Class['rabbitmq'] + } + EOS + + apply_manifest(pp, :expect_failures => true) + end + + describe file('/var/lib/rabbitmq/.erlang.cookie') do + it { should_not contain 'TESTCOOKIE' } + end + + end + context 'rabbitmq::wipe_db_on_cookie_change => true' do + it 'should run successfully' do + pp = <<-EOS + class { 'rabbitmq': + config_cluster => true, + cluster_nodes => ['rabbit1', 'rabbit2'], + cluster_node_type => 'ram', + erlang_cookie => 'TESTCOOKIE', + wipe_db_on_cookie_change => true, + } + if $::osfamily == 'RedHat' { + class { 'erlang': epel_enable => true} + Class['erlang'] -> Class['rabbitmq'] + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + describe file('/etc/rabbitmq/rabbitmq.config') do + it { should be_file } + it { should contain 'cluster_nodes' } + it { should contain 'rabbit@rabbit1' } + it { should contain 'rabbit@rabbit2' } + it { should contain 'ram' } + end + + describe file('/var/lib/rabbitmq/.erlang.cookie') do + it { should be_file } + it { should contain 'TESTCOOKIE' } + end + end +end diff --git a/3rdparty/modules/rabbitmq/spec/acceptance/delete_guest_user_spec.rb b/3rdparty/modules/rabbitmq/spec/acceptance/delete_guest_user_spec.rb new file mode 100644 index 00000000..d480e884 --- /dev/null +++ b/3rdparty/modules/rabbitmq/spec/acceptance/delete_guest_user_spec.rb @@ -0,0 +1,26 @@ +require 'spec_helper_acceptance' + +describe 'rabbitmq with delete_guest_user' do + context 'delete_guest_user' do + it 'should run successfully' do + pp = <<-EOS + class { 'rabbitmq': + port => '5672', + delete_guest_user => true, + } + if $::osfamily == 'RedHat' { + class { 'erlang': epel_enable => true} + Class['erlang'] -> Class['rabbitmq'] + } + EOS + + apply_manifest(pp, :catch_failures => true) + shell('rabbitmqctl list_users > /tmp/rabbitmqctl_users') + end + + describe file('/tmp/rabbitmqctl_users') do + it { should be_file } + it { should_not contain 'guest' } + end + end +end diff --git a/3rdparty/modules/rabbitmq/spec/acceptance/nodesets/centos-59-x64.yml b/3rdparty/modules/rabbitmq/spec/acceptance/nodesets/centos-59-x64.yml new file mode 100644 index 00000000..2ad90b86 --- /dev/null +++ b/3rdparty/modules/rabbitmq/spec/acceptance/nodesets/centos-59-x64.yml @@ -0,0 +1,10 @@ +HOSTS: + centos-59-x64: + roles: + - master + platform: el-5-x86_64 + box : centos-59-x64-vbox4210-nocm + box_url : http://puppet-vagrant-boxes.puppetlabs.com/centos-59-x64-vbox4210-nocm.box + hypervisor : vagrant +CONFIG: + type: git diff --git a/3rdparty/modules/rabbitmq/spec/acceptance/nodesets/centos-6-x64-vcloud.yml b/3rdparty/modules/rabbitmq/spec/acceptance/nodesets/centos-6-x64-vcloud.yml new file mode 100644 index 00000000..9c5a3d0e --- /dev/null +++ b/3rdparty/modules/rabbitmq/spec/acceptance/nodesets/centos-6-x64-vcloud.yml @@ -0,0 +1,16 @@ +HOSTS: + 'centos-6-vcloud': + roles: + - master + platform: el-6-x86_64 + hypervisor: vcloud + template: centos-6-x86_64 +CONFIG: + type: foss + ssh: + keys: "~/.ssh/id_rsa-acceptance" + datastore: instance0 + folder: Delivery/Quality Assurance/Enterprise/Dynamic + resourcepool: delivery/Quality Assurance/Enterprise/Dynamic + pooling_api: http://vcloud.delivery.puppetlabs.net/ + diff --git a/3rdparty/modules/rabbitmq/spec/acceptance/nodesets/centos-64-x64-pe.yml b/3rdparty/modules/rabbitmq/spec/acceptance/nodesets/centos-64-x64-pe.yml new file mode 100644 index 00000000..7d9242f1 --- /dev/null +++ b/3rdparty/modules/rabbitmq/spec/acceptance/nodesets/centos-64-x64-pe.yml @@ -0,0 +1,12 @@ +HOSTS: + centos-64-x64: + roles: + - master + - database + - dashboard + platform: el-6-x86_64 + box : centos-64-x64-vbox4210-nocm + box_url : http://puppet-vagrant-boxes.puppetlabs.com/centos-64-x64-vbox4210-nocm.box + hypervisor : vagrant +CONFIG: + type: pe diff --git a/3rdparty/modules/rabbitmq/spec/acceptance/nodesets/centos-65-x64.yml b/3rdparty/modules/rabbitmq/spec/acceptance/nodesets/centos-65-x64.yml new file mode 100644 index 00000000..4e2cb809 --- /dev/null +++ b/3rdparty/modules/rabbitmq/spec/acceptance/nodesets/centos-65-x64.yml @@ -0,0 +1,10 @@ +HOSTS: + centos-65-x64: + roles: + - master + platform: el-6-x86_64 + box : centos-65-x64-vbox436-nocm + box_url : http://puppet-vagrant-boxes.puppetlabs.com/centos-65-x64-virtualbox-nocm.box + hypervisor : vagrant +CONFIG: + type: foss diff --git a/3rdparty/modules/rabbitmq/spec/acceptance/nodesets/debian-7-x64-vcloud.yml b/3rdparty/modules/rabbitmq/spec/acceptance/nodesets/debian-7-x64-vcloud.yml new file mode 100644 index 00000000..6082c189 --- /dev/null +++ b/3rdparty/modules/rabbitmq/spec/acceptance/nodesets/debian-7-x64-vcloud.yml @@ -0,0 +1,16 @@ +HOSTS: + 'debian-7-vcloud': + roles: + - master + platform: debian-7-x86_64 + hypervisor: vcloud + template: debian-7-x86_64 +CONFIG: + type: foss + ssh: + keys: "~/.ssh/id_rsa-acceptance" + datastore: instance0 + folder: Delivery/Quality Assurance/Enterprise/Dynamic + resourcepool: delivery/Quality Assurance/Enterprise/Dynamic + pooling_api: http://vcloud.delivery.puppetlabs.net/ + diff --git a/3rdparty/modules/rabbitmq/spec/acceptance/nodesets/default.yml b/3rdparty/modules/rabbitmq/spec/acceptance/nodesets/default.yml new file mode 100644 index 00000000..ce47212a --- /dev/null +++ b/3rdparty/modules/rabbitmq/spec/acceptance/nodesets/default.yml @@ -0,0 +1,11 @@ +HOSTS: + centos-64-x64: + roles: + - master + platform: el-6-x86_64 + box : centos-64-x64-vbox4210-nocm + box_url : http://puppet-vagrant-boxes.puppetlabs.com/centos-64-x64-vbox4210-nocm.box + hypervisor : vagrant +CONFIG: + log_level: debug + type: git diff --git a/3rdparty/modules/rabbitmq/spec/acceptance/nodesets/ubuntu-server-10044-x64.yml b/3rdparty/modules/rabbitmq/spec/acceptance/nodesets/ubuntu-server-10044-x64.yml new file mode 100644 index 00000000..5ca1514e --- /dev/null +++ b/3rdparty/modules/rabbitmq/spec/acceptance/nodesets/ubuntu-server-10044-x64.yml @@ -0,0 +1,10 @@ +HOSTS: + ubuntu-server-10044-x64: + roles: + - master + platform: ubuntu-10.04-amd64 + box : ubuntu-server-10044-x64-vbox4210-nocm + box_url : http://puppet-vagrant-boxes.puppetlabs.com/ubuntu-server-10044-x64-vbox4210-nocm.box + hypervisor : vagrant +CONFIG: + type: foss diff --git a/3rdparty/modules/rabbitmq/spec/acceptance/nodesets/ubuntu-server-12042-x64.yml b/3rdparty/modules/rabbitmq/spec/acceptance/nodesets/ubuntu-server-12042-x64.yml new file mode 100644 index 00000000..d065b304 --- /dev/null +++ b/3rdparty/modules/rabbitmq/spec/acceptance/nodesets/ubuntu-server-12042-x64.yml @@ -0,0 +1,10 @@ +HOSTS: + ubuntu-server-12042-x64: + roles: + - master + platform: ubuntu-12.04-amd64 + box : ubuntu-server-12042-x64-vbox4210-nocm + box_url : http://puppet-vagrant-boxes.puppetlabs.com/ubuntu-server-12042-x64-vbox4210-nocm.box + hypervisor : vagrant +CONFIG: + type: foss diff --git a/3rdparty/modules/rabbitmq/spec/acceptance/nodesets/ubuntu-server-1404-x64.yml b/3rdparty/modules/rabbitmq/spec/acceptance/nodesets/ubuntu-server-1404-x64.yml new file mode 100644 index 00000000..cba1cd04 --- /dev/null +++ b/3rdparty/modules/rabbitmq/spec/acceptance/nodesets/ubuntu-server-1404-x64.yml @@ -0,0 +1,11 @@ +HOSTS: + ubuntu-server-1404-x64: + roles: + - master + platform: ubuntu-14.04-amd64 + box : puppetlabs/ubuntu-14.04-64-nocm + box_url : https://vagrantcloud.com/puppetlabs/ubuntu-14.04-64-nocm + hypervisor : vagrant +CONFIG: + log_level : debug + type: git diff --git a/3rdparty/modules/rabbitmq/spec/acceptance/policy_spec.rb b/3rdparty/modules/rabbitmq/spec/acceptance/policy_spec.rb new file mode 100644 index 00000000..26858ecc --- /dev/null +++ b/3rdparty/modules/rabbitmq/spec/acceptance/policy_spec.rb @@ -0,0 +1,47 @@ +require 'spec_helper_acceptance' + +describe 'rabbitmq policy on a vhost:' do + + + context "create policy resource" do + it 'should run successfully' do + pp = <<-EOS + if $::osfamily == 'RedHat' { + class { 'erlang': epel_enable => true } + Class['erlang'] -> Class['::rabbitmq'] + } + class { '::rabbitmq': + service_manage => true, + port => '5672', + delete_guest_user => true, + admin_enable => true, + } -> + + rabbitmq_vhost { 'myhost': + ensure => present, + } -> + + rabbitmq_policy { 'ha-all@myhost': + pattern => '.*', + priority => 0, + applyto => 'all', + definition => { + 'ha-mode' => 'all', + 'ha-sync-mode' => 'automatic', + }, + } + EOS + + apply_manifest(pp, :catch_failures => true) + apply_manifest(pp, :catch_changes => true) + end + + it 'should have the policy' do + shell('rabbitmqctl list_policies -p myhost') do |r| + expect(r.stdout).to match(/myhost.*ha-all.*ha-sync-mode/) + expect(r.exit_code).to be_zero + end + end + + end +end diff --git a/3rdparty/modules/rabbitmq/spec/acceptance/queue_spec.rb b/3rdparty/modules/rabbitmq/spec/acceptance/queue_spec.rb new file mode 100644 index 00000000..a1643a63 --- /dev/null +++ b/3rdparty/modules/rabbitmq/spec/acceptance/queue_spec.rb @@ -0,0 +1,157 @@ +require 'spec_helper_acceptance' + +describe 'rabbitmq binding:' do + + + context "create binding and queue resources when rabbit using default management port" do + it 'should run successfully' do + pp = <<-EOS + if $::osfamily == 'RedHat' { + class { 'erlang': epel_enable => true } + Class['erlang'] -> Class['::rabbitmq'] + } + class { '::rabbitmq': + service_manage => true, + port => '5672', + delete_guest_user => true, + admin_enable => true, + } -> + + rabbitmq_user { 'dan': + admin => true, + password => 'bar', + tags => ['monitoring', 'tag1'], + } -> + + rabbitmq_user_permissions { 'dan@host1': + configure_permission => '.*', + read_permission => '.*', + write_permission => '.*', + } + + rabbitmq_vhost { 'host1': + ensure => present, + } -> + + rabbitmq_exchange { 'exchange1@host1': + user => 'dan', + password => 'bar', + type => 'topic', + ensure => present, + } -> + + rabbitmq_queue { 'queue1@host1': + user => 'dan', + password => 'bar', + durable => true, + auto_delete => false, + ensure => present, + } -> + + rabbitmq_binding { 'exchange1@queue1@host1': + user => 'dan', + password => 'bar', + destination_type => 'queue', + routing_key => '#', + ensure => present, + } + + EOS + + apply_manifest(pp, :catch_failures => true) + apply_manifest(pp, :catch_changes => true) + end + + it 'should have the binding' do + shell('rabbitmqctl list_bindings -q -p host1') do |r| + expect(r.stdout).to match(/exchange1\sexchange\squeue1\squeue\s#/) + expect(r.exit_code).to be_zero + end + end + + it 'should have the queue' do + shell('rabbitmqctl list_queues -q -p host1') do |r| + expect(r.stdout).to match(/queue1/) + expect(r.exit_code).to be_zero + end + end + + end + + context "create binding and queue resources when rabbit using a non-default management port" do + it 'should run successfully' do + pp = <<-EOS + if $::osfamily == 'RedHat' { + class { 'erlang': epel_enable => true } + Class['erlang'] -> Class['::rabbitmq'] + } + class { '::rabbitmq': + service_manage => true, + port => '5672', + management_port => '11111', + delete_guest_user => true, + admin_enable => true, + } -> + + rabbitmq_user { 'dan': + admin => true, + password => 'bar', + tags => ['monitoring', 'tag1'], + } -> + + rabbitmq_user_permissions { 'dan@host2': + configure_permission => '.*', + read_permission => '.*', + write_permission => '.*', + } + + rabbitmq_vhost { 'host2': + ensure => present, + } -> + + rabbitmq_exchange { 'exchange2@host2': + user => 'dan', + password => 'bar', + type => 'topic', + ensure => present, + } -> + + rabbitmq_queue { 'queue2@host2': + user => 'dan', + password => 'bar', + durable => true, + auto_delete => false, + ensure => present, + } -> + + rabbitmq_binding { 'exchange2@queue2@host2': + user => 'dan', + password => 'bar', + destination_type => 'queue', + routing_key => '#', + ensure => present, + } + + EOS + + apply_manifest(pp, :catch_failures => true) + apply_manifest(pp, :catch_changes => true) + end + + it 'should have the binding' do + shell('rabbitmqctl list_bindings -q -p host2') do |r| + expect(r.stdout).to match(/exchange2\sexchange\squeue2\squeue\s#/) + expect(r.exit_code).to be_zero + end + end + + it 'should have the queue' do + shell('rabbitmqctl list_queues -q -p host2') do |r| + expect(r.stdout).to match(/queue2/) + expect(r.exit_code).to be_zero + end + end + + end + +end diff --git a/3rdparty/modules/rabbitmq/spec/acceptance/rabbitmqadmin_spec.rb b/3rdparty/modules/rabbitmq/spec/acceptance/rabbitmqadmin_spec.rb new file mode 100644 index 00000000..e9d619cd --- /dev/null +++ b/3rdparty/modules/rabbitmq/spec/acceptance/rabbitmqadmin_spec.rb @@ -0,0 +1,85 @@ +require 'spec_helper_acceptance' + +describe 'rabbitmq::install::rabbitmqadmin class' do + context 'does nothing if service is unmanaged' do + it 'should run successfully' do + pp = <<-EOS + class { 'rabbitmq': + admin_enable => true, + service_manage => false, + } + if $::osfamily == 'RedHat' { + class { 'erlang': epel_enable => true} + Class['erlang'] -> Class['rabbitmq'] + } + EOS + + shell('rm -f /var/lib/rabbitmq/rabbitmqadmin') + apply_manifest(pp, :catch_failures => true) + end + + describe file('/var/lib/rabbitmq/rabbitmqadmin') do + it { should_not be_file } + end + end + + context 'downloads the cli tools' do + it 'should run successfully' do + pp = <<-EOS + class { 'rabbitmq': + admin_enable => true, + service_manage => true, + } + if $::osfamily == 'RedHat' { + class { 'erlang': epel_enable => true} + Class['erlang'] -> Class['rabbitmq'] + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + describe file('/var/lib/rabbitmq/rabbitmqadmin') do + it { should be_file } + end + end + + context 'works with specified default credentials' do + it 'should run successfully' do + # make sure credential change takes effect before admin_enable + pp_pre = <<-EOS + class { 'rabbitmq': + service_manage => true, + default_user => 'foobar', + default_pass => 'bazblam', + } + if $::osfamily == 'RedHat' { + class { 'erlang': epel_enable => true} + Class['erlang'] -> Class['rabbitmq'] + } + EOS + + pp = <<-EOS + class { 'rabbitmq': + admin_enable => true, + service_manage => true, + default_user => 'foobar', + default_pass => 'bazblam', + } + if $::osfamily == 'RedHat' { + class { 'erlang': epel_enable => true} + Class['erlang'] -> Class['rabbitmq'] + } + EOS + + shell('rm -f /var/lib/rabbitmq/rabbitmqadmin') + apply_manifest(pp_pre, :catch_failures => true) + apply_manifest(pp, :catch_failures => true) + end + + describe file('/var/lib/rabbitmq/rabbitmqadmin') do + it { should be_file } + end + end + +end diff --git a/3rdparty/modules/rabbitmq/spec/acceptance/server_spec.rb b/3rdparty/modules/rabbitmq/spec/acceptance/server_spec.rb new file mode 100644 index 00000000..d99f995d --- /dev/null +++ b/3rdparty/modules/rabbitmq/spec/acceptance/server_spec.rb @@ -0,0 +1,96 @@ +require 'spec_helper_acceptance' + +describe 'rabbitmq server:' do + case fact('osfamily') + when 'RedHat' + package_name = 'rabbitmq-server' + service_name = 'rabbitmq-server' + when 'SUSE' + package_name = 'rabbitmq-server' + service_name = 'rabbitmq-server' + when 'Debian' + package_name = 'rabbitmq-server' + service_name = 'rabbitmq-server' + when 'Archlinux' + package_name = 'rabbitmq' + service_name = 'rabbitmq' + end + + context "default class inclusion" do + it 'should run successfully' do + pp = <<-EOS + class { 'rabbitmq::server': } + if $::osfamily == 'RedHat' { + class { 'erlang': epel_enable => true} + Class['erlang'] -> Class['rabbitmq::server'] + } + EOS + + # Apply twice to ensure no errors the second time. + apply_manifest(pp, :catch_failures => true) + expect(apply_manifest(pp, :catch_changes => true).exit_code).to be_zero + end + + describe package(package_name) do + it { should be_installed } + end + + describe service(service_name) do + it { should be_enabled } + it { should be_running } + end + end + + context "disable and stop service" do + it 'should run successfully' do + pp = <<-EOS + class { 'rabbitmq::server': + service_ensure => 'stopped', + } + if $::osfamily == 'RedHat' { + class { 'erlang': epel_enable => true} + Class['erlang'] -> Class['rabbitmq::server'] + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + describe service(service_name) do + it { should_not be_enabled } + it { should_not be_running } + end + end + + context "service is unmanaged" do + it 'should run successfully' do + pp_pre = <<-EOS + class { 'rabbitmq::server': } + if $::osfamily == 'RedHat' { + class { 'erlang': epel_enable => true} + Class['erlang'] -> Class['rabbitmq::server'] + } + EOS + + pp = <<-EOS + class { 'rabbitmq::server': + service_manage => false, + service_ensure => 'stopped', + } + if $::osfamily == 'RedHat' { + class { 'erlang': epel_enable => true} + Class['erlang'] -> Class['rabbitmq::server'] + } + EOS + + + apply_manifest(pp_pre, :catch_failures => true) + apply_manifest(pp, :catch_failures => true) + end + + describe service(service_name) do + it { should be_enabled } + it { should be_running } + end + end +end diff --git a/3rdparty/modules/rabbitmq/spec/acceptance/user_spec.rb b/3rdparty/modules/rabbitmq/spec/acceptance/user_spec.rb new file mode 100644 index 00000000..6aab665a --- /dev/null +++ b/3rdparty/modules/rabbitmq/spec/acceptance/user_spec.rb @@ -0,0 +1,38 @@ +require 'spec_helper_acceptance' + +describe 'rabbitmq user:' do + + + context "create user resource" do + it 'should run successfully' do + pp = <<-EOS + if $::osfamily == 'RedHat' { + class { 'erlang': epel_enable => true } + Class['erlang'] -> Class['::rabbitmq'] + } + class { '::rabbitmq': + service_manage => true, + port => '5672', + delete_guest_user => true, + admin_enable => true, + } -> + + rabbitmq_user { 'dan': + admin => true, + password => 'bar', + } + EOS + + apply_manifest(pp, :catch_failures => true) + apply_manifest(pp, :catch_changes => true) + end + + it 'should have the user' do + shell('rabbitmqctl list_users') do |r| + expect(r.stdout).to match(/dan.*administrator/) + expect(r.exit_code).to be_zero + end + end + + end +end diff --git a/3rdparty/modules/rabbitmq/spec/acceptance/vhost_spec.rb b/3rdparty/modules/rabbitmq/spec/acceptance/vhost_spec.rb new file mode 100644 index 00000000..ef1c2a34 --- /dev/null +++ b/3rdparty/modules/rabbitmq/spec/acceptance/vhost_spec.rb @@ -0,0 +1,37 @@ +require 'spec_helper_acceptance' + +describe 'rabbitmq vhost:' do + + + context "create vhost resource" do + it 'should run successfully' do + pp = <<-EOS + if $::osfamily == 'RedHat' { + class { 'erlang': epel_enable => true } + Class['erlang'] -> Class['::rabbitmq'] + } + class { '::rabbitmq': + service_manage => true, + port => '5672', + delete_guest_user => true, + admin_enable => true, + } -> + + rabbitmq_vhost { 'myhost': + ensure => present, + } + EOS + + apply_manifest(pp, :catch_failures => true) + apply_manifest(pp, :catch_changes => true) + end + + it 'should have the vhost' do + shell('rabbitmqctl list_vhosts') do |r| + expect(r.stdout).to match(/myhost/) + expect(r.exit_code).to be_zero + end + end + + end +end diff --git a/3rdparty/modules/rabbitmq/spec/acceptance/zz281_spec.rb b/3rdparty/modules/rabbitmq/spec/acceptance/zz281_spec.rb new file mode 100644 index 00000000..05e5ef40 --- /dev/null +++ b/3rdparty/modules/rabbitmq/spec/acceptance/zz281_spec.rb @@ -0,0 +1,213 @@ +require 'spec_helper_acceptance' +# +# beacuse of some serious issues with upgrading and downgrading rabbitmq on RedHat, +# we need to run all of the 2.8.1 tests last. +# +# NOTE that this is only tested on RedHat and probably only works there. But I can't seem +# to get 'confine' to work... +# + +describe 'rabbitmq class with 2.8.1:' do + case fact('osfamily') + when 'RedHat' + package_name = 'rabbitmq-server' + service_name = 'rabbitmq-server' + package_source = "http://www.rabbitmq.com/releases/rabbitmq-server/v2.8.1/rabbitmq-server-2.8.1-1.noarch.rpm" + package_ensure = '2.8.1-1' + when 'SUSE' + package_name = 'rabbitmq-server' + service_name = 'rabbitmq-server' + package_source = "http://www.rabbitmq.com/releases/rabbitmq-server/v2.8.1/rabbitmq-server-2.8.1-1.noarch.rpm" + package_ensure = '2.8.1-1' + when 'Debian' + package_name = 'rabbitmq-server' + service_name = 'rabbitmq-server' + package_source = '' + package_ensure = '2.8.1' + when 'Archlinux' + package_name = 'rabbitmq' + service_name = 'rabbitmq' + package_source = '' + package_ensure = '2.8.1' + end + + context "default class inclusion" do + it 'should run successfully' do + pp = <<-EOS + class { 'rabbitmq': + version => '2.8.1-1', + package_source => '#{package_source}', + package_ensure => '#{package_ensure}', + package_provider => 'rpm', + management_port => '55672', + } + if $::osfamily == 'RedHat' { + class { 'erlang': epel_enable => true} + Class['erlang'] -> Class['rabbitmq'] + } + EOS + + # clean up previous 3.x install - can't be ungraded cleanly via RPM + shell('service rabbitmq-server stop') + shell('yum -y erase rabbitmq-server') + shell('rm -Rf /var/lib/rabbitmq/mnesia /etc/rabbitmq /var/lib/rabbitmq/rabbitmqadmin') + # Apply twice to ensure no errors the second time. + apply_manifest(pp, :catch_failures => true) + expect(apply_manifest(pp, :catch_changes => true).exit_code).to be_zero + # DEBUG + shell('netstat -lntp') + end + + describe command('rabbitmqctl status') do + its(:stdout) { should match /{rabbit,"RabbitMQ","2.8.1"}/ } + end + + describe package(package_name) do + it { should be_installed } + end + + describe service(service_name) do + it { should be_enabled } + it { should be_running } + end + end + + context "disable and stop service" do + it 'should run successfully' do + pp = <<-EOS + class { 'rabbitmq': + version => '2.8.1-1', + package_source => '#{package_source}', + package_ensure => '#{package_ensure}', + package_provider => 'rpm', + management_port => '55672', + service_ensure => 'stopped', + admin_enable => false, + } + if $::osfamily == 'RedHat' { + class { 'erlang': epel_enable => true} + Class['erlang'] -> Class['rabbitmq'] + } + EOS + + apply_manifest(pp, :catch_failures => true) + end + + describe service(service_name) do + it { should_not be_enabled } + it { should_not be_running } + end + end + + context "service is unmanaged" do + it 'should run successfully' do + pp_pre = <<-EOS + class { 'rabbitmq': + version => '2.8.1-1', + package_source => '#{package_source}', + package_ensure => '#{package_ensure}', + package_provider => 'rpm', + management_port => '55672', + } + if $::osfamily == 'RedHat' { + class { 'erlang': epel_enable => true} + Class['erlang'] -> Class['rabbitmq'] + } + EOS + + pp = <<-EOS + class { 'rabbitmq': + service_manage => false, + service_ensure => 'stopped', + } + if $::osfamily == 'RedHat' { + class { 'erlang': epel_enable => true} + Class['erlang'] -> Class['rabbitmq'] + } + EOS + + apply_manifest(pp_pre, :catch_failures => true) + apply_manifest(pp, :catch_failures => true) + end + + describe service(service_name) do + it { should be_enabled } + it { should be_running } + end + end + + context 'rabbitmqadmin' do + #confine :to, :platform => 'el-6-x86' + + it 'should run successfully' do + pp = <<-EOS + class { 'rabbitmq': + admin_enable => true, + service_manage => true, + version => '2.8.1-1', + package_source => '#{package_source}', + package_ensure => '#{package_ensure}', + package_provider => 'rpm', + management_port => '55672', + } + if $::osfamily == 'RedHat' { + class { 'erlang': epel_enable => true} + Class['erlang'] -> Class['rabbitmq'] + } + EOS + + shell('rm -f /var/lib/rabbitmq/rabbitmqadmin') + apply_manifest(pp, :catch_failures => true) + end + + # since serverspec (used by beaker-rspec) can only tell present/absent for packages + describe file('/var/lib/rabbitmq/rabbitmqadmin') do + it { should be_file } + end + + describe command('/usr/local/bin/rabbitmqadmin --help') do + its(:exit_status) { should eq 0 } + end + + end + + context 'rabbitmqadmin with specified default credentials' do + + it 'should run successfully' do + # make sure credential change takes effect before admin_enable + pp = <<-EOS + class { 'rabbitmq': + admin_enable => true, + service_manage => true, + version => '2.8.1-1', + package_source => '#{package_source}', + package_ensure => '#{package_ensure}', + package_provider => 'rpm', + management_port => '55672', + default_user => 'foobar', + default_pass => 'bazblam', + } + if $::osfamily == 'RedHat' { + class { 'erlang': epel_enable => true} + Class['erlang'] -> Class['rabbitmq'] + } + EOS + + # next 3 lines - see MODULES-1085 + shell('service rabbitmq-server stop') + shell('rm -Rf /var/lib/rabbitmq/mnesia /var/lib/rabbitmq/rabbitmqadmin') + apply_manifest(pp, :catch_failures => true) + end + + # since serverspec (used by beaker-rspec) can only tell present/absent for packages + describe file('/var/lib/rabbitmq/rabbitmqadmin') do + it { should be_file } + end + + describe command('/usr/local/bin/rabbitmqadmin --help') do + its(:exit_status) { should eq 0 } + end + + end + +end diff --git a/3rdparty/modules/rabbitmq/spec/classes/rabbitmq_spec.rb b/3rdparty/modules/rabbitmq/spec/classes/rabbitmq_spec.rb new file mode 100644 index 00000000..675a8759 --- /dev/null +++ b/3rdparty/modules/rabbitmq/spec/classes/rabbitmq_spec.rb @@ -0,0 +1,1099 @@ +require 'spec_helper' + +describe 'rabbitmq' do + + context 'on unsupported distributions' do + let(:facts) {{ :osfamily => 'Unsupported' }} + + it 'we fail' do + expect { catalogue }.to raise_error(Puppet::Error, /not supported on an Unsupported/) + end + end + + context 'on Debian' do + let(:facts) {{ :osfamily => 'Debian', :lsbdistid => 'Debian', :lsbdistcodename => 'squeeze' }} + it 'includes rabbitmq::repo::apt' do + should contain_class('rabbitmq::repo::apt') + end + + describe 'apt::source default values' do + it 'should add a repo with defaults values' do + should contain_apt__source('rabbitmq').with( { + :ensure => 'present', + :location => 'http://www.rabbitmq.com/debian/', + :release => 'testing', + :repos => 'main', + }) + end + end + end + + context 'on Debian' do + let(:params) {{ :manage_repos => false }} + let(:facts) {{ :osfamily => 'Debian', :lsbdistid => 'Debian', :lsbdistcodename => 'squeeze' }} + it 'does ensure rabbitmq apt::source is absent when manage_repos is false' do + should_not contain_apt__source('rabbitmq') + end + end + + context 'on Debian' do + let(:params) {{ :manage_repos => true }} + let(:facts) {{ :osfamily => 'Debian', :lsbdistid => 'Debian', :lsbdistcodename => 'squeeze' }} + + it 'includes rabbitmq::repo::apt' do + should contain_class('rabbitmq::repo::apt') + end + + describe 'apt::source default values' do + it 'should add a repo with defaults values' do + should contain_apt__source('rabbitmq').with( { + :ensure => 'present', + :location => 'http://www.rabbitmq.com/debian/', + :release => 'testing', + :repos => 'main', + }) + end + end + end + + context 'on Debian' do + let(:params) {{ :repos_ensure => false }} + let(:facts) {{ :osfamily => 'Debian', :lsbdistid => 'Debian', :lsbdistcodename => 'squeeze' }} + it 'does ensure rabbitmq apt::source is absent when repos_ensure is false' do + should contain_apt__source('rabbitmq').with( + 'ensure' => 'absent' + ) + end + end + + context 'on Debian' do + let(:params) {{ :repos_ensure => true }} + let(:facts) {{ :osfamily => 'Debian', :lsbdistid => 'Debian', :lsbdistcodename => 'squeeze' }} + + it 'includes rabbitmq::repo::apt' do + should contain_class('rabbitmq::repo::apt') + end + + describe 'apt::source default values' do + it 'should add a repo with defaults values' do + should contain_apt__source('rabbitmq').with( { + :ensure => 'present', + :location => 'http://www.rabbitmq.com/debian/', + :release => 'testing', + :repos => 'main', + }) + end + end + end + + context 'on Debian' do + let(:params) {{ :manage_repos => true, :repos_ensure => false }} + let(:facts) {{ :osfamily => 'Debian', :lsbdistid => 'Debian', :lsbdistcodename => 'squeeze' }} + + it 'includes rabbitmq::repo::apt' do + should contain_class('rabbitmq::repo::apt') + end + + describe 'apt::source default values' do + it 'should add a repo with defaults values' do + should contain_apt__source('rabbitmq').with( { + :ensure => 'absent', + }) + end + end + end + + context 'on Debian' do + let(:params) {{ :manage_repos => true, :repos_ensure => true }} + let(:facts) {{ :osfamily => 'Debian', :lsbdistid => 'Debian', :lsbdistcodename => 'squeeze' }} + + it 'includes rabbitmq::repo::apt' do + should contain_class('rabbitmq::repo::apt') + end + + describe 'apt::source default values' do + it 'should add a repo with defaults values' do + should contain_apt__source('rabbitmq').with( { + :ensure => 'present', + :location => 'http://www.rabbitmq.com/debian/', + :release => 'testing', + :repos => 'main', + }) + end + end + end + + context 'on Debian' do + let(:params) {{ :manage_repos => false, :repos_ensure => true }} + let(:facts) {{ :osfamily => 'Debian', :lsbdistid => 'Debian', :lsbdistcodename => 'squeeze' }} + it 'does ensure rabbitmq apt::source is absent when manage_repos is false and repos_ensure is true' do + should_not contain_apt__source('rabbitmq') + end + end + + context 'on Debian' do + let(:facts) {{ :osfamily => 'Debian', :lsbdistid => 'Debian', :lsbdistcodename => 'squeeze' }} + context 'with manage_repos => false and repos_ensure => false' do + let(:params) {{ :manage_repos => false, :repos_ensure => false }} + it 'does ensure rabbitmq apt::source is absent when manage_repos is false and repos_ensure is false' do + should_not contain_apt__source('rabbitmq') + end + end + + context 'with file_limit => unlimited' do + let(:params) {{ :file_limit => 'unlimited' }} + it { should contain_file('/etc/default/rabbitmq-server').with_content(/ulimit -n unlimited/) } + end + + context 'with file_limit => infinity' do + let(:params) {{ :file_limit => 'infinity' }} + it { should contain_file('/etc/default/rabbitmq-server').with_content(/ulimit -n infinity/) } + end + + context 'with file_limit => -1' do + let(:params) {{ :file_limit => -1 }} + it { should contain_file('/etc/default/rabbitmq-server').with_content(/ulimit -n -1/) } + end + + context 'with file_limit => \'1234\'' do + let(:params) {{ :file_limit => '1234' }} + it { should contain_file('/etc/default/rabbitmq-server').with_content(/ulimit -n 1234/) } + end + + context 'with file_limit => foo' do + let(:params) {{ :file_limit => 'foo' }} + it 'does not compile' do + expect { catalogue }.to raise_error(Puppet::Error, /\$file_limit must be an integer, 'unlimited', or 'infinity'/) + end + end + end + + context 'on Redhat' do + let(:facts) {{ :osfamily => 'RedHat' }} + it 'includes rabbitmq::repo::rhel' do + should contain_class('rabbitmq::repo::rhel') + should contain_exec('rpm --import http://www.rabbitmq.com/rabbitmq-signing-key-public.asc') + end + end + + context 'on Redhat' do + let(:params) {{ :repos_ensure => false }} + let(:facts) {{ :osfamily => 'RedHat' }} + it 'does not import repo public key when repos_ensure is false' do + should contain_class('rabbitmq::repo::rhel') + should_not contain_exec('rpm --import http://www.rabbitmq.com/rabbitmq-signing-key-public.asc') + end + end + + context 'on Redhat' do + let(:params) {{ :repos_ensure => true }} + let(:facts) {{ :osfamily => 'RedHat' }} + it 'does import repo public key when repos_ensure is true' do + should contain_class('rabbitmq::repo::rhel') + should contain_exec('rpm --import http://www.rabbitmq.com/rabbitmq-signing-key-public.asc') + end + end + + context 'on Redhat' do + let(:params) {{ :manage_repos => false }} + let(:facts) {{ :osfamily => 'RedHat' }} + it 'does not import repo public key when manage_repos is false' do + should_not contain_class('rabbitmq::repo::rhel') + should_not contain_exec('rpm --import http://www.rabbitmq.com/rabbitmq-signing-key-public.asc') + end + end + + context 'on Redhat' do + let(:params) {{ :manage_repos => true }} + let(:facts) {{ :osfamily => 'RedHat' }} + it 'does import repo public key when manage_repos is true' do + should contain_class('rabbitmq::repo::rhel') + should contain_exec('rpm --import http://www.rabbitmq.com/rabbitmq-signing-key-public.asc') + end + end + + context 'on Redhat' do + let(:params) {{ :manage_repos => false, :repos_ensure => true }} + let(:facts) {{ :osfamily => 'RedHat' }} + it 'does not import repo public key when manage_repos is false and repos_ensure is true' do + should_not contain_class('rabbitmq::repo::rhel') + should_not contain_exec('rpm --import http://www.rabbitmq.com/rabbitmq-signing-key-public.asc') + end + end + + context 'on Redhat' do + let(:params) {{ :manage_repos => true, :repos_ensure => true }} + let(:facts) {{ :osfamily => 'RedHat' }} + it 'does import repo public key when manage_repos is true and repos_ensure is true' do + should contain_class('rabbitmq::repo::rhel') + should contain_exec('rpm --import http://www.rabbitmq.com/rabbitmq-signing-key-public.asc') + end + end + + context 'on Redhat' do + let(:params) {{ :manage_repos => false, :repos_ensure => false }} + let(:facts) {{ :osfamily => 'RedHat' }} + it 'does not import repo public key when manage_repos is false and repos_ensure is false' do + should_not contain_class('rabbitmq::repo::rhel') + should_not contain_exec('rpm --import http://www.rabbitmq.com/rabbitmq-signing-key-public.asc') + end + end + + context 'on Redhat' do + let(:params) {{ :manage_repos => true, :repos_ensure => false }} + let(:facts) {{ :osfamily => 'RedHat' }} + it 'does not import repo public key when manage_repos is true and repos_ensure is false' do + should contain_class('rabbitmq::repo::rhel') + should_not contain_exec('rpm --import http://www.rabbitmq.com/rabbitmq-signing-key-public.asc') + end + end + + context 'on RedHat 7.0 or more' do + let(:facts) {{ :osfamily => 'RedHat', :operatingsystemmajrelease => '7' }} + + it { should contain_file('/etc/systemd/system/rabbitmq-server.service.d').with( + 'ensure' => 'directory', + 'owner' => '0', + 'group' => '0', + 'mode' => '0755', + 'selinux_ignore_defaults' => true + ) } + + it { should contain_exec('rabbitmq-systemd-reload').with( + 'command' => '/usr/bin/systemctl daemon-reload', + 'notify' => 'Class[Rabbitmq::Service]', + 'refreshonly' => true + ) } + context 'with file_limit => unlimited' do + let(:params) {{ :file_limit => 'unlimited' }} + it { should contain_file('/etc/systemd/system/rabbitmq-server.service.d/limits.conf').with( + 'owner' => '0', + 'group' => '0', + 'mode' => '0644', + 'notify' => 'Exec[rabbitmq-systemd-reload]', + 'content' => '[Service] +LimitNOFILE=unlimited +' + ) } + end + + context 'with file_limit => infinity' do + let(:params) {{ :file_limit => 'infinity' }} + it { should contain_file('/etc/systemd/system/rabbitmq-server.service.d/limits.conf').with( + 'owner' => '0', + 'group' => '0', + 'mode' => '0644', + 'notify' => 'Exec[rabbitmq-systemd-reload]', + 'content' => '[Service] +LimitNOFILE=infinity +' + ) } + end + + context 'with file_limit => -1' do + let(:params) {{ :file_limit => -1 }} + it { should contain_file('/etc/systemd/system/rabbitmq-server.service.d/limits.conf').with( + 'owner' => '0', + 'group' => '0', + 'mode' => '0644', + 'notify' => 'Exec[rabbitmq-systemd-reload]', + 'content' => '[Service] +LimitNOFILE=-1 +' + ) } + end + + context 'with file_limit => \'1234\'' do + let(:params) {{ :file_limit => '1234' }} + it { should contain_file('/etc/systemd/system/rabbitmq-server.service.d/limits.conf').with( + 'owner' => '0', + 'group' => '0', + 'mode' => '0644', + 'notify' => 'Exec[rabbitmq-systemd-reload]', + 'content' => '[Service] +LimitNOFILE=1234 +' + ) } + end + + context 'with file_limit => foo' do + let(:params) {{ :file_limit => 'foo' }} + it 'does not compile' do + expect { catalogue }.to raise_error(Puppet::Error, /\$file_limit must be an integer, 'unlimited', or 'infinity'/) + end + end + end + + context 'on RedHat before 7.0' do + let(:facts) {{ :osfamily => 'RedHat', :operatingsystemmajrelease => '6' }} + + context 'with file_limit => unlimited' do + let(:params) {{ :file_limit => 'unlimited' }} + it { should contain_file('/etc/security/limits.d/rabbitmq-server.conf').with( + 'owner' => '0', + 'group' => '0', + 'mode' => '0644', + 'notify' => 'Class[Rabbitmq::Service]', + 'content' => 'rabbitmq soft nofile unlimited +rabbitmq hard nofile unlimited +' + ) } + end + + context 'with file_limit => infinity' do + let(:params) {{ :file_limit => 'infinity' }} + it { should contain_file('/etc/security/limits.d/rabbitmq-server.conf').with( + 'owner' => '0', + 'group' => '0', + 'mode' => '0644', + 'notify' => 'Class[Rabbitmq::Service]', + 'content' => 'rabbitmq soft nofile infinity +rabbitmq hard nofile infinity +' + ) } + end + + context 'with file_limit => -1' do + let(:params) {{ :file_limit => -1 }} + it { should contain_file('/etc/security/limits.d/rabbitmq-server.conf').with( + 'owner' => '0', + 'group' => '0', + 'mode' => '0644', + 'notify' => 'Class[Rabbitmq::Service]', + 'content' => 'rabbitmq soft nofile -1 +rabbitmq hard nofile -1 +' + ) } + end + + context 'with file_limit => \'1234\'' do + let(:params) {{ :file_limit => '1234' }} + it { should contain_file('/etc/security/limits.d/rabbitmq-server.conf').with( + 'owner' => '0', + 'group' => '0', + 'mode' => '0644', + 'notify' => 'Class[Rabbitmq::Service]', + 'content' => 'rabbitmq soft nofile 1234 +rabbitmq hard nofile 1234 +' + ) } + end + + context 'with file_limit => foo' do + let(:params) {{ :file_limit => 'foo' }} + it 'does not compile' do + expect { catalogue }.to raise_error(Puppet::Error, /\$file_limit must be an integer, 'unlimited', or 'infinity'/) + end + end + end + + ['Debian', 'RedHat', 'SUSE', 'Archlinux'].each do |distro| + context "on #{distro}" do + let(:facts) {{ + :osfamily => distro, + :lsbdistcodename => 'squeeze', + :lsbdistid => 'Debian' + }} + + it { should contain_class('rabbitmq::install') } + it { should contain_class('rabbitmq::config') } + it { should contain_class('rabbitmq::service') } + + context 'with admin_enable set to true' do + let(:params) {{ :admin_enable => true }} + context 'with service_manage set to true' do + it 'we enable the admin interface by default' do + should contain_class('rabbitmq::install::rabbitmqadmin') + should contain_rabbitmq_plugin('rabbitmq_management').with( + 'require' => 'Class[Rabbitmq::Install]', + 'notify' => 'Class[Rabbitmq::Service]' + ) + should contain_staging__file('rabbitmqadmin').with_source("http://guest:guest@localhost:15672/cli/rabbitmqadmin") + end + end + context 'with service_manage set to true and default user/pass specified' do + let(:params) {{ :admin_enable => true, :default_user => 'foobar', :default_pass => 'hunter2' }} + it 'we use the correct URL to rabbitmqadmin' do + should contain_staging__file('rabbitmqadmin').with_source("http://foobar:hunter2@localhost:15672/cli/rabbitmqadmin") + end + end + context 'with service_manage set to true and management port specified' do + # note that the 2.x management port is 55672 not 15672 + let(:params) {{ :admin_enable => true, :management_port => '55672' }} + it 'we use the correct URL to rabbitmqadmin' do + should contain_staging__file('rabbitmqadmin').with_source("http://guest:guest@localhost:55672/cli/rabbitmqadmin") + end + end + context 'with service_manage set to false' do + let(:params) {{ :admin_enable => true, :service_manage => false }} + it 'should do nothing' do + should_not contain_class('rabbitmq::install::rabbitmqadmin') + should_not contain_rabbitmq_plugin('rabbitmq_management') + end + end + end + + describe 'manages configuration directory correctly' do + it { should contain_file('/etc/rabbitmq').with( + 'ensure' => 'directory' + )} + end + + describe 'manages configuration file correctly' do + it { should contain_file('rabbitmq.config') } + end + + context 'configures config_cluster' do + let(:facts) {{ :osfamily => distro, :lsbdistid => 'Debian' }} + let(:params) {{ + :config_cluster => true, + :cluster_nodes => ['hare-1', 'hare-2'], + :cluster_node_type => 'ram', + :wipe_db_on_cookie_change => false + }} + + describe 'with defaults' do + it 'fails' do + expect { catalogue }.to raise_error(Puppet::Error, /You must set the \$erlang_cookie value/) + end + end + + describe 'with erlang_cookie set' do + let(:params) {{ + :config_cluster => true, + :cluster_nodes => ['hare-1', 'hare-2'], + :cluster_node_type => 'ram', + :erlang_cookie => 'TESTCOOKIE', + :wipe_db_on_cookie_change => true + }} + it 'contains the rabbitmq_erlang_cookie' do + should contain_rabbitmq_erlang_cookie('/var/lib/rabbitmq/.erlang.cookie') + end + end + + describe 'and sets appropriate configuration' do + let(:params) {{ + :config_cluster => true, + :cluster_nodes => ['hare-1', 'hare-2'], + :cluster_node_type => 'ram', + :erlang_cookie => 'ORIGINAL', + :wipe_db_on_cookie_change => true + }} + it 'for cluster_nodes' do + should contain_file('rabbitmq.config').with({ + 'content' => /cluster_nodes.*\['rabbit@hare-1', 'rabbit@hare-2'\], ram/, + }) + end + + end + end + + describe 'rabbitmq-env configuration' do + let(:params) {{ :environment_variables => { + 'NODE_IP_ADDRESS' => '1.1.1.1', + 'NODE_PORT' => '5656', + 'NODENAME' => 'HOSTNAME', + 'SERVICENAME' => 'RabbitMQ', + 'CONSOLE_LOG' => 'RabbitMQ.debug', + 'CTL_ERL_ARGS' => 'verbose', + 'SERVER_ERL_ARGS' => 'v', + 'SERVER_START_ARGS' => 'debug' + }}} + it 'should set environment variables' do + should contain_file('rabbitmq-env.config') \ + .with_content(/NODE_IP_ADDRESS=1.1.1.1/) \ + .with_content(/NODE_PORT=5656/) \ + .with_content(/NODENAME=HOSTNAME/) \ + .with_content(/SERVICENAME=RabbitMQ/) \ + .with_content(/CONSOLE_LOG=RabbitMQ.debug/) \ + .with_content(/CTL_ERL_ARGS=verbose/) \ + .with_content(/SERVER_ERL_ARGS=v/) \ + .with_content(/SERVER_START_ARGS=debug/) + end + end + + context 'delete_guest_user' do + describe 'should do nothing by default' do + it { should_not contain_rabbitmq_user('guest') } + end + + describe 'delete user when delete_guest_user set' do + let(:params) {{ :delete_guest_user => true }} + it 'removes the user' do + should contain_rabbitmq_user('guest').with( + 'ensure' => 'absent', + 'provider' => 'rabbitmqctl' + ) + end + end + end + + context 'configuration setting' do + describe 'node_ip_address when set' do + let(:params) {{ :node_ip_address => '172.0.0.1' }} + it 'should set NODE_IP_ADDRESS to specified value' do + should contain_file('rabbitmq-env.config'). + with_content(%r{NODE_IP_ADDRESS=172\.0\.0\.1}) + end + end + + describe 'stomp by default' do + it 'should not specify stomp parameters in rabbitmq.config' do + should contain_file('rabbitmq.config').without({ + 'content' => /stomp/,}) + end + end + describe 'stomp when set' do + let(:params) {{ :config_stomp => true, :stomp_port => 5679 }} + it 'should specify stomp port in rabbitmq.config' do + should contain_file('rabbitmq.config').with({ + 'content' => /rabbitmq_stomp.*tcp_listeners, \[5679\]/m, + }) + end + end + describe 'stomp when set ssl port w/o ssl enabled' do + let(:params) {{ :config_stomp => true, :stomp_port => 5679, :ssl => false, :ssl_stomp_port => 5680 }} + it 'should not configure ssl_listeners in rabbitmq.config' do + should contain_file('rabbitmq.config').without({ + 'content' => /rabbitmq_stomp.*ssl_listeners, \[5680\]/m, + }) + end + end + describe 'stomp when set with ssl' do + let(:params) {{ :config_stomp => true, :stomp_port => 5679, :ssl => true, :ssl_stomp_port => 5680 }} + it 'should specify stomp port and ssl stomp port in rabbitmq.config' do + should contain_file('rabbitmq.config').with({ + 'content' => /rabbitmq_stomp.*tcp_listeners, \[5679\].*ssl_listeners, \[5680\]/m, + }) + end + end + end + + describe 'configuring ldap authentication' do + let :params do + { :config_stomp => true, + :ldap_auth => true, + :ldap_server => 'ldap.example.com', + :ldap_user_dn_pattern => 'ou=users,dc=example,dc=com', + :ldap_other_bind => 'as_user', + :ldap_use_ssl => false, + :ldap_port => '389', + :ldap_log => true, + :ldap_config_variables => { 'foo' => 'bar' } + } + end + + it { should contain_rabbitmq_plugin('rabbitmq_auth_backend_ldap') } + + it 'should contain ldap parameters' do + verify_contents(catalogue, 'rabbitmq.config', + ['[', ' {rabbit, [', ' {auth_backends, [rabbit_auth_backend_internal, rabbit_auth_backend_ldap]},', ' ]}', + ' {rabbitmq_auth_backend_ldap, [', ' {other_bind, as_user},', + ' {servers, ["ldap.example.com"]},', + ' {user_dn_pattern, "ou=users,dc=example,dc=com"},', ' {use_ssl, false},', + ' {port, 389},', ' {foo, bar},', ' {log, true}']) + end + end + + describe 'configuring ldap authentication' do + let :params do + { :config_stomp => false, + :ldap_auth => true, + :ldap_server => 'ldap.example.com', + :ldap_user_dn_pattern => 'ou=users,dc=example,dc=com', + :ldap_other_bind => 'as_user', + :ldap_use_ssl => false, + :ldap_port => '389', + :ldap_log => true, + :ldap_config_variables => { 'foo' => 'bar' } + } + end + + it { should contain_rabbitmq_plugin('rabbitmq_auth_backend_ldap') } + + it 'should contain ldap parameters' do + verify_contents(catalogue, 'rabbitmq.config', + ['[', ' {rabbit, [', ' {auth_backends, [rabbit_auth_backend_internal, rabbit_auth_backend_ldap]},', ' ]}', + ' {rabbitmq_auth_backend_ldap, [', ' {other_bind, as_user},', + ' {servers, ["ldap.example.com"]},', + ' {user_dn_pattern, "ou=users,dc=example,dc=com"},', ' {use_ssl, false},', + ' {port, 389},', ' {foo, bar},', ' {log, true}']) + end + end + + describe 'default_user and default_pass set' do + let(:params) {{ :default_user => 'foo', :default_pass => 'bar' }} + it 'should set default_user and default_pass to specified values' do + should contain_file('rabbitmq.config').with({ + 'content' => /default_user, <<"foo">>.*default_pass, <<"bar">>/m, + }) + end + end + + describe 'interfaces option with no ssl' do + let(:params) { + { :interface => '0.0.0.0', + } } + + it 'should set ssl options to specified values' do + should contain_file('rabbitmq.config').with_content(%r{tcp_listeners, \[\{"0.0.0.0", 5672\}\]}) + end + end + + describe 'ssl options' do + let(:params) { + { :ssl => true, + :ssl_port => 3141, + :ssl_cacert => '/path/to/cacert', + :ssl_cert => '/path/to/cert', + :ssl_key => '/path/to/key' + } } + + it 'should set ssl options to specified values' do + should contain_file('rabbitmq.config').with_content( + %r{ssl_listeners, \[3141\]} + ) + should contain_file('rabbitmq.config').with_content( + %r{ssl_options, \[} + ) + should contain_file('rabbitmq.config').with_content( + %r{cacertfile,"/path/to/cacert"} + ) + should contain_file('rabbitmq.config').with_content( + %r{certfile,"/path/to/cert"} + ) + should contain_file('rabbitmq.config').with_content( + %r{keyfile,"/path/to/key"} + ) + end + end + + + describe 'ssl options with ssl_interfaces' do + let(:params) { + { :ssl => true, + :ssl_port => 3141, + :ssl_interface => '0.0.0.0', + :ssl_cacert => '/path/to/cacert', + :ssl_cert => '/path/to/cert', + :ssl_key => '/path/to/key' + } } + + it 'should set ssl options to specified values' do + should contain_file('rabbitmq.config').with_content(%r{ssl_listeners, \[\{"0.0.0.0", 3141\}\]}) + should contain_file('rabbitmq.config').with_content(%r{cacertfile,"/path/to/cacert"}) + should contain_file('rabbitmq.config').with_content(%r{certfile,"/path/to/cert"}) + should contain_file('rabbitmq.config').with_content(%r{keyfile,"/path/to/key}) + end + end + + + + describe 'ssl options with ssl_only' do + let(:params) { + { :ssl => true, + :ssl_only => true, + :ssl_port => 3141, + :ssl_cacert => '/path/to/cacert', + :ssl_cert => '/path/to/cert', + :ssl_key => '/path/to/key' + } } + + it 'should set ssl options to specified values' do + should contain_file('rabbitmq.config').with_content(%r{tcp_listeners, \[\]}) + should contain_file('rabbitmq.config').with_content(%r{ssl_listeners, \[3141\]}) + should contain_file('rabbitmq.config').with_content(%r{ssl_options, \[}) + should contain_file('rabbitmq.config').with_content(%r{cacertfile,"/path/to/cacert"}) + should contain_file('rabbitmq.config').with_content(%r{certfile,"/path/to/cert"}) + should contain_file('rabbitmq.config').with_content(%r{keyfile,"/path/to/key}) + end + end + + describe 'ssl options with ssl_only and ssl_interfaces' do + let(:params) { + { :ssl => true, + :ssl_only => true, + :ssl_port => 3141, + :ssl_interface => '0.0.0.0', + :ssl_cacert => '/path/to/cacert', + :ssl_cert => '/path/to/cert', + :ssl_key => '/path/to/key' + } } + + it 'should set ssl options to specified values' do + should contain_file('rabbitmq.config').with_content(%r{tcp_listeners, \[\]}) + should contain_file('rabbitmq.config').with_content(%r{ssl_listeners, \[\{"0.0.0.0", 3141\}\]}) + should contain_file('rabbitmq.config').with_content(%r{cacertfile,"/path/to/cacert"}) + should contain_file('rabbitmq.config').with_content(%r{certfile,"/path/to/cert"}) + should contain_file('rabbitmq.config').with_content(%r{keyfile,"/path/to/key}) + end + end + + describe 'ssl options with specific ssl versions' do + let(:params) { + { :ssl => true, + :ssl_port => 3141, + :ssl_cacert => '/path/to/cacert', + :ssl_cert => '/path/to/cert', + :ssl_key => '/path/to/key', + :ssl_versions => ['tlsv1.2', 'tlsv1.1'] + } } + + it 'should set ssl options to specified values' do + should contain_file('rabbitmq.config').with_content(%r{ssl_listeners, \[3141\]}) + should contain_file('rabbitmq.config').with_content(%r{ssl_options, \[}) + should contain_file('rabbitmq.config').with_content(%r{cacertfile,"/path/to/cacert"}) + should contain_file('rabbitmq.config').with_content(%r{certfile,"/path/to/cert"}) + should contain_file('rabbitmq.config').with_content(%r{keyfile,"/path/to/key}) + should contain_file('rabbitmq.config').with_content(%r{ssl, \[\{versions, \['tlsv1.1', 'tlsv1.2'\]\}\]}) + should contain_file('rabbitmq.config').with_content(%r{versions, \['tlsv1.1', 'tlsv1.2'\]}) + end + end + + describe 'ssl options with invalid ssl_versions type' do + let(:params) { + { :ssl => true, + :ssl_port => 3141, + :ssl_cacert => '/path/to/cacert', + :ssl_cert => '/path/to/cert', + :ssl_key => '/path/to/key', + :ssl_versions => 'tlsv1.2, tlsv1.1' + } } + + it 'fails' do + expect { catalogue }.to raise_error(Puppet::Error, /is not an Array/) + end + end + + describe 'ssl options with ssl_versions and not ssl' do + let(:params) { + { :ssl => false, + :ssl_port => 3141, + :ssl_cacert => '/path/to/cacert', + :ssl_cert => '/path/to/cert', + :ssl_key => '/path/to/key', + :ssl_versions => ['tlsv1.2', 'tlsv1.1'] + } } + + it 'fails' do + expect { catalogue }.to raise_error(Puppet::Error, /\$ssl_versions requires that \$ssl => true/) + end + end + + describe 'ssl options with ssl ciphers' do + let(:params) { + { :ssl => true, + :ssl_port => 3141, + :ssl_cacert => '/path/to/cacert', + :ssl_cert => '/path/to/cert', + :ssl_key => '/path/to/key', + :ssl_ciphers => ['ecdhe_rsa,aes_256_cbc,sha', 'dhe_rsa,aes_256_cbc,sha'] + } } + + it 'should set ssl ciphers to specified values' do + should contain_file('rabbitmq.config').with_content(%r{ciphers,\[[[:space:]]+{dhe_rsa,aes_256_cbc,sha},[[:space:]]+{ecdhe_rsa,aes_256_cbc,sha}[[:space:]]+\]}) + end + end + + describe 'ssl admin options with specific ssl versions' do + let(:params) { + { :ssl => true, + :ssl_management_port => 5926, + :ssl_cacert => '/path/to/cacert', + :ssl_cert => '/path/to/cert', + :ssl_key => '/path/to/key', + :ssl_versions => ['tlsv1.2', 'tlsv1.1'], + :admin_enable => true + } } + + it 'should set admin ssl opts to specified values' do + should contain_file('rabbitmq.config').with_content(%r{rabbitmq_management, \[}) + should contain_file('rabbitmq.config').with_content(%r{listener, \[}) + should contain_file('rabbitmq.config').with_content(%r{port, 5926\}}) + should contain_file('rabbitmq.config').with_content(%r{ssl, true\}}) + should contain_file('rabbitmq.config').with_content(%r{ssl_opts, \[\{cacertfile, "/path/to/cacert"\},}) + should contain_file('rabbitmq.config').with_content(%r{certfile, "/path/to/cert"\},}) + should contain_file('rabbitmq.config').with_content(%r{keyfile, "/path/to/key"\}}) + should contain_file('rabbitmq.config').with_content(%r{,\{versions, \['tlsv1.1', 'tlsv1.2'\]\}[\r\n ]*\]\}}) + end + end + + describe 'ssl admin options' do + let(:params) { + { :ssl => true, + :ssl_management_port => 3141, + :ssl_cacert => '/path/to/cacert', + :ssl_cert => '/path/to/cert', + :ssl_key => '/path/to/key', + :admin_enable => true + } } + + it 'should set rabbitmq_management ssl options to specified values' do + should contain_file('rabbitmq.config').with_content(%r{rabbitmq_management, \[}) + should contain_file('rabbitmq.config').with_content(%r{listener, \[}) + should contain_file('rabbitmq.config').with_content(%r{port, 3141\}}) + should contain_file('rabbitmq.config').with_content(%r{ssl, true\}}) + should contain_file('rabbitmq.config').with_content(%r{ssl_opts, \[\{cacertfile, "/path/to/cacert"\},}) + should contain_file('rabbitmq.config').with_content(%r{certfile, "/path/to/cert"\},}) + should contain_file('rabbitmq.config').with_content(%r{keyfile, "/path/to/key"\}[\r\n ]*\]\}}) + end + end + + describe 'admin without ssl' do + let(:params) { + { :ssl => false, + :management_port => 3141, + :admin_enable => true + } } + + it 'should set rabbitmq_management options to specified values' do + should contain_file('rabbitmq.config').with_content(%r{rabbitmq_management, \[}) + should contain_file('rabbitmq.config').with_content(%r{listener, \[}) + should contain_file('rabbitmq.config').with_content(%r{port, 3141\}}) + end + end + + describe 'ssl admin options' do + let(:params) { + { :ssl => true, + :ssl_management_port => 3141, + :ssl_cacert => '/path/to/cacert', + :ssl_cert => '/path/to/cert', + :ssl_key => '/path/to/key', + :admin_enable => true + } } + + it 'should set rabbitmq_management ssl options to specified values' do + should contain_file('rabbitmq.config').with_content(%r{rabbitmq_management, \[}) + should contain_file('rabbitmq.config').with_content(%r{listener, \[}) + should contain_file('rabbitmq.config').with_content(%r{port, 3141\},}) + should contain_file('rabbitmq.config').with_content(%r{ssl, true\},}) + should contain_file('rabbitmq.config').with_content(%r{ssl_opts, \[\{cacertfile, "/path/to/cacert"\},}) + should contain_file('rabbitmq.config').with_content(%r{certfile, "/path/to/cert"\},}) + should contain_file('rabbitmq.config').with_content(%r{keyfile, "/path/to/key"\}[\r\n ]*\]\}}) + end + end + + describe 'admin without ssl' do + let(:params) { + { :ssl => false, + :management_port => 3141, + :admin_enable => true + } } + + it 'should set rabbitmq_management options to specified values' do + should contain_file('rabbitmq.config') \ + .with_content(/\{rabbitmq_management, \[/) \ + .with_content(/\{listener, \[/) \ + .with_content(/\{port, 3141\}/) + end + end + + describe 'config_variables options' do + let(:params) {{ :config_variables => { + 'hipe_compile' => true, + 'vm_memory_high_watermark' => 0.4, + 'frame_max' => 131072, + 'collect_statistics' => "none", + 'auth_mechanisms' => "['PLAIN', 'AMQPLAIN']", + }}} + it 'should set environment variables' do + should contain_file('rabbitmq.config') \ + .with_content(/\{hipe_compile, true\}/) \ + .with_content(/\{vm_memory_high_watermark, 0.4\}/) \ + .with_content(/\{frame_max, 131072\}/) \ + .with_content(/\{collect_statistics, none\}/) \ + .with_content(/\{auth_mechanisms, \['PLAIN', 'AMQPLAIN'\]\}/) + end + end + + describe 'config_kernel_variables options' do + let(:params) {{ :config_kernel_variables => { + 'inet_dist_listen_min' => 9100, + 'inet_dist_listen_max' => 9105, + }}} + it 'should set config variables' do + should contain_file('rabbitmq.config') \ + .with_content(/\{inet_dist_listen_min, 9100\}/) \ + .with_content(/\{inet_dist_listen_max, 9105\}/) + end + end + + describe 'tcp_keepalive enabled' do + let(:params) {{ :tcp_keepalive => true }} + it 'should set tcp_listen_options keepalive true' do + should contain_file('rabbitmq.config') \ + .with_content(/\{tcp_listen_options, \[\{keepalive, true\}\]\},/) + end + end + + describe 'tcp_keepalive disabled (default)' do + it 'should not set tcp_listen_options' do + should contain_file('rabbitmq.config') \ + .without_content(/\{tcp_listen_options, \[\{keepalive, true\}\]\},/) + end + end + + describe 'non-bool tcp_keepalive parameter' do + let :params do + { :tcp_keepalive => 'string' } + end + + it 'should raise an error' do + expect { + should contain_file('rabbitmq.config') + }.to raise_error(Puppet::Error, /is not a boolean/) + end + end + + context 'delete_guest_user' do + describe 'should do nothing by default' do + it { should_not contain_rabbitmq_user('guest') } + end + + describe 'delete user when delete_guest_user set' do + let(:params) {{ :delete_guest_user => true }} + it 'removes the user' do + should contain_rabbitmq_user('guest').with( + 'ensure' => 'absent', + 'provider' => 'rabbitmqctl' + ) + end + end + end + + ## + ## rabbitmq::service + ## + describe 'service with default params' do + it { should contain_service('rabbitmq-server').with( + 'ensure' => 'running', + 'enable' => 'true', + 'hasstatus' => 'true', + 'hasrestart' => 'true' + )} + end + + describe 'service with ensure stopped' do + let :params do + { :service_ensure => 'stopped' } + end + + it { should contain_service('rabbitmq-server').with( + 'ensure' => 'stopped', + 'enable' => false + ) } + end + + describe 'service with ensure neither running neither stopped' do + let :params do + { :service_ensure => 'foo' } + end + + it 'should raise an error' do + expect { + should contain_service('rabbitmq-server').with( + 'ensure' => 'stopped' ) + }.to raise_error(Puppet::Error, /validate_re\(\): "foo" does not match "\^\(running\|stopped\)\$"/) + end + end + + describe 'service with service_manage equal to false' do + let :params do + { :service_manage => false } + end + + it { should_not contain_service('rabbitmq-server') } + end + + end + end + + ## + ## rabbitmq::install + ## + context "on RHEL" do + let(:facts) {{ :osfamily => 'RedHat' }} + let(:params) {{ :package_source => 'http://www.rabbitmq.com/releases/rabbitmq-server/v3.2.3/rabbitmq-server-3.2.3-1.noarch.rpm' }} + it 'installs the rabbitmq package' do + should contain_package('rabbitmq-server').with( + 'ensure' => 'installed', + 'name' => 'rabbitmq-server', + 'provider' => 'rpm', + 'source' => 'http://www.rabbitmq.com/releases/rabbitmq-server/v3.2.3/rabbitmq-server-3.2.3-1.noarch.rpm' + ) + end + end + + context "on Debian" do + let(:facts) {{ :osfamily => 'Debian', :lsbdistid => 'Debian', :lsbdistcodename => 'precise' }} + it 'installs the rabbitmq package' do + should contain_package('rabbitmq-server').with( + 'ensure' => 'installed', + 'name' => 'rabbitmq-server', + 'provider' => 'apt' + ) + end + end + + context "on Archlinux" do + let(:facts) {{ :osfamily => 'Archlinux' }} + it 'installs the rabbitmq package' do + should contain_package('rabbitmq-server').with( + 'ensure' => 'installed', + 'name' => 'rabbitmq') + end + end + + describe 'repo management on Debian' do + let(:facts) {{ :osfamily => 'Debian', :lsbdistid => 'Debian' }} + + context 'with no pin' do + let(:params) {{ :package_apt_pin => '' }} + describe 'it sets up an apt::source' do + + it { should contain_apt__source('rabbitmq').with( + 'location' => 'http://www.rabbitmq.com/debian/', + 'release' => 'testing', + 'repos' => 'main', + 'include_src' => false, + 'key' => 'F78372A06FF50C80464FC1B4F7B8CEA6056E8E56' + ) } + end + end + + context 'with pin' do + let(:params) {{ :package_apt_pin => '700' }} + describe 'it sets up an apt::source and pin' do + + it { should contain_apt__source('rabbitmq').with( + 'location' => 'http://www.rabbitmq.com/debian/', + 'release' => 'testing', + 'repos' => 'main', + 'include_src' => false, + 'key' => 'F78372A06FF50C80464FC1B4F7B8CEA6056E8E56' + ) } + + it { should contain_apt__pin('rabbitmq').with( + 'packages' => 'rabbitmq-server', + 'priority' => '700' + ) } + + end + end + end + + ['RedHat', 'SuSE'].each do |distro| + describe "repo management on #{distro}" do + describe 'imports the key' do + let(:facts) {{ :osfamily => distro }} + let(:params) {{ :package_gpg_key => 'http://www.rabbitmq.com/rabbitmq-signing-key-public.asc' }} + + it { should contain_exec("rpm --import #{params[:package_gpg_key]}").with( + 'path' => ['/bin','/usr/bin','/sbin','/usr/sbin'] + ) } + end + end + end + +end diff --git a/3rdparty/modules/rabbitmq/spec/spec.opts b/3rdparty/modules/rabbitmq/spec/spec.opts new file mode 100644 index 00000000..91cd6427 --- /dev/null +++ b/3rdparty/modules/rabbitmq/spec/spec.opts @@ -0,0 +1,6 @@ +--format +s +--colour +--loadby +mtime +--backtrace diff --git a/3rdparty/modules/rabbitmq/spec/spec_helper.rb b/3rdparty/modules/rabbitmq/spec/spec_helper.rb new file mode 100644 index 00000000..2c6f5664 --- /dev/null +++ b/3rdparty/modules/rabbitmq/spec/spec_helper.rb @@ -0,0 +1 @@ +require 'puppetlabs_spec_helper/module_spec_helper' diff --git a/3rdparty/modules/rabbitmq/spec/spec_helper_acceptance.rb b/3rdparty/modules/rabbitmq/spec/spec_helper_acceptance.rb new file mode 100644 index 00000000..b82ba36c --- /dev/null +++ b/3rdparty/modules/rabbitmq/spec/spec_helper_acceptance.rb @@ -0,0 +1,38 @@ +require 'beaker-rspec' + +UNSUPPORTED_PLATFORMS = [] + +unless ENV['RS_PROVISION'] == 'no' or ENV['BEAKER_provision'] == 'no' + if hosts.first.is_pe? + install_pe + else + install_puppet + end + hosts.each do |host| + on hosts, "mkdir -p #{host['distmoduledir']}" + end +end + +RSpec.configure do |c| + # Project root + proj_root = File.expand_path(File.join(File.dirname(__FILE__), '..')) + + # Readable test descriptions + c.formatter = :documentation + c.before :suite do + hosts.each do |host| + copy_module_to(host, :source => proj_root, :module_name => 'rabbitmq') + + shell("/bin/touch #{default['puppetpath']}/hiera.yaml") + shell('puppet module install puppetlabs-stdlib', { :acceptable_exit_codes => [0,1] }) + if fact('osfamily') == 'Debian' + shell('puppet module install puppetlabs-apt --version 1.8.0 --force', { :acceptable_exit_codes => [0,1] }) + end + shell('puppet module install nanliu-staging', { :acceptable_exit_codes => [0,1] }) + if fact('osfamily') == 'RedHat' + shell('puppet module install garethr-erlang', { :acceptable_exit_codes => [0,1] }) + end + end + end +end + diff --git a/3rdparty/modules/rabbitmq/spec/unit/puppet/provider/rabbitmq_binding/rabbitmqadmin_spec.rb b/3rdparty/modules/rabbitmq/spec/unit/puppet/provider/rabbitmq_binding/rabbitmqadmin_spec.rb new file mode 100644 index 00000000..e165d557 --- /dev/null +++ b/3rdparty/modules/rabbitmq/spec/unit/puppet/provider/rabbitmq_binding/rabbitmqadmin_spec.rb @@ -0,0 +1,59 @@ +require 'puppet' +require 'mocha/api' +RSpec.configure do |config| + config.mock_with :mocha +end +provider_class = Puppet::Type.type(:rabbitmq_binding).provider(:rabbitmqadmin) +describe provider_class do + before :each do + @resource = Puppet::Type::Rabbitmq_binding.new( + {:name => 'source@target@/', + :destination_type => :queue, + :routing_key => 'blablub', + :arguments => {} + } + ) + @provider = provider_class.new(@resource) + end + + it 'should return instances' do + provider_class.expects(:rabbitmqctl).with('list_vhosts', '-q').returns <<-EOT +/ +EOT + provider_class.expects(:rabbitmqctl).with('list_bindings', '-q', '-p', '/', 'source_name', 'destination_name', 'destination_kind', 'routing_key', 'arguments').returns <<-EOT + queue queue queue [] +EOT + instances = provider_class.instances + instances.size.should == 1 + end + + it 'should call rabbitmqadmin to create' do + @provider.expects(:rabbitmqadmin).with('declare', 'binding', '--vhost=/', '--user=guest', '--password=guest', '-c', '/etc/rabbitmq/rabbitmqadmin.conf', 'source=source', 'destination=target', 'arguments={}', 'routing_key=blablub', 'destination_type=queue') + @provider.create + end + + it 'should call rabbitmqadmin to destroy' do + @provider.expects(:rabbitmqadmin).with('delete', 'binding', '--vhost=/', '--user=guest', '--password=guest', '-c', '/etc/rabbitmq/rabbitmqadmin.conf', 'source=source', 'destination_type=queue', 'destination=target', 'properties_key=blablub') + @provider.destroy + end + + context 'specifying credentials' do + before :each do + @resource = Puppet::Type::Rabbitmq_binding.new( + {:name => 'source@test2@/', + :destination_type => :queue, + :routing_key => 'blablubd', + :arguments => {}, + :user => 'colin', + :password => 'secret' + } + ) + @provider = provider_class.new(@resource) + end + + it 'should call rabbitmqadmin to create' do + @provider.expects(:rabbitmqadmin).with('declare', 'binding', '--vhost=/', '--user=colin', '--password=secret', '-c', '/etc/rabbitmq/rabbitmqadmin.conf', 'source=source', 'destination=test2', 'arguments={}', 'routing_key=blablubd', 'destination_type=queue') + @provider.create + end + end +end diff --git a/3rdparty/modules/rabbitmq/spec/unit/puppet/provider/rabbitmq_exchange/rabbitmqadmin_spec.rb b/3rdparty/modules/rabbitmq/spec/unit/puppet/provider/rabbitmq_exchange/rabbitmqadmin_spec.rb new file mode 100644 index 00000000..10e39acf --- /dev/null +++ b/3rdparty/modules/rabbitmq/spec/unit/puppet/provider/rabbitmq_exchange/rabbitmqadmin_spec.rb @@ -0,0 +1,75 @@ +require 'puppet' +require 'mocha' +RSpec.configure do |config| + config.mock_with :mocha +end +provider_class = Puppet::Type.type(:rabbitmq_exchange).provider(:rabbitmqadmin) +describe provider_class do + before :each do + @resource = Puppet::Type::Rabbitmq_exchange.new( + {:name => 'test.headers@/', + :type => :headers, + :internal => :false, + :durable => :true, + :auto_delete => :false, + :arguments => { + "hash-headers" => "message-distribution-hash" + }, + } + ) + @provider = provider_class.new(@resource) + end + + it 'should return instances' do + provider_class.expects(:rabbitmqctl).with('-q', 'list_vhosts').returns <<-EOT +/ +EOT + provider_class.expects(:rabbitmqctl).with('-q', 'list_exchanges', '-p', '/', 'name', 'type', 'internal', 'durable', 'auto_delete', 'arguments').returns <<-EOT + direct false true false [] +amq.direct direct false true false [] +amq.fanout fanout false true false [] +amq.headers headers false true false [] +amq.match headers false true false [] +amq.rabbitmq.log topic true true false [] +amq.rabbitmq.trace topic true true false [] +amq.topic topic false true false [] +test.headers x-consistent-hash false true false [{"hash-header","message-distribution-hash"}] +EOT + instances = provider_class.instances + instances.size.should == 9 + end + + it 'should call rabbitmqadmin to create as guest' do + @provider.expects(:rabbitmqadmin).with('declare', 'exchange', '--vhost=/', '--user=guest', '--password=guest', 'name=test.headers', 'type=headers', 'internal=false', 'durable=true', 'auto_delete=false', 'arguments={"hash-headers":"message-distribution-hash"}', '-c', '/etc/rabbitmq/rabbitmqadmin.conf') + @provider.create + end + + it 'should call rabbitmqadmin to destroy' do + @provider.expects(:rabbitmqadmin).with('delete', 'exchange', '--vhost=/', '--user=guest', '--password=guest', 'name=test.headers', '-c', '/etc/rabbitmq/rabbitmqadmin.conf') + @provider.destroy + end + + context 'specifying credentials' do + before :each do + @resource = Puppet::Type::Rabbitmq_exchange.new( + {:name => 'test.headers@/', + :type => :headers, + :internal => 'false', + :durable => 'true', + :auto_delete => 'false', + :user => 'colin', + :password => 'secret', + :arguments => { + "hash-header" => "message-distribution-hash" + }, + } + ) + @provider = provider_class.new(@resource) + end + + it 'should call rabbitmqadmin to create with credentials' do + @provider.expects(:rabbitmqadmin).with('declare', 'exchange', '--vhost=/', '--user=colin', '--password=secret', 'name=test.headers', 'type=headers', 'internal=false', 'durable=true', 'auto_delete=false', 'arguments={"hash-header":"message-distribution-hash"}', '-c', '/etc/rabbitmq/rabbitmqadmin.conf') + @provider.create + end + end +end diff --git a/3rdparty/modules/rabbitmq/spec/unit/puppet/provider/rabbitmq_plugin/rabbitmqctl_spec.rb b/3rdparty/modules/rabbitmq/spec/unit/puppet/provider/rabbitmq_plugin/rabbitmqctl_spec.rb new file mode 100644 index 00000000..c398b629 --- /dev/null +++ b/3rdparty/modules/rabbitmq/spec/unit/puppet/provider/rabbitmq_plugin/rabbitmqctl_spec.rb @@ -0,0 +1,26 @@ +require 'puppet' +require 'mocha' +RSpec.configure do |config| + config.mock_with :mocha +end +provider_class = Puppet::Type.type(:rabbitmq_plugin).provider(:rabbitmqplugins) +describe provider_class do + before :each do + @resource = Puppet::Type::Rabbitmq_plugin.new( + {:name => 'foo'} + ) + @provider = provider_class.new(@resource) + end + it 'should match plugins' do + @provider.expects(:rabbitmqplugins).with('list', '-E', '-m').returns("foo\n") + @provider.exists?.should == 'foo' + end + it 'should call rabbitmqplugins to enable' do + @provider.expects(:rabbitmqplugins).with('enable', 'foo') + @provider.create + end + it 'should call rabbitmqplugins to disable' do + @provider.expects(:rabbitmqplugins).with('disable', 'foo') + @provider.destroy + end +end diff --git a/3rdparty/modules/rabbitmq/spec/unit/puppet/provider/rabbitmq_policy/rabbitmqctl_spec.rb b/3rdparty/modules/rabbitmq/spec/unit/puppet/provider/rabbitmq_policy/rabbitmqctl_spec.rb new file mode 100644 index 00000000..cddb6c0c --- /dev/null +++ b/3rdparty/modules/rabbitmq/spec/unit/puppet/provider/rabbitmq_policy/rabbitmqctl_spec.rb @@ -0,0 +1,114 @@ +require 'puppet' +require 'mocha' + +RSpec.configure do |config| + config.mock_with :mocha +end + +describe Puppet::Type.type(:rabbitmq_policy).provider(:rabbitmqctl) do + + let(:resource) do + Puppet::Type.type(:rabbitmq_policy).new( + :name => 'ha-all@/', + :pattern => '.*', + :definition => { + 'ha-mode' => 'all' + }, + :provider => described_class.name + ) + end + + let(:provider) { resource.provider } + + after(:each) do + described_class.instance_variable_set(:@policies, nil) + end + + it 'should accept @ in policy name' do + resource = Puppet::Type.type(:rabbitmq_policy).new( + :name => 'ha@home@/', + :pattern => '.*', + :definition => { + 'ha-mode' => 'all' + }, + :provider => described_class.name + ) + provider = described_class.new(resource) + provider.should_policy.should == 'ha@home' + provider.should_vhost.should == '/' + end + + it 'should fail with invalid output from list' do + provider.class.expects(:rabbitmqctl).with('list_policies', '-q', '-p', '/').returns 'foobar' + expect { provider.exists? }.to raise_error(Puppet::Error, /cannot parse line from list_policies/) + end + + it 'should match policies from list (>=3.2.0)' do + provider.class.expects(:rabbitmqctl).with('list_policies', '-q', '-p', '/').returns <<-EOT +/ ha-all all .* {"ha-mode":"all","ha-sync-mode":"automatic"} 0 +/ test exchanges .* {"ha-mode":"all"} 0 +EOT + provider.exists?.should == { + :applyto => 'all', + :pattern => '.*', + :priority => '0', + :definition => { + 'ha-mode' => 'all', + 'ha-sync-mode' => 'automatic'} + } + end + + it 'should match policies from list (<3.2.0)' do + provider.class.expects(:rabbitmqctl).with('list_policies', '-q', '-p', '/').returns <<-EOT +/ ha-all .* {"ha-mode":"all","ha-sync-mode":"automatic"} 0 +/ test .* {"ha-mode":"all"} 0 +EOT + provider.exists?.should == { + :applyto => 'all', + :pattern => '.*', + :priority => '0', + :definition => { + 'ha-mode' => 'all', + 'ha-sync-mode' => 'automatic'} + } + end + + it 'should not match an empty list' do + provider.class.expects(:rabbitmqctl).with('list_policies', '-q', '-p', '/').returns '' + provider.exists?.should == nil + end + + it 'should destroy policy' do + provider.expects(:rabbitmqctl).with('clear_policy', '-p', '/', 'ha-all') + provider.destroy + end + + it 'should only call set_policy once (<3.2.0)' do + provider.class.expects(:rabbitmq_version).returns '3.1.0' + provider.resource[:priority] = '10' + provider.resource[:applyto] = 'exchanges' + provider.expects(:rabbitmqctl).with('set_policy', + '-p', '/', + 'ha-all', + '.*', + '{"ha-mode":"all"}', + '10').once + provider.priority = '10' + provider.applyto = 'exchanges' + end + + it 'should only call set_policy once (>=3.2.0)' do + provider.class.expects(:rabbitmq_version).returns '3.2.0' + provider.resource[:priority] = '10' + provider.resource[:applyto] = 'exchanges' + provider.expects(:rabbitmqctl).with('set_policy', + '-p', '/', + '--priority', '10', + '--apply-to', 'exchanges', + 'ha-all', + '.*', + '{"ha-mode":"all"}').once + provider.priority = '10' + provider.applyto = 'exchanges' + end +end diff --git a/3rdparty/modules/rabbitmq/spec/unit/puppet/provider/rabbitmq_queue/rabbitmqadmin_spec.rb b/3rdparty/modules/rabbitmq/spec/unit/puppet/provider/rabbitmq_queue/rabbitmqadmin_spec.rb new file mode 100644 index 00000000..85843da8 --- /dev/null +++ b/3rdparty/modules/rabbitmq/spec/unit/puppet/provider/rabbitmq_queue/rabbitmqadmin_spec.rb @@ -0,0 +1,60 @@ +require 'puppet' +require 'mocha/api' +RSpec.configure do |config| + config.mock_with :mocha +end +provider_class = Puppet::Type.type(:rabbitmq_queue).provider(:rabbitmqadmin) +describe provider_class do + before :each do + @resource = Puppet::Type::Rabbitmq_queue.new( + {:name => 'test@/', + :durable => :true, + :auto_delete => :false, + :arguments => {} + } + ) + @provider = provider_class.new(@resource) + end + + it 'should return instances' do + provider_class.expects(:rabbitmqctl).with('list_vhosts', '-q').returns <<-EOT +/ +EOT + provider_class.expects(:rabbitmqctl).with('list_queues', '-q', '-p', '/', 'name', 'durable', 'auto_delete', 'arguments').returns <<-EOT +test true false [] +test2 true false [{"x-message-ttl",342423},{"x-expires",53253232},{"x-max-length",2332},{"x-max-length-bytes",32563324242},{"x-dead-letter-exchange","amq.direct"},{"x-dead-letter-routing-key","test.routing"}] +EOT + instances = provider_class.instances + instances.size.should == 2 + end + + it 'should call rabbitmqadmin to create' do + @provider.expects(:rabbitmqadmin).with('declare', 'queue', '--vhost=/', '--user=guest', '--password=guest', '-c', '/etc/rabbitmq/rabbitmqadmin.conf', 'name=test', 'durable=true', 'auto_delete=false', 'arguments={}') + @provider.create + end + + it 'should call rabbitmqadmin to destroy' do + @provider.expects(:rabbitmqadmin).with('delete', 'queue', '--vhost=/', '--user=guest', '--password=guest', '-c', '/etc/rabbitmq/rabbitmqadmin.conf', 'name=test') + @provider.destroy + end + + context 'specifying credentials' do + before :each do + @resource = Puppet::Type::Rabbitmq_queue.new( + {:name => 'test@/', + :durable => 'true', + :auto_delete => 'false', + :arguments => {}, + :user => 'colin', + :password => 'secret', + } + ) + @provider = provider_class.new(@resource) + end + + it 'should call rabbitmqadmin to create' do + @provider.expects(:rabbitmqadmin).with('declare', 'queue', '--vhost=/', '--user=colin', '--password=secret', '-c', '/etc/rabbitmq/rabbitmqadmin.conf', 'name=test', 'durable=true', 'auto_delete=false', 'arguments={}') + @provider.create + end + end +end diff --git a/3rdparty/modules/rabbitmq/spec/unit/puppet/provider/rabbitmq_user/rabbitmqctl_spec.rb b/3rdparty/modules/rabbitmq/spec/unit/puppet/provider/rabbitmq_user/rabbitmqctl_spec.rb new file mode 100644 index 00000000..ed828ea9 --- /dev/null +++ b/3rdparty/modules/rabbitmq/spec/unit/puppet/provider/rabbitmq_user/rabbitmqctl_spec.rb @@ -0,0 +1,215 @@ +require 'puppet' +require 'mocha' +RSpec.configure do |config| + config.mock_with :mocha +end +provider_class = Puppet::Type.type(:rabbitmq_user).provider(:rabbitmqctl) +describe provider_class do + before :each do + @resource = Puppet::Type::Rabbitmq_user.new( + {:name => 'foo', :password => 'bar'} + ) + @provider = provider_class.new(@resource) + end + it 'should match user names' do + @provider.expects(:rabbitmqctl).with('-q', 'list_users').returns <<-EOT +foo +EOT + @provider.exists?.should == 'foo' + end + it 'should match user names with 2.4.1 syntax' do + @provider.expects(:rabbitmqctl).with('-q', 'list_users').returns <<-EOT +foo bar +EOT + @provider.exists?.should == 'foo bar' + end + it 'should not match if no users on system' do + @provider.expects(:rabbitmqctl).with('-q', 'list_users').returns <<-EOT +EOT + @provider.exists?.should be_nil + end + it 'should not match if no matching users on system' do + @provider.expects(:rabbitmqctl).with('-q', 'list_users').returns <<-EOT +fooey +EOT + @provider.exists?.should be_nil + end + it 'should match user names from list' do + @provider.expects(:rabbitmqctl).with('-q', 'list_users').returns <<-EOT +one +two three +foo +bar +EOT + @provider.exists?.should == 'foo' + end + it 'should create user and set password' do + @resource[:password] = 'bar' + @provider.expects(:rabbitmqctl).with('add_user', 'foo', 'bar') + @provider.create + end + it 'should create user, set password and set to admin' do + @resource[:password] = 'bar' + @resource[:admin] = 'true' + @provider.expects(:rabbitmqctl).with('add_user', 'foo', 'bar') + @provider.expects(:rabbitmqctl).with('-q', 'list_users').returns <<-EOT +foo [] +icinga [monitoring] +kitchen [] +kitchen2 [abc, def, ghi] +EOT + @provider.expects(:rabbitmqctl).with('set_user_tags', 'foo', ['administrator']) + @provider.create + end + it 'should call rabbitmqctl to delete' do + @provider.expects(:rabbitmqctl).with('delete_user', 'foo') + @provider.destroy + end + it 'should be able to retrieve admin value' do + @provider.expects(:rabbitmqctl).with('-q', 'list_users').returns <<-EOT +foo [administrator] +EOT + @provider.admin.should == :true + @provider.expects(:rabbitmqctl).with('-q', 'list_users').returns <<-EOT +one [administrator] +foo [] +EOT + @provider.admin.should == :false + end + it 'should fail if admin value is invalid' do + @provider.expects(:rabbitmqctl).with('-q', 'list_users').returns <<-EOT +foo fail +EOT + expect { @provider.admin }.to raise_error(Puppet::Error, /Could not match line/) + end + it 'should be able to set admin value' do + @provider.expects(:rabbitmqctl).with('-q', 'list_users').returns <<-EOT +foo [] +icinga [monitoring] +kitchen [] +kitchen2 [abc, def, ghi] +EOT + @provider.expects(:rabbitmqctl).with('set_user_tags', 'foo', ['administrator']) + @provider.admin=:true + end + it 'should not interfere with existing tags on the user when setting admin value' do + @provider.expects(:rabbitmqctl).with('-q', 'list_users').returns <<-EOT +foo [bar, baz] +icinga [monitoring] +kitchen [] +kitchen2 [abc, def, ghi] +EOT + @provider.expects(:rabbitmqctl).with('set_user_tags', 'foo', ['bar','baz', 'administrator'].sort) + @provider.admin=:true + end + it 'should be able to unset admin value' do + @provider.expects(:rabbitmqctl).with('-q', 'list_users').returns <<-EOT +foo [administrator] +guest [administrator] +icinga [] +EOT + @provider.expects(:rabbitmqctl).with('set_user_tags', 'foo', []) + @provider.admin=:false + end + it 'should not interfere with existing tags on the user when unsetting admin value' do + @provider.expects(:rabbitmqctl).with('-q', 'list_users').returns <<-EOT +foo [administrator, bar, baz] +icinga [monitoring] +kitchen [] +kitchen2 [abc, def, ghi] +EOT + @provider.expects(:rabbitmqctl).with('set_user_tags', 'foo', ['bar','baz'].sort) + @provider.admin=:false + end + + it 'should clear all tags on existing user' do + @provider.expects(:rabbitmqctl).with('-q', 'list_users').returns <<-EOT +one [administrator] +foo [tag1,tag2] +icinga [monitoring] +kitchen [] +kitchen2 [abc, def, ghi] +EOT + @provider.expects(:rabbitmqctl).with('set_user_tags', 'foo', []) + @provider.tags=[] + end + + it 'should set multiple tags' do + @provider.expects(:rabbitmqctl).with('-q', 'list_users').returns <<-EOT +one [administrator] +foo [] +icinga [monitoring] +kitchen [] +kitchen2 [abc, def, ghi] +EOT + @provider.expects(:rabbitmqctl).with('set_user_tags', 'foo', ['tag1','tag2']) + @provider.tags=['tag1','tag2'] + end + + it 'should clear tags while keep admin tag' do + @resource[:admin] = true + @provider.expects(:rabbitmqctl).with('-q', 'list_users').returns <<-EOT +one [administrator] +foo [administrator, tag1, tag2] +icinga [monitoring] +kitchen [] +kitchen2 [abc, def, ghi] +EOT + @provider.expects(:rabbitmqctl).with('set_user_tags', 'foo', ["administrator"]) + @provider.tags=[] + end + + it 'should change tags while keep admin tag' do + @resource[:admin] = true + @provider.expects(:rabbitmqctl).with('-q', 'list_users').returns <<-EOT +one [administrator] +foo [administrator, tag1, tag2] +icinga [monitoring] +kitchen [] +kitchen2 [abc, def, ghi] +EOT + @provider.expects(:rabbitmqctl).with('set_user_tags', 'foo', ["administrator","tag1","tag3","tag7"]) + @provider.tags=['tag1','tag7','tag3'] + end + + it 'should create user with tags and without admin' do + @resource[:tags] = [ "tag1", "tag2" ] + @provider.expects(:rabbitmqctl).with('add_user', 'foo', 'bar') + @provider.expects(:rabbitmqctl).with('set_user_tags', 'foo', ["tag1","tag2"]) + @provider.expects(:rabbitmqctl).with('-q', 'list_users').returns <<-EOT +foo [] +EOT + @provider.create + end + + it 'should create user with tags and with admin' do + @resource[:tags] = [ "tag1", "tag2" ] + @resource[:admin] = true + @provider.expects(:rabbitmqctl).with('add_user', 'foo', 'bar') + @provider.expects(:rabbitmqctl).with('-q', 'list_users').twice.returns <<-EOT +foo [] +EOT + @provider.expects(:rabbitmqctl).with('set_user_tags', 'foo', ["administrator"]) + @provider.expects(:rabbitmqctl).with('set_user_tags', 'foo', ["administrator","tag1","tag2"]) + @provider.create + end + + it 'should not return the administrator tag in tags for admins' do + @resource[:tags] = [] + @resource[:admin] = true + @provider.expects(:rabbitmqctl).with('-q', 'list_users').returns <<-EOT +foo [administrator] +EOT + @provider.tags.should == [] + end + + it 'should return the administrator tag for non-admins' do + # this should not happen though. + @resource[:tags] = [] + @resource[:admin] = :false + @provider.expects(:rabbitmqctl).with('-q', 'list_users').returns <<-EOT +foo [administrator] +EOT + @provider.tags.should == ["administrator"] + end +end diff --git a/3rdparty/modules/rabbitmq/spec/unit/puppet/provider/rabbitmq_user_permissions/rabbitmqctl_spec.rb b/3rdparty/modules/rabbitmq/spec/unit/puppet/provider/rabbitmq_user_permissions/rabbitmqctl_spec.rb new file mode 100644 index 00000000..bdbc73e8 --- /dev/null +++ b/3rdparty/modules/rabbitmq/spec/unit/puppet/provider/rabbitmq_user_permissions/rabbitmqctl_spec.rb @@ -0,0 +1,93 @@ +require 'puppet' +require 'mocha' +RSpec.configure do |config| + config.mock_with :mocha +end +describe 'Puppet::Type.type(:rabbitmq_user_permissions).provider(:rabbitmqctl)' do + before :each do + @provider_class = Puppet::Type.type(:rabbitmq_user_permissions).provider(:rabbitmqctl) + @resource = Puppet::Type::Rabbitmq_user_permissions.new( + {:name => 'foo@bar'} + ) + @provider = @provider_class.new(@resource) + end + after :each do + @provider_class.instance_variable_set(:@users, nil) + end + it 'should match user permissions from list' do + @provider.class.expects(:rabbitmqctl).with('-q', 'list_user_permissions', 'foo').returns <<-EOT +bar 1 2 3 +EOT + @provider.exists?.should == {:configure=>"1", :write=>"2", :read=>"3"} + end + it 'should match user permissions with empty columns' do + @provider.class.expects(:rabbitmqctl).with('-q', 'list_user_permissions', 'foo').returns <<-EOT +bar 3 +EOT + @provider.exists?.should == {:configure=>"", :write=>"", :read=>"3"} + end + it 'should not match user permissions with more than 3 columns' do + @provider.class.expects(:rabbitmqctl).with('-q', 'list_user_permissions', 'foo').returns <<-EOT +bar 1 2 3 4 +EOT + expect { @provider.exists? }.to raise_error(Puppet::Error, /cannot parse line from list_user_permissions/) + end + it 'should not match an empty list' do + @provider.class.expects(:rabbitmqctl).with('-q', 'list_user_permissions', 'foo').returns <<-EOT +EOT + @provider.exists?.should == nil + end + it 'should create default permissions' do + @provider.instance_variable_set(:@should_vhost, "bar") + @provider.instance_variable_set(:@should_user, "foo") + @provider.expects(:rabbitmqctl).with('set_permissions', '-p', 'bar', 'foo', "''", "''", "''") + @provider.create + end + it 'should destroy permissions' do + @provider.instance_variable_set(:@should_vhost, "bar") + @provider.instance_variable_set(:@should_user, "foo") + @provider.expects(:rabbitmqctl).with('clear_permissions', '-p', 'bar', 'foo') + @provider.destroy + end + {:configure_permission => '1', :write_permission => '2', :read_permission => '3'}.each do |k,v| + it "should be able to retrieve #{k}" do + @provider.class.expects(:rabbitmqctl).with('-q', 'list_user_permissions', 'foo').returns <<-EOT +bar 1 2 3 +EOT + @provider.send(k).should == v + end + end + {:configure_permission => '1', :write_permission => '2', :read_permission => '3'}.each do |k,v| + it "should be able to retrieve #{k} after exists has been called" do + @provider.class.expects(:rabbitmqctl).with('-q', 'list_user_permissions', 'foo').returns <<-EOT +bar 1 2 3 +EOT + @provider.exists? + @provider.send(k).should == v + end + end + {:configure_permission => ['foo', '2', '3'], + :read_permission => ['1', '2', 'foo'], + :write_permission => ['1', 'foo', '3'] + }.each do |perm, columns| + it "should be able to sync #{perm}" do + @provider.class.expects(:rabbitmqctl).with('-q', 'list_user_permissions', 'foo').returns <<-EOT +bar 1 2 3 +EOT + @provider.resource[perm] = 'foo' + @provider.expects(:rabbitmqctl).with('set_permissions', '-p', 'bar', 'foo', *columns) + @provider.send("#{perm}=".to_sym, 'foo') + end + end + it 'should only call set_permissions once' do + @provider.class.expects(:rabbitmqctl).with('-q', 'list_user_permissions', 'foo').returns <<-EOT +bar 1 2 3 +EOT + @provider.resource[:configure_permission] = 'foo' + @provider.resource[:read_permission] = 'foo' + @provider.expects(:rabbitmqctl).with('set_permissions', '-p', 'bar', 'foo', 'foo', '2', 'foo').once + @provider.configure_permission='foo' + @provider.read_permission='foo' + end +end + diff --git a/3rdparty/modules/rabbitmq/spec/unit/puppet/provider/rabbitmq_vhost/rabbitmqctl_spec.rb b/3rdparty/modules/rabbitmq/spec/unit/puppet/provider/rabbitmq_vhost/rabbitmqctl_spec.rb new file mode 100644 index 00000000..6c8cce8d --- /dev/null +++ b/3rdparty/modules/rabbitmq/spec/unit/puppet/provider/rabbitmq_vhost/rabbitmqctl_spec.rb @@ -0,0 +1,45 @@ +require 'puppet' +require 'mocha' +RSpec.configure do |config| + config.mock_with :mocha +end +provider_class = Puppet::Type.type(:rabbitmq_vhost).provider(:rabbitmqctl) +describe provider_class do + before :each do + @resource = Puppet::Type::Rabbitmq_vhost.new( + {:name => 'foo'} + ) + @provider = provider_class.new(@resource) + end + it 'should match vhost names' do + @provider.expects(:rabbitmqctl).with('-q', 'list_vhosts').returns <<-EOT +Listing vhosts ... +foo +...done. +EOT + @provider.exists?.should == 'foo' + end + it 'should not match if no vhosts on system' do + @provider.expects(:rabbitmqctl).with('-q', 'list_vhosts').returns <<-EOT +Listing vhosts ... +...done. +EOT + @provider.exists?.should be_nil + end + it 'should not match if no matching vhosts on system' do + @provider.expects(:rabbitmqctl).with('-q', 'list_vhosts').returns <<-EOT +Listing vhosts ... +fooey +...done. +EOT + @provider.exists?.should be_nil + end + it 'should call rabbitmqctl to create' do + @provider.expects(:rabbitmqctl).with('add_vhost', 'foo') + @provider.create + end + it 'should call rabbitmqctl to create' do + @provider.expects(:rabbitmqctl).with('delete_vhost', 'foo') + @provider.destroy + end +end diff --git a/3rdparty/modules/rabbitmq/spec/unit/puppet/type/rabbitmq_binding_spec.rb b/3rdparty/modules/rabbitmq/spec/unit/puppet/type/rabbitmq_binding_spec.rb new file mode 100644 index 00000000..b0671e7c --- /dev/null +++ b/3rdparty/modules/rabbitmq/spec/unit/puppet/type/rabbitmq_binding_spec.rb @@ -0,0 +1,50 @@ +require 'puppet' +require 'puppet/type/rabbitmq_binding' +describe Puppet::Type.type(:rabbitmq_binding) do + before :each do + @binding = Puppet::Type.type(:rabbitmq_binding).new( + :name => 'foo@blub@bar', + :destination_type => :queue + ) + end + it 'should accept an queue name' do + @binding[:name] = 'dan@dude@pl' + @binding[:name].should == 'dan@dude@pl' + end + it 'should require a name' do + expect { + Puppet::Type.type(:rabbitmq_binding).new({}) + }.to raise_error(Puppet::Error, 'Title or name must be provided') + end + it 'should not allow whitespace in the name' do + expect { + @binding[:name] = 'b r' + }.to raise_error(Puppet::Error, /Valid values match/) + end + it 'should not allow names without one @' do + expect { + @binding[:name] = 'b_r' + }.to raise_error(Puppet::Error, /Valid values match/) + end + + it 'should not allow names without two @' do + expect { + @binding[:name] = 'b@r' + }.to raise_error(Puppet::Error, /Valid values match/) + end + + it 'should accept an binding destination_type' do + @binding[:destination_type] = :exchange + @binding[:destination_type].should == :exchange + end + + it 'should accept a user' do + @binding[:user] = :root + @binding[:user].should == :root + end + + it 'should accept a password' do + @binding[:password] = :PaSsw0rD + @binding[:password].should == :PaSsw0rD + end +end diff --git a/3rdparty/modules/rabbitmq/spec/unit/puppet/type/rabbitmq_exchange_spec.rb b/3rdparty/modules/rabbitmq/spec/unit/puppet/type/rabbitmq_exchange_spec.rb new file mode 100644 index 00000000..1500122c --- /dev/null +++ b/3rdparty/modules/rabbitmq/spec/unit/puppet/type/rabbitmq_exchange_spec.rb @@ -0,0 +1,57 @@ +require 'puppet' +require 'puppet/type/rabbitmq_exchange' +describe Puppet::Type.type(:rabbitmq_exchange) do + before :each do + @exchange = Puppet::Type.type(:rabbitmq_exchange).new( + :name => 'foo@bar', + :type => :topic, + :internal => false, + :auto_delete => false, + :durable => true + ) + end + it 'should accept an exchange name' do + @exchange[:name] = 'dan@pl' + @exchange[:name].should == 'dan@pl' + end + it 'should require a name' do + expect { + Puppet::Type.type(:rabbitmq_exchange).new({}) + }.to raise_error(Puppet::Error, 'Title or name must be provided') + end + it 'should not allow whitespace in the name' do + expect { + @exchange[:name] = 'b r' + }.to raise_error(Puppet::Error, /Valid values match/) + end + it 'should not allow names without @' do + expect { + @exchange[:name] = 'b_r' + }.to raise_error(Puppet::Error, /Valid values match/) + end + + it 'should accept an exchange type' do + @exchange[:type] = :direct + @exchange[:type].should == :direct + end + it 'should require a type' do + expect { + Puppet::Type.type(:rabbitmq_exchange).new(:name => 'foo@bar') + }.to raise_error(/.*must set type when creating exchange.*/) + end + it 'should not require a type when destroying' do + expect { + Puppet::Type.type(:rabbitmq_exchange).new(:name => 'foo@bar', :ensure => :absent) + }.to_not raise_error + end + + it 'should accept a user' do + @exchange[:user] = :root + @exchange[:user].should == :root + end + + it 'should accept a password' do + @exchange[:password] = :PaSsw0rD + @exchange[:password].should == :PaSsw0rD + end +end diff --git a/3rdparty/modules/rabbitmq/spec/unit/puppet/type/rabbitmq_policy_spec.rb b/3rdparty/modules/rabbitmq/spec/unit/puppet/type/rabbitmq_policy_spec.rb new file mode 100644 index 00000000..36bf2a73 --- /dev/null +++ b/3rdparty/modules/rabbitmq/spec/unit/puppet/type/rabbitmq_policy_spec.rb @@ -0,0 +1,119 @@ +require 'puppet' +require 'puppet/type/rabbitmq_policy' + +describe Puppet::Type.type(:rabbitmq_policy) do + + before do + @policy = Puppet::Type.type(:rabbitmq_policy).new( + :name => 'ha-all@/', + :pattern => '.*', + :definition => { + 'ha-mode' => 'all' + }) + end + + it 'should accept a valid name' do + @policy[:name] = 'ha-all@/' + @policy[:name].should == 'ha-all@/' + end + + it 'should require a name' do + expect { + Puppet::Type.type(:rabbitmq_policy).new({}) + }.to raise_error(Puppet::Error, 'Title or name must be provided') + end + + it 'should fail when name does not have a @' do + expect { + @policy[:name] = 'ha-all' + }.to raise_error(Puppet::Error, /Valid values match/) + end + + it 'should accept a valid regex for pattern' do + @policy[:pattern] = '.*?' + @policy[:pattern].should == '.*?' + end + + it 'should accept an empty string for pattern' do + @policy[:pattern] = '' + @policy[:pattern].should == '' + end + + it 'should not accept invalid regex for pattern' do + expect { + @policy[:pattern] = '*' + }.to raise_error(Puppet::Error, /Invalid regexp/) + end + + it 'should accept valid value for applyto' do + [:all, :exchanges, :queues].each do |v| + @policy[:applyto] = v + @policy[:applyto].should == v + end + end + + it 'should not accept invalid value for applyto' do + expect { + @policy[:applyto] = 'me' + }.to raise_error(Puppet::Error, /Invalid value/) + end + + it 'should accept a valid hash for definition' do + definition = {'ha-mode' => 'all', 'ha-sync-mode' => 'automatic'} + @policy[:definition] = definition + @policy[:definition].should == definition + end + + it 'should not accept invalid hash for definition' do + expect { + @policy[:definition] = 'ha-mode' + }.to raise_error(Puppet::Error, /Invalid definition/) + + expect { + @policy[:definition] = {'ha-mode' => ['a', 'b']} + }.to raise_error(Puppet::Error, /Invalid definition/) + end + + it 'should accept valid value for priority' do + [0, 10, '0', '10'].each do |v| + @policy[:priority] = v + @policy[:priority].should == v + end + end + + it 'should not accept invalid value for priority' do + ['-1', -1, '1.0', 1.0, 'abc', ''].each do |v| + expect { + @policy[:priority] = v + }.to raise_error(Puppet::Error, /Invalid value/) + end + end + + it 'should accept and convert ha-params for ha-mode exactly' do + definition = {'ha-mode' => 'exactly', 'ha-params' => '2'} + @policy[:definition] = definition + @policy[:definition]['ha-params'].should be_a(Fixnum) + @policy[:definition]['ha-params'].should == 2 + end + + it 'should not accept non-numeric ha-params for ha-mode exactly' do + definition = {'ha-mode' => 'exactly', 'ha-params' => 'nonnumeric'} + expect { + @policy[:definition] = definition + }.to raise_error(Puppet::Error, /Invalid ha-params.*nonnumeric.*exactly/) + end + + it 'should accept and convert the expires value' do + definition = {'expires' => '1800000'} + @policy[:definition] = definition + @policy[:definition]['expires'].should be_a(Fixnum) + @policy[:definition]['expires'].should == 1800000 + end + + it 'should not accept non-numeric expires value' do + definition = {'expires' => 'future'} + expect { + @policy[:definition] = definition + }.to raise_error(Puppet::Error, /Invalid expires value.*future/) + end +end diff --git a/3rdparty/modules/rabbitmq/spec/unit/puppet/type/rabbitmq_queue_spec.rb b/3rdparty/modules/rabbitmq/spec/unit/puppet/type/rabbitmq_queue_spec.rb new file mode 100644 index 00000000..4fd7b34e --- /dev/null +++ b/3rdparty/modules/rabbitmq/spec/unit/puppet/type/rabbitmq_queue_spec.rb @@ -0,0 +1,60 @@ +require 'puppet' +require 'puppet/type/rabbitmq_queue' +require 'json' +describe Puppet::Type.type(:rabbitmq_queue) do + before :each do + @queue = Puppet::Type.type(:rabbitmq_queue).new( + :name => 'foo@bar', + :durable => :true, + :arguments => { + 'x-message-ttl' => 45, + 'x-dead-letter-exchange' => 'deadexchange' + } + ) + end + it 'should accept an queue name' do + @queue[:name] = 'dan@pl' + @queue[:name].should == 'dan@pl' + end + it 'should require a name' do + expect { + Puppet::Type.type(:rabbitmq_queue).new({}) + }.to raise_error(Puppet::Error, 'Title or name must be provided') + end + it 'should not allow whitespace in the name' do + expect { + @queue[:name] = 'b r' + }.to raise_error(Puppet::Error, /Valid values match/) + end + it 'should not allow names without @' do + expect { + @queue[:name] = 'b_r' + }.to raise_error(Puppet::Error, /Valid values match/) + end + + it 'should accept an arguments with numbers value' do + @queue[:arguments] = {'x-message-ttl' => 30} + @queue[:arguments].to_json.should == "{\"x-message-ttl\":30}" + @queue[:arguments]['x-message-ttl'].should == 30 + end + + it 'should accept an arguments with string value' do + @queue[:arguments] = {'x-dead-letter-exchange' => 'catchallexchange'} + @queue[:arguments].to_json.should == "{\"x-dead-letter-exchange\":\"catchallexchange\"}" + end + + it 'should accept an queue durable' do + @queue[:durable] = :true + @queue[:durable].should == :true + end + + it 'should accept a user' do + @queue[:user] = :root + @queue[:user].should == :root + end + + it 'should accept a password' do + @queue[:password] = :PaSsw0rD + @queue[:password].should == :PaSsw0rD + end +end diff --git a/3rdparty/modules/rabbitmq/spec/unit/puppet/type/rabbitmq_user_permissions_spec.rb b/3rdparty/modules/rabbitmq/spec/unit/puppet/type/rabbitmq_user_permissions_spec.rb new file mode 100644 index 00000000..7cb66eac --- /dev/null +++ b/3rdparty/modules/rabbitmq/spec/unit/puppet/type/rabbitmq_user_permissions_spec.rb @@ -0,0 +1,55 @@ +require 'puppet' +require 'puppet/type/rabbitmq_user_permissions' +describe Puppet::Type.type(:rabbitmq_user_permissions) do + before :each do + @perms = Puppet::Type.type(:rabbitmq_user_permissions).new(:name => 'foo@bar') + end + it 'should accept a valid hostname name' do + @perms[:name] = 'dan@bar' + @perms[:name].should == 'dan@bar' + end + it 'should require a name' do + expect { + Puppet::Type.type(:rabbitmq_user_permissions).new({}) + }.to raise_error(Puppet::Error, 'Title or name must be provided') + end + it 'should fail when names dont have a @' do + expect { + @perms[:name] = 'bar' + }.to raise_error(Puppet::Error, /Valid values match/) + end + [:configure_permission, :read_permission, :write_permission].each do |param| + it 'should not default to anything' do + @perms[param].should == nil + end + it "should accept a valid regex for #{param}" do + @perms[param] = '.*?' + @perms[param].should == '.*?' + end + it "should accept an empty string for #{param}" do + @perms[param] = '' + @perms[param].should == '' + end + it "should not accept invalid regex for #{param}" do + expect { + @perms[param] = '*' + }.to raise_error(Puppet::Error, /Invalid regexp/) + end + end + {:rabbitmq_vhost => 'dan@test', :rabbitmq_user => 'test@dan'}.each do |k,v| + it "should autorequire #{k}" do + if k == :rabbitmq_vhost + vhost = Puppet::Type.type(k).new(:name => "test") + else + vhost = Puppet::Type.type(k).new(:name => "test", :password => 'pass') + end + perm = Puppet::Type.type(:rabbitmq_user_permissions).new(:name => v) + config = Puppet::Resource::Catalog.new :testing do |conf| + [vhost, perm].each { |resource| conf.add_resource resource } + end + rel = perm.autorequire[0] + rel.source.ref.should == vhost.ref + rel.target.ref.should == perm.ref + end + end +end diff --git a/3rdparty/modules/rabbitmq/spec/unit/puppet/type/rabbitmq_user_spec.rb b/3rdparty/modules/rabbitmq/spec/unit/puppet/type/rabbitmq_user_spec.rb new file mode 100644 index 00000000..a73d9c93 --- /dev/null +++ b/3rdparty/modules/rabbitmq/spec/unit/puppet/type/rabbitmq_user_spec.rb @@ -0,0 +1,52 @@ +require 'puppet' +require 'puppet/type/rabbitmq_user' +describe Puppet::Type.type(:rabbitmq_user) do + before :each do + @user = Puppet::Type.type(:rabbitmq_user).new(:name => 'foo', :password => 'pass') + end + it 'should accept a user name' do + @user[:name] = 'dan' + @user[:name].should == 'dan' + @user[:admin].should == :false + end + it 'should accept a password' do + @user[:password] = 'foo' + @user[:password].should == 'foo' + end + it 'should require a password' do + expect { + Puppet::Type.type(:rabbitmq_user).new(:name => 'foo') + }.to raise_error(/must set password/) + end + it 'should require a name' do + expect { + Puppet::Type.type(:rabbitmq_user).new({}) + }.to raise_error(Puppet::Error, 'Title or name must be provided') + end + it 'should not allow whitespace in the name' do + expect { + @user[:name] = 'b r' + }.to raise_error(Puppet::Error, /Valid values match/) + end + [true, false, 'true', 'false'].each do |val| + it "admin property should accept #{val}" do + @user[:admin] = val + @user[:admin].should == val.to_s.to_sym + end + end + it 'should not accept non-boolean values for admin' do + expect { + @user[:admin] = 'yes' + }.to raise_error(Puppet::Error, /Invalid value/) + end + it 'should not accept tags with spaces' do + expect { + @user[:tags] = ['policy maker'] + }.to raise_error(Puppet::Error, /Invalid tag/) + end + it 'should not accept the administrator tag' do + expect { + @user[:tags] = ['administrator'] + }.to raise_error(Puppet::Error, /must use admin property/) + end +end diff --git a/3rdparty/modules/rabbitmq/spec/unit/puppet/type/rabbitmq_vhost_spec.rb b/3rdparty/modules/rabbitmq/spec/unit/puppet/type/rabbitmq_vhost_spec.rb new file mode 100644 index 00000000..70b8e374 --- /dev/null +++ b/3rdparty/modules/rabbitmq/spec/unit/puppet/type/rabbitmq_vhost_spec.rb @@ -0,0 +1,21 @@ +require 'puppet' +require 'puppet/type/rabbitmq_vhost' +describe Puppet::Type.type(:rabbitmq_vhost) do + before :each do + @vhost = Puppet::Type.type(:rabbitmq_vhost).new(:name => 'foo') + end + it 'should accept a vhost name' do + @vhost[:name] = 'dan' + @vhost[:name].should == 'dan' + end + it 'should require a name' do + expect { + Puppet::Type.type(:rabbitmq_vhost).new({}) + }.to raise_error(Puppet::Error, 'Title or name must be provided') + end + it 'should not allow whitespace in the name' do + expect { + @vhost[:name] = 'b r' + }.to raise_error(Puppet::Error, /Valid values match/) + end +end diff --git a/3rdparty/modules/rabbitmq/templates/README.markdown b/3rdparty/modules/rabbitmq/templates/README.markdown new file mode 100644 index 00000000..575bbeae --- /dev/null +++ b/3rdparty/modules/rabbitmq/templates/README.markdown @@ -0,0 +1,23 @@ +Templates +========= + +Puppet supports templates and templating via ERB, which is part of the Ruby +standard library and is used for many other projects including Ruby on Rails. +Templates allow you to manage the content of template files, for example +configuration files that cannot yet be managed as a Puppet type. Learn more at +http://projects.puppetlabs.com/projects/puppet/wiki/Puppet_Templating + +You can use templates like this: + + class myclass { + package { mypackage: ensure => latest } + service { myservice: ensure => running } + file { "/etc/myfile": + content => template("mymodule/myfile.erb") + } + } + +The templates are searched for in: + + $templatedir/mymodule/myfile.erb + $modulepath/mymodule/templates/myfile.erb diff --git a/3rdparty/modules/rabbitmq/templates/default.erb b/3rdparty/modules/rabbitmq/templates/default.erb new file mode 100644 index 00000000..a2bea95e --- /dev/null +++ b/3rdparty/modules/rabbitmq/templates/default.erb @@ -0,0 +1,10 @@ +# File managed by Puppet. + +# This file is sourced by /etc/init.d/rabbitmq-server. Its primary +# reason for existing is to allow adjustment of system limits for the +# rabbitmq-server process. +# +# Maximum number of open file handles. This will need to be increased +# to handle many simultaneous connections. Refer to the system +# documentation for ulimit (in man bash) for more information. +ulimit -n <%= @file_limit %> diff --git a/3rdparty/modules/rabbitmq/templates/limits.conf b/3rdparty/modules/rabbitmq/templates/limits.conf new file mode 100644 index 00000000..bb017fd9 --- /dev/null +++ b/3rdparty/modules/rabbitmq/templates/limits.conf @@ -0,0 +1,2 @@ +rabbitmq soft nofile <%= @file_limit %> +rabbitmq hard nofile <%= @file_limit %> diff --git a/3rdparty/modules/rabbitmq/templates/rabbitmq-env.conf.erb b/3rdparty/modules/rabbitmq/templates/rabbitmq-env.conf.erb new file mode 100644 index 00000000..0c6b5d19 --- /dev/null +++ b/3rdparty/modules/rabbitmq/templates/rabbitmq-env.conf.erb @@ -0,0 +1,5 @@ +<%- @environment_variables.keys.sort.each do |key| -%> +<%- if @environment_variables[key] != 'UNSET' -%> +<%= key %>=<%= @environment_variables[key] %> +<%- end -%> +<%- end -%> diff --git a/3rdparty/modules/rabbitmq/templates/rabbitmq-server.service.d/limits.conf b/3rdparty/modules/rabbitmq/templates/rabbitmq-server.service.d/limits.conf new file mode 100644 index 00000000..78773864 --- /dev/null +++ b/3rdparty/modules/rabbitmq/templates/rabbitmq-server.service.d/limits.conf @@ -0,0 +1,2 @@ +[Service] +LimitNOFILE=<%= @file_limit %> diff --git a/3rdparty/modules/rabbitmq/templates/rabbitmq.config.erb b/3rdparty/modules/rabbitmq/templates/rabbitmq.config.erb new file mode 100644 index 00000000..4e2154e5 --- /dev/null +++ b/3rdparty/modules/rabbitmq/templates/rabbitmq.config.erb @@ -0,0 +1,110 @@ +% This file managed by Puppet +% Template Path: <%= @module_name %>/templates/rabbitmq.config +[ +<%- if @ssl and @ssl_versions -%> + {ssl, [{versions, [<%= @ssl_versions.sort.map { |v| "'#{v}'" }.join(', ') %>]}]}, +<%- end -%> + {rabbit, [ +<% if @ldap_auth -%> + {auth_backends, [rabbit_auth_backend_internal, rabbit_auth_backend_ldap]}, +<% end -%> +<% if @config_cluster -%> + {cluster_nodes, {[<%= @cluster_nodes.map { |n| "\'rabbit@#{n}\'" }.join(', ') %>], <%= @cluster_node_type %>}}, + {cluster_partition_handling, <%= @cluster_partition_handling %>}, +<% end -%> +<%- if @tcp_keepalive -%> + {tcp_listen_options, [{keepalive, true}]}, +<%- end -%> +<%- if @ssl_only -%> + {tcp_listeners, []}, +<%- elsif @interface != 'UNSET' -%> + {tcp_listeners, [{"<%= @interface%>", <%= @port %>}]}, +<%- end -%> +<%- if @ssl -%> + <%- if @ssl_interface != 'UNSET' -%> + {ssl_listeners, [{"<%= @ssl_interface%>", <%= @ssl_port %>}]}, + <%- else -%> + {ssl_listeners, [<%= @ssl_port %>]}, + <%- end -%> + {ssl_options, [ + <%- if @ssl_cacert != 'UNSET' -%> + {cacertfile,"<%= @ssl_cacert %>"}, + <%- end -%> + {certfile,"<%= @ssl_cert %>"}, + {keyfile,"<%= @ssl_key %>"}, + {verify,<%= @ssl_verify %>}, + {fail_if_no_peer_cert,<%= @ssl_fail_if_no_peer_cert %>} + <%- if @ssl_versions -%> + ,{versions, [<%= @ssl_versions.sort.map { |v| "'#{v}'" }.join(', ') %>]} + <%- end -%> + <%- if @ssl_ciphers and @ssl_ciphers.size > 0 -%> + ,{ciphers,[ + <%= @ssl_ciphers.sort.map{|k| "{#{k}}"}.join(",\n ") %> + ]} + <%- end -%> + ]}, +<%- end -%> +<% if @config_variables -%> +<%- @config_variables.keys.sort.each do |key| -%> + {<%= key %>, <%= @config_variables[key] %>}, +<%- end -%> +<%- end -%> + {default_user, <<"<%= @default_user %>">>}, + {default_pass, <<"<%= @default_pass %>">>} + ]}<% if @config_kernel_variables -%>, + {kernel, [ + <%= @config_kernel_variables.sort.map{|k,v| "{#{k}, #{v}}"}.join(",\n ") %> + ]} +<%- end -%> +<%- if @admin_enable -%>, + {rabbitmq_management, [ + {listener, [ +<%- if @ssl -%> + {port, <%= @ssl_management_port %>}, + {ssl, true}, + {ssl_opts, [<%- if @ssl_cacert != 'UNSET' -%>{cacertfile, "<%= @ssl_cacert %>"},<%- end -%> + + {certfile, "<%= @ssl_cert %>"}, + {keyfile, "<%= @ssl_key %>"} + <%- if @ssl_versions -%> + ,{versions, [<%= @ssl_versions.sort.map { |v| "'#{v}'" }.join(', ') %>]} + <%- end -%> + <%- if @ssl_ciphers and @ssl_ciphers.size > 0 -%> + ,{ciphers,[ + <%= @ssl_ciphers.sort.map{|k| "{#{k}}"}.join(",\n ") %> + ]} + <%- end -%> + ]} +<%- else -%> + {port, <%= @management_port %>} +<%- end -%> + ]} + ]} +<%- end -%> +<% if @config_stomp -%>, +% Configure the Stomp Plugin listening port + {rabbitmq_stomp, [ + {tcp_listeners, [<%= @stomp_port %>]} + <%- if @ssl && @ssl_stomp_port -%>, + {ssl_listeners, [<%= @ssl_stomp_port %>]} + <%- end -%> + ]} +<% end -%> +<%- if @ldap_auth -%>, +% Configure the LDAP authentication plugin + {rabbitmq_auth_backend_ldap, [ + {other_bind, <%= @ldap_other_bind %>}, + {servers, ["<%= @ldap_server %>"]}, + {user_dn_pattern, "<%= @ldap_user_dn_pattern %>"}, + {use_ssl, <%= @ldap_use_ssl %>}, + {port, <%= @ldap_port %>}, +<% if @ldap_config_variables -%> +<%- @ldap_config_variables.keys.sort.each do |key| -%> + {<%= key %>, <%= @ldap_config_variables[key] %>}, +<%- end -%> +<%- end -%> + {log, <%= @ldap_log %>} + ]} +<%- end -%> +]. +% EOF diff --git a/3rdparty/modules/rabbitmq/templates/rabbitmqadmin.conf.erb b/3rdparty/modules/rabbitmq/templates/rabbitmqadmin.conf.erb new file mode 100644 index 00000000..d76c8114 --- /dev/null +++ b/3rdparty/modules/rabbitmq/templates/rabbitmqadmin.conf.erb @@ -0,0 +1,7 @@ +[default] +<% if @ssl -%> +ssl = True +port = <%= @ssl_management_port %> +<% else -%> +port = <%= @management_port %> +<% end -%> diff --git a/3rdparty/modules/rabbitmq/tests/erlang_deps.pp b/3rdparty/modules/rabbitmq/tests/erlang_deps.pp new file mode 100644 index 00000000..d34d9441 --- /dev/null +++ b/3rdparty/modules/rabbitmq/tests/erlang_deps.pp @@ -0,0 +1,5 @@ +# install first the garethr-erlang module. See README.md +include 'erlang' + +class { 'erlang': epel_enable => true} +Class['erlang'] -> Class['rabbitmq'] diff --git a/3rdparty/modules/rabbitmq/tests/full.pp b/3rdparty/modules/rabbitmq/tests/full.pp new file mode 100644 index 00000000..c6dfc5cf --- /dev/null +++ b/3rdparty/modules/rabbitmq/tests/full.pp @@ -0,0 +1,21 @@ +class { 'rabbitmq::repo::apt': + pin => '900', +}-> +class { 'rabbitmq::server': + delete_guest_user => true, +# version => '2.4.1', +}-> +rabbitmq_user { 'dan': + admin => true, + password => 'pass', + provider => 'rabbitmqctl', +}-> +rabbitmq_vhost { 'myhost': + provider => 'rabbitmqctl', +} +rabbitmq_user_permissions { 'dan@myhost': + configure_permission => '.*', + read_permission => '.*', + write_permission => '.*', + provider => 'rabbitmqctl', +} diff --git a/3rdparty/modules/rabbitmq/tests/permissions/add.pp b/3rdparty/modules/rabbitmq/tests/permissions/add.pp new file mode 100644 index 00000000..fb71af14 --- /dev/null +++ b/3rdparty/modules/rabbitmq/tests/permissions/add.pp @@ -0,0 +1,9 @@ +rabbitmq_user { 'blah7': + password => 'foo', +} +rabbitmq_vhost { 'test5': } +rabbitmq_user_permissions { 'blah7@test5': + configure_permission => 'config2', + read_permission => 'ready', + #write_permission => 'ready', +} diff --git a/3rdparty/modules/rabbitmq/tests/plugin.pp b/3rdparty/modules/rabbitmq/tests/plugin.pp new file mode 100644 index 00000000..6c5605b9 --- /dev/null +++ b/3rdparty/modules/rabbitmq/tests/plugin.pp @@ -0,0 +1,11 @@ +class { 'rabbitmq::server': + config_stomp => true, +} + +$rabbitmq_plugins = [ 'amqp_client', 'rabbitmq_stomp' ] + +rabbitmq_plugin { $rabbitmq_plugins: + ensure => present, + require => Class['rabbitmq::server'], + provider => 'rabbitmqplugins', +} diff --git a/3rdparty/modules/rabbitmq/tests/repo/apt.pp b/3rdparty/modules/rabbitmq/tests/repo/apt.pp new file mode 100644 index 00000000..f1373737 --- /dev/null +++ b/3rdparty/modules/rabbitmq/tests/repo/apt.pp @@ -0,0 +1,2 @@ +# requires pupetlabs-apt +include rabbitmq::repo::apt diff --git a/3rdparty/modules/rabbitmq/tests/server.pp b/3rdparty/modules/rabbitmq/tests/server.pp new file mode 100644 index 00000000..caea8937 --- /dev/null +++ b/3rdparty/modules/rabbitmq/tests/server.pp @@ -0,0 +1,5 @@ +class { 'rabbitmq::server': + port => '5672', + delete_guest_user => true, + version => 'latest', +} diff --git a/3rdparty/modules/rabbitmq/tests/service.pp b/3rdparty/modules/rabbitmq/tests/service.pp new file mode 100644 index 00000000..9a00d2b6 --- /dev/null +++ b/3rdparty/modules/rabbitmq/tests/service.pp @@ -0,0 +1 @@ +class { 'rabbitmq::service': } diff --git a/3rdparty/modules/rabbitmq/tests/site.pp b/3rdparty/modules/rabbitmq/tests/site.pp new file mode 100644 index 00000000..75ebcfed --- /dev/null +++ b/3rdparty/modules/rabbitmq/tests/site.pp @@ -0,0 +1,16 @@ +node default { + + $rabbitmq_plugins = [ 'amqp_client', 'rabbitmq_stomp' ] + + class { 'rabbitmq::server': + config => '[ {rabbit_stomp, [{tcp_listeners, [1234]} ]} ].', + } + + # Required for MCollective + rabbitmq_plugin { $rabbitmq_plugins: + ensure => present, + require => Class['rabbitmq::server'], + provider => 'rabbitmqplugins', + } +} + diff --git a/3rdparty/modules/rabbitmq/tests/user/add.pp b/3rdparty/modules/rabbitmq/tests/user/add.pp new file mode 100644 index 00000000..2c3a8709 --- /dev/null +++ b/3rdparty/modules/rabbitmq/tests/user/add.pp @@ -0,0 +1,4 @@ +rabbitmq_user { ['blah2', 'blah3', 'blah4']: + password => 'phoey!', + #provider => 'rabbitmqctl', +} diff --git a/3rdparty/modules/rabbitmq/tests/vhosts/add.pp b/3rdparty/modules/rabbitmq/tests/vhosts/add.pp new file mode 100644 index 00000000..d818a192 --- /dev/null +++ b/3rdparty/modules/rabbitmq/tests/vhosts/add.pp @@ -0,0 +1 @@ +rabbitmq_vhost { ['fooey', 'blah']: } diff --git a/modules/rabbitmq/lib/puppet/provider/rabbitmq_plugin/default.rb b/modules/rabbitmq/lib/puppet/provider/rabbitmq_plugin/default.rb deleted file mode 100644 index 9f71a9ac..00000000 --- a/modules/rabbitmq/lib/puppet/provider/rabbitmq_plugin/default.rb +++ /dev/null @@ -1,22 +0,0 @@ -Puppet::Type.type(:rabbitmq_plugin).provide(:default) do - - def self.instances - [] - end - - def create - default_fail - end - - def destroy - default_fail - end - - def exists? - default_fail - end - - def default_fail - fail('This is just the default provider for rabbitmq_plugin, all it does is fail') - end -end diff --git a/modules/rabbitmq/lib/puppet/provider/rabbitmq_plugin/rabbitmqplugins.rb b/modules/rabbitmq/lib/puppet/provider/rabbitmq_plugin/rabbitmqplugins.rb deleted file mode 100644 index 325fd200..00000000 --- a/modules/rabbitmq/lib/puppet/provider/rabbitmq_plugin/rabbitmqplugins.rb +++ /dev/null @@ -1,30 +0,0 @@ -Puppet::Type.type(:rabbitmq_plugin).provide(:rabbitmqplugins) do - - commands :rabbitmqplugins => '/usr/lib/rabbitmq/bin/rabbitmq-plugins' - defaultfor :feature => :posix - - def self.instances - rabbitmqplugins('list', '-E').split(/\n/).map do |line| - if line.split(/\s+/)[1] =~ /^(\S+)$/ - new(:name => $1) - else - raise Puppet::Error, "Cannot parse invalid plugins line: #{line}" - end - end - end - - def create - rabbitmqplugins('enable', resource[:name]) - end - - def destroy - rabbitmqplugins('disable', resource[:name]) - end - - def exists? - out = rabbitmqplugins('list', '-E').split(/\n/).detect do |line| - line.split(/\s+/)[1].match(/^#{resource[:name]}$/) - end - end - -end diff --git a/modules/rabbitmq/lib/puppet/provider/rabbitmq_policy/default.rb b/modules/rabbitmq/lib/puppet/provider/rabbitmq_policy/default.rb deleted file mode 100644 index 77cb9bfb..00000000 --- a/modules/rabbitmq/lib/puppet/provider/rabbitmq_policy/default.rb +++ /dev/null @@ -1,23 +0,0 @@ -Puppet::Type.type(:rabbitmq_policy).provide(:default) do - - def self.instances - [] - end - - def create - default_fail - end - - def destroy - default_fail - end - - def exists? - default_fail - end - - def default_fail - fail('This is just the default provider for rabbitmq_policy, all it does is fail') - end -end - diff --git a/modules/rabbitmq/lib/puppet/provider/rabbitmq_policy/rabbitmqctl.rb b/modules/rabbitmq/lib/puppet/provider/rabbitmq_policy/rabbitmqctl.rb deleted file mode 100644 index 8e125103..00000000 --- a/modules/rabbitmq/lib/puppet/provider/rabbitmq_policy/rabbitmqctl.rb +++ /dev/null @@ -1,39 +0,0 @@ -require 'puppet' -Puppet::Type.type(:rabbitmq_policy).provide(:rabbitmqctl) do - - commands :rabbitmqctl => 'rabbitmqctl' - defaultfor :feature => :posix - - def should_vhost - if @should_vhost - @should_vhost - else - @should_vhost = resource[:vhost] - end - end - - def self.instances - rabbitmqctl('list_policies', '-p', should_vhost).split(/\n/)[1..-2].detect do |line| - if line =~ /^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+.*$/ - new(:name => $2, :vhost => $1, :match => $3, :policy => $4) - else - raise Puppet::Error, "Cannot parse invalid user line: #{line}" - end - end - end - - def create - rabbitmqctl('set_policy', '-p', should_vhost, resource[:name], resource[:match], resource[:policy]) - end - - def destroy - rabbitmqctl('clear_policy', '-p', should_vhost, resource[:name]) - end - - def exists? - out = rabbitmqctl('list_policies', '-p', should_vhost).split(/\n/)[1..-2].detect do |line| - line.match(/^\S+\s+#{resource[:name]}\s+\S+.*$/) - end - end - -end diff --git a/modules/rabbitmq/lib/puppet/provider/rabbitmq_user/default.rb b/modules/rabbitmq/lib/puppet/provider/rabbitmq_user/default.rb deleted file mode 100644 index 8915dd81..00000000 --- a/modules/rabbitmq/lib/puppet/provider/rabbitmq_user/default.rb +++ /dev/null @@ -1,22 +0,0 @@ -Puppet::Type.type(:rabbitmq_user).provide(:default) do - - def self.instances - [] - end - - def create - default_fail - end - - def destroy - default_fail - end - - def exists? - default_fail - end - - def default_fail - fail('This is just the default provider for rabbitmq_user, all it does is fail') - end -end diff --git a/modules/rabbitmq/lib/puppet/provider/rabbitmq_user/rabbitmqctl.rb b/modules/rabbitmq/lib/puppet/provider/rabbitmq_user/rabbitmqctl.rb deleted file mode 100644 index fde194cc..00000000 --- a/modules/rabbitmq/lib/puppet/provider/rabbitmq_user/rabbitmqctl.rb +++ /dev/null @@ -1,59 +0,0 @@ -require 'puppet' -Puppet::Type.type(:rabbitmq_user).provide(:rabbitmqctl) do - - commands :rabbitmqctl => 'rabbitmqctl' - defaultfor :feature => :posix - - def self.instances - rabbitmqctl('list_users').split(/\n/)[1..-2].collect do |line| - if line =~ /^(\S+)(\s+\S+|)$/ - new(:name => $1) - else - raise Puppet::Error, "Cannot parse invalid user line: #{line}" - end - end - end - - def create - rabbitmqctl('add_user', resource[:name], resource[:password]) - if resource[:admin] == :true - make_user_admin() - end - end - - def destroy - rabbitmqctl('delete_user', resource[:name]) - end - - def exists? - out = rabbitmqctl('list_users').split(/\n/)[1..-2].detect do |line| - line.match(/^#{resource[:name]}(\s+\S+|)$/) - end - end - - # def password - # def password=() - def admin - match = rabbitmqctl('list_users').split(/\n/)[1..-2].collect do |line| - line.match(/^#{resource[:name]}\s+\[(administrator)?\]/) - end.compact.first - if match - (:true if match[1].to_s == 'administrator') || :false - else - raise Puppet::Error, "Could not match line '#{resource[:name]} (true|false)' from list_users (perhaps you are running on an older version of rabbitmq that does not support admin users?)" - end - end - - def admin=(state) - if state == :true - make_user_admin() - else - rabbitmqctl('set_user_tags', resource[:name]) - end - end - - def make_user_admin - rabbitmqctl('set_user_tags', resource[:name], 'administrator') - end - -end diff --git a/modules/rabbitmq/lib/puppet/provider/rabbitmq_user_permissions/default.rb b/modules/rabbitmq/lib/puppet/provider/rabbitmq_user_permissions/default.rb deleted file mode 100644 index 42ee0d91..00000000 --- a/modules/rabbitmq/lib/puppet/provider/rabbitmq_user_permissions/default.rb +++ /dev/null @@ -1,18 +0,0 @@ -Puppet::Type.type(:rabbitmq_user_permissions).provide(:default) do - - def create - default_fail - end - - def destroy - default_fail - end - - def exists? - default_fail - end - - def default_fail - fail('This is just the default provider for rabbitmq_user, all it does is fail') - end -end diff --git a/modules/rabbitmq/lib/puppet/provider/rabbitmq_vhost/default.rb b/modules/rabbitmq/lib/puppet/provider/rabbitmq_vhost/default.rb deleted file mode 100644 index c4118176..00000000 --- a/modules/rabbitmq/lib/puppet/provider/rabbitmq_vhost/default.rb +++ /dev/null @@ -1,22 +0,0 @@ -Puppet::Type.type(:rabbitmq_vhost).provide(:default) do - - def self.instances - [] - end - - def create - default_fail - end - - def destroy - default_fail - end - - def exists? - default_fail - end - - def default_fail - fail('This is just the default provider for rabbitmq_vhost, all it does is fail') - end -end diff --git a/modules/rabbitmq/lib/puppet/provider/rabbitmq_vhost/rabbitmqctl.rb b/modules/rabbitmq/lib/puppet/provider/rabbitmq_vhost/rabbitmqctl.rb deleted file mode 100644 index 39d3c0b8..00000000 --- a/modules/rabbitmq/lib/puppet/provider/rabbitmq_vhost/rabbitmqctl.rb +++ /dev/null @@ -1,30 +0,0 @@ -Puppet::Type.type(:rabbitmq_vhost).provide(:rabbitmqctl) do - - commands :rabbitmqctl => 'rabbitmqctl' - defaultfor :feature => :posix - - def self.instances - rabbitmqctl('list_vhosts').split(/\n/)[1..-2].map do |line| - if line =~ /^(\S+)$/ - new(:name => $1) - else - raise Puppet::Error, "Cannot parse invalid user line: #{line}" - end - end - end - - def create - rabbitmqctl('add_vhost', resource[:name]) - end - - def destroy - rabbitmqctl('delete_vhost', resource[:name]) - end - - def exists? - out = rabbitmqctl('list_vhosts').split(/\n/)[1..-2].detect do |line| - line.match(/^#{resource[:name]}$/) - end - end - -end diff --git a/modules/rabbitmq/lib/puppet/type/rabbitmq_policy.rb b/modules/rabbitmq/lib/puppet/type/rabbitmq_policy.rb deleted file mode 100644 index c7fb7064..00000000 --- a/modules/rabbitmq/lib/puppet/type/rabbitmq_policy.rb +++ /dev/null @@ -1,39 +0,0 @@ -Puppet::Type.newtype(:rabbitmq_policy) do - desc 'Native type for managing rabbitmq policy' - - ensurable do - defaultto(:present) - newvalue(:present) do - provider.create - end - newvalue(:absent) do - provider.destroy - end - end - - newparam(:name, :namevar => true) do - desc 'Name of policy' - newvalues(/^\S+$/) - end - - newparam(:vhost) do - desc 'Vhost for policy' - newvalues(/^\S+$/) - end - - newparam(:match) do - desc 'Regex match for policy' - end - - newparam(:policy) do - desc 'Policy to set' - end - - validate do - if self[:ensure] == :present and ! self[:policy] and ! self[:match] - raise ArgumentError, 'must set policy and match' unless self[:policy] and self[:match] - end - end - -end - diff --git a/modules/rabbitmq/lib/puppet/type/rabbitmq_user.rb b/modules/rabbitmq/lib/puppet/type/rabbitmq_user.rb deleted file mode 100644 index 9ad9c0f7..00000000 --- a/modules/rabbitmq/lib/puppet/type/rabbitmq_user.rb +++ /dev/null @@ -1,40 +0,0 @@ -Puppet::Type.newtype(:rabbitmq_user) do - desc 'Native type for managing rabbitmq users' - - ensurable do - defaultto(:present) - newvalue(:present) do - provider.create - end - newvalue(:absent) do - provider.destroy - end - end - - newparam(:name, :namevar => true) do - desc 'Name of user' - newvalues(/^\S+$/) - end - - # newproperty(:password) do - newparam(:password) do - desc 'User password to be set *on creation*' - end - - newproperty(:admin) do - desc 'rather or not user should be an admin' - newvalues(/true|false/) - munge do |value| - # converting to_s incase its a boolean - value.to_s.to_sym - end - defaultto :false - end - - validate do - if self[:ensure] == :present and ! self[:password] - raise ArgumentError, 'must set password when creating user' unless self[:password] - end - end - -end diff --git a/modules/rabbitmq/manifests/autouser.pp b/modules/rabbitmq/manifests/autouser.pp deleted file mode 100644 index 90758b78..00000000 --- a/modules/rabbitmq/manifests/autouser.pp +++ /dev/null @@ -1,32 +0,0 @@ -# == Define: rabbitmq::autouser -# -# Create a user in rabbitmq automatically for debian.org hosts -# Should automatically create a password -# -# === Parameters -# -# === Examples -# -# rabbitmq::autouser { 'master.debian.org': } -# -define rabbitmq::autouser () { - - $rabbit_password = hkdf('/etc/puppet/secret', "mq-client-${name}") - - rabbitmq_user { $name: - admin => false, - password => $rabbit_password, - provider => 'rabbitmqctl', - } - - rabbitmq_user_permissions { "${name}@dsa": - configure_permission => '.*', - read_permission => '.*', - write_permission => '.*', - provider => 'rabbitmqctl', - require => [ - Rabbitmq_user[$name], - Rabbitmq_vhost['dsa'] - ] - } -} diff --git a/modules/rabbitmq/manifests/config.pp b/modules/rabbitmq/manifests/config.pp deleted file mode 100644 index d6a26447..00000000 --- a/modules/rabbitmq/manifests/config.pp +++ /dev/null @@ -1,14 +0,0 @@ -# == Class: rabbitmq::config -# -# Sets up the rabbitmq config file -# -class rabbitmq::config { - - concat { '/etc/rabbitmq/rabbitmq.config': - require => Package['rabbitmq-server'], - notify => Service['rabbitmq-server'], - owner => root, - group => $::root_group, - mode => '0644', - } -} diff --git a/modules/rabbitmq/manifests/init.pp b/modules/rabbitmq/manifests/init.pp deleted file mode 100644 index 33182da0..00000000 --- a/modules/rabbitmq/manifests/init.pp +++ /dev/null @@ -1,97 +0,0 @@ -# == Class: rabbitmq -# -# Top level class for all things rabbitmq -# -class rabbitmq ( - $cluster=false, - $clustermembers=[], - $clustercookie='', - $delete_guest_user=false, - $rabbit_num_ofiles=4096, - $master='' -) { - include rabbitmq::config - - package { 'rabbitmq-server': - ensure => installed, - } - - service { 'rabbitmq-server': - ensure => running, - enable => true, - require => Package['rabbitmq-server'] - } - - Service['rabbitmq-server'] -> Rabbitmq_user <| |> - Service['rabbitmq-server'] -> Rabbitmq_vhost <| |> - Service['rabbitmq-server'] -> Rabbitmq_user_permissions <| |> - - concat::fragment { 'rabbitmq_main_conf': - target => '/etc/rabbitmq/rabbitmq.config', - order => 00, - content => template('rabbitmq/rabbitmq.conf.erb'), - } - - concat::fragment { 'rabbit_foot': - target => '/etc/rabbitmq/rabbitmq.config', - order => 50, - content => "]}\n" - } - - concat::fragment { 'rabbitmq_conf_foot': - target => '/etc/rabbitmq/rabbitmq.config', - order => 99, - content => "].\n" - } - - file { '/etc/default/rabbitmq-server': - content => template('rabbitmq/rabbitmq.ulimit.erb'), - notify => Service['rabbitmq-server'] - } - - if $cluster { - if $clustercookie { - file { '/var/lib/rabbitmq': - ensure => directory, - mode => '0755', - owner => rabbitmq, - group => rabbitmq, - } - - file { '/var/lib/rabbitmq/.erlang.cookie': - content => $clustercookie, - mode => '0500', - owner => rabbitmq, - group => rabbitmq, - before => Package['rabbitmq-server'], - notify => Service['rabbitmq-server'] - } - } - - if $::hostname != $master { - exec { 'reset_mq': - command => 'rabbitmqctl stop_app && rabbitmqctl reset > /var/lib/rabbitmq/.node_reset', - path => '/usr/bin:/bin:/usr/sbin:/sbin', - creates => '/var/lib/rabbitmq/.node_reset', - require => Package['rabbitmq-server'], - notify => Service['rabbitmq-server'] - } - Exec['reset_mq'] -> Rabbitmq_user <| |> - Exec['reset_mq'] -> Rabbitmq_vhost <| |> - Exec['reset_mq'] -> Rabbitmq_user_permissions <| |> - } - } - - if $delete_guest_user { - rabbitmq_user { 'guest': - ensure => absent, - provider => 'rabbitmqctl', - } - } - - site::limit { 'rabbitmq_openfiles': - limit_user => rabbitmq, - limit_value => $rabbit_num_ofiles, - notify => Service['rabbitmq-server'] - } -} diff --git a/modules/rabbitmq/templates/rabbitmq.conf.erb b/modules/rabbitmq/templates/rabbitmq.conf.erb deleted file mode 100644 index 4d95f9ef..00000000 --- a/modules/rabbitmq/templates/rabbitmq.conf.erb +++ /dev/null @@ -1,12 +0,0 @@ -[ -{rabbit, [ -<% if @cluster -%> - {cluster_nodes, ['<%= @clustermembers.to_a.flatten.join("', '") %>']}, - {tcp_listen_options, [binary, - {packet, raw}, - {reuseaddr, true}, - {backlog, 128}, - {nodelay, true}, - {exit_on_close, false}, - {keepalive, true}]} -<% end -%> diff --git a/modules/rabbitmq/templates/rabbitmq.ulimit.erb b/modules/rabbitmq/templates/rabbitmq.ulimit.erb deleted file mode 100644 index d1544b39..00000000 --- a/modules/rabbitmq/templates/rabbitmq.ulimit.erb +++ /dev/null @@ -1 +0,0 @@ -ulimit -n <%= @rabbit_num_ofiles %> diff --git a/modules/roles/files/pubsub/rabbitmq-mgmt.config b/modules/roles/files/pubsub/rabbitmq-mgmt.config deleted file mode 100644 index c32d3e09..00000000 --- a/modules/roles/files/pubsub/rabbitmq-mgmt.config +++ /dev/null @@ -1,10 +0,0 @@ -,{rabbitmq_management, - [{listener, [ - {port, 15672}, - {ssl, true}, - {ssl_opts, [ - {cacertfile,"/etc/ssl/debian/certs/ca.crt"}, - {certfile,"/etc/ssl/debian/certs/thishost-server.crt"}, - {keyfile,"/etc/ssl/debian/keys/thishost-server.key"}]} - ]} -]} diff --git a/modules/roles/files/pubsub/rabbitmq.config b/modules/roles/files/pubsub/rabbitmq.config deleted file mode 100644 index f3b9f0cb..00000000 --- a/modules/roles/files/pubsub/rabbitmq.config +++ /dev/null @@ -1,7 +0,0 @@ - ,{ssl_listeners, [5671]}, - {ssl_options, [{cacertfile,"/etc/ssl/debian/certs/ca.crt"}, - {certfile,"/etc/ssl/debian/certs/thishost-server.crt"}, - {keyfile,"/etc/ssl/debian/keys/thishost-server.key"}, - {verify,verify_none}, - {fail_if_no_peer_cert,false}]} - diff --git a/modules/roles/manifests/pubsub.pp b/modules/roles/manifests/pubsub.pp index 8ebe3e70..86b5807a 100644 --- a/modules/roles/manifests/pubsub.pp +++ b/modules/roles/manifests/pubsub.pp @@ -8,32 +8,27 @@ class roles::pubsub { $cc_secondary = rapoport class { 'rabbitmq': - cluster => true, - clustermembers => [ + config_cluster => true, + cluster_nodes => [ "rabbit@${cc_master}", "rabbit@${cc_secondary}", ], - clustercookie => '8r17so6o1s124ns49sr08n0o24342160', + cluster_node_type => 'disc', + erlang_cookie => '8r17so6o1s124ns49sr08n0o24342160', delete_guest_user => true, - master => $cc_master, + tcp_keepalive => true, + ssl_only => true, + ssl => true, + ssl_cacert => '/etc/ssl/debian/certs/ca.crt', + ssl_cert => '/etc/ssl/debian/certs/thishost-server.crt', + ssl_key => '/etc/ssl/debian/keys/thishost-server.key', + manage_repo => false, } user { 'rabbitmq': groups => 'ssl-cert' } - concat::fragment { 'rabbit_ssl': - target => '/etc/rabbitmq/rabbitmq.config', - order => 35, - source => 'puppet:///modules/roles/pubsub/rabbitmq.config' - } - - concat::fragment { 'rabbit_mgmt_ssl': - target => '/etc/rabbitmq/rabbitmq.config', - order => 55, - source => 'puppet:///modules/roles/pubsub/rabbitmq-mgmt.config' - } - @ferm::rule { 'rabbitmq': description => 'rabbitmq connections', rule => '&SERVICE_RANGE(tcp, 5671, $HOST_DEBIAN_V4)' diff --git a/modules/roles/manifests/pubsub/entities.pp b/modules/roles/manifests/pubsub/entities.pp index 5248bbc7..d0c13f2b 100644 --- a/modules/roles/manifests/pubsub/entities.pp +++ b/modules/roles/manifests/pubsub/entities.pp @@ -21,243 +21,175 @@ class roles::pubsub::entities { rabbitmq_user { 'admin': admin => true, password => $admin_password, - provider => 'rabbitmqctl', } rabbitmq_user { 'ftpteam': admin => false, password => $ftp_password, - provider => 'rabbitmqctl', } rabbitmq_user { 'buildd': admin => false, password => $buildd_password, - provider => 'rabbitmqctl', } rabbitmq_user { 'wbadm': admin => false, password => $wbadm_password, - provider => 'rabbitmqctl', } rabbitmq_user { 'mailadm': admin => false, password => $mailadm_password, - provider => 'rabbitmqctl', } rabbitmq_user { 'mailly': admin => false, password => $mailly_password, - provider => 'rabbitmqctl', } rabbitmq_user { 'muffat': admin => false, password => $muffat_password, - provider => 'rabbitmqctl', } rabbitmq_user { 'pet-devel': admin => false, password => $pet_password, - provider => 'rabbitmqctl', } - $do_hosts = keys($site::localinfo) - - rabbitmq::autouser { $do_hosts: } - rabbitmq_vhost { 'packages': ensure => present, - provider => 'rabbitmqctl', } rabbitmq_vhost { 'buildd': ensure => present, - provider => 'rabbitmqctl', } rabbitmq_vhost { 'dsa': ensure => present, - provider => 'rabbitmqctl', } rabbitmq_vhost { 'pet': ensure => present, - provider => 'rabbitmqctl', } rabbitmq_user_permissions { 'admin@/': configure_permission => '.*', read_permission => '.*', write_permission => '.*', - provider => 'rabbitmqctl', - require => Rabbitmq_user['admin'] } rabbitmq_user_permissions { 'admin@buildd': configure_permission => '.*', read_permission => '.*', write_permission => '.*', - provider => 'rabbitmqctl', - require => [ - Rabbitmq_user['admin'], - Rabbitmq_vhost['buildd'] - ] } rabbitmq_user_permissions { 'admin@dsa': configure_permission => '.*', read_permission => '.*', write_permission => '.*', - provider => 'rabbitmqctl', - require => [ - Rabbitmq_user['admin'], - Rabbitmq_vhost['dsa'] - ] } rabbitmq_user_permissions { 'admin@packages': configure_permission => '.*', read_permission => '.*', write_permission => '.*', - provider => 'rabbitmqctl', - require => [ - Rabbitmq_user['admin'], - Rabbitmq_vhost['packages'] - ] } rabbitmq_user_permissions { 'admin@pet': configure_permission => '.*', read_permission => '.*', write_permission => '.*', - provider => 'rabbitmqctl', - require => [ - Rabbitmq_user['admin'], - Rabbitmq_vhost['pet'] - ] } rabbitmq_user_permissions { 'ftpteam@packages': configure_permission => '.*', read_permission => '.*', write_permission => '.*', - provider => 'rabbitmqctl', - require => [ - Rabbitmq_user['ftpteam'], - Rabbitmq_vhost['packages'] - ] } rabbitmq_user_permissions { 'wbadm@packages': read_permission => 'unchecked', write_permission => 'wbadm', - provider => 'rabbitmqctl', - require => [ - Rabbitmq_user['wbadm'], - Rabbitmq_vhost['packages'] - ] } rabbitmq_user_permissions { 'buildd@buildd': configure_permission => '.*', read_permission => '.*', write_permission => '.*', - provider => 'rabbitmqctl', - require => [ - Rabbitmq_user['buildd'], - Rabbitmq_vhost['buildd'] - ] } rabbitmq_user_permissions { 'wbadm@buildd': configure_permission => '.*', read_permission => '.*', write_permission => '.*', - provider => 'rabbitmqctl', - require => [ - Rabbitmq_user['wbadm'], - Rabbitmq_vhost['buildd'] - ] } rabbitmq_user_permissions { 'mailadm@dsa': configure_permission => '.*', read_permission => '.*', write_permission => '.*', - provider => 'rabbitmqctl', - require => [ - Rabbitmq_user['mailadm'], - Rabbitmq_vhost['dsa'] - ] } rabbitmq_user_permissions { 'pet-devel@pet': configure_permission => '.*', read_permission => '.*', write_permission => '.*', - provider => 'rabbitmqctl', - require => [ - Rabbitmq_user['pet-devel'], - Rabbitmq_vhost['pet'] - ] } - rabbitmq_policy { 'mirror-dsa': - vhost => 'dsa', - match => '.*', - policy => '{"ha-mode":"all"}', - require => Rabbitmq_vhost['dsa'] + rabbitmq_policy { 'mirror-dsa@dsa': + pattern => '.*', + priority => 0, + applyto => 'all', + definition => { + 'ha-mode' => 'all', + 'ha-sync-mode' => 'automatic', + }, } - rabbitmq_policy { 'mirror-buildd': - vhost => 'buildd', - match => '.*', - policy => '{"ha-mode":"all"}', - require => Rabbitmq_vhost['buildd'] + rabbitmq_policy { 'mirror-buildd@buildd': + pattern => '.*', + priority => 0, + applyto => 'all', + definition => { + 'ha-mode' => 'all', + 'ha-sync-mode' => 'automatic', + }, } - rabbitmq_policy { 'mirror-packages': - vhost => 'packages', - match => '.*', - policy => '{"ha-mode":"all"}', - require => Rabbitmq_vhost['packages'] + rabbitmq_policy { 'mirror-packages@packages': + pattern => '.*', + priority => 0, + applyto => 'all', + definition => { + 'ha-mode' => 'all', + 'ha-sync-mode' => 'automatic', + }, } - rabbitmq_policy { 'mirror_pet': - vhost => 'pet', - match => '.*', - policy => '{"ha-mode":"all"}', - require => Rabbitmq_vhost['pet'] + rabbitmq_policy { 'mirror_pet@pet': + pattern => '.*', + priority => 0, + applyto => 'all', + definition => { + 'ha-mode' => 'all', + 'ha-sync-mode' => 'automatic', + }, } rabbitmq_plugin { 'rabbitmq_management': ensure => present, - provider => 'rabbitmqplugins', - require => Package['rabbitmq-server'], - notify => Service['rabbitmq-server'] } rabbitmq_plugin { 'rabbitmq_management_agent': ensure => present, - provider => 'rabbitmqplugins', - require => Package['rabbitmq-server'], - notify => Service['rabbitmq-server'] } rabbitmq_plugin { 'rabbitmq_tracing': ensure => present, - provider => 'rabbitmqplugins', - require => Package['rabbitmq-server'], - notify => Service['rabbitmq-server'] } rabbitmq_plugin { 'rabbitmq_management_visualiser': ensure => present, - provider => 'rabbitmqplugins', - require => Package['rabbitmq-server'], - notify => Service['rabbitmq-server'] } } -- 2.39.2