Skip to content

Commit 4aefd60

Browse files
authored
Implement purge_behavior parameter (#60)
* Implement `purge_behavior` parameter This allows for specifying classes or data keys that should be present or have defined values, without removing or modifying other data already present in the node group. * Implement tests for purge_behavior * Fix bugs in purge_behavior implementation Also simplify merge logic * Clean up rspec tests Update all mocking to rspec mocks, update gem versions * Increase test coverage, simplify purge_behavior There should be no need to define a special insync? method for the classes and data properties, given that we set `should` to an appropriate value, and Ruby compares hashes in a way such that key order is irrelevant. * Further simplify tests For easier reading
1 parent b058fcf commit 4aefd60

File tree

8 files changed

+199
-23
lines changed

8 files changed

+199
-23
lines changed

Diff for: CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## Unreleased
2+
3+
- Add `purge_behavior` parameter to node\_group resource type
4+
15
## 2019-12-27 - Release 0.7.3
26

37
## Summary

Diff for: Gemfile

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
source "https://rubygems.org"
2+
gem 'pry'
23

34
group :test do
45
gem "rake"
56
# gem "puppet", ENV['PUPPET_VERSION'] || '~> 3.7.3'
67
gem "rspec-puppet", :git => 'https://github.com/rodjek/rspec-puppet.git'
7-
# gem "puppetlabs_spec_helper"
88
gem 'rspec-puppet-utils', :git => 'https://github.com/Accuity/rspec-puppet-utils.git'
99
gem 'hiera-puppet-helper', :git => 'https://github.com/bobtfish/hiera-puppet-helper.git'
1010
# there seems to be a bug with puppet-blacksmith and metadata-json-lint
@@ -26,6 +26,6 @@ end
2626
source 'https://rubygems.org'
2727

2828
gem 'puppet', nil || ENV['PUPPET_VERSION']
29-
gem 'puppetlabs_spec_helper', '0.10.3'
30-
gem 'webmock', '1.22.1'
29+
gem 'puppetlabs_spec_helper'
30+
gem 'webmock'
3131
gem 'puppetclassify', '0.1.7'

Diff for: README.md

+8
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,14 @@ This parameter is supported for PE >=2017.3.x.
135135

136136
Default (empty hash): `{}`
137137

138+
* `purge_behavior`
139+
140+
Defines how purging of classification or data will be handled. By default, or when set to `all`, the node\_group resource will ensure classes and data are matched exactly, and remove any values not described by the resource. When set to `none`, the node\_group resource will ensure data and classes described are present with the prescribed values, but will not remove other classification, or other data, present in the node group. The `data` setting purges only data values, and the `classes` setting purges only classes values.
141+
142+
Default: `all`
143+
144+
Values: `all`, `data`, `classes`, `none`
145+
138146
## Tasks
139147

140148
### update_classes

Diff for: lib/puppet/type/node_group.rb

+27
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@
66
newparam(:name, :namevar => true) do
77
desc 'This is the common name for the node group'
88
end
9+
newparam(:purge_behavior) do
10+
desc 'Whether or not to remove data or class parameters not specified'
11+
newvalues(:none, :data, :classes, :all)
12+
defaultto :all
13+
end
914
newproperty(:id) do
1015
desc 'The ID of the group'
1116
validate do |value|
@@ -45,6 +50,17 @@
4550
munge do |value|
4651
PuppetX::Node_manager::Common.sort_hash(value)
4752
end
53+
def should
54+
case @resource[:purge_behavior]
55+
when :classes, :all
56+
super
57+
else
58+
a = @resource.property(:classes).retrieve || {}
59+
b = shouldorig.first
60+
merged = a.merge(b) { |k, x, y| x.merge(y) }
61+
PuppetX::Node_manager::Common.sort_hash(merged)
62+
end
63+
end
4864
end
4965
newproperty(:data) do
5066
desc 'Data applied to this group'
@@ -56,6 +72,17 @@
5672
munge do |value|
5773
PuppetX::Node_manager::Common.sort_hash(value)
5874
end
75+
def should
76+
case @resource[:purge_behavior]
77+
when :data, :all
78+
super
79+
else
80+
a = @resource.property(:data).retrieve || {}
81+
b = shouldorig.first
82+
merged = a.merge(b) { |k, x, y| x.merge(y) }
83+
PuppetX::Node_manager::Common.sort_hash(merged)
84+
end
85+
end
5986
end
6087
newproperty(:description) do
6188
desc 'Description of this group'

Diff for: spec/functions/node_groups_spec.rb

+13-7
Original file line numberDiff line numberDiff line change
@@ -71,20 +71,26 @@
7171
}
7272

