Skip to content

Instantly share code, notes, and snippets.

@mgagne
Created October 24, 2014 00:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mgagne/65fdf555299544e58a40 to your computer and use it in GitHub Desktop.
Save mgagne/65fdf555299544e58a40 to your computer and use it in GitHub Desktop.
rabbitmq_policy
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] = {}
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
if @should_policy
@should_policy
else
@should_policy = resource[:name].split('@')[0]
end
end
def should_vhost
if @should_vhost
@should_vhost
else
@should_vhost = resource[:name].split('@')[1]
end
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
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
end
require 'puppet'
require 'mocha'
RSpec.configure do |config|
config.mock_with :mocha
end
describe Puppet::Type.type(:rabbitmq_policy).provider(:rabbitmqctl), :broken => true 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 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
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 autorequire rabbitmq_vhost" do
# vhost = Puppet::Type.type(:rabbitmq_vhost).new(:name => "/")
# 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 == @policy.ref
#end
end
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
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
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment