Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor rabbitmq_user provider (mpolenchuk) #598

Merged
merged 2 commits into from
Sep 28, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 70 additions & 57 deletions lib/puppet/provider/rabbitmq_user/rabbitmqctl.rb
Original file line number Diff line number Diff line change
@@ -1,78 +1,92 @@
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
Puppet::Type.type(:rabbitmq_user).provide(
:rabbitmqctl,
parent: Puppet::Provider::Rabbitmqctl
) do
has_command(:rabbitmqctl, 'rabbitmqctl') do
environment HOME: '/tmp'
end

defaultfor feature: :posix

def initialize(value = {})
super(value)
@property_flush = {}
end

def self.instances
user_list = run_with_retries do
rabbitmqctl('-q', 'list_users')
end

user_list.split(%r{\n}).map do |line|
raise Puppet::Error, "Cannot parse invalid user line: #{line}" unless line =~ %r{^(\S+)(\s+\[.*?\]|)$}
new(name: Regexp.last_match(1))
raise Puppet::Error, "Cannot parse invalid user line: #{line}" unless line =~ %r{^(\S+)\s+\[(.*?)\]$}
user = Regexp.last_match(1)
tags = Regexp.last_match(2).split(%r{,\s*})
new(
ensure: :present,
name: user,
tags: tags
)
end
end

def self.prefetch(resources)
users = instances
resources.each_key do |user|
if (provider = users.find { |u| u.name == user })
resources[user].provider = provider
end
end
end

def exists?
@property_hash[:ensure] == :present
end

def create
# Fail here (rather than a validate block in the type) if password is not
# set, so that "puppet resource" still works.
raise Puppet::Error, "Password is a required parameter for rabbitmq_user (user: #{name})" if @resource[:password].nil?

rabbitmqctl('add_user', resource[:name], resource[:password])
make_user_admin if resource[:admin] == :true
set_user_tags(resource[:tags]) unless resource[:tags].empty?
end
rabbitmqctl('add_user', @resource[:name], @resource[:password])

def change_password
rabbitmqctl('change_password', resource[:name], resource[:password])
end
tags = @resource[:tags]
tags << admin_tag if @resource[:admin] == :true
rabbitmqctl('set_user_tags', @resource[:name], tags) unless tags.empty?

def password
nil
@property_hash[:ensure] = :present
end

def check_password
response = self.class.run_with_retries do
rabbitmqctl('eval', 'rabbit_access_control:check_user_pass_login(list_to_binary("' + resource[:name] + '"), list_to_binary("' + resource[:password] + '")).')
end
if response.include? 'refused'
false
else
true
end
def destroy
rabbitmqctl('delete_user', @resource[:name])
@property_hash[:ensure] = :absent
end

def destroy
rabbitmqctl('delete_user', resource[:name])
def password=(password)
rabbitmqctl('change_password', @resource[:name], password)
end

def exists?
user_list = self.class.run_with_retries do
rabbitmqctl('-q', 'list_users')
end
def password; end

user_list.split(%r{\n}).find do |line|
line.match(%r{^#{Regexp.escape(resource[:name])}(\s+(\[.*?\]|\S+)|)$})
end
def check_password(password)
check_access_control = [
'rabbit_access_control:check_user_pass_login(',
%[list_to_binary("#{@resource[:name]}"), ],
%[list_to_binary("#{password}")).]
]

response = rabbitmqctl('eval', check_access_control.join)
!response.include? 'refused'
end

def tags
tags = get_user_tags
# do not expose the administrator tag for admins
tags.delete('administrator') if resource[:admin] == :true
tags.entries.sort
@property_hash[:tags].reject { |tag| tag == admin_tag }
end

def tags=(tags)
set_user_tags(tags) unless tags.nil?
@property_flush[:tags] = tags
end

def admin
Expand All @@ -91,26 +105,25 @@ def admin=(state)
end
end

def set_user_tags(tags) # rubocop:disable Style/AccessorMethodName
is_admin = get_user_tags.member?('administrator') \
|| resource[:admin] == :true
usertags = Set.new(tags)
usertags.add('administrator') if is_admin
rabbitmqctl('set_user_tags', resource[:name], usertags.entries.sort)
def admin
@property_hash[:tags].include?(admin_tag) ? :true : :false
end

def make_user_admin
usertags = get_user_tags
usertags.add('administrator')
rabbitmqctl('set_user_tags', resource[:name], usertags.entries.sort)
def admin=(state)
@property_flush[:admin] = state
end

def flush
return if @property_flush.empty?
tags = @property_flush[:tags] || @resource[:tags]
tags << admin_tag if @resource[:admin] == :true
rabbitmqctl('set_user_tags', @resource[:name], tags)
@property_flush.clear
end

private

def get_user_tags # rubocop:disable Style/AccessorMethodName
match = rabbitmqctl('-q', 'list_users').split(%r{\n}).map do |line|
line.match(%r{^#{Regexp.escape(resource[:name])}\s+\[(.*?)\]})
end.compact.first
Set.new(match[1].split(' ').map { |x| x.gsub(%r{,$}, '') }) if match
def admin_tag
'administrator'
end
end
28 changes: 4 additions & 24 deletions lib/puppet/type/rabbitmq_user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,20 +40,12 @@
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
provider.check_password(should)
end

def change_to_s(_current, _desired)
'password has been changed'
end

def should_to_s(_newvalue = @should)
'<new password>'
end
end

newproperty(:admin) do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use newproperty(:admin, boolean: true, parent: Parent::Property::Boolean) and remove the body (except desc). Then you can use true booleans in the provider. You might need a require 'puppet/property/boolean but I'm not sure about that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a good idea, but I feel like it should be implemented separately, and then we can review all the type parameters at once, and adjust any that we can switch to true booleans / integers / etc in one 'breaking' changeset.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's fair

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like the existing code is (in some cases) as simple as:

  newparam(:force) do
    defaultto(:false)
    newvalues(:true, :false)
  end

which actually seems simpler than the suggested code in https://docs.puppet.com/puppet/5.1/custom_types.html, and without that extra require. Also, the puppet boolean type allows 'true' yes, and various other things, which seems kind of regressive.

Expand All @@ -80,23 +72,11 @@ def should_to_s(_newvalue = @should)
defaultto []

def insync?(is)
is_to_s(is) == should_to_s
is.sort == should.sort
end

def is_to_s(currentvalue = @is) # rubocop:disable Style/PredicateName
if currentvalue
"[#{currentvalue.sort.join(', ')}]"
else
'[]'
end
end

def should_to_s(newvalue = @should)
if newvalue
"[#{newvalue.sort.join(', ')}]"
else
'[]'
end
def should_to_s(value)
Array(value)
end
end
end
21 changes: 20 additions & 1 deletion spec/acceptance/user_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,30 @@ class { '::rabbitmq':

# rubocop:disable RSpec/MultipleExpectations
it 'has the user' do
shell('rabbitmqctl list_users') do |r|
shell('rabbitmqctl list_users -q') do |r|
expect(r.stdout).to match(%r{dan.*administrator})
expect(r.exit_code).to be_zero
end
end
# rubocop:enable RSpec/MultipleExpectations
end

context 'destroy user resource' do
it 'runs successfully' do
pp = <<-EOS
rabbitmq_user { 'dan':
ensure => absent,
}
EOS

apply_manifest(pp, catch_failures: true)
apply_manifest(pp, catch_changes: true)
end

it 'does not have the user' do
shell('rabbitmqctl list_users -q') do |r|
expect(r.stdout).not_to match(%r{dan\s+})
end
end
end
end
Loading