7373
before do
74-
YAML.stubs(:load_file).returns({
75-
'server' => 'stubserver',
76-
'port' => '8080',
77-
})
74+
allow(YAML).to(receive(:load_file))
75+
.with('/dev/null/classifier.yaml')
76+
.and_return({'server' => 'stubserver', 'port' => '8080'})
77+
78+
allow(File).to receive(:read).and_call_original
79+
allow(File).to receive(:read).with(%r{/dev/null/ssl/}).and_return('helloworld')
80+
81+
allow(OpenSSL::X509::Certificate).to(receive(:new))
82+
.and_return(double("Certificate", :save => true))
83+
84+
allow(OpenSSL::PKey::RSA).to(receive(:new))
85+
.and_return(double("Key", :save => true))
86+
7887
stub_request(
7988
:get,
8089
'https://stubserver:8080/classifier-api/v1/groups',
8190
).to_return(
8291
:status => 200,
8392
:body => groups_response
8493
)
85-
File.stubs(:read).returns('helloworld')
86-
OpenSSL::X509::Certificate.stubs(:new) {mock_model(OpenSSL::X509::Certificate, :save => true)}
87-
OpenSSL::PKey::RSA.stubs(:new) {mock_model(OpenSSL::PKey::RSA, :save => true)}
8894
end
8995

9096
describe 'without an argument' do

Diff for: spec/integration/puppet/provider/node_group/https_spec.rb

+13-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
require 'puppetlabs_spec_helper/module_spec_helper'
1+
require 'spec_helper'
22
require 'webmock/rspec'
33

44
describe Puppet::Type.type(:node_group).provider(:https) do
@@ -58,11 +58,18 @@
5858
end
5959

6060
before do
61-
YAML.stubs(:load_file).with('/dev/null/classifier.yaml')
62-
.returns({'server' => 'stubserver', 'port' => '8080'})
63-
File.stubs(:read).returns('helloworld')
64-
OpenSSL::X509::Certificate.stubs(:new) {mock_model(OpenSSL::X509::Certificate, :save => true)}
65-
OpenSSL::PKey::RSA.stubs(:new) {mock_model(OpenSSL::PKey::RSA, :save => true)}
61+
allow(YAML).to(receive(:load_file))
62+
.with('/dev/null/classifier.yaml')
63+
.and_return({'server' => 'stubserver', 'port' => '8080'})
64+
65+
allow(File).to receive(:read).and_call_original
66+
allow(File).to receive(:read).with(%r{/dev/null/ssl/}).and_return('helloworld')
67+
68+
allow(OpenSSL::X509::Certificate).to(receive(:new))
69+
.and_return(double("Certificate", :save => true))
70+
71+
allow(OpenSSL::PKey::RSA).to(receive(:new))
72+
.and_return(double("Key", :save => true))
6673
end
6774

6875
describe "#instances" do

Diff for: spec/spec_helper.rb

+6-5
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
require 'puppetlabs_spec_helper/module_spec_helper'
21
require 'rspec-puppet-utils'
32

43
# Uncomment this to show coverage report, also useful for debugging
54
#at_exit { RSpec::Puppet::Coverage.report! }
65

7-
#RSpec.configure do |c|
8-
# c.formatter = 'documentation'
9-
# config.mock_with :rspec
10-
#end
6+
RSpec.configure do |c|
7+
c.mock_with :rspec
8+
c.mock_framework = :rspec
9+
end
10+
11+
require 'puppetlabs_spec_helper/module_spec_helper'

Diff for: spec/unit/puppet/type/node_group_spec.rb

+125-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
require 'puppetlabs_spec_helper/module_spec_helper'
1+
require 'spec_helper'
22

