diff --git a/lib/puppet/provider/glance_property_protections_config/ini_setting.rb b/lib/puppet/provider/glance_property_protections_config/ini_setting.rb new file mode 100644 index 00000000..baedfc11 --- /dev/null +++ b/lib/puppet/provider/glance_property_protections_config/ini_setting.rb @@ -0,0 +1,8 @@ +Puppet::Type.type(:glance_property_protections_config).provide( + :ini_setting, + :parent => Puppet::Type.type(:openstack_config).provider(:ini_setting) +) do + def self.file_path + '/etc/glance/property-protections.conf' + end +end diff --git a/lib/puppet/type/glance_property_protections_config.rb b/lib/puppet/type/glance_property_protections_config.rb new file mode 100644 index 00000000..d118590f --- /dev/null +++ b/lib/puppet/type/glance_property_protections_config.rb @@ -0,0 +1,27 @@ +Puppet::Type.newtype(:glance_property_protections_config) do + + ensurable + + newparam(:name, :namevar => true) do + desc 'Section/setting name to manage from property-protections.conf' + newvalues(/\S+\/\S+/) + end + + newparam(:ensure_absent_val) do + desc 'A value that is specified as the value property will behave as if ensure => absent was specified' + defaultto('') + end + + autorequire(:anchor) do + ['glance::install::end'] + end + + newproperty(:value) do + desc 'The value of the setting to be defined.' + munge do |value| + value = value.to_s.strip + value.capitalize! if value =~ /^(true|false)$/i + value + end + end +end diff --git a/manifests/deps.pp b/manifests/deps.pp index df12c517..ea5dc05d 100644 --- a/manifests/deps.pp +++ b/manifests/deps.pp @@ -50,6 +50,7 @@ class glance::deps { Anchor['glance::config::begin'] -> Glance_image_import_config<||> ~> Anchor['glance::config::end'] Anchor['glance::config::begin'] -> Glance_swift_config<||> ~> Anchor['glance::config::end'] Anchor['glance::config::begin'] -> Glance_rootwrap_config<||> ~> Anchor['glance::config::end'] + Anchor['glance::config::begin'] -> Glance_property_protections_config<||> ~> Anchor['glance::config::end'] # glance-cache.conf is used by CLI commands so service restart is not needed Anchor['glance::config::begin'] -> Glance_cache_config<||> -> Anchor['glance::config::end'] diff --git a/manifests/property_protection.pp b/manifests/property_protection.pp new file mode 100644 index 00000000..871aed4a --- /dev/null +++ b/manifests/property_protection.pp @@ -0,0 +1,78 @@ +# == Class: glance::property_protection +# +# Configure property protection +# +# === Parameters +# +# [*property_protection_rule_format*] +# (Optional) Rule format for property protection. +# Defaults to undef +# +# [*rules*] +# (Optional) Property protection rules +# Defaults to undef +# +# [*purge_config*] +# (Optional) Whether to set only the specified config options +# in the property protections config. +# Defaults to false. +# +class glance::property_protection( + Optional[Enum['roles', 'policies']] $property_protection_rule_format = undef, + Hash[String[1], Hash] $rules = {}, + Boolean $purge_config = false, +) { + + include glance::deps + include glance::params + + resources { 'glance_property_protections_config': + purge => $purge_config, + } + + case $property_protection_rule_format { + 'roles', 'policies': { + glance_api_config { + 'DEFAULT/property_protection_file': value => '/etc/glance/property-protections.conf'; + 'DEFAULT/property_protection_rule_format': value => $property_protection_rule_format; + } + + # NOTE(tkajinam): property-protections.conf is not installed by + # the packages so create the file in advance. + file { '/etc/glance/property-protections.conf': + ensure => 'file', + owner => 'root', + group => $::glance::params::group, + mode => '0640', + require => Anchor['glance::config::begin'], + notify => Anchor['glance::config::end'], + } + + $rules.each |$key, $value| { + $value_override = $value['value'] ? { + undef => {}, + default => {'value' => join(any2array($value['value']), ',')}, + } + + create_resources( + 'glance_property_protections_config', + { $key => merge($value, $value_override)} + ) + } + + File['/etc/glance/property-protections.conf'] -> Glance_property_protections_config<||> + } + default: { + glance_api_config { + 'DEFAULT/property_protection_file': value => $facts['os_service_default']; + 'DEFAULT/property_protection_rule_format': value => $facts['os_service_default']; + } + + file { '/etc/glance/property-protections.conf': + ensure => absent, + require => Anchor['glance::config::begin'], + notify => Anchor['glance::config::end'], + } + } + } +} diff --git a/releasenotes/notes/property-protections-3db869c274eb175f.yaml b/releasenotes/notes/property-protections-3db869c274eb175f.yaml new file mode 100644 index 00000000..310a1929 --- /dev/null +++ b/releasenotes/notes/property-protections-3db869c274eb175f.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + The new ``glance::property_protections`` class has been added. This class + allows configuration of the image property protections feature. diff --git a/spec/acceptance/99_glance_config_spec.rb b/spec/acceptance/99_glance_config_spec.rb index 03621b8b..e963d24d 100644 --- a/spec/acceptance/99_glance_config_spec.rb +++ b/spec/acceptance/99_glance_config_spec.rb @@ -14,6 +14,7 @@ describe 'basic glance config resource' do File <||> -> Glance_swift_config <||> File <||> -> Glance_api_paste_ini <||> File <||> -> Glance_rootwrap_config <||> + File <||> -> Glance_property_protections_config <||> file { '/etc/glance' : ensure => directory, @@ -33,6 +34,9 @@ describe 'basic glance config resource' do file { '/etc/glance/rootwrap.conf' : ensure => file, } + file { '/etc/glance/property-protections.conf' : + ensure => file, + } glance_api_config { 'DEFAULT/thisshouldexist' : value => 'foo', @@ -154,6 +158,24 @@ describe 'basic glance config resource' do value => 'toto', ensure_absent_val => 'toto', } + + glance_property_protections_config { 'DEFAULT/thisshouldexist' : + value => 'foo', + } + + glance_property_protections_config { 'DEFAULT/thisshouldnotexist' : + value => '', + } + + glance_property_protections_config { 'DEFAULT/thisshouldexist2' : + value => '', + ensure_absent_val => 'toto', + } + + glance_property_protections_config { 'DEFAULT/thisshouldnotexist2' : + value => 'toto', + ensure_absent_val => 'toto', + } EOS @@ -232,5 +254,16 @@ describe 'basic glance config resource' do it { is_expected.not_to match /thisshouldnotexist/ } end end + + describe file('/etc/glance/property-protections.conf') do + it { is_expected.to exist } + it { is_expected.to contain('thisshouldexist=foo') } + it { is_expected.to contain('thisshouldexist2=') } + + describe '#content' do + subject { super().content } + it { is_expected.not_to match /thisshouldnotexist/ } + end + end end end diff --git a/spec/classes/glance_property_protections_spec.rb b/spec/classes/glance_property_protections_spec.rb new file mode 100644 index 00000000..4d48f537 --- /dev/null +++ b/spec/classes/glance_property_protections_spec.rb @@ -0,0 +1,109 @@ +require 'spec_helper' + +describe 'glance::property_protection' do + shared_examples 'glance::property_protection' do + + context 'with defaults' do + it 'configures the property protection parameters' do + is_expected.to contain_glance_api_config('DEFAULT/property_protection_file') + .with_value('') + is_expected.to contain_glance_api_config('DEFAULT/property_protection_rule_format') + .with_value('') + end + + it 'shoul remove the property protection config file' do + is_expected.to contain_file('/etc/glance/property-protections.conf').with( + :ensure => 'absent', + ) + end + end + + context 'with parameters (policies format)' do + let :params do + { + :property_protection_rule_format => 'policies', + :rules => { + '^x_.*/create' => { 'value' => 'default' }, + '^x_.*/read' => { 'value' => 'default' }, + '^x_.*/update' => { 'value' => 'default' }, + '^x_.*/delete' => { 'value' => 'default' }, + } + } + end + + it 'configures the property protection parameters' do + is_expected.to contain_glance_api_config('DEFAULT/property_protection_file') + .with_value('/etc/glance/property-protections.conf') + is_expected.to contain_glance_api_config('DEFAULT/property_protection_rule_format') + .with_value('policies') + end + + it 'should configure the property protection config file' do + is_expected.to contain_file('/etc/glance/property-protections.conf').with( + :ensure => 'file', + :owner => 'root', + :group => 'glance', + :mode => '0640', + ) + is_expected.to contain_glance_property_protections_config('^x_.*/create') + .with_value('default') + is_expected.to contain_glance_property_protections_config('^x_.*/read') + .with_value('default') + is_expected.to contain_glance_property_protections_config('^x_.*/update') + .with_value('default') + is_expected.to contain_glance_property_protections_config('^x_.*/delete') + .with_value('default') + end + end + + context 'with parameters (roles format)' do + let :params do + { + :property_protection_rule_format => 'roles', + :rules => { + '^x_.*/create' => { 'value' => ['admin', 'member', '_member_'] }, + '^x_.*/read' => { 'value' => ['admin', 'member', '_member_'] }, + '^x_.*/update' => { 'value' => ['admin', 'member', '_member_'] }, + '^x_.*/delete' => { 'value' => ['admin', 'member', '_member_'] }, + } + } + end + + it 'configures the property protection parameters' do + is_expected.to contain_glance_api_config('DEFAULT/property_protection_file') + .with_value('/etc/glance/property-protections.conf') + is_expected.to contain_glance_api_config('DEFAULT/property_protection_rule_format') + .with_value('roles') + end + + it 'should configure the property protection config file' do + is_expected.to contain_file('/etc/glance/property-protections.conf').with( + :ensure => 'file', + :owner => 'root', + :group => 'glance', + :mode => '0640', + ) + is_expected.to contain_glance_property_protections_config('^x_.*/create') + .with_value('admin,member,_member_') + is_expected.to contain_glance_property_protections_config('^x_.*/read') + .with_value('admin,member,_member_') + is_expected.to contain_glance_property_protections_config('^x_.*/update') + .with_value('admin,member,_member_') + is_expected.to contain_glance_property_protections_config('^x_.*/delete') + .with_value('admin,member,_member_') + end + end + end + + on_supported_os({ + :supported_os => OSDefaults.get_supported_os + }).each do |os,facts| + context "on #{os}" do + let (:facts) do + facts.merge!(OSDefaults.get_facts()) + end + + it_behaves_like 'glance::property_protection' + end + end +end diff --git a/spec/unit/provider/glance_property_protections/ini_setting_spec.rb b/spec/unit/provider/glance_property_protections/ini_setting_spec.rb new file mode 100644 index 00000000..af1461e2 --- /dev/null +++ b/spec/unit/provider/glance_property_protections/ini_setting_spec.rb @@ -0,0 +1,41 @@ +require 'spec_helper' +provider_class = Puppet::Type.type(:glance_property_protections_config).provider(:ini_setting) +describe provider_class do + + it 'should default to the default setting when no other one is specified' do + resource = Puppet::Type::Glance_property_protections_config.new( + {:name => 'DEFAULT/foo', :value => 'bar'} + ) + provider = provider_class.new(resource) + expect(provider.section).to eq('DEFAULT') + expect(provider.setting).to eq('foo') + end + + it 'should allow setting to be set explicitly' do + resource = Puppet::Type::Glance_property_protections_config.new( + {:name => 'dude/foo', :value => 'bar'} + ) + provider = provider_class.new(resource) + expect(provider.section).to eq('dude') + expect(provider.setting).to eq('foo') + end + + it 'should ensure absent when is specified as a value' do + resource = Puppet::Type::Glance_property_protections_config.new( + {:name => 'dude/foo', :value => ''} + ) + provider = provider_class.new(resource) + provider.exists? + expect(resource[:ensure]).to eq :absent + end + + it 'should ensure absent when value matches ensure_absent_val' do + resource = Puppet::Type::Glance_property_protections_config.new( + {:name => 'dude/foo', :value => 'foo', :ensure_absent_val => 'foo' } + ) + provider = provider_class.new(resource) + provider.exists? + expect(resource[:ensure]).to eq :absent + end + +end diff --git a/spec/unit/type/glance_property_protections_config_spec.rb b/spec/unit/type/glance_property_protections_config_spec.rb new file mode 100644 index 00000000..e466d4d5 --- /dev/null +++ b/spec/unit/type/glance_property_protections_config_spec.rb @@ -0,0 +1,64 @@ +require 'puppet' +require 'puppet/type/glance_property_protections_config' + +describe 'Puppet::Type.type(:glance_property_protections_config)' do + before :each do + @glance_property_protections_config = Puppet::Type.type(:glance_property_protections_config).new(:name => 'DEFAULT/foo', :value => 'bar') + end + + it 'should require a name' do + expect { + Puppet::Type.type(:glance_property_protections_config).new({}) + }.to raise_error(Puppet::Error, 'Title or name must be provided') + end + + it 'should not expect a name with whitespace' do + expect { + Puppet::Type.type(:glance_property_protections_config).new(:name => 'f oo') + }.to raise_error(Puppet::Error, /Parameter name failed/) + end + + it 'should fail when there is no section' do + expect { + Puppet::Type.type(:glance_property_protections_config).new(:name => 'foo') + }.to raise_error(Puppet::Error, /Parameter name failed/) + end + + it 'should not require a value when ensure is absent' do + Puppet::Type.type(:glance_property_protections_config).new(:name => 'DEFAULT/foo', :ensure => :absent) + end + + it 'should accept a valid value' do + @glance_property_protections_config[:value] = 'bar' + expect(@glance_property_protections_config[:value]).to eq('bar') + end + + it 'should accept a value with whitespace' do + @glance_property_protections_config[:value] = 'b ar' + expect(@glance_property_protections_config[:value]).to eq('b ar') + end + + it 'should accept valid ensure values' do + @glance_property_protections_config[:ensure] = :present + expect(@glance_property_protections_config[:ensure]).to eq(:present) + @glance_property_protections_config[:ensure] = :absent + expect(@glance_property_protections_config[:ensure]).to eq(:absent) + end + + it 'should not accept invalid ensure values' do + expect { + @glance_property_protections_config[:ensure] = :latest + }.to raise_error(Puppet::Error, /Invalid value/) + end + + it 'should autorequire the package that install the file' do + catalog = Puppet::Resource::Catalog.new + anchor = Puppet::Type.type(:anchor).new(:name => 'glance::install::end') + catalog.add_resource anchor, @glance_property_protections_config + dependency = @glance_property_protections_config.autorequire + expect(dependency.size).to eq(1) + expect(dependency[0].target).to eq(@glance_property_protections_config) + expect(dependency[0].source).to eq(anchor) + end + +end