From d3be001c1497df0bee61bcb5fb48177e8e9df357 Mon Sep 17 00:00:00 2001 From: Dave Armstrong Date: Tue, 27 Mar 2018 11:11:38 +0100 Subject: [PATCH 1/3] Unit test fix ups --- spec/puppet/resource_api_spec.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/puppet/resource_api_spec.rb b/spec/puppet/resource_api_spec.rb index 0c5870a0..69980f5f 100644 --- a/spec/puppet/resource_api_spec.rb +++ b/spec/puppet/resource_api_spec.rb @@ -359,10 +359,10 @@ def extract_values(function) it { expect { described_class.register_type(definition) }.not_to raise_error } describe 'the registered type' do - subject(:type) { Puppet::Type.type(:with_parameters) } + subject(:type) { Puppet::Type.type(:no_type) } it { is_expected.not_to be_nil } - it { expect(type.parameters[1]).to eq :test_string } + it { expect(type.parameters[0]).to eq :name } end end @@ -382,10 +382,10 @@ def extract_values(function) it { expect { described_class.register_type(definition) }.not_to raise_error } describe 'the registered type' do - subject(:type) { Puppet::Type.type(:with_parameters) } + subject(:type) { Puppet::Type.type(:behaviour) } it { is_expected.not_to be_nil } - it { expect(type.parameters[1]).to eq :test_string } + it { expect(type.parameters[0]).to eq :name } end end From ebf7586866fba8237ecf827a281696e374523327 Mon Sep 17 00:00:00 2001 From: Dave Armstrong Date: Wed, 28 Mar 2018 17:09:34 +0100 Subject: [PATCH 2/3] Use dev specific error type for canonicalization checks --- lib/puppet/resource_api.rb | 2 +- spec/puppet/resource_api_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/puppet/resource_api.rb b/lib/puppet/resource_api.rb index 5985f7a2..546d55b0 100644 --- a/lib/puppet/resource_api.rb +++ b/lib/puppet/resource_api.rb @@ -302,7 +302,7 @@ def feature_support?(feature) when :warning Puppet.warning(message) when :error - raise Puppet::Error, message + raise Puppet::DevError, message end return nil diff --git a/spec/puppet/resource_api_spec.rb b/spec/puppet/resource_api_spec.rb index 69980f5f..99060e0f 100644 --- a/spec/puppet/resource_api_spec.rb +++ b/spec/puppet/resource_api_spec.rb @@ -592,7 +592,7 @@ def set(_context, changes) it 'will throw an exception' do expect { instance.strict_check({}) - }.to raise_error(Puppet::Error, %r{has not provided canonicalized values}) + }.to raise_error(Puppet::DevError, %r{has not provided canonicalized values}) end end From 6d215b7387bd3158996da4d78db3c587a16c5ee6 Mon Sep 17 00:00:00 2001 From: Dave Armstrong Date: Wed, 28 Mar 2018 17:11:24 +0100 Subject: [PATCH 3/3] (PDK-885) Add support for init_only attributes --- lib/puppet/resource_api.rb | 16 +++- spec/puppet/resource_api_spec.rb | 129 +++++++++++++++++++++++++++++++ 2 files changed, 144 insertions(+), 1 deletion(-) diff --git a/lib/puppet/resource_api.rb b/lib/puppet/resource_api.rb index 546d55b0..5486cf7e 100644 --- a/lib/puppet/resource_api.rb +++ b/lib/puppet/resource_api.rb @@ -105,7 +105,7 @@ def feature_support?(feature) # puts "#{name}: #{options.inspect}" if options[:behaviour] - unless [:read_only, :namevar, :parameter].include? options[:behaviour] + unless [:read_only, :namevar, :parameter, :init_only].include? options[:behaviour] raise Puppet::ResourceError, "`#{options[:behaviour]}` is not a valid behaviour value" end end @@ -270,6 +270,20 @@ def feature_support?(feature) Puppet.debug("Target State: #{target_state.inspect}") + # enforce init_only attributes + if Puppet.settings[:strict] != :off && @rapi_current_state && (@rapi_current_state[:ensure] == :present && target_state[:ensure] == :present) + target_state.each do |name, value| + next unless definition[:attributes][name][:behaviour] == :init_only && value != @rapi_current_state[name] + message = "Attempting to change `#{name}` init_only attribute value from `#{@rapi_current_state[name]}` to `#{value}`" + case Puppet.settings[:strict] + when :warning + Puppet.warning(message) + when :error + raise Puppet::ResourceError, message + end + end + end + if feature_support?('supports_noop') my_provider.set(context, { title => { is: @rapi_current_state, should: target_state } }, noop: noop?) else diff --git a/spec/puppet/resource_api_spec.rb b/spec/puppet/resource_api_spec.rb index 99060e0f..c5b6675e 100644 --- a/spec/puppet/resource_api_spec.rb +++ b/spec/puppet/resource_api_spec.rb @@ -389,6 +389,119 @@ def extract_values(function) end end + context 'when registering a type with an `init_only` attribute', agent_test: true do + let(:definition) do + { + name: 'init_behaviour', + attributes: { + ensure: { + type: 'Enum[present, absent]', + }, + name: { + type: 'String', + behavior: :namevar, + }, + immutable: { + type: 'String', + behaviour: :init_only, + }, + mutable: { + type: 'String', + }, + }, + } + end + + it { expect { described_class.register_type(definition) }.not_to raise_error } + + describe 'the registered type' do + subject(:type) { Puppet::Type.type(:init_behaviour) } + + it { is_expected.not_to be_nil } + it { expect(type.parameters[0]).to eq :name } + end + + describe 'an instance of the type' do + let(:provider_class) do + Class.new do + def get(_context) + [{ name: 'init', ensure: :present, immutable: 'physics', mutable: 'bank balance' }] + end + + def set(_context, _changes); end + end + end + + before(:each) do + stub_const('Puppet::Provider::InitBehaviour', Module.new) + stub_const('Puppet::Provider::InitBehaviour::InitBehaviour', provider_class) + end + + context 'when a manifest wants to set the value of an init_only attribute' do + let(:instance) { Puppet::Type.type(:init_behaviour).new(name: 'new_init', ensure: :present, immutable: 'new', mutable: 'flexible') } + + context 'when Puppet strict setting is :error' do + let(:strict_level) { :error } + + it { expect { instance.flush }.not_to raise_error } + it { + expect(Puppet).not_to receive(:warning) + instance.flush + } + end + + context 'when Puppet strict setting is :warning' do + let(:strict_level) { :warning } + + it { expect { instance.flush }.not_to raise_error } + it { + expect(Puppet).not_to receive(:warning) + instance.flush + } + end + + context 'when Puppet strict setting is :off' do + let(:strict_level) { :off } + + it { expect { instance.flush }.not_to raise_error } + it { + expect(Puppet).not_to receive(:warning) + instance.flush + } + end + end + + context 'when a manifest wants to change the value of an init_only attribute' do + let(:instance) { Puppet::Type.type(:init_behaviour).new(name: 'init', ensure: :present, immutable: 'lies', mutable: 'overdraft') } + + context 'when Puppet strict setting is :error' do + let(:strict_level) { :error } + + it { expect { instance.flush }.to raise_error Puppet::ResourceError, %r{Attempting to change `immutable` init_only attribute value from} } + end + + context 'when Puppet strict setting is :warning' do + let(:strict_level) { :warning } + + it { + expect(Puppet).to receive(:warning).with(%r{Attempting to change `immutable` init_only attribute value from}) + instance.flush + } + end + + context 'when Puppet strict setting is :off' do + let(:strict_level) { :off } + + it { expect { instance.flush }.not_to raise_error } + it { + expect(Puppet).not_to receive(:warning) + instance.flush + } + end + end + end + end + context 'when registering a type with a malformed attributes' do context 'without a type' do let(:definition) do @@ -1096,6 +1209,22 @@ def set(_context, changes) end it { expect { described_class.register_type(definition) }.not_to raise_error } end + context 'with :init_only behaviour' do + let(:definition) do + { + name: 'test_behaviour', + attributes: { + param_ro: { + type: 'String', + behavior: :init_only, + }, + }, + } + end + + it { expect { described_class.register_type(definition) }.not_to raise_error } + end + context 'with :namevar behaviour' do let(:definition) do {