bolt 0.18.1 → 0.18.2

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.

@@ -16,11 +16,17 @@ module Bolt
16
16
  local: Bolt::Transport::Local
17
17
  }.freeze
18
18
 
19
+ class UnknownTransportError < Bolt::Error
20
+ def initialize(transport, uri = nil)
21
+ msg = uri.nil? ? "Unknown transport #{transport}" : "Unknown transport #{transport} found for #{uri}"
22
+ super(msg, 'bolt/unknown-transport')
23
+ end
24
+ end
25
+
19
26
  Config = Struct.new(
20
27
  :concurrency,
21
28
  :format,
22
29
  :inventoryfile,
23
- :log_level,
24
30
  :log,
25
31
  :modulepath,
26
32
  :puppetdb,
@@ -204,6 +210,10 @@ module Bolt
204
210
  raise Bolt::CLIError, "Unsupported format: '#{self[:format]}'"
205
211
  end
206
212
 
213
+ unless self[:transport].nil? || Bolt::TRANSPORTS.include?(self[:transport].to_sym)
214
+ raise UnknownTransportError, self[:transport]
215
+ end
216
+
207
217
  TRANSPORTS.each do |transport, impl|
208
218
  impl.validate(self[:transports][transport])
209
219
  end
@@ -19,16 +19,12 @@ module Bolt
19
19
  def initialize(config = Bolt::Config.new, noop = nil, plan_logging = false)
20
20
  @config = config
21
21
  @logger = Logging.logger[self]
22
+ @plan_logging = plan_logging
22
23
 
23
24
  @transports = Bolt::TRANSPORTS.each_with_object({}) do |(key, val), coll|
24
25
  coll[key.to_s] = Concurrent::Delay.new { val.new }
25
26
  end
26
27
 
27
- # If a specific elevated log level has been requested, honor that.
28
- # Otherwise, escalate the log level to "info" if running in plan mode, so
29
- # that certain progress messages will be visible.
30
- default_log_level = plan_logging ? :info : :notice
31
- @logger.level = @config[:log_level] || default_log_level
32
28
  @noop = noop
33
29
  @run_as = nil
34
30
  @pool = Concurrent::CachedThreadPool.new(max_threads: @config[:concurrency])
@@ -43,11 +39,11 @@ module Bolt
43
39
  impl.value
44
40
  end
45
41
 
46
- def summary(action, object, result)
42
+ def summary(description, result)
47
43
  fc = result.error_set.length
48
44
  npl = result.length == 1 ? '' : 's'
49
45
  fpl = fc == 1 ? '' : 's'
50
- "Ran #{action} '#{object}' on #{result.length} node#{npl} with #{fc} failure#{fpl}"
46
+ "Finished: #{description} on #{result.length} node#{npl} with #{fc} failure#{fpl}"
51
47
  end
52
48
  private :summary
53
49
 
@@ -97,75 +93,88 @@ module Bolt
97
93
  ResultSet.new(promises.map(&:value))
98
94
  end
99
95
 
96
+ # When running a plan, info messages like starting a task are promoted to notice.
97
+ def log_action(msg)
98
+ @plan_logging ? @logger.notice(msg) : @logger.info(msg)
99
+ end
100
+ private :log_action
101
+
102
+ def with_node_logging(description, batch)
103
+ @logger.info("#{description} on #{batch.map(&:uri)}")
104
+ result = yield
105
+ @logger.info(result.to_json)
106
+ result
107
+ end
108
+ private :with_node_logging
109
+
100
110
  def run_command(targets, command, options = {}, &callback)
101
- @logger.info("Starting command run '#{command}' on #{targets.map(&:uri)}")
111
+ description = options.fetch('_description', "command '#{command}'")
112
+ log_action("Starting: #{description} on #{targets.map(&:uri)}")
102
113
  notify = proc { |event| @notifier.notify(callback, event) if callback }
103
114
  options = { '_run_as' => run_as }.merge(options) if run_as
104
115
 
105
116
  results = batch_execute(targets) do |transport, batch|
106
- transport.batch_command(batch, command, options, &notify)
117
+ with_node_logging("Running command '#{command}'", batch) do
118
+ transport.batch_command(batch, command, options, &notify)
119
+ end
107
120
  end
108
121
 
109
- @logger.info(summary('command', command, results))
122
+ log_action(summary(description, results))
110
123
  @notifier.shutdown
111
124
  results
112
125
  end
113
126
 
114
127
  def run_script(targets, script, arguments, options = {}, &callback)
115
- @logger.info("Starting script run #{script} on #{targets.map(&:uri)}")
116
- @logger.debug("Arguments: #{arguments}")
128
+ description = options.fetch('_description', "script #{script}")
129
+ log_action("Starting: #{description} on #{targets.map(&:uri)}")
130
+
117
131
  notify = proc { |event| @notifier.notify(callback, event) if callback }
118
132
  options = { '_run_as' => run_as }.merge(options) if run_as
119
133
 
120
134
  results = batch_execute(targets) do |transport, batch|
121
- transport.batch_script(batch, script, arguments, options, &notify)
135
+ with_node_logging("Running script #{script} with '#{arguments}'", batch) do
136
+ transport.batch_script(batch, script, arguments, options, &notify)
137
+ end
122
138
  end
123
139
 
124
- @logger.info(summary('script', script, results))
140
+ log_action(summary(description, results))
125
141
  @notifier.shutdown
126
142
  results
127
143
  end
128
144
 
129
145
  def run_task(targets, task, arguments, options = {}, &callback)
130
- task_name = task.name
131
- @logger.info("Starting task #{task_name} on #{targets.map(&:uri)}")
132
- @logger.debug("Arguments: #{arguments} Input method: #{task.input_method}")
146
+ description = options.fetch('_description', "task #{task.name}")
147
+ log_action("Starting: #{description} on #{targets.map(&:uri)}")
148
+
133
149
  notify = proc { |event| @notifier.notify(callback, event) if callback }
134
150
  options = { '_run_as' => run_as }.merge(options) if run_as
135
151
 
136
152
  results = batch_execute(targets) do |transport, batch|
137
- transport.batch_task(batch, task, arguments, options, &notify)
153
+ with_node_logging("Running task #{task.name} with '#{arguments}' via #{task.input_method}", batch) do
154
+ transport.batch_task(batch, task, arguments, options, &notify)
155
+ end
138
156
  end
139
157
 
140
- @logger.info(summary('task', task_name, results))
158
+ log_action(summary(description, results))
141
159
  @notifier.shutdown
142
160
  results
143
161
  end
144
162
 
145
163
  def file_upload(targets, source, destination, options = {}, &callback)
146
- @logger.info("Starting file upload from #{source} to #{destination} on #{targets.map(&:uri)}")
164
+ description = options.fetch('_description', "file upload from #{source} to #{destination}")
165
+ log_action("Starting: #{description} on #{targets.map(&:uri)}")
147
166
  notify = proc { |event| @notifier.notify(callback, event) if callback }
148
167
  options = { '_run_as' => run_as }.merge(options) if run_as
149
168
 
150
169
  results = batch_execute(targets) do |transport, batch|
151
- transport.batch_upload(batch, source, destination, options, &notify)
170
+ with_node_logging("Uploading file #{source} to #{destination}", batch) do
171
+ transport.batch_upload(batch, source, destination, options, &notify)
172
+ end
152
173
  end
153
174
 
154
- @logger.info(summary('upload', source, results))
175
+ log_action(summary(description, results))
155
176
  @notifier.shutdown
156
177
  results
157
178
  end
158
-
159
- def puppetdb_client
160
- return @puppetdb_client if @puppetdb_client
161
- puppetdb_config = Bolt::PuppetDB::Config.new(nil, @config.puppetdb)
162
- @puppetdb_client = Bolt::PuppetDB::Client.from_config(puppetdb_config)
163
- end
164
-
165
- def puppetdb_fact(certnames)
166
- puppetdb_client.facts_for_node(certnames)
167
- rescue StandardError => e
168
- raise Bolt::CLIError, "Could not retrieve targets from PuppetDB: #{e}"
169
- end
170
179
  end
171
180
  end
@@ -138,6 +138,10 @@ module Bolt
138
138
  conf.update_from_inventory(data['config'])
139
139
  conf.validate
140
140
 
141
+ unless target.protocol.nil? || Bolt::TRANSPORTS.include?(target.protocol.to_sym)
142
+ raise Bolt::UnknownTransportError.new(target.protocol, target.uri)
143
+ end
144
+
141
145
  target.update_conf(conf.transport_conf)
142
146
  end
143
147
  private :update_target
@@ -12,14 +12,15 @@ module Bolt
12
12
  # Nothing works without initialized this global state. Reinitializing
13
13
  # is safe and in practice only happen in tests
14
14
  self.class.load_puppet
15
- self.class.configure_logging(config[:log_level])
15
+ self.class.configure_logging
16
+ # This makes sure we don't accidentally create puppet dirs
17
+ with_puppet_settings { |_| nil }
16
18
 
17
19
  @config = config
18
20
  end
19
21
 
20
22
  # Puppet logging is global so this is class method to avoid confusion
21
- def self.configure_logging(log_level)
22
- Puppet[:log_level] = log_level == :debug ? 'debug' : 'notice'
23
+ def self.configure_logging
23
24
  Puppet::Util::Log.newdestination(:console)
24
25
  end
25
26
 
@@ -40,10 +41,6 @@ module Bolt
40
41
 
41
42
  # Now that puppet is loaded we can include puppet mixins in data types
42
43
  Bolt::ResultSet.include_iterable
43
-
44
- unless Puppet.settings.global_defaults_initialized?
45
- Puppet.initialize_settings
46
- end
47
44
  end
48
45
 
49
46
  # Create a top-level alias for TargetSpec so that users don't have to
@@ -98,12 +95,15 @@ module Bolt
98
95
  r
99
96
  end
100
97
 
101
- def with_bolt_executor(executor, inventory, &block)
102
- Puppet.override({ bolt_executor: executor, bolt_inventory: inventory }, &block)
98
+ def with_bolt_executor(executor, inventory, pdb_client = nil, &block)
99
+ Puppet.override({ bolt_executor: executor, bolt_inventory: inventory, bolt_pdb_client: pdb_client }, &block)
103
100
  end
104
101
 
105
- def in_plan_compiler(executor, inventory)
106
- with_bolt_executor(executor, inventory) do
102
+ def in_plan_compiler(executor, inventory, pdb_client)
103
+ with_bolt_executor(executor, inventory, pdb_client) do
104
+ # TODO: remove this call and see if anything breaks when
105
+ # settings dirs don't actually exist. Plans shouldn't
106
+ # actually be using them.
107
107
  with_puppet_settings do
108
108
  in_bolt_compiler do |compiler|
109
109
  yield compiler
@@ -219,14 +219,14 @@ module Bolt
219
219
  plan_info
220
220
  end
221
221
 
222
- def run_task(task_name, targets, params, executor, inventory, &eventblock)
222
+ def run_task(task_name, targets, params, executor, inventory, description = nil, &eventblock)
223
223
  in_task_compiler(executor, inventory) do |compiler|
224
- compiler.call_function('run_task', task_name, targets, params, &eventblock)
224
+ compiler.call_function('run_task', task_name, targets, description, params, &eventblock)
225
225
  end
226
226
  end
227
227
 
228
- def run_plan(plan_name, params, executor = nil, inventory = nil)
229
- in_plan_compiler(executor, inventory) do |compiler|
228
+ def run_plan(plan_name, params, executor = nil, inventory = nil, pdb_client = nil)
229
+ in_plan_compiler(executor, inventory, pdb_client) do |compiler|
230
230
  r = compiler.call_function('run_plan', plan_name, params)
231
231
  Bolt::PuppetError.convert_puppet_errors(r)
232
232
  end
@@ -55,27 +55,21 @@ module Bolt
55
55
  def facts_for_node(certnames)
56
56
  return {} if certnames.empty? || certnames.nil?
57
57
 
58
- # This inits a hash of {certname1 => {}, certname2 => {}} etc.
59
- output = Hash[certnames.product([{}])]
60
-
61
58
  certnames.uniq!
62
59
  name_query = certnames.map { |c| ["=", "certname", c] }
63
60
  name_query.insert(0, "or")
64
61
  body = JSON.generate(query: name_query)
65
62
 
66
- response = http_client.post("#{@uri}/pdb/query/v4/facts", body: body, header: headers)
63
+ response = http_client.post("#{@uri}/pdb/query/v4/inventory", body: body, header: headers)
67
64
  if response.code != 200
68
65
  raise Bolt::PuppetDBError, "Failed to query PuppetDB: #{response.body}"
69
66
  else
70
67
  parsed = JSON.parse(response.body)
71
68
  end
72
69
 
73
- # Parse the data
74
- parsed&.each do |f|
75
- output[f['certname']][f['name']] = f['value']
70
+ parsed&.each_with_object({}) do |node, coll|
71
+ coll[node['certname']] = node['facts']
76
72
  end
77
-
78
- output
79
73
  end
80
74
 
81
75
  def http_client
@@ -68,6 +68,11 @@ module Bolt
68
68
  new(target, message: "Uploaded '#{source}' to '#{target.host}:#{destination}'")
69
69
  end
70
70
 
71
+ # Satisfies the Puppet datatypes API
72
+ def self.from_asserted_args(target, value)
73
+ new(target, value: value)
74
+ end
75
+
71
76
  def initialize(target, error: nil, message: nil, value: nil)
72
77
  @target = target
73
78
  @value = value || {}
@@ -38,14 +38,16 @@ module Bolt
38
38
  OrchestratorClient.new(client_opts, true)
39
39
  end
40
40
 
41
- def build_request(targets, task, arguments)
42
- { task: task.name,
43
- environment: targets.first.options["task-environment"],
44
- noop: arguments['_noop'],
45
- params: arguments.reject { |k, _| k == '_noop' },
46
- scope: {
47
- nodes: targets.map(&:host)
48
- } }
41
+ def build_request(targets, task, arguments, description = nil)
42
+ body = { task: task.name,
43
+ environment: targets.first.options["task-environment"],
44
+ noop: arguments['_noop'],
45
+ params: arguments.reject { |k, _| k == '_noop' },
46
+ scope: {
47
+ nodes: targets.map(&:host)
48
+ } }
49
+ body[:description] = description if description
50
+ body
49
51
  end
50
52
 
51
53
  def process_run_results(targets, results)
@@ -75,11 +77,15 @@ module Bolt
75
77
  end
76
78
  end
77
79
 
78
- def batch_command(targets, command, _options = {}, &callback)
80
+ def batch_command(targets, command, options = {}, &callback)
81
+ params = {
82
+ action: 'command',
83
+ command: command
84
+ }
79
85
  results = run_task_job(targets,
80
86
  BOLT_MOCK_TASK,
81
- action: 'command',
82
- command: command,
87
+ params,
88
+ options,
83
89
  &callback)
84
90
  callback ||= proc {}
85
91
  results.map! { |result| unwrap_bolt_result(result.target, result) }
@@ -88,7 +94,7 @@ module Bolt
88
94
  end
89
95
  end
90
96
 
91
- def batch_script(targets, script, arguments, _options = {}, &callback)
97
+ def batch_script(targets, script, arguments, options = {}, &callback)
92
98
  content = File.open(script, &:read)
93
99
  content = Base64.encode64(content)
94
100
  params = {
@@ -97,14 +103,14 @@ module Bolt
97
103
  arguments: arguments
98
104
  }
99
105
  callback ||= proc {}
100
- results = run_task_job(targets, BOLT_MOCK_TASK, params, &callback)
106
+ results = run_task_job(targets, BOLT_MOCK_TASK, params, options, &callback)
101
107
  results.map! { |result| unwrap_bolt_result(result.target, result) }
102
108
  results.each do |result|
103
109
  callback.call(type: :node_result, result: result)
104
110
  end
105
111
  end
106
112
 
107
- def batch_upload(targets, source, destination, _options = {}, &callback)
113
+ def batch_upload(targets, source, destination, options = {}, &callback)
108
114
  content = File.open(source, &:read)
109
115
  content = Base64.encode64(content)
110
116
  mode = File.stat(source).mode
@@ -115,7 +121,7 @@ module Bolt
115
121
  mode: mode
116
122
  }
