From 01ffd0e4c3deea7b559aac58e70ab0aedf949936 Mon Sep 17 00:00:00 2001 From: Takashi Kajinami Date: Mon, 6 Nov 2023 00:04:24 +0900 Subject: [PATCH] Add resource to manage implied roles Keystone supports implied roles, and some of the default roles imply different roles. (eg. admin implies manager) This introduces a resource type to manage implied roles, and also ensures the implied roles are created in bootstrap. Depends-on: https://review.opendev.org/900138 Change-Id: I36ef3ddfcb2f60bdca8674ea8055b6f57a149512 --- .../keystone_implied_role/openstack.rb | 77 ++++++++++++++++ .../provider/keystone_role/openstack.rb | 3 +- lib/puppet/type/keystone_implied_role.rb | 44 +++++++++ manifests/bootstrap.pp | 12 ++- .../notes/implied-role-894ec2595b94aed7.yaml | 4 + spec/classes/keystone_bootstrap_spec.rb | 16 ++++ .../keystone_implied_role/openstack_spec.rb | 89 +++++++++++++++++++ spec/unit/type/keystone_implied_role_spec.rb | 36 ++++++++ 8 files changed, 278 insertions(+), 3 deletions(-) create mode 100644 lib/puppet/provider/keystone_implied_role/openstack.rb create mode 100644 lib/puppet/type/keystone_implied_role.rb create mode 100644 releasenotes/notes/implied-role-894ec2595b94aed7.yaml create mode 100644 spec/unit/provider/keystone_implied_role/openstack_spec.rb create mode 100644 spec/unit/type/keystone_implied_role_spec.rb diff --git a/lib/puppet/provider/keystone_implied_role/openstack.rb b/lib/puppet/provider/keystone_implied_role/openstack.rb new file mode 100644 index 000000000..36001ab1c --- /dev/null +++ b/lib/puppet/provider/keystone_implied_role/openstack.rb @@ -0,0 +1,77 @@ +require File.join(File.dirname(__FILE__), '..','..','..', 'puppet/provider/keystone') +require File.join(File.dirname(__FILE__), '..','..','..', 'puppet_x/keystone/composite_namevar') + +Puppet::Type.type(:keystone_implied_role).provide( + :openstack, + :parent => Puppet::Provider::Keystone +) do + + desc 'Provider for keystone implied roles.' + + @credentials = Puppet::Provider::Openstack::CredentialsV3.new + + include PuppetX::Keystone::CompositeNamevar::Helpers + + def initialize(value={}) + super(value) + end + + def self.do_not_manage + @do_not_manage + end + + def self.do_not_manage=(value) + @do_not_manage = value + end + + def create + if self.class.do_not_manage + fail("Not managing Keystone_implied_role[#{@resource[:role]}@#{@resource[:implied_role]}] due to earlier Keystone API failures.") + end + self.class.system_request('implied role', 'create', [@resource[:role], '--implied-role', @resource[:implied_role]]) + @property_hash[:ensure] = :present + @property_hash[:role] = @resource[:role] + @property_hash[:implied_role] = @resource[:implied_role] + end + + def destroy + if self.class.do_not_manage + fail("Not managing Keystone_implied_role[#{@resource[:role]}@#{@resource[:implied_role]}] due to earlier Keystone API failures.") + end + self.class.system_request('implied role', 'delete', [@resource[:role], '--implied-role', @resource[:implied_role]]) + @property_hash.clear + end + + def exists? + @property_hash[:ensure] == :present + end + + mk_resource_methods + + [ + :role, + :implied_role, + ].each do |attr| + define_method(attr.to_s + "=") do |value| + fail("Property #{attr.to_s} does not support being updated") + end + end + + def self.instances + self.do_not_manage = true + list = system_request('implied role', 'list') + reallist = list.collect do |role| + new( + :ensure => :present, + :role => role[:prior_role_name].downcase, + :implied_role => role[:implied_role_name].downcase, + ) + end + self.do_not_manage = false + reallist + end + + def self.prefetch(resources) + prefetch_composite(resources) + end +end diff --git a/lib/puppet/provider/keystone_role/openstack.rb b/lib/puppet/provider/keystone_role/openstack.rb index 7feca1792..41251af6e 100644 --- a/lib/puppet/provider/keystone_role/openstack.rb +++ b/lib/puppet/provider/keystone_role/openstack.rb @@ -11,7 +11,6 @@ Puppet::Type.type(:keystone_role).provide( def initialize(value={}) super(value) - @property_flush = {} end def self.do_not_manage @@ -63,7 +62,7 @@ Puppet::Type.type(:keystone_role).provide( def self.prefetch(resources) roles = instances resources.keys.each do |name| - if provider = roles.find{ |role| role.name == name.downcase } + if provider = roles.find{ |role| role.name == name.downcase } resources[name].provider = provider end end diff --git a/lib/puppet/type/keystone_implied_role.rb b/lib/puppet/type/keystone_implied_role.rb new file mode 100644 index 000000000..318bde30a --- /dev/null +++ b/lib/puppet/type/keystone_implied_role.rb @@ -0,0 +1,44 @@ +# LP#1408531 +File.expand_path('../..', File.dirname(__FILE__)).tap { |dir| $LOAD_PATH.unshift(dir) unless $LOAD_PATH.include?(dir) } +File.expand_path('../../../../openstacklib/lib', File.dirname(__FILE__)).tap { |dir| $LOAD_PATH.unshift(dir) unless $LOAD_PATH.include?(dir) } + +Puppet::Type.newtype(:keystone_implied_role) do + + desc <<-EOT + This is currently used to model the creation of + keystone implied roles. + EOT + + ensurable + + newparam(:role) do + isnamevar + newvalues(/\S+/) + end + + newparam(:implied_role) do + isnamevar + newvalues(/\S+/) + end + + # we should not do anything until the keystone service is started + autorequire(:anchor) do + ['keystone::service::end'] + end + + autorequire(:keystone_role) do + [self[:role], self[:implied_role]] + end + + def self.title_patterns + [ + [ + /^(\S+)@(\S+)$/, + [ + [:role], + [:implied_role], + ] + ], + ] + end +end diff --git a/manifests/bootstrap.pp b/manifests/bootstrap.pp index 11f614b2d..5c4af3960 100644 --- a/manifests/bootstrap.pp +++ b/manifests/bootstrap.pp @@ -116,7 +116,17 @@ class keystone::bootstrap ( # use the below resources to make sure the current resources are # correct so if some value was updated we set that. - ensure_resource('keystone_role', $role_name, { + ensure_resource('keystone_role', + [$role_name, 'manager', 'member', 'reader', 'service'], { + 'ensure' => 'present', + }) + + ensure_resource('keystone_implied_role', + [ + "${role_name}@manager", + 'manager@member', + 'member@reader', + ], { 'ensure' => 'present', }) diff --git a/releasenotes/notes/implied-role-894ec2595b94aed7.yaml b/releasenotes/notes/implied-role-894ec2595b94aed7.yaml new file mode 100644 index 000000000..ec68b7f81 --- /dev/null +++ b/releasenotes/notes/implied-role-894ec2595b94aed7.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + The new ``keystone_implied_role`` resource type has been added. diff --git a/spec/classes/keystone_bootstrap_spec.rb b/spec/classes/keystone_bootstrap_spec.rb index abf1c0123..f19c4882e 100644 --- a/spec/classes/keystone_bootstrap_spec.rb +++ b/spec/classes/keystone_bootstrap_spec.rb @@ -33,6 +33,14 @@ describe 'keystone::bootstrap' do )} it { is_expected.to contain_keystone_role('admin').with_ensure('present') } + it { is_expected.to contain_keystone_role('manager').with_ensure('present') } + it { is_expected.to contain_keystone_role('member').with_ensure('present') } + it { is_expected.to contain_keystone_role('reader').with_ensure('present') } + it { is_expected.to contain_keystone_role('service').with_ensure('present') } + + it { is_expected.to contain_keystone_implied_role('admin@manager').with_ensure('present') } + it { is_expected.to contain_keystone_implied_role('manager@member').with_ensure('present') } + it { is_expected.to contain_keystone_implied_role('member@reader').with_ensure('present') } it { is_expected.to contain_keystone_user('admin').with( :ensure => 'present', @@ -137,6 +145,14 @@ describe 'keystone::bootstrap' do )} it { is_expected.to contain_keystone_role('adminrole').with_ensure('present') } + it { is_expected.to contain_keystone_role('manager').with_ensure('present') } + it { is_expected.to contain_keystone_role('member').with_ensure('present') } + it { is_expected.to contain_keystone_role('reader').with_ensure('present') } + it { is_expected.to contain_keystone_role('service').with_ensure('present') } + + it { is_expected.to contain_keystone_implied_role('adminrole@manager').with_ensure('present') } + it { is_expected.to contain_keystone_implied_role('manager@member').with_ensure('present') } + it { is_expected.to contain_keystone_implied_role('member@reader').with_ensure('present') } it { is_expected.to contain_keystone_user('user').with( :ensure => 'present', diff --git a/spec/unit/provider/keystone_implied_role/openstack_spec.rb b/spec/unit/provider/keystone_implied_role/openstack_spec.rb new file mode 100644 index 000000000..41696470f --- /dev/null +++ b/spec/unit/provider/keystone_implied_role/openstack_spec.rb @@ -0,0 +1,89 @@ +require 'puppet' +require 'spec_helper' +require 'puppet/provider/keystone_implied_role/openstack' + +provider_class = Puppet::Type.type(:keystone_implied_role).provider(:openstack) + +describe provider_class do + + let(:set_env) do + ENV['OS_USERNAME'] = 'test' + ENV['OS_PASSWORD'] = 'abc123' + ENV['OS_SYSTEM_SCOPE'] = 'all' + ENV['OS_AUTH_URL'] = 'http://127.0.0.1:5000' + end + + before(:each) do + set_env + end + + describe 'when creating an implied role' do + let(:implied_role_attrs) do + { + :title => 'foo@bar', + :ensure => 'present', + } + end + + let(:resource) do + Puppet::Type::Keystone_implied_role.new(implied_role_attrs) + end + + let(:provider) do + provider_class.new(resource) + end + + describe '#create' do + it 'creates an implied role' do + expect(provider.class).to receive(:openstack) + .with('implied role', 'create', '--format', 'shell', + ['foo', '--implied-role', 'bar']) + .and_return('implies="54d545116da64b68bb75244130ba51b2" +prior_role="3553ab20c4dd497a867dd822913b6d30" +') + provider.create + expect(provider.exists?).to be_truthy + end + end + + describe '#destroy' do + it 'destroys an implied role' do + expect(provider.class).to receive(:openstack) + .with('implied role', 'delete', + ['foo', '--implied-role', 'bar']) + provider.destroy + expect(provider.exists?).to be_falsey + end + + end + + describe '#exists' do + context 'when implied role does not exist' do + subject(:response) do + response = provider.exists? + end + it { is_expected.to be_falsey } + end + end + + describe '#instances' do + it 'finds every role' do + expect(provider.class).to receive(:openstack) + .with('implied role', 'list', '--quiet', '--format', 'csv', []) + .and_return('"Prior Role ID","Prior Role Name","Implied Role ID","Implied Role Name" +"1d7f28c7d646463dba7b0c6c5851c59b","admin","da9eac51634e41fa902de65e4ec7f165","manager" +"d00138e69f7c427693e437f33e3765af","member","906b88ee8a824e96aa93ea887337d8ac","reader" +"da9eac51634e41fa902de65e4ec7f165","manager","d00138e69f7c427693e437f33e3765af","member" +') + instances = Puppet::Type::Keystone_implied_role::ProviderOpenstack.instances + expect(instances.count).to eq(3) + expect(instances[0].role).to eq('admin') + expect(instances[0].implied_role).to eq('manager') + expect(instances[1].role).to eq('member') + expect(instances[1].implied_role).to eq('reader') + expect(instances[2].role).to eq('manager') + expect(instances[2].implied_role).to eq('member') + end + end + end +end diff --git a/spec/unit/type/keystone_implied_role_spec.rb b/spec/unit/type/keystone_implied_role_spec.rb new file mode 100644 index 000000000..53062e8f4 --- /dev/null +++ b/spec/unit/type/keystone_implied_role_spec.rb @@ -0,0 +1,36 @@ +require 'spec_helper' +require 'puppet' +require 'puppet/type/keystone_implied_role' + +describe Puppet::Type.type(:keystone_implied_role) do + + describe 'role@implied_role' do + include_examples 'parse title correctly', + :role => 'role', + :implied_role => 'implied_role' + end + + describe '#autorequire' do + let(:child) do + Puppet::Type.type(:keystone_role).new(:title => 'child') + end + + let(:parent) do + Puppet::Type.type(:keystone_role).new(:title => 'parent') + end + + let(:another) do + Puppet::Type.type(:keystone_role).new(:title => 'another') + end + + context 'role autorequire from title' do + let(:implied_role) do + Puppet::Type.type(:keystone_implied_role).new(:title => 'child@parent') + end + describe 'should require the correct domain' do + let(:resources) { [implied_role, child, parent, another] } + include_examples 'autorequire the correct resources' + end + end + end +end