Skip to content

Commit 66fe423

Browse files
committed
(gh-26) Update rather than overwrite puppet.conf
In particular, openvox-server lays down some settings in puppet.conf at package installation time, so it would be best if this task didn't throw out an existing puppet.conf. Using puppet-config set is safe (it's what the openvox-server package uses); it does not trigger any of puppet's auto certificate generation code the way an agent run does.
1 parent 809a156 commit 66fe423

File tree

3 files changed

+173
-43
lines changed

3 files changed

+173
-43
lines changed

spec/tasks/configure_spec.rb

Lines changed: 114 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,45 @@
55

66
# rubocop:disable RSpec/MessageSpies
77
# rubocop:disable RSpec/StubbedMock
8+
# rubocop:disable RSpec/MultipleMemoizedHelpers
89
describe 'openvox_bootstrap::configure' do
910
let(:tmpdir) { Dir.mktmpdir('openvox_bootstrap-configure-spec') }
1011
let(:task) { OpenvoxBootstrap::Configure.new }
12+
let(:puppet_config_set_calls) { [] }
1113

1214
around do |example|
1315
example.run
1416
ensure
1517
FileUtils.remove_entry_secure(tmpdir)
1618
end
1719

18-
describe '#write_puppet_conf' do
20+
before do
21+
allow(task).to receive(:puppet_config_set) do |section, key, value|
22+
puppet_config_set_calls << [section, key, value]
23+
if key == 'oops'
24+
['error output', instance_double(Process::Status, success?: false)]
25+
else
26+
['', instance_double(Process::Status, success?: true)]
27+
end
28+
end
29+
end
30+
31+
describe '#puppet_config_set' do
32+
it 'calls puppet config set with the correct arguments' do
33+
expect(Open3).to receive(:capture2e).with(
34+
'/opt/puppetlabs/bin/puppet',
35+
'config',
36+
'set',
37+
'--section', 'main',
38+
'server',
39+
'puppet.spec'
40+
)
41+
t = OpenvoxBootstrap::Configure.new
42+
t.puppet_config_set('main', 'server', 'puppet.spec')
43+
end
44+
end
45+
46+
describe '#update_puppet_conf' do
1947
let(:puppet_conf) do
2048
{
2149
'main' => {
@@ -28,34 +56,49 @@
2856
}
2957
end
3058
let(:puppet_conf_path) { File.join(tmpdir, 'puppet.conf') }
31-
let(:puppet_conf_contents) do
32-
<<~CONF
33-
[main]
34-
server = puppet.spec
35-
certname = agent.spec
36-
37-
[agent]
38-
environment = test
39-
CONF
40-
end
4159

42-
it 'writes a puppet.conf ini' do
43-
expect(task.write_puppet_conf(puppet_conf, tmpdir)).to(
60+
it 'call puppet config set' do
61+
expect(task.update_puppet_conf(puppet_conf, tmpdir)).to(
4462
eq(
4563
{
4664
puppet_conf: {
4765
path: puppet_conf_path,
48-
contents: puppet_conf_contents
66+
contents: '',
67+
successful: true,
4968
}
5069
}
5170
)
5271
)
53-
expect(File.read(puppet_conf_path)).to eq(puppet_conf_contents)
72+
expect(puppet_config_set_calls).to eq(
73+
[
74+
['main', 'server', 'puppet.spec'],
75+
['main', 'certname', 'agent.spec'],
76+
['agent', 'environment', 'test'],
77+
]
78+
)
5479
end
5580

5681
it 'does nothing if given an empty config' do
57-
expect(task.write_puppet_conf(nil)).to eq({})
58-
expect(task.write_puppet_conf({})).to eq({})
82+
expect(task.update_puppet_conf(nil)).to eq({})
83+
expect(task.update_puppet_conf({})).to eq({})
84+
end
85+
86+
it 'records error output if puppet config set fails' do
87+
puppet_conf['main']['oops'] = 'fail'
88+
expect(task.update_puppet_conf(puppet_conf, tmpdir)).to(
89+
eq(
90+
{
91+
puppet_conf: {
92+
path: puppet_conf_path,
93+
contents: '',
94+
successful: false,
95+
errors: {
96+
'--section=main oops=fail' => 'error output',
97+
},
98+
}
99+
}
100+
)
101+
)
59102
end
60103
end
61104

@@ -115,6 +158,7 @@ def check_returned_id(uid_or_gid)
115158
csr_attributes: {
116159
path: csr_attributes_path,
117160
contents: csr_attributes_contents,
161+
successful: true,
118162
}
119163
}
120164
)
@@ -181,6 +225,20 @@ def status(code = 0)
181225
)
182226
end
183227

