bolt 3.21.0 → 3.23.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.

Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/Puppetfile +7 -7
  3. data/bolt-modules/boltlib/lib/puppet/functions/puppetdb_command.rb +32 -1
  4. data/bolt-modules/boltlib/lib/puppet/functions/puppetdb_fact.rb +20 -1
  5. data/bolt-modules/boltlib/lib/puppet/functions/puppetdb_query.rb +23 -1
  6. data/bolt-modules/boltlib/lib/puppet/functions/run_task.rb +28 -23
  7. data/bolt-modules/boltlib/lib/puppet/functions/run_task_with.rb +22 -19
  8. data/lib/bolt/applicator.rb +8 -2
  9. data/lib/bolt/bolt_option_parser.rb +4 -1
  10. data/lib/bolt/catalog.rb +1 -1
  11. data/lib/bolt/config/options.rb +73 -49
  12. data/lib/bolt/config.rb +12 -3
  13. data/lib/bolt/module.rb +6 -0
  14. data/lib/bolt/outputter/human.rb +8 -3
  15. data/lib/bolt/pal/yaml_plan/loader.rb +1 -1
  16. data/lib/bolt/pal.rb +6 -2
  17. data/lib/bolt/plugin/env_var.rb +17 -1
  18. data/lib/bolt/plugin/puppetdb.rb +14 -4
  19. data/lib/bolt/plugin.rb +2 -1
  20. data/lib/bolt/project.rb +4 -0
  21. data/lib/bolt/puppetdb/client.rb +90 -129
  22. data/lib/bolt/puppetdb/config.rb +21 -8
  23. data/lib/bolt/puppetdb/instance.rb +146 -0
  24. data/lib/bolt/result.rb +1 -1
  25. data/lib/bolt/transport/ssh/exec_connection.rb +5 -2
  26. data/lib/bolt/util.rb +1 -1
  27. data/lib/bolt/version.rb +1 -1
  28. data/lib/bolt_server/transport_app.rb +1 -0
  29. data/lib/bolt_spec/plans/action_stubs/download_stub.rb +1 -1
  30. data/lib/bolt_spec/plans/action_stubs/plan_stub.rb +1 -1
  31. data/lib/bolt_spec/plans/action_stubs/task_stub.rb +1 -1
  32. data/lib/bolt_spec/plans/action_stubs/upload_stub.rb +1 -1
  33. data/lib/bolt_spec/plans/action_stubs.rb +2 -2
  34. data/lib/bolt_spec/plans/mock_executor.rb +1 -1
  35. data/lib/bolt_spec/plans.rb +11 -1
  36. 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
@@ -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']] || opts['default']
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
@@ -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
- pdb_config = Bolt::PuppetDB::Config.load_config(config, context.boltdir)
21
- @puppetdb_client = Bolt::PuppetDB::Client.new(pdb_config)
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 if 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
@@ -128,6 +128,10 @@ module Bolt
128
128
 
129
129
  @data = data.slice(*Bolt::Config::PROJECT_OPTIONS)
130
130
 
131
+ if @data['rerunfile']
132
+ @rerunfile = File.expand_path(@data['rerunfile'], @path)
133
+ end
134
+
131
135
  validate if project_file?
132
136
  end
133
137
 
@@ -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
- attr_reader :config
10
-
11
- def initialize(config)
12
- @config = config
13
- @bad_urls = []
14
- @current_url = nil
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
- def query_certnames(query)
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
- # This method expects an array of certnames to get facts for
32
- def facts_for_node(certnames)
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
- def fact_values(certnames = [], facts = [])
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
- def make_query(query, path = nil)
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
- # @return A UUID identifying the submitted command.
105
- #
106
- def send_command(command, version, payload)
107
- command = command.dup.force_encoding('utf-8')
108
- body = JSON.generate(payload)
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
- def uri
172
- require 'addressable/uri'
173
-
174
- @current_url ||= (@config.server_urls - @bad_urls).first
175
- unless @current_url
176
- msg = "Failed to connect to all PuppetDB server_urls: #{@config.server_urls.to_a.join(', ')}."
177
- raise Bolt::PuppetDBError, msg
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
@@ -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
- def self.load_config(options, project_path = nil)
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 = config.fetch('puppetdb', {})
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
- batch_mode = @target.transport_config['batch-mode'] ? 'yes' : 'no'
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 !stat.file? && (!allow_dir || !stat.directory?)
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bolt
4
- VERSION = '3.21.0'
4
+ VERSION = '3.23.0'
5
5
  end
@@ -326,6 +326,7 @@ module BoltServer
326
326
  cli << "--basemodulepath" << basemodulepath
327
327
  Puppet.settings.send(:clear_everything_for_tests)
328
328
  Puppet.initialize_settings(cli)
329
+ Puppet[:versioned_environment_dirs] = true
329
330
  yield
330
331
  end
331
332
  end
@@ -41,7 +41,7 @@ module BoltSpec
41
41
  @invocation[:options]
42
42
  end
43
43
 
44
- def result_for(_target, _data)
44
+ def result_for(_target, **_data)
45
45
  raise 'Download result cannot be changed'
46
46
  end
47
47
 
@@ -30,7 +30,7 @@ module BoltSpec
30
30
  end
31
31
 
32
32
  # Allow any data.
33
- def result_for(_target, data)
33
+ def result_for(_target, **data)
34
34
  Bolt::PlanResult.new(Bolt::Util.walk_keys(data, &:to_s), 'success')
35
35
  end
36
36