117
123
  callback ||= proc {}
118
- results = run_task_job(targets, BOLT_MOCK_TASK, params, &callback)
124
+ results = run_task_job(targets, BOLT_MOCK_TASK, params, options, &callback)
119
125
  results.map! do |result|
120
126
  if result.error_hash
121
127
  result
@@ -136,8 +142,8 @@ module Bolt
136
142
  end.values
137
143
  end
138
144
 
139
- def run_task_job(targets, task, arguments)
140
- body = build_request(targets, task, arguments)
145
+ def run_task_job(targets, task, arguments, options)
146
+ body = build_request(targets, task, arguments, options['_description'])
141
147
 
142
148
  targets.each do |target|
143
149
  yield(type: :node_start, target: target) if block_given?
@@ -158,9 +164,9 @@ module Bolt
158
164
  end
159
165
  end
160
166
 
161
- def batch_task(targets, task, arguments, _options = {}, &callback)
167
+ def batch_task(targets, task, arguments, options = {}, &callback)
162
168
  callback ||= proc {}
163
- results = run_task_job(targets, task, arguments, &callback)
169
+ results = run_task_job(targets, task, arguments, options, &callback)
164
170
  results.each do |result|
165
171
  callback.call(type: :node_result, result: result)
166
172
  end
@@ -83,6 +83,7 @@ try
83
83
  }