228+
it 'returns a result has if all steps are successful' do
229+
expect(task).to receive(:update_puppet_conf).and_return({ puppet_conf: { successful: true } })
230+
expect(task).to receive(:write_csr_attributes).and_return({ csr_attributes: { successful: true } })
231+
expect(task).to receive(:manage_puppet_service).and_return({ puppet_service: { successful: true } })
232+
233+
expect(task.task).to eq(
234+
{
235+
csr_attributes: { successful: true },
236+
puppet_conf: { successful: true },
237+
puppet_service: { successful: true },
238+
}
239+
)
240+
end
241+
184242
it 'prints results and exits 1 if puppet service fails' do
185243
expect(task).to(
186244
receive(:manage_puppet_service).
@@ -204,14 +262,52 @@ def status(code = 0)
204262
}
205263
}
206264
207-
Failed managing the puppet service:
265+
Failed managing puppet_service:
208266
209267
apply failed
210268
EOM
211269
).and(output('').to_stderr)
212270
)
213271
end
272+
273+
it 'prints results and exits 1 if any step fails' do
274+
expect(task).to receive(:manage_puppet_service).and_return({ puppet_service: { successful: true } })
275+
expect(task).to(
276+
receive(:update_puppet_conf).
277+
and_return(
278+
{
279+
puppet_conf: {
280+
successful: false,
281+
errors: { '--section=main server=puppet.spec' => 'error output' }
282+
}
283+
}
284+
)
285+
)
286+
287+
expect { task.task }.to(
288+
raise_error(SystemExit).and(
289+
output(<<~EOM).to_stdout
290+
{
291+
"puppet_conf": {
292+
"successful": false,
293+
"errors": {
294+
"--section=main server=puppet.spec": "error output"
295+
}
296+
},
297+
"puppet_service": {
298+
"successful": true
299+
}
300+
}
301+
302+
Failed managing puppet_conf:
303+
304+
{"--section=main server=puppet.spec"=>"error output"}
305+
EOM
306+
).and(output('').to_stderr)
307+
)
308+
end
214309
end
215310
end
216311
# rubocop:enable RSpec/MessageSpies
217312
# rubocop:enable RSpec/StubbedMock
313+
# rubocop:enable RSpec/MultipleMemoizedHelpers

tasks/configure.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@
66
],
77
"parameters": {
88
"puppet_conf": {
9-
"description": "Hash of puppet configuration settings to write to the puppet.conf ini file. NOTE: This will completely overwrite any existing settings in the file; there is no merging behavior in this task.",
9+
"description": "Hash of puppet configuration settings to add to the puppet.conf ini file. These will be merged into the existing puppet.conf, if any.",
1010
"type": "Optional[Openvox_bootstrap::Ini_file]"
1111
},
1212
"csr_attributes": {
13-
"description": "Hash of CSR attributes (custom_attributes and extension_requests) to write to the csr_attributes.yaml file. NOTE: This will completely overwrite any existing settings in the file; there is no merging behavior in this task.",
13+
"description": "Hash of CSR attributes (custom_attributes and extension_requests) to write to the csr_attributes.yaml file. NOTE: This will completely overwrite any pre-existing csr_attributes.yaml.",
1414
"type": "Optional[Openvox_bootstrap::Csr_attributes]"
1515
},
1616
"puppet_service_running": {

tasks/configure.rb

Lines changed: 57 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
require 'open3'
77
require 'yaml'
88

9+
# rubocop:disable Style/NegatedIf
910
module OpenvoxBootstrap
1011
class Configure < Task
1112
def puppet_uid
@@ -20,33 +21,59 @@ def puppet_gid
2021
nil
2122
end
2223

23-
# Overwrite puppet.conf with the values in the puppet_conf hash.
24+
def puppet_config_set(section, key, value)
25+
command = [
26+
'/opt/puppetlabs/bin/puppet',
27+
'config',
28+
'set',
29+
'--section', section,
30+
key,
31+
value,
32+
]
33+
Open3.capture2e(*command)
34+
end
35+
36+
# Add the given settings to the puppet.conf file using
37+
# puppet-config.
2438
#
25-
# Does nothing if given an empty or nil puppet_conf.
39+
# Does nothing if given an empty or nil settings hash.
2640
#
27-
# @param puppet_conf [Hash<String,Hash<String,String>>] A hash of
28-
# sections and settings to write to the puppet.conf file.
41+
# @param settings [Hash<String,Hash<String,String>>]
42+
# A hash of sections and settings to add to the
43+
# puppet.conf file.
2944
# @return [Hash]
30-
def write_puppet_conf(puppet_conf, etc_puppet_path = '/etc/puppetlabs/puppet')
31-
return {} if puppet_conf.nil? || puppet_conf.empty?
45+
def update_puppet_conf(settings, etc_puppet_path = '/etc/puppetlabs/puppet')
46+
return {} if settings.nil? || settings.empty?
3247

3348
conf_path = File.join(etc_puppet_path, 'puppet.conf')
34-
sections = puppet_conf.map do |section, settings|
35-
"[#{section}]\n" +
36-
settings.map { |key, value| "#{key} = #{value}" }.join("\n")
49+
success = true
50+
errors = {}
51+
settings.each do |section, section_settings|
52+
section_settings.each do |key, value|
53+
output, status = puppet_config_set(section, key, value)
54+
success &&= status.success?
55+
if !status.success?
56+
err_key = "--section=#{section} #{key}=#{value}"
57+
errors[err_key] = output
58+
end
59+
end
3760
end
38-
puppet_conf_contents = "#{sections.join("\n\n")}\n"
3961

40-
File.open(conf_path, 'w', perm: 0o644) do |f|
41-
f.write(puppet_conf_contents)
42-
end
62+
puppet_conf_contents = if File.exist?(conf_path)
63+
File.read(conf_path)
64+
else
65+
''
66+
end
4367

44-
{
68+
result = {
4569
puppet_conf: {
4670
path: conf_path,
4771
contents: puppet_conf_contents,
72+
successful: success,
4873
}
4974
}
75+
result[:puppet_conf][:errors] = errors if !success
76+
result
5077
end
5178

5279
# Overwrite the csr_attributes.yaml file with the given
@@ -78,6 +105,7 @@ def write_csr_attributes(csr_attributes, etc_puppet_path = '/etc/puppetlabs/pupp
78105
csr_attributes: {
79106
path: csr_attributes_path,
80107
contents: csr_attributes_contents,
108+
successful: true,
81109
}
82110
}
83111
end
@@ -118,25 +146,31 @@ def task(
118146
puppet_service_enabled: true,
119147
**_kwargs
120148
)
121-
puppet_conf_result = write_puppet_conf(puppet_conf)
122-
csr_result = write_csr_attributes(csr_attributes)
123-
puppet_service_result = manage_puppet_service(puppet_service_running, puppet_service_enabled)
149+
results = {}
150+
results.merge!(update_puppet_conf(puppet_conf))
151+
results.merge!(write_csr_attributes(csr_attributes))
152+
results.merge!(
153+
manage_puppet_service(puppet_service_running, puppet_service_enabled)
154+
)
124155

125-
results = puppet_conf_result.merge(
126-
csr_result
127-
).merge(puppet_service_result)
156+
success = results.all? { |_, details| details[:successful] }
128157

129-
success = results[:puppet_service][:successful]
130158
if success
131159
results
132160
else
133161
puts JSON.pretty_generate(results)
134-
puts "\nFailed managing the puppet service:\n\n"
135-
puts results[:puppet_service][:output]
162+
results.each do |config, details|
163+
next if details[:successful]
164+
165+
puts "\nFailed managing #{config}:\n\n"
166+
puts details[:output] if details.key?(:output)
167+
pp details[:errors] if details.key?(:errors)
168+
end
136169
exit 1
137170
end
138171
end
139172
end
140173
end
174+
# rubocop:enable Style/NegatedIf
141175

142176
OpenvoxBootstrap::Configure.run if __FILE__ == $PROGRAM_NAME

0 commit comments

Comments
 (0)