bolt 3.21.0 → 3.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of bolt might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/Puppetfile +7 -7
- 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/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 +73 -49
- data/lib/bolt/config.rb +12 -3
- data/lib/bolt/module.rb +6 -0
- data/lib/bolt/outputter/human.rb +8 -3
- data/lib/bolt/pal/yaml_plan/loader.rb +1 -1
- data/lib/bolt/pal.rb +6 -2
- data/lib/bolt/plugin/env_var.rb +17 -1
- data/lib/bolt/plugin/puppetdb.rb +14 -4
- data/lib/bolt/plugin.rb +2 -1
- data/lib/bolt/project.rb +4 -0
- 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/ssh/exec_connection.rb +5 -2
- data/lib/bolt/util.rb +1 -1
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt_server/transport_app.rb +1 -0
- 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 +5 -4
data/lib/bolt/pal.rb
CHANGED
@@ -531,7 +531,9 @@ module Bolt
|
|
531
531
|
'description' => description,
|
532
532
|
'parameters' => parameters,
|
533
533
|
'module' => mod,
|
534
|
-
'private' => private_plan?(plan)
|
534
|
+
'private' => private_plan?(plan),
|
535
|
+
'summary' => plan.tag(:summary)&.text,
|
536
|
+
'docstring' => (plan.docstring unless plan.docstring.empty?)
|
535
537
|
}
|
536
538
|
|
537
539
|
pp_info.merge!(get_plan_mtime(plan.file)) if with_mtime
|
@@ -564,7 +566,9 @@ module Bolt
|
|
564
566
|
'description' => plan.description,
|
565
567
|
'parameters' => parameters,
|
566
568
|
'module' => mod,
|
567
|
-
'private' => !!plan.private
|
569
|
+
'private' => !!plan.private,
|
570
|
+
'docstring' => plan.description,
|
571
|
+
'summary' => nil
|
568
572
|
}
|
569
573
|
|
570
574
|
yaml_info.merge!(get_plan_mtime(yaml_path)) if with_mtime
|
data/lib/bolt/plugin/env_var.rb
CHANGED
@@ -1,8 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'json'
|
3
4
|
module Bolt
|
4
5
|
class Plugin
|
5
6
|
class EnvVar
|
7
|
+
class InvalidPluginData < Bolt::Plugin::PluginError
|
8
|
+
def initialize(msg, plugin)
|
9
|
+
msg = "Invalid Plugin Data for #{plugin}: #{msg}"
|
10
|
+
super(msg, 'bolt/invalid-plugin-data')
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
6
14
|
def initialize(*_args); end
|
7
15
|
|
8
16
|
def name
|
@@ -31,7 +39,15 @@ module Bolt
|
|
31
39
|
end
|
32
40
|
|
33
41
|
def resolve_reference(opts)
|
34
|
-
ENV[opts['var']]
|
42
|
+
reference = ENV[opts['var']]
|
43
|
+
if opts['json'] && reference
|
44
|
+
begin
|
45
|
+
reference = JSON.parse(reference)
|
46
|
+
rescue JSON::ParserError => e
|
47
|
+
raise InvalidPluginData.new(e.message, name)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
reference || opts['default']
|
35
51
|
end
|
36
52
|
end
|
37
53
|
end
|
data/lib/bolt/plugin/puppetdb.rb
CHANGED
@@ -12,13 +12,16 @@ module Bolt
|
|
12
12
|
end
|
13
13
|
|
14
14
|
TEMPLATE_OPTS = %w[alias config facts features name uri vars].freeze
|
15
|
-
PLUGIN_OPTS = %w[_plugin _cache query target_mapping].freeze
|
15
|
+
PLUGIN_OPTS = %w[_plugin _cache query target_mapping instance].freeze
|
16
16
|
|
17
17
|
attr_reader :puppetdb_client
|
18
18
|
|
19
19
|
def initialize(config:, context:)
|
20
|
-
|
21
|
-
|
20
|
+
@puppetdb_client = Bolt::PuppetDB::Client.new(default: config.delete('default'),
|
21
|
+
instances: config.delete('instances') || {},
|
22
|
+
config: config,
|
23
|
+
project: context.boltdir)
|
24
|
+
|
22
25
|
@logger = Bolt::Logger.logger(self)
|
23
26
|
end
|
24
27
|
|
@@ -42,6 +45,13 @@ module Bolt
|
|
42
45
|
|
43
46
|
def fact_path(raw_fact)
|
44
47
|
fact_path = raw_fact.split(".")
|
48
|
+
fact_path = fact_path.map do |segment|
|
49
|
+
# Turn it into an integer if we can
|
50
|
+
Integer(segment)
|
51
|
+
rescue ArgumentError
|
52
|
+
# Otherwise return the value
|
53
|
+
segment
|
54
|
+
end
|
45
55
|
if fact_path[0] == 'facts'
|
46
56
|
fact_path.drop(1)
|
47
57
|
elsif fact_path == ['certname']
|
@@ -52,7 +62,7 @@ module Bolt
|
|
52
62
|
end
|
53
63
|
|
54
64
|
def resolve_reference(opts)
|
55
|
-
targets = @puppetdb_client.query_certnames(opts['query'])
|
65
|
+
targets = @puppetdb_client.query_certnames(opts['query'], opts['instance'])
|
56
66
|
facts = []
|
57
67
|
|
58
68
|
template = opts.delete('target_mapping') || {}
|
data/lib/bolt/plugin.rb
CHANGED
@@ -151,7 +151,8 @@ module Bolt
|
|
151
151
|
msg = "Configuration for the PuppetDB plugin must be in the 'puppetdb' config section, not 'plugins'"
|
152
152
|
raise Bolt::Error.new(msg, 'bolt/plugin-error')
|
153
153
|
end
|
154
|
-
@unresolved_plugin_configs['puppetdb'] = config.puppetdb
|
154
|
+
@unresolved_plugin_configs['puppetdb'] = config.puppetdb.merge('default' => config.default_puppetdb,
|
155
|
+
'instances' => config.puppetdb_instances)
|
155
156
|
end
|
156
157
|
|
157
158
|
# Returns a map of configured plugin hooks. Any unresolved plugin references
|
data/lib/bolt/project.rb
CHANGED
data/lib/bolt/puppetdb/client.rb
CHANGED
@@ -2,34 +2,90 @@
|
|
2
2
|
|
3
3
|
require 'json'
|
4
4
|
require 'logging'
|
5
|
+
require_relative '../../bolt/puppetdb/instance'
|
5
6
|
|
6
7
|
module Bolt
|
7
8
|
module PuppetDB
|
8
9
|
class Client
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
10
|
+
# @param config [Hash] A map of default PuppetDB configuration.
|
11
|
+
# @param instances [Hash] A map of configuration for named PuppetDB instances.
|
12
|
+
# @param default [String] The name of PuppetDB instance to use as the default.
|
13
|
+
# @param project [String] The path to the Bolt project.
|
14
|
+
#
|
15
|
+
def initialize(config:, instances: {}, default: nil, project: nil)
|
15
16
|
@logger = Bolt::Logger.logger(self)
|
17
|
+
|
18
|
+
@instances = instances.transform_values do |instance_config|
|
19
|
+
Bolt::PuppetDB::Instance.new(config: instance_config, project: project)
|
20
|
+
end
|
21
|
+
|
22
|
+
@default_instance = if default
|
23
|
+
validate_instance(default)
|
24
|
+
@instances[default]
|
25
|
+
else
|
26
|
+
Bolt::PuppetDB::Instance.new(config: config, project: project, load_defaults: true)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Checks whether a given named instance is configured, erroring if not.
|
31
|
+
#
|
32
|
+
# @param name [String] The name of the PuppetDB instance.
|
33
|
+
#
|
34
|
+
private def validate_instance(name)
|
35
|
+
unless @instances[name]
|
36
|
+
raise Bolt::PuppetDBError, "PuppetDB instance '#{name}' has not been configured, unable to connect"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Yields the PuppetDB instance to connect to.
|
41
|
+
#
|
42
|
+
# @param name [String] The name of the PuppetDB instance.
|
43
|
+
# @yield [Bolt::PuppetDB::Instance]
|
44
|
+
#
|
45
|
+
private def with_instance(name = nil)
|
46
|
+
yield instance(name)
|
16
47
|
end
|
17
48
|
|
18
|
-
|
49
|
+
# Selects the PuppetDB instance to connect to. If an instance is not specified,
|
50
|
+
# the default instance is used.
|
51
|
+
#
|
52
|
+
# @param name [String] The name of the PuppetDB instance.
|
53
|
+
# @return [Bolt::PuppetDB::Instance]
|
54
|
+
#
|
55
|
+
def instance(name = nil)
|
56
|
+
if name
|
57
|
+
validate_instance(name)
|
58
|
+
@instances[name]
|
59
|
+
else
|
60
|
+
@default_instance
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Queries certnames from the PuppetDB instance.
|
65
|
+
#
|
66
|
+
# @param query [String] The PDB query.
|
67
|
+
# @param instance [String] The name of the PuppetDB instance.
|
68
|
+
#
|
69
|
+
def query_certnames(query, instance = nil)
|
19
70
|
return [] unless query
|
20
71
|
|
21
72
|
@logger.debug("Querying certnames")
|
22
|
-
results = make_query(query)
|
73
|
+
results = make_query(query, nil, instance)
|
23
74
|
|
24
75
|
if results&.first && !results.first&.key?('certname')
|
25
76
|
fields = results.first&.keys
|
26
77
|
raise Bolt::PuppetDBError, "Query results did not contain a 'certname' field: got #{fields.join(', ')}"
|
27
78
|
end
|
79
|
+
|
28
80
|
results&.map { |result| result['certname'] }&.uniq
|
29
81
|
end
|
30
82
|
|
31
|
-
#
|
32
|
-
|
83
|
+
# Retrieve facts from PuppetDB for a list of nodes.
|
84
|
+
#
|
85
|
+
# @param certnames [Array] The list of certnames to retrieve facts for.
|
86
|
+
# @param instance [String] The name of the PuppetDB instance.
|
87
|
+
#
|
88
|
+
def facts_for_node(certnames, instance = nil)
|
33
89
|
return {} if certnames.empty? || certnames.nil?
|
34
90
|
|
35
91
|
certnames.uniq!
|
@@ -37,14 +93,20 @@ module Bolt
|
|
37
93
|
name_query.insert(0, "or")
|
38
94
|
|
39
95
|
@logger.debug("Querying certnames")
|
40
|
-
result = make_query(name_query, 'inventory')
|
96
|
+
result = make_query(name_query, 'inventory', instance)
|
41
97
|
|
42
98
|
result&.each_with_object({}) do |node, coll|
|
43
99
|
coll[node['certname']] = node['facts']
|
44
100
|
end
|
45
101
|
end
|
46
102
|
|
47
|
-
|
103
|
+
# Retrive fact values for a list of nodes.
|
104
|
+
#
|
105
|
+
# @param certnames [Array] The list of certnames to retrieve fact values for.
|
106
|
+
# @param facts [Array] The list of facts to retrive.
|
107
|
+
# @param instance [String] The name of the PuppetDB instance.
|
108
|
+
#
|
109
|
+
def fact_values(certnames = [], facts = [], instance = nil)
|
48
110
|
return {} if certnames.empty? || facts.empty?
|
49
111
|
|
50
112
|
certnames.uniq!
|
@@ -57,135 +119,34 @@ module Bolt
|
|
57
119
|
query = ['and', name_query, facts_query]
|
58
120
|
|
59
121
|
@logger.debug("Querying certnames")
|
60
|
-
result = make_query(query, 'fact-contents')
|
122
|
+
result = make_query(query, 'fact-contents', instance)
|
61
123
|
result.map! { |h| h.delete_if { |k, _v| %w[environment name].include?(k) } }
|
62
124
|
result.group_by { |c| c['certname'] }
|
63
125
|
end
|
64
126
|
|
65
|
-
|
66
|
-
body = JSON.generate(query: query)
|
67
|
-
url = "#{uri}/pdb/query/v4"
|
68
|
-
url += "/#{path}" if path
|
69
|
-
|
70
|
-
begin
|
71
|
-
@logger.debug("Sending PuppetDB query to #{url}")
|
72
|
-
response = http_client.post(url, body: body, header: headers)
|
73
|
-
rescue StandardError => e
|
74
|
-
raise Bolt::PuppetDBFailoverError, "Failed to query PuppetDB: #{e}"
|
75
|
-
end
|
76
|
-
|
77
|
-
@logger.debug("Got response code #{response.code} from PuppetDB")
|
78
|
-
if response.code != 200
|
79
|
-
msg = "Failed to query PuppetDB: #{response.body}"
|
80
|
-
if response.code == 400
|
81
|
-
raise Bolt::PuppetDBError, msg
|
82
|
-
else
|
83
|
-
raise Bolt::PuppetDBFailoverError, msg
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
begin
|
88
|
-
JSON.parse(response.body)
|
89
|
-
rescue JSON::ParserError
|
90
|
-
raise Bolt::PuppetDBError, "Unable to parse response as JSON: #{response.body}"
|
91
|
-
end
|
92
|
-
rescue Bolt::PuppetDBFailoverError => e
|
93
|
-
@logger.error("Request to puppetdb at #{@current_url} failed with #{e}.")
|
94
|
-
reject_url
|
95
|
-
make_query(query, path)
|
96
|
-
end
|
97
|
-
|
98
|
-
# Sends a command to PuppetDB using version 1 of the commands API.
|
99
|
-
# https://puppet.com/docs/puppetdb/latest/api/command/v1/commands.html
|
127
|
+
# Sends a command to PuppetDB using the commands API.
|
100
128
|
#
|
101
129
|
# @param command [String] The command to invoke.
|
102
130
|
# @param version [Integer] The version of the command to invoke.
|
103
131
|
# @param payload [Hash] The payload to send with the command.
|
104
|
-
# @
|
105
|
-
#
|
106
|
-
def send_command(command, version, payload)
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
# PDB requires the following query parameters to the POST request.
|
111
|
-
# Error early if there's no certname, as PDB does not return a
|
112
|
-
# message indicating it's required.
|
113
|
-
unless payload['certname']
|
114
|
-
raise Bolt::Error.new(
|
115
|
-
"Payload must include 'certname', unable to invoke command.",
|
116
|
-
'bolt/pdb-command'
|
117
|
-
)
|
118
|
-
end
|
119
|
-
|
120
|
-
url = uri.tap do |u|
|
121
|
-
u.path = 'pdb/cmd/v1'
|
122
|
-
u.query_values = { 'command' => command,
|
123
|
-
'version' => version,
|
124
|
-
'certname' => payload['certname'] }
|
125
|
-
end
|
126
|
-
|
127
|
-
# Send the command to PDB
|
128
|
-
begin
|
129
|
-
@logger.debug("Sending PuppetDB command '#{command}' to #{url}")
|
130
|
-
response = http_client.post(url.to_s, body: body, header: headers)
|
131
|
-
rescue StandardError => e
|
132
|
-
raise Bolt::PuppetDBFailoverError, "Failed to invoke PuppetDB command: #{e}"
|
133
|
-
end
|
134
|
-
|
135
|
-
@logger.debug("Got response code #{response.code} from PuppetDB")
|
136
|
-
if response.code != 200
|
137
|
-
raise Bolt::PuppetDBError, "Failed to invoke PuppetDB command: #{response.body}"
|
138
|
-
end
|
139
|
-
|
140
|
-
# Return the UUID string from the response body
|
141
|
-
begin
|
142
|
-
JSON.parse(response.body).fetch('uuid', nil)
|
143
|
-
rescue JSON::ParserError
|
144
|
-
raise Bolt::PuppetDBError, "Unable to parse response as JSON: #{response.body}"
|
132
|
+
# @param instance [String] The name of the PuppetDB instance.
|
133
|
+
#
|
134
|
+
def send_command(command, version, payload, instance = nil)
|
135
|
+
with_instance(instance) do |pdb|
|
136
|
+
pdb.send_command(command, version, payload)
|
145
137
|
end
|
146
|
-
rescue Bolt::PuppetDBFailoverError => e
|
147
|
-
@logger.error("Request to puppetdb at #{@current_url} failed with #{e}.")
|
148
|
-
reject_url
|
149
|
-
send_command(command, version, payload)
|
150
|
-
end
|
151
|
-
|
152
|
-
def http_client
|
153
|
-
return @http if @http
|
154
|
-
# lazy-load expensive gem code
|
155
|
-
require 'httpclient'
|
156
|
-
@logger.trace("Creating HTTP Client")
|
157
|
-
@http = HTTPClient.new
|
158
|
-
@http.ssl_config.set_client_cert_file(@config.cert, @config.key) if @config.cert
|
159
|
-
@http.ssl_config.add_trust_ca(@config.cacert)
|
160
|
-
@http.connect_timeout = @config.connect_timeout if @config.connect_timeout
|
161
|
-
@http.receive_timeout = @config.read_timeout if @config.read_timeout
|
162
|
-
|
163
|
-
@http
|
164
|
-
end
|
165
|
-
|
166
|
-
def reject_url
|
167
|
-
@bad_urls << @current_url if @current_url
|
168
|
-
@current_url = nil
|
169
138
|
end
|
170
139
|
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
140
|
+
# Sends a query to PuppetDB.
|
141
|
+
#
|
142
|
+
# @param query [String] The query to send to PuppetDB.
|
143
|
+
# @param path [String] The API path to append to the query URL.
|
144
|
+
# @param instance [String] The name of the PuppetDB instance.
|
145
|
+
#
|
146
|
+
def make_query(query, path = nil, instance = nil)
|
147
|
+
with_instance(instance) do |pdb|
|
148
|
+
pdb.make_query(query, path)
|
178
149
|
end
|
179
|
-
|
180
|
-
uri = Addressable::URI.parse(@current_url)
|
181
|
-
uri.port ||= 8081
|
182
|
-
uri
|
183
|
-
end
|
184
|
-
|
185
|
-
def headers
|
186
|
-
headers = { 'Content-Type' => 'application/json' }
|
187
|
-
headers['X-Authentication'] = @config.token if @config.token
|
188
|
-
headers
|
189
150
|
end
|
190
151
|
end
|
191
152
|
end
|
data/lib/bolt/puppetdb/config.rb
CHANGED
@@ -17,11 +17,30 @@ module Bolt
|
|
17
17
|
|
18
18
|
end
|
19
19
|
|
20
|
+
def initialize(config:, project: nil, load_defaults: false)
|
21
|
+
@settings = if load_defaults
|
22
|
+
self.class.default_config.merge(config)
|
23
|
+
else
|
24
|
+
config
|
25
|
+
end
|
26
|
+
|
27
|
+
expand_paths(project)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns the path to the puppetdb.conf file on Windows.
|
31
|
+
#
|
32
|
+
# @return [String]
|
33
|
+
#
|
20
34
|
def self.default_windows_config
|
21
35
|
File.expand_path(File.join(ENV['ALLUSERSPROFILE'], 'PuppetLabs/client-tools/puppetdb.conf'))
|
22
36
|
end
|
23
37
|
|
24
|
-
|
38
|
+
# Loads default configuration from the puppetdb.conf file on system. If
|
39
|
+
# the file is not present, defaults to an empty hash.
|
40
|
+
#
|
41
|
+
# @return [Hash]
|
42
|
+
#
|
43
|
+
def self.default_config
|
25
44
|
config = {}
|
26
45
|
global_path = Bolt::Util.windows? ? default_windows_config : DEFAULT_CONFIG[:global]
|
27
46
|
|
@@ -37,13 +56,7 @@ module Bolt
|
|
37
56
|
Bolt::Logger.logger(self).error("Could not load puppetdb.conf from #{filepath}: #{e.message}")
|
38
57
|
end
|
39
58
|
|
40
|
-
config
|
41
|
-
new(config.merge(options), project_path)
|
42
|
-
end
|
43
|
-
|
44
|
-
def initialize(settings, project_path = nil)
|
45
|
-
@settings = settings
|
46
|
-
expand_paths(project_path)
|
59
|
+
config.fetch('puppetdb', {})
|
47
60
|
end
|
48
61
|
|
49
62
|
def token
|
@@ -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
|
@@ -44,11 +44,12 @@ module Bolt
|
|
44
44
|
end
|
45
45
|
|
46
46
|
def ssh_opts
|
47
|
+
# NOTE: not all commands we might use here support various `-o` options,
|
48
|
+
# always provide a way to run without them.
|
47
49
|
cmd = []
|
48
50
|
# BatchMode is SSH's noninteractive option: if key authentication
|
49
51
|
# fails it will error out instead of falling back to password prompt
|
50
|
-
|
51
|
-
cmd += %W[-o BatchMode=#{batch_mode}]
|
52
|
+
cmd += %w[-o BatchMode=yes] if @target.transport_config['batch-mode']
|
52
53
|
|
53
54
|
cmd += %W[-o Port=#{@target.port}] if @target.port
|
54
55
|
|
@@ -68,6 +69,8 @@ module Bolt
|
|
68
69
|
ssh_cmd = Array(ssh_conf)
|
69
70
|
ssh_cmd += ssh_opts
|
70
71
|
ssh_cmd << userhost
|
72
|
+
# Add option separator before command for wrappers around SSH
|
73
|
+
ssh_cmd << '--'
|
71
74
|
ssh_cmd << command
|
72
75
|
end
|
73
76
|
|
data/lib/bolt/util.rb
CHANGED
@@ -344,7 +344,7 @@ module Bolt
|
|
344
344
|
|
345
345
|
if !stat.readable?
|
346
346
|
raise Bolt::FileError.new("The #{type} '#{path}' is unreadable", path)
|
347
|
-
elsif !
|
347
|
+
elsif !allow_dir && stat.directory?
|
348
348
|
expected = allow_dir ? 'file or directory' : 'file'
|
349
349
|
raise Bolt::FileError.new("The #{type} '#{path}' is not a #{expected}", path)
|
350
350
|
elsif stat.directory?
|
data/lib/bolt/version.rb
CHANGED