bolt 3.22.1 → 3.24.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of bolt might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/Puppetfile +9 -9
- data/bolt-modules/boltlib/lib/puppet/functions/puppetdb_command.rb +32 -1
- data/bolt-modules/boltlib/lib/puppet/functions/puppetdb_fact.rb +20 -1
- data/bolt-modules/boltlib/lib/puppet/functions/puppetdb_query.rb +23 -1
- data/bolt-modules/boltlib/lib/puppet/functions/run_task.rb +28 -23
- data/bolt-modules/boltlib/lib/puppet/functions/run_task_with.rb +22 -19
- data/lib/bolt/application.rb +17 -10
- data/lib/bolt/applicator.rb +8 -2
- data/lib/bolt/bolt_option_parser.rb +4 -1
- data/lib/bolt/catalog.rb +1 -1
- data/lib/bolt/config/options.rb +65 -49
- data/lib/bolt/config/transport/local.rb +1 -0
- data/lib/bolt/config/transport/lxd.rb +9 -0
- data/lib/bolt/config.rb +12 -3
- data/lib/bolt/inventory/inventory.rb +30 -11
- data/lib/bolt/outputter/human.rb +10 -4
- data/lib/bolt/outputter/json.rb +3 -1
- data/lib/bolt/outputter/rainbow.rb +2 -1
- data/lib/bolt/pal/yaml_plan/loader.rb +1 -1
- data/lib/bolt/pal.rb +6 -2
- data/lib/bolt/plugin/puppetdb.rb +8 -5
- data/lib/bolt/plugin.rb +2 -1
- data/lib/bolt/puppetdb/client.rb +90 -129
- data/lib/bolt/puppetdb/config.rb +21 -8
- data/lib/bolt/puppetdb/instance.rb +146 -0
- data/lib/bolt/result.rb +1 -1
- data/lib/bolt/transport/orch/connection.rb +2 -1
- data/lib/bolt/transport/winrm/connection.rb +18 -11
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt_server/file_cache.rb +10 -8
- data/lib/bolt_spec/plans/action_stubs/download_stub.rb +1 -1
- data/lib/bolt_spec/plans/action_stubs/plan_stub.rb +1 -1
- data/lib/bolt_spec/plans/action_stubs/task_stub.rb +1 -1
- data/lib/bolt_spec/plans/action_stubs/upload_stub.rb +1 -1
- data/lib/bolt_spec/plans/action_stubs.rb +2 -2
- data/lib/bolt_spec/plans/mock_executor.rb +1 -1
- data/lib/bolt_spec/plans.rb +11 -1
- metadata +10 -3
@@ -0,0 +1,146 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'logging'
|
5
|
+
require_relative '../../bolt/puppetdb/config'
|
6
|
+
|
7
|
+
module Bolt
|
8
|
+
module PuppetDB
|
9
|
+
class Instance
|
10
|
+
attr_reader :config
|
11
|
+
|
12
|
+
def initialize(config:, project: nil, load_defaults: false)
|
13
|
+
@config = Bolt::PuppetDB::Config.new(config: config, project: project, load_defaults: load_defaults)
|
14
|
+
@bad_urls = []
|
15
|
+
@current_url = nil
|
16
|
+
@logger = Bolt::Logger.logger(self)
|
17
|
+
end
|
18
|
+
|
19
|
+
def make_query(query, path = nil)
|
20
|
+
body = JSON.generate(query: query)
|
21
|
+
url = "#{uri}/pdb/query/v4"
|
22
|
+
url += "/#{path}" if path
|
23
|
+
|
24
|
+
begin
|
25
|
+
@logger.debug("Sending PuppetDB query to #{url}")
|
26
|
+
response = http_client.post(url, body: body, header: headers)
|
27
|
+
rescue StandardError => e
|
28
|
+
raise Bolt::PuppetDBFailoverError, "Failed to query PuppetDB: #{e}"
|
29
|
+
end
|
30
|
+
|
31
|
+
@logger.debug("Got response code #{response.code} from PuppetDB")
|
32
|
+
if response.code != 200
|
33
|
+
msg = "Failed to query PuppetDB: #{response.body}"
|
34
|
+
if response.code == 400
|
35
|
+
raise Bolt::PuppetDBError, msg
|
36
|
+
else
|
37
|
+
raise Bolt::PuppetDBFailoverError, msg
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
begin
|
42
|
+
JSON.parse(response.body)
|
43
|
+
rescue JSON::ParserError
|
44
|
+
raise Bolt::PuppetDBError, "Unable to parse response as JSON: #{response.body}"
|
45
|
+
end
|
46
|
+
rescue Bolt::PuppetDBFailoverError => e
|
47
|
+
@logger.error("Request to puppetdb at #{@current_url} failed with #{e}.")
|
48
|
+
reject_url
|
49
|
+
make_query(query, path)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Sends a command to PuppetDB using version 1 of the commands API.
|
53
|
+
# https://puppet.com/docs/puppetdb/latest/api/command/v1/commands.html
|
54
|
+
#
|
55
|
+
# @param command [String] The command to invoke.
|
56
|
+
# @param version [Integer] The version of the command to invoke.
|
57
|
+
# @param payload [Hash] The payload to send with the command.
|
58
|
+
# @return A UUID identifying the submitted command.
|
59
|
+
#
|
60
|
+
def send_command(command, version, payload)
|
61
|
+
command = command.dup.force_encoding('utf-8')
|
62
|
+
body = JSON.generate(payload)
|
63
|
+
|
64
|
+
# PDB requires the following query parameters to the POST request.
|
65
|
+
# Error early if there's no certname, as PDB does not return a
|
66
|
+
# message indicating it's required.
|
67
|
+
unless payload['certname']
|
68
|
+
raise Bolt::Error.new(
|
69
|
+
"Payload must include 'certname', unable to invoke command.",
|
70
|
+
'bolt/pdb-command'
|
71
|
+
)
|
72
|
+
end
|
73
|
+
|
74
|
+
url = uri.tap do |u|
|
75
|
+
u.path = 'pdb/cmd/v1'
|
76
|
+
u.query_values = { 'command' => command,
|
77
|
+
'version' => version,
|
78
|
+
'certname' => payload['certname'] }
|
79
|
+
end
|
80
|
+
|
81
|
+
# Send the command to PDB
|
82
|
+
begin
|
83
|
+
@logger.debug("Sending PuppetDB command '#{command}' to #{url}")
|
84
|
+
response = http_client.post(url.to_s, body: body, header: headers)
|
85
|
+
rescue StandardError => e
|
86
|
+
raise Bolt::PuppetDBFailoverError, "Failed to invoke PuppetDB command: #{e}"
|
87
|
+
end
|
88
|
+
|
89
|
+
@logger.debug("Got response code #{response.code} from PuppetDB")
|
90
|
+
if response.code != 200
|
91
|
+
raise Bolt::PuppetDBError, "Failed to invoke PuppetDB command: #{response.body}"
|
92
|
+
end
|
93
|
+
|
94
|
+
# Return the UUID string from the response body
|
95
|
+
begin
|
96
|
+
JSON.parse(response.body).fetch('uuid', nil)
|
97
|
+
rescue JSON::ParserError
|
98
|
+
raise Bolt::PuppetDBError, "Unable to parse response as JSON: #{response.body}"
|
99
|
+
end
|
100
|
+
rescue Bolt::PuppetDBFailoverError => e
|
101
|
+
@logger.error("Request to puppetdb at #{@current_url} failed with #{e}.")
|
102
|
+
reject_url
|
103
|
+
send_command(command, version, payload)
|
104
|
+
end
|
105
|
+
|
106
|
+
def http_client
|
107
|
+
return @http if @http
|
108
|
+
# lazy-load expensive gem code
|
109
|
+
require 'httpclient'
|
110
|
+
@logger.trace("Creating HTTP Client")
|
111
|
+
@http = HTTPClient.new
|
112
|
+
@http.ssl_config.set_client_cert_file(@config.cert, @config.key) if @config.cert
|
113
|
+
@http.ssl_config.add_trust_ca(@config.cacert)
|
114
|
+
@http.connect_timeout = @config.connect_timeout if @config.connect_timeout
|
115
|
+
@http.receive_timeout = @config.read_timeout if @config.read_timeout
|
116
|
+
|
117
|
+
@http
|
118
|
+
end
|
119
|
+
|
120
|
+
def reject_url
|
121
|
+
@bad_urls << @current_url if @current_url
|
122
|
+
@current_url = nil
|
123
|
+
end
|
124
|
+
|
125
|
+
def uri
|
126
|
+
require 'addressable/uri'
|
127
|
+
|
128
|
+
@current_url ||= (@config.server_urls - @bad_urls).first
|
129
|
+
unless @current_url
|
130
|
+
msg = "Failed to connect to all PuppetDB server_urls: #{@config.server_urls.to_a.join(', ')}."
|
131
|
+
raise Bolt::PuppetDBError, msg
|
132
|
+
end
|
133
|
+
|
134
|
+
uri = Addressable::URI.parse(@current_url)
|
135
|
+
uri.port ||= 8081
|
136
|
+
uri
|
137
|
+
end
|
138
|
+
|
139
|
+
def headers
|
140
|
+
headers = { 'Content-Type' => 'application/json' }
|
141
|
+
headers['X-Authentication'] = @config.token if @config.token
|
142
|
+
headers
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
data/lib/bolt/result.rb
CHANGED
@@ -128,7 +128,7 @@ module Bolt
|
|
128
128
|
|
129
129
|
def _pcore_init_from_hash(init_hash)
|
130
130
|
opts = init_hash.reject { |k, _v| k == 'target' }
|
131
|
-
initialize(init_hash['target'], opts.transform_keys(&:to_sym))
|
131
|
+
initialize(init_hash['target'], **opts.transform_keys(&:to_sym))
|
132
132
|
end
|
133
133
|
|
134
134
|
def _pcore_init_hash
|
@@ -6,7 +6,7 @@ module Bolt
|
|
6
6
|
class Connection
|
7
7
|
attr_reader :logger, :key
|
8
8
|
|
9
|
-
CONTEXT_KEYS = Set.new(%i[plan_name description params]).freeze
|
9
|
+
CONTEXT_KEYS = Set.new(%i[plan_name description params sensitive]).freeze
|
10
10
|
|
11
11
|
def self.get_key(opts)
|
12
12
|
[
|
@@ -46,6 +46,7 @@ module Bolt
|
|
46
46
|
if plan_context
|
47
47
|
begin
|
48
48
|
opts = plan_context.select { |k, _| CONTEXT_KEYS.include? k }
|
49
|
+
opts[:params] = opts[:params].reject { |k, _| plan_context[:sensitive].include?(k) }
|
49
50
|
@client.command.plan_start(opts)['name']
|
50
51
|
rescue OrchestratorClient::ApiError => e
|
51
52
|
if e.code == '404'
|
@@ -53,9 +53,11 @@ module Bolt
|
|
53
53
|
@connection = ::WinRM::Connection.new(options)
|
54
54
|
@connection.logger = @transport_logger
|
55
55
|
|
56
|
-
@
|
57
|
-
|
58
|
-
|
56
|
+
@connection.shell(:powershell) do |session|
|
57
|
+
session.run('$PSVersionTable.PSVersion')
|
58
|
+
end
|
59
|
+
|
60
|
+
@logger.trace { "Opened connection" }
|
59
61
|
end
|
60
62
|
rescue Timeout::Error
|
61
63
|
# If we're using the default port with SSL, a timeout probably means the
|
@@ -95,9 +97,8 @@ module Bolt
|
|
95
97
|
end
|
96
98
|
|
97
99
|
def disconnect
|
98
|
-
@session&.close
|
99
100
|
@client&.disconnect!
|
100
|
-
@logger.trace { "Closed
|
101
|
+
@logger.trace { "Closed connection" }
|
101
102
|
end
|
102
103
|
|
103
104
|
def execute(command)
|
@@ -116,12 +117,18 @@ module Bolt
|
|
116
117
|
# propagate to the main thread via the shell, there's no chance
|
117
118
|
# they will be unhandled, so the default stack trace is unneeded.
|
118
119
|
Thread.current.report_on_exception = false
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
120
|
+
|
121
|
+
# Open a new shell instance for each command executed. PowerShell is
|
122
|
+
# unable to unload any DLLs loaded when running a PowerShell script
|
123
|
+
# or task from the same shell instance they were loaded in, which
|
124
|
+
# prevents Bolt from cleaning up the temp directory successfully.
|
125
|
+
# Using a new PowerShell instance avoids this limitation.
|
126
|
+
@connection.shell(:powershell) do |session|
|
127
|
+
result = session.run(command)
|
128
|
+
out_wr << result.stdout
|
129
|
+
err_wr << result.stderr
|
130
|
+
result.exitcode
|
131
|
+
end
|
125
132
|
ensure
|
126
133
|
# Close the streams to avoid the caller deadlocking
|
127
134
|
out_wr.close
|
data/lib/bolt/version.rb
CHANGED
@@ -8,6 +8,7 @@ require 'digest'
|
|
8
8
|
require 'fileutils'
|
9
9
|
require 'net/http'
|
10
10
|
require 'logging'
|
11
|
+
require 'timeout'
|
11
12
|
|
12
13
|
require 'bolt/error'
|
13
14
|
|
@@ -38,8 +39,7 @@ module BoltServer
|
|
38
39
|
|
39
40
|
if do_purge
|
40
41
|
@purge = Concurrent::TimerTask.new(execution_interval: purge_interval,
|
41
|
-
|
42
|
-
run_now: true) { expire(purge_ttl) }
|
42
|
+
run_now: true) { expire(purge_ttl, purge_timeout) }
|
43
43
|
@purge.execute
|
44
44
|
end
|
45
45
|
end
|
@@ -171,13 +171,15 @@ module BoltServer
|
|
171
171
|
serial_execute { download_file(file_path, sha, file_data['uri']) }
|
172
172
|
end
|
173
173
|
|
174
|
-
def expire(purge_ttl)
|
174
|
+
def expire(purge_ttl, purge_timeout)
|
175
175
|
expired_time = Time.now - purge_ttl
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
176
|
+
Timeout.timeout(purge_timeout) do
|
177
|
+
@cache_dir_mutex.with_write_lock do
|
178
|
+
Dir.glob(File.join(@cache_dir, '*')).select { |f| File.directory?(f) }.each do |dir|
|
179
|
+
if (mtime = File.mtime(dir)) < expired_time && dir != tmppath
|
180
|
+
@logger.debug("Removing #{dir}, last used at #{mtime}")
|
181
|
+
FileUtils.remove_dir(dir)
|
182
|
+
end
|
181
183
|
end
|
182
184
|
end
|
183
185
|
end
|
@@ -93,7 +93,7 @@ module BoltSpec
|
|
93
93
|
when Bolt::Error
|
94
94
|
Bolt::Result.from_exception(target, @data[:default])
|
95
95
|
when Hash
|
96
|
-
result_for(target, Bolt::Util.walk_keys(@data[:default], &:to_sym))
|
96
|
+
result_for(target, **Bolt::Util.walk_keys(@data[:default], &:to_sym))
|
97
97
|
else
|
98
98
|
raise 'Default result must be a Hash'
|
99
99
|
end
|
@@ -156,7 +156,7 @@ module BoltSpec
|
|
156
156
|
# set the inventory from the BoltSpec::Plans, otherwise if we try to convert
|
157
157
|
# this target to a string, it will fail to string conversion because the
|
158
158
|
# inventory is nil
|
159
|
-
hsh[target] = result_for(Bolt::Target.new(target, @inventory), Bolt::Util.walk_keys(result, &:to_sym))
|
159
|
+
hsh[target] = result_for(Bolt::Target.new(target, @inventory), **Bolt::Util.walk_keys(result, &:to_sym))
|
160
160
|
end
|
161
161
|
raise "Cannot set return values and return block." if @return_block
|
162
162
|
@data_set = true
|
data/lib/bolt_spec/plans.rb
CHANGED
@@ -103,6 +103,16 @@ module BoltSpec
|
|
103
103
|
|
104
104
|
# Provided as a class so expectations can be placed on it.
|
105
105
|
class MockPuppetDBClient
|
106
|
+
def initialize(config)
|
107
|
+
@instance = MockPuppetDBInstance.new(config)
|
108
|
+
end
|
109
|
+
|
110
|
+
def instance(_instance)
|
111
|
+
@instance
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
class MockPuppetDBInstance
|
106
116
|
attr_reader :config
|
107
117
|
|
108
118
|
def initialize(config)
|
@@ -111,7 +121,7 @@ module BoltSpec
|
|
111
121
|
end
|
112
122
|
|
113
123
|
def puppetdb_client
|
114
|
-
@puppetdb_client ||= MockPuppetDBClient.new(
|
124
|
+
@puppetdb_client ||= MockPuppetDBClient.new({})
|
115
125
|
end
|
116
126
|
|
117
127
|
def run_plan(name, params)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bolt
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.24.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Puppet
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-06-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: addressable
|
@@ -163,6 +163,9 @@ dependencies:
|
|
163
163
|
- - ">="
|
164
164
|
- !ruby/object:Gem::Version
|
165
165
|
version: '4.0'
|
166
|
+
- - "<"
|
167
|
+
- !ruby/object:Gem::Version
|
168
|
+
version: '7.0'
|
166
169
|
type: :runtime
|
167
170
|
prerelease: false
|
168
171
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -170,6 +173,9 @@ dependencies:
|
|
170
173
|
- - ">="
|
171
174
|
- !ruby/object:Gem::Version
|
172
175
|
version: '4.0'
|
176
|
+
- - "<"
|
177
|
+
- !ruby/object:Gem::Version
|
178
|
+
version: '7.0'
|
173
179
|
- !ruby/object:Gem::Dependency
|
174
180
|
name: net-ssh-krb
|
175
181
|
requirement: !ruby/object:Gem::Requirement
|
@@ -566,6 +572,7 @@ files:
|
|
566
572
|
- lib/bolt/puppetdb.rb
|
567
573
|
- lib/bolt/puppetdb/client.rb
|
568
574
|
- lib/bolt/puppetdb/config.rb
|
575
|
+
- lib/bolt/puppetdb/instance.rb
|
569
576
|
- lib/bolt/r10k_log_proxy.rb
|
570
577
|
- lib/bolt/rerun.rb
|
571
578
|
- lib/bolt/resource_instance.rb
|
@@ -664,7 +671,7 @@ require_paths:
|
|
664
671
|
- lib
|
665
672
|
required_ruby_version: !ruby/object:Gem::Requirement
|
666
673
|
requirements:
|
667
|
-
- - "
|
674
|
+
- - ">="
|
668
675
|
- !ruby/object:Gem::Version
|
669
676
|
version: '2.5'
|
670
677
|
required_rubygems_version: !ruby/object:Gem::Requirement
|