From ebc20c216d6c294a10b5ae015495fb404a878288 Mon Sep 17 00:00:00 2001 From: Michele Catalano Date: Fri, 21 Nov 2014 16:34:23 +0100 Subject: [PATCH] Add new resource rabbitmq_queue --- .../provider/rabbitmq_queue/rabbitmqadmin.rb | 105 ++++++++++++++++++ lib/puppet/type/rabbitmq_queue.rb | 68 ++++++++++++ .../rabbitmq_queue/rabbitmqadmin_spec.rb | 60 ++++++++++ spec/unit/puppet/type/rabbitmq_queue_spec.rb | 60 ++++++++++ 4 files changed, 293 insertions(+) create mode 100644 lib/puppet/provider/rabbitmq_queue/rabbitmqadmin.rb create mode 100644 lib/puppet/type/rabbitmq_queue.rb create mode 100644 spec/unit/puppet/provider/rabbitmq_queue/rabbitmqadmin_spec.rb create mode 100644 spec/unit/puppet/type/rabbitmq_queue_spec.rb diff --git a/lib/puppet/provider/rabbitmq_queue/rabbitmqadmin.rb b/lib/puppet/provider/rabbitmq_queue/rabbitmqadmin.rb new file mode 100644 index 000000000..b0ecb1f5a --- /dev/null +++ b/lib/puppet/provider/rabbitmq_queue/rabbitmqadmin.rb @@ -0,0 +1,105 @@ +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]}", + "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]}", "name=#{name}") + @property_hash[:ensure] = :absent + end + +end diff --git a/lib/puppet/type/rabbitmq_queue.rb b/lib/puppet/type/rabbitmq_queue.rb new file mode 100644 index 000000000..464a2ca6e --- /dev/null +++ b/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/spec/unit/puppet/provider/rabbitmq_queue/rabbitmqadmin_spec.rb b/spec/unit/puppet/provider/rabbitmq_queue/rabbitmqadmin_spec.rb new file mode 100644 index 000000000..97410a8b4 --- /dev/null +++ b/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', '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', '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', 'name=test', 'durable=true', 'auto_delete=false', 'arguments={}') + @provider.create + end + end +end diff --git a/spec/unit/puppet/type/rabbitmq_queue_spec.rb b/spec/unit/puppet/type/rabbitmq_queue_spec.rb new file mode 100644 index 000000000..4fd7b34ef --- /dev/null +++ b/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