Skip to content

Commit 7433df7

Browse files
authored
Merge pull request #139 from puppetlabs/infra-upgrade-improvement
Infra upgrade improvement
2 parents 15ff356 + d41dfe0 commit 7433df7

7 files changed

+130
-60
lines changed

.sync.yml

+2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ Rakefile:
1414
changelog_since_tag: '2.1.0'
1515
extras:
1616
- 'PuppetSyntax.exclude_paths = ["plans/**/*.pp", "vendor/**/*"]'
17+
spec/spec_helper.rb:
18+
mock_with: ':rspec'
1719
.gitignore:
1820
paths:
1921
- '.rerun.json'

CHANGELOG.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
### Improvements
77

8-
- Handle exit code 11 from replica upgrade (still waiting for PuppetDB sync to complete) gracefully.
8+
- Handle exit code 11 from replica upgrade task gracefully. Code 11 means "PuppetDB sync in progress but not yet complete".
99

1010
## 2.4.5
1111
### Summary

metadata.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262
"version_requirement": ">= 6.0.2 < 7.0.0"
6363
}
6464
],
65-
"pdk-version": "1.18.0",
65+
"pdk-version": "1.18.1",
6666
"template-url": "https://github.com/puppetlabs/pdk-templates.git#1.18.0",
6767
"template-ref": "tags/1.18.0-0-g095317c"
6868
}

spec/spec_helper.rb

+4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# frozen_string_literal: true
22

3+
RSpec.configure do |c|
4+
c.mock_with :rspec
5+
end
6+
37
require 'puppetlabs_spec_helper/module_spec_helper'
48
require 'rspec-puppet-facts'
59

spec/spec_helper_local.rb

+4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
require 'puppet'
44

5+
# This environment variable can be read by Ruby Bolt tasks to prevent unwanted
6+
# auto-execution, enabling easy unit testing.
7+
ENV["RSPEC_UNIT_TEST_MODE"] ||= "TRUE"
8+
59
if Gem::Version.new(Puppet.version) >= Gem::Version.new('6.0.0')
610
begin
711
require 'bolt_spec/plans'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
require 'spec_helper'
2+
require_relative '../../../tasks/puppet_infra_upgrade'
3+
4+
describe PEAdm::Task::PuppetInfraUpgrade do
5+
context 'replica' do
6+
let(:upgrade) do
7+
described_class.new('type' => 'replica',
8+
'targets' => ['replica.example.com'],
9+
'wait_until_connected_timeout' => 120)
10+
end
11+
12+
it 'Returns when the command exits with an expected code' do
13+
status_dbl_0 = double('Status', :exitstatus => 0)
14+
status_dbl_11 = double('Status', :exitstatus => 11)
15+
allow(STDOUT).to receive(:puts)
16+
allow(upgrade).to receive(:wait_until_connected)
17+
18+
allow(Open3).to receive(:capture2e).and_return(['hello world', status_dbl_0])
19+
expect(upgrade.execute!).to eq(nil)
20+
21+
allow(Open3).to receive(:capture2e).and_return(['hello world', status_dbl_11])
22+
expect(upgrade.execute!).to eq(nil)
23+
end
24+
25+
it 'Exits non-zero when the command exits with an unexpected code' do
26+
status_dbl_1 = double('Status', :exitstatus => 1)
27+
allow(STDOUT).to receive(:puts)
28+
allow(upgrade).to receive(:wait_until_connected)
29+
allow(Open3).to receive(:capture2e).and_return(['hello world', status_dbl_1])
30+
expect { upgrade.execute! }.to raise_error(SystemExit) do |error|
31+
expect(error.status).to eq(1)
32+
end
33+
end
34+
end
35+
36+
context 'compiler' do
37+
let(:upgrade) do
38+
described_class.new('type' => 'compiler',
39+
'targets' => ['replica.example.com'],
40+
'wait_until_connected_timeout' => 120)
41+
end
42+
end
43+
end

tasks/puppet_infra_upgrade.rb

+75-58
Original file line numberDiff line numberDiff line change
@@ -8,76 +8,93 @@
88
require 'timeout'
99
require 'etc'
1010

11-
def main
12-
params = JSON.parse(STDIN.read)
13-
type = params['type']
14-
targets = params['targets']
15-
timeout = params['wait_until_connected_timeout']
16-
token_file = params['token_file'] || File.join(Etc.getpwuid.dir, '.puppetlabs', 'token')
11+
class PEAdm
12+
class Task
13+
class PuppetInfraUpgrade
14+
def initialize(params)
15+
@type = params['type']
16+
@targets = params['targets']
17+
@timeout = params['wait_until_connected_timeout']
18+
@token_file = params['token_file']
19+
end
1720

18-
exit 0 if targets.empty?
21+
def execute!
22+
exit 0 if @targets.empty?
23+
token_file = @token_file || File.join(Etc.getpwuid.dir, '.puppetlabs', 'token')
1924