33
describe Puppet::Type.type(:node_group) do
4-
54
it "should allow agent-specified environment" do
65
expect {
76
Puppet::Type.type(:node_group).new(
@@ -73,4 +72,128 @@
7372
}.to_not raise_error
7473
end
7574

75+
describe "purge_behavior" do
76+
let(:resource_hash) do
77+
{
78+
:name => 'test_group',
79+
:environment => 'test_env',
80+
:data => {
81+
'data::class1' => { 'param1' => 'resource',
82+
'param2' => 'resource' },
83+
'data::class2' => { 'param1' => 'resource',
84+
'param2' => 'resource' },
85+
},
86+
:classes => {
87+
'classes::class1' => { 'param1' => 'resource',
88+
'param2' => 'resource' },
89+
},
90+
}
91+
end
92+
93+
let(:existing_data) do
94+
{ 'data::class1' => { 'param1' => 'existing',
95+
'param3' => 'existing' },
96+
'data::class3' => { 'param1' => 'existing',
97+
'param2' => 'existing' }}
98+
end
99+
let(:merged_data) do
100+
{ "data::class1" => { "param1" => "resource",
101+
"param2" => "resource",
102+
"param3" => "existing"},
103+
"data::class2" => { "param1" => "resource",
104+
"param2" => "resource"},
105+
"data::class3" => { "param1" => "existing",
106+
"param2" => "existing"}}
107+
end
108+
109+
let(:existing_classes) do
110+
{ 'classes::class1' => { 'param1' => 'existing',
111+
'param3' => 'existing' },
112+
'classes::class3' => { 'param1' => 'existing',
113+
'param2' => 'existing' }}
114+
end
115+
let(:merged_classes) do
116+
{ "classes::class1" => { "param1" => "resource",
117+
"param2" => "resource",
118+
"param3" => "existing"},
119+
"classes::class3" => { "param1" => "existing",
120+
"param2" => "existing"}}
121+
end
122+
123+
it "should match classes and data exactly by default" do
124+
rsrc = described_class.new(resource_hash)
125+
allow(rsrc.property(:data)).to receive(:retrieve).and_return(existing_data)
126+
allow(rsrc.property(:classes)).to receive(:retrieve).and_return(existing_classes)
127+
expect(rsrc.property(:data).should).to eq resource_hash[:data]
128+
expect(rsrc.property(:classes).should).to eq resource_hash[:classes]
129+
end
130+
131+
it "should merge in classes and data when set to :none" do
132+
rsrc = described_class.new(resource_hash.merge(:purge_behavior => 'none'))
133+
allow(rsrc.property(:data)).to receive(:retrieve).and_return(existing_data)
134+
allow(rsrc.property(:classes)).to receive(:retrieve).and_return(existing_classes)
135+
expect(rsrc.property(:data).should).to eq (merged_data)
136+
expect(rsrc.property(:classes).should).to eq (merged_classes)
137+
end
138+
139+
it "should merge in classes and match data exactly when set to :data" do
140+
rsrc = described_class.new(resource_hash.merge(:purge_behavior => 'data'))
141+
allow(rsrc.property(:data)).to receive(:retrieve).and_return(existing_data)
142+
allow(rsrc.property(:classes)).to receive(:retrieve).and_return(existing_classes)
143+
expect(rsrc.property(:data).should).to eq (resource_hash[:data])
144+
expect(rsrc.property(:classes).should).to eq (merged_classes)
145+
end
146+
147+
it "should merge in data and match classes exactly when set to :classes" do
148+
rsrc = described_class.new(resource_hash.merge(:purge_behavior => 'classes'))
149+
allow(rsrc.property(:data)).to receive(:retrieve).and_return(existing_data)
150+
allow(rsrc.property(:classes)).to receive(:retrieve).and_return(existing_classes)
151+
expect(rsrc.property(:data).should).to eq (merged_data)
152+
expect(rsrc.property(:classes).should).to eq (resource_hash[:classes])
153+
end
154+
end
155+
156+
describe ".insync? for data, classes" do
157+
let(:hash) do
158+
{
159+
'class1' => { 'param1' => 'value1',
160+
'param2' => 'value2' },
161+
'class2' => { 'param1' => 'value1',
162+
'param2' => 'value2' },
163+
'class3' => { 'param1' => 'value1',
164+
'param2' => 'value2' },
165+
}
166+
end
167+
let(:resource) do
168+
described_class.new({
169+
:name => 'test_group',
170+
:environment => 'test_env',
171+
:classes => hash,
172+
:data => hash,
173+
})
174+
end
175+
176+
before(:each) do
177+
allow(resource.property(:data)).to receive(:should).and_return(hash)
178+
allow(resource.property(:classes)).to receive(:should).and_return(hash)
179+
end
180+
181+
it 'is insync when `is` and `should` are identical' do
182+
expect(resource.property(:data).insync?(hash)).to eq(true)
183+
expect(resource.property(:classes).insync?(hash)).to eq(true)
184+
end
185+
186+
it 'is insync when `is` and `should` are identical but have different ordering' do
187+
reverse_hash = hash.to_a.map{ |i| [i[0], i[1].to_a.reverse.to_h] }.reverse.to_h
188+
expect(resource.property(:data).insync?(reverse_hash)).to eq(true)
189+
expect(resource.property(:classes).insync?(reverse_hash)).to eq(true)
190+
end
191+
192+
it 'is not insync when `is` is only a subset of `should`' do
193+
subset = hash.select { |k| k != 'class2' }
194+
expect(resource.property(:data).insync?(subset)).to eq(false)
195+
expect(resource.property(:classes).insync?(subset)).to eq(false)
196+
end
197+
end
198+
76199
end

0 commit comments

Comments
 (0)