84
84
  catch
85
85
  {
86
+ Write-Error $_.Exception
86
87
  exit 1
87
88
  }
88
89
  PS
@@ -124,10 +125,10 @@ catch
124
125
  $private:taskArgs = Get-ContentAsJson (
125
126
  $utf8.GetString([System.Convert]::FromBase64String('#{Base64.encode64(JSON.dump(arguments))}'))
126
127
  )
127
- try { & "#{remote_path}" @taskArgs } catch { exit 1 }
128
+ try { & "#{remote_path}" @taskArgs } catch { Write-Error $_.Exception; exit 1 }
128
129
  PS
129
130
  else
130
- conn.execute(%(try { & "#{remote_path}" } catch { exit 1 }))
131
+ conn.execute(%(try { & "#{remote_path}" } catch { Write-Error $_.Exception; exit 1 }))
131
132
  end
132
133
  else
133
134
  path, args = *process_from_extension(remote_path)
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bolt
4
+ module Util
5
+ class OnAccess
6
+ def initialize(&block)
7
+ @constructor = block
8
+ @obj = nil
9
+ end
10
+
11
+ # If a method is called and we haven't constructed the object,
12
+ # construct it. Then pass the call to the object.
13
+ # rubocop:disable Style/MethodMissing
14
+ def method_missing(method, *args, &block)
15
+ if @obj.nil?
16
+ @obj = @constructor.call
17
+ end
18
+
19
+ @obj.send(method, *args, &block)
20
+ end
21
+ # rubocop:enable Style/MethodMissing
22
+ end
23
+ end
24
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bolt
4
- VERSION = '0.18.1'
4
+ VERSION = '0.18.2'
5
5
  end