20-
cmd = ['/opt/puppetlabs/bin/puppet-infrastructure', '--render-as', 'json', 'upgrade']
21-
cmd << '--token-file' << token_file unless params['token_file'].nil?
22-
cmd << type << targets.join(',')
25+
cmd = ['/opt/puppetlabs/bin/puppet-infrastructure', '--render-as', 'json', 'upgrade']
26+
cmd << '--token-file' << token_file unless @token_file.nil?
27+
cmd << @type << @targets.join(',')
2328

24-
wait_until_connected(nodes: targets, token_file: token_file, timeout: timeout)
29+
wait_until_connected(nodes: @targets, token_file: token_file, timeout: @timeout)
2530

26-
stdouterr, status = Open3.capture2e(*cmd)
27-
puts stdouterr
28-
if status.success?
29-
exit 0
30-
elsif status.exitstatus == 11 # Waiting for PuppetDB sync to complete, but otherwise successful
31-
exit 0
32-
else
33-
exit status.exitstatus
34-
end
35-
end
31+
stdouterr, status = Open3.capture2e(*cmd)
32+
STDOUT.puts stdouterr
3633

37-
def inventory_uri
38-
@inventory_uri ||= URI.parse('https://localhost:8143/orchestrator/v1/inventory')
39-
end
34+
# Exit code 11 indicates PuppetDB sync in progress, just not yet
35+
# finished. We consider that success.
36+
if [0, 11].include?(status.exitstatus)
37+
return
38+
else
39+
exit status.exitstatus
40+
end
41+
end
4042

41-
def request_object(nodes:, token_file:)
42-
token = File.read(token_file)
43-
body = {
44-
'nodes' => nodes,
45-
}.to_json
43+
def inventory_uri
44+
@inventory_uri ||= URI.parse('https://localhost:8143/orchestrator/v1/inventory')
45+
end
4646

47-
request = Net::HTTP::Post.new(inventory_uri.request_uri)
48-
request['Content-Type'] = 'application/json'
49-
request['X-Authentication'] = token
50-
request.body = body
47+
def request_object(nodes:, token_file:)
48+
token = File.read(token_file)
49+
body = {
50+
'nodes' => nodes,
51+
}.to_json
5152

52-
request
53-
end
53+
request = Net::HTTP::Post.new(inventory_uri.request_uri)
54+
request['Content-Type'] = 'application/json'
55+
request['X-Authentication'] = token
56+
request.body = body
5457

55-
def http_object
56-
http = Net::HTTP.new(inventory_uri.host, inventory_uri.port)
57-
http.use_ssl = true
58-
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
58+
request
59+
end
5960

60-
http
61-
end
61+
def http_object
62+
http = Net::HTTP.new(inventory_uri.host, inventory_uri.port)
63+
http.use_ssl = true
64+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
65+
66+
http
67+
end
6268

63-
def wait_until_connected(nodes:, token_file:, timeout: 120)
64-
http = http_object
65-
request = request_object(nodes: nodes, token_file: token_file)
66-
inventory = {}
67-
Timeout.timeout(timeout) do
68-
loop do
69-
response = http.request(request)
70-
raise unless response.is_a? Net::HTTPSuccess
71-
inventory = JSON.parse(response.body)
72-
break if inventory['items'].all? { |item| item['connected'] }
73-
sleep(1)
69+
def wait_until_connected(nodes:, token_file:, timeout: 120)
70+
http = http_object
71+
request = request_object(nodes: nodes, token_file: token_file)
72+
inventory = {}
73+
Timeout.timeout(timeout) do
74+
loop do
75+
response = http.request(request)
76+
unless response.is_a? Net::HTTPSuccess
77+
raise "Unexpected result from orchestrator: #{response.class}\n#{response}"
78+
end
79+
inventory = JSON.parse(response.body)
80+
break if inventory['items'].all? { |item| item['connected'] }
81+
sleep(1)
82+
end
83+
end
84+
rescue Timeout::Error
85+
raise 'Timed out waiting for nodes to be connected to orchestrator: ' +
86+
inventory['items'].reject { |item| item['connected'] }
87+
.map { |item| item['name'] }
88+
.to_s
89+
end
7490
end
7591
end
76-
rescue Timeout::Error
77-
raise 'Timed out waiting for nodes to be connected to orchestrator: ' +
78-
inventory['items'].reject { |item| item['connected'] }
79-
.map { |item| item['name'] }
80-
.to_s
8192
end
8293

83-
main
94+
# Run the task unless an environment flag has been set, signaling not to. The
95+
# environment flag is used to disable auto-execution and enable Ruby unit
96+
# testing of this task.
97+
unless ENV['RSPEC_UNIT_TEST_MODE']
98+
upgrade = PEAdm::Task::PuppetInfraUpgrade.new(JSON.parse(STDIN.read))
99+
upgrade.execute!
100+
end

0 commit comments

Comments
 (0)