Skip to content

Commit

Permalink
Add support for vhost metadata
Browse files Browse the repository at this point in the history
  • Loading branch information
jimmybigcommerce committed Dec 6, 2023
1 parent 8854872 commit 33c3328
Show file tree
Hide file tree
Showing 4 changed files with 225 additions and 19 deletions.
21 changes: 20 additions & 1 deletion REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2080,14 +2080,27 @@ $ puppet resource rabbitmq_vhost`

```puppet
rabbitmq_vhost { 'myvhost':
ensure => present,
ensure => present,
description => 'myvhost description',
tags => ['tag1', 'tag2'],
default_queue_type => 'quorum',
}
```

#### Properties

The following properties are available in the `rabbitmq_vhost` type.

##### `default_queue_type`

Valid values: `classic`, `quorum`, `stream`

The default queue type for queues in this vhost

##### `description`

A description of the vhost

##### `ensure`

Valid values: `present`, `absent`
Expand All @@ -2096,6 +2109,12 @@ The basic property that the resource should be in.

Default value: `present`

##### `tags`

additional tags for the vhost

Default value: `[]`

#### Parameters

The following parameters are available in the `rabbitmq_vhost` type.
Expand Down
97 changes: 89 additions & 8 deletions lib/puppet/provider/rabbitmq_vhost/rabbitmqctl.rb
Original file line number Diff line number Diff line change
@@ -1,30 +1,111 @@
# frozen_string_literal: true

require File.expand_path(File.join(File.dirname(__FILE__), '..', 'rabbitmq_cli'))
Puppet::Type.type(:rabbitmq_vhost).provide(:rabbitmqctl, parent: Puppet::Provider::RabbitmqCli) do
Puppet::Type.type(:rabbitmq_vhost).provide(
:rabbitmqctl,
parent: Puppet::Provider::RabbitmqCli
) do
confine feature: :posix

def self.instances
vhost_list = run_with_retries do
rabbitmqctl_list('vhosts')
def self.prefetch(resources)
instances.each do |prov|
if (resource = resources[prov.name])
resource.provider = prov
end
end
end

# To maintain compatibility with older versions of RabbitMQ,
# we only deal with vhost metadata >= version 3.11.0
def self.supports_metadata?
Puppet::Util::Package.versioncmp(rabbitmq_version, '3.11') >= 0
end

def supports_metadata?
self.class.supports_metadata?
end

def self.vhost_list
run_with_retries do
if supports_metadata?
rabbitmqctl_list('vhosts', 'name,description,default_queue_type,tags', '-s')
else
rabbitmqctl_list('vhosts')
end
end
end

def self.instances
vhost_list.split(%r{\n}).map do |line|
raise Puppet::Error, "Cannot parse invalid vhost line: #{line}" unless line =~ %r{^(\S+)$}
if supports_metadata?
raise Puppet::Error, "Cannot parse invalid vhost line: #{line}" unless \
(matches = line.match(%r{^(\S+)\t+(.*?)\t+(undefined|quorum|classic|stream)?\t+\[(.*?)\]$}i))

new(name: Regexp.last_match(1))
name, description, default_queue_type, tags = matches.captures
# RMQ returns 'undefined' as default_queue_type if it has never been set
default_queue_type = nil if default_queue_type == 'undefined'
new(ensure: :present, name: name, description: description, default_queue_type: default_queue_type, tags: tags.split(%r{,\s*}))
else
raise Puppet::Error, "Cannot parse invalid vhost line: #{line}" unless line =~ %r{^(\S+)$}

new(ensure: :present, name: Regexp.last_match(1))
end
end
end

def create
rabbitmqctl('add_vhost', resource[:name])
rabbitmqctl('add_vhost', *params)
end

def params
params = [resource[:name]]
if supports_metadata?
params << ['--description', resource[:description]] if resource[:description]
params << ['--default-queue-type', resource[:default_queue_type]] if resource[:default_queue_type] && resource[:default_queue_type] != 'undefined'
params << ['--tags', resource[:tags].join(',')] if resource[:tags]
end
params
end

def description
@property_hash[:description]
end

def tags
@property_hash[:tags]
end

def default_queue_type
@property_hash[:default_queue_type]
end

def tags=(tags)
@property_hash[:tags] = tags
end

def description=(value)
@property_hash[:description] = value
end

def default_queue_type=(value)
@property_hash[:default_queue_type] = value
end

def flush
return if @property_hash.empty? || !supports_metadata? || !exists?

params = [resource[:name]]
params << ['--description', @property_hash[:description]] if @property_hash[:description]
params << ['--default-queue-type', @property_hash[:default_queue_type]] if @property_hash[:default_queue_type]
params << ['--tags', @property_hash[:tags].join(',')] if @property_hash[:tags]
rabbitmqctl('update_vhost_metadata', *params)
end

def destroy
rabbitmqctl('delete_vhost', resource[:name])
end

def exists?
run_with_retries { rabbitmqctl_list('vhosts') }.split(%r{\n}).include? resource[:name]
rabbitmqctl_list('vhosts').split(%r{\n}).include? resource[:name]
end
end
23 changes: 22 additions & 1 deletion lib/puppet/type/rabbitmq_vhost.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@
@example Create a rabbitmq_vhost
rabbitmq_vhost { 'myvhost':
ensure => present,
ensure => present,
description => 'myvhost description',
tags => ['tag1', 'tag2'],
default_queue_type => 'quorum',
}
DESC

Expand All @@ -29,4 +32,22 @@
desc 'The name of the vhost to add'
newvalues(%r{^\S+$})
end

newproperty(:description) do
desc 'A description of the vhost'
end

newproperty(:default_queue_type) do
desc 'The default queue type for queues in this vhost'
newvalues(:classic, :quorum, :stream)
munge(&:to_s)
end

newproperty(:tags, array_matching: :all) do
desc 'additional tags for the vhost'
validate do |value|
raise ArgumentError, "Invalid tag: #{value.inspect}" unless value =~ %r{^\S+$}
end
defaultto []
end
end
103 changes: 94 additions & 9 deletions spec/unit/puppet/provider/rabbitmq_vhost/rabbitmqctl_spec.rb
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
# frozen_string_literal: true

require 'spec_helper'

provider_class = Puppet::Type.type(:rabbitmq_vhost).provider(:rabbitmqctl)
describe provider_class do
describe Puppet::Type.type(:rabbitmq_vhost).provider(:rabbitmqctl) do
let(:resource) do
Puppet::Type::Rabbitmq_vhost.new(
name: 'foo'
name: 'foo',
description: 'foo description',
default_queue_type: 'quorum',
tags: %w[foo bar]
)
end
let(:provider) { provider_class.new(resource) }
let(:provider) { described_class.new(resource) }

it 'matches vhost names' do
provider.expects(:rabbitmqctl_list).with('vhosts').returns <<~EOT
Expand Down Expand Up @@ -37,12 +38,96 @@
expect(provider.exists?).to eq(false)
end

it 'calls rabbitmqctl to create' do
provider.expects(:rabbitmqctl).with('add_vhost', 'foo')
provider.create
context 'with RabbitMQ version <3.11.0 (no metadata support)' do
it 'calls rabbitmqctl to create' do
provider.expects(:supports_metadata?).at_least_once.returns false
provider.expects(:rabbitmqctl).with('add_vhost', 'foo')
provider.create
end
end

context 'with RabbitMQ version >=3.11.0 (metadata support)' do
it 'parses vhost list with valid metadata' do
provider.class.expects(:supports_metadata?).at_least_once.returns true
provider.class.expects(:vhost_list).returns <<~EOT
inventory classic []
/ Default virtual host undefined []
search quorum []
testing My cool vhost undefined [tag1, tag2]
EOT
instances = provider.class.instances
expect(instances.size).to eq(4)
expect(instances.map do |prov|
{
name: prov.get(:name),
description: prov.get(:description),
default_queue_type: prov.get(:default_queue_type),
tags: prov.get(:tags)
}
end).to eq(
[
{
name: 'inventory',
description: '',
default_queue_type: 'classic',
tags: []
},
{
name: '/',
description: 'Default virtual host',
default_queue_type: :absent,
tags: []
},
{
name: 'search',
description: '',
default_queue_type: 'quorum',
tags: []
},
{
name: 'testing',
description: 'My cool vhost',
default_queue_type: :absent,
tags: %w[tag1 tag2]
}
]
)
end

it 'throws error when parsing invalid vhost metadata' do
provider.class.expects(:supports_metadata?).at_least_once.returns true
provider.class.expects(:vhost_list).returns <<~EOT
inventory undefined []
/ Default virtual host undefined
EOT
expect { print provider.class.instances }.to raise_error(Puppet::Error, %r{Cannot parse invalid vhost line: / Default virtual host undefined})
end

it 'calls rabbitmqctl to create with metadata' do
provider.expects(:supports_metadata?).at_least_once.returns true
provider.expects(:rabbitmqctl).with('add_vhost', 'foo', ['--description', 'foo description'], \
['--default-queue-type', 'quorum'], ['--tags', 'foo,bar'])
provider.create
end

it 'updates tags' do
provider.set(tags: %w[tag1 tag2])
provider.expects(:exists?).at_least_once.returns true
provider.expects(:supports_metadata?).at_least_once.returns true
provider.expects(:rabbitmqctl).with('update_vhost_metadata', 'foo', ['--tags', 'tag1,tag2'])
provider.flush
end

it 'updates description' do
provider.set(description: 'this is the new description')
provider.expects(:exists?).at_least_once.returns true
provider.expects(:supports_metadata?).at_least_once.returns true
provider.expects(:rabbitmqctl).with('update_vhost_metadata', 'foo', ['--description', 'this is the new description'])
provider.flush
end
end

it 'calls rabbitmqctl to create' do
it 'calls rabbitmqctl to delete' do
provider.expects(:rabbitmqctl).with('delete_vhost', 'foo')
provider.destroy
end
Expand Down

0 comments on commit 33c3328

Please sign in to comment.