bolt 1.19.0 → 1.20.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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/bolt-modules/boltlib/lib/puppet/datatypes/applyresult.rb +2 -0
  3. data/bolt-modules/boltlib/lib/puppet/datatypes/result.rb +2 -0
  4. data/bolt-modules/boltlib/lib/puppet/datatypes/resultset.rb +1 -0
  5. data/bolt-modules/boltlib/lib/puppet/functions/add_facts.rb +3 -10
  6. data/bolt-modules/boltlib/lib/puppet/functions/add_to_group.rb +3 -10
  7. data/bolt-modules/boltlib/lib/puppet/functions/apply_prep.rb +3 -9
  8. data/bolt-modules/boltlib/lib/puppet/functions/facts.rb +2 -8
  9. data/bolt-modules/boltlib/lib/puppet/functions/fail_plan.rb +2 -2
  10. data/bolt-modules/boltlib/lib/puppet/functions/get_resources.rb +3 -8
  11. data/bolt-modules/boltlib/lib/puppet/functions/get_targets.rb +2 -8
  12. data/bolt-modules/boltlib/lib/puppet/functions/puppetdb_fact.rb +2 -7
  13. data/bolt-modules/boltlib/lib/puppet/functions/puppetdb_query.rb +2 -7
  14. data/bolt-modules/boltlib/lib/puppet/functions/run_command.rb +2 -7
  15. data/bolt-modules/boltlib/lib/puppet/functions/run_plan.rb +1 -6
  16. data/bolt-modules/boltlib/lib/puppet/functions/run_script.rb +2 -7
  17. data/bolt-modules/boltlib/lib/puppet/functions/run_task.rb +7 -33
  18. data/bolt-modules/boltlib/lib/puppet/functions/set_feature.rb +3 -10
  19. data/bolt-modules/boltlib/lib/puppet/functions/set_var.rb +3 -10
  20. data/bolt-modules/boltlib/lib/puppet/functions/upload_file.rb +2 -7
  21. data/bolt-modules/boltlib/lib/puppet/functions/vars.rb +2 -8
  22. data/bolt-modules/boltlib/lib/puppet/functions/wait_until_available.rb +2 -7
  23. data/bolt-modules/boltlib/lib/puppet/functions/without_default_logging.rb +1 -1
  24. data/lib/bolt/applicator.rb +7 -3
  25. data/lib/bolt/apply_result.rb +8 -21
  26. data/lib/bolt/bolt_option_parser.rb +2 -2
  27. data/lib/bolt/catalog.rb +0 -1
  28. data/lib/bolt/cli.rb +51 -29
  29. data/lib/bolt/config.rb +0 -2
  30. data/lib/bolt/executor.rb +38 -50
  31. data/lib/bolt/inventory/group.rb +5 -0
  32. data/lib/bolt/inventory/group2.rb +5 -0
  33. data/lib/bolt/logger.rb +7 -3
  34. data/lib/bolt/outputter.rb +6 -4
  35. data/lib/bolt/outputter/human.rb +90 -6
  36. data/lib/bolt/outputter/json.rb +4 -4
  37. data/lib/bolt/outputter/logger.rb +53 -0
  38. data/lib/bolt/pal.rb +3 -3
  39. data/lib/bolt/pal/yaml_plan/step.rb +1 -1
  40. data/lib/bolt/plugin.rb +2 -0
  41. data/lib/bolt/plugin/terraform.rb +84 -0
  42. data/lib/bolt/result.rb +12 -8
  43. data/lib/bolt/result_set.rb +4 -0
  44. data/lib/bolt/transport/orch.rb +4 -4
  45. data/lib/bolt/version.rb +1 -1
  46. metadata +4 -3
  47. data/lib/bolt/notifier.rb +0 -23
@@ -2,19 +2,20 @@
2
2
 
3
3
  module Bolt
4
4
  class Outputter
5
- def self.for_format(format, color, trace)
5
+ def self.for_format(format, color, verbose, trace)
6
6
  case format
7
7
  when 'human'
8
- Bolt::Outputter::Human.new(color, trace)
8
+ Bolt::Outputter::Human.new(color, verbose, trace)
9
9
  when 'json'
10
- Bolt::Outputter::JSON.new(color, trace)
10
+ Bolt::Outputter::JSON.new(color, verbose, trace)
11
11
  when nil
12
12
  raise "Cannot use outputter before parsing."
13
13
  end
14
14
  end
15
15
 
16
- def initialize(color, trace, stream = $stdout)
16
+ def initialize(color, verbose, trace, stream = $stdout)
17
17
  @color = color
18
+ @verbose = verbose
18
19
  @trace = trace
19
20
  @stream = stream
20
21
  end
@@ -23,3 +24,4 @@ end
23
24
 
24
25
  require 'bolt/outputter/human'
25
26
  require 'bolt/outputter/json'
27
+ require 'bolt/outputter/logger'
@@ -11,6 +11,14 @@ module Bolt
11
11
 
12
12
  def print_head; end
13
13
 
14
+ def initialize(color, verbose, trace, stream = $stdout)
15
+ super
16
+ # Plans and without_default_logging() calls can both be nested, so we
17
+ # track each of them with a "stack" consisting of an integer.
18
+ @plan_depth = 0
19
+ @disable_depth = 0
20
+ end
21
+
14
22
  def colorize(color, string)
15
23
  if @color && @stream.isatty
16
24
  "\033[#{COLORS[color]}m#{string}\033[0m"
@@ -28,15 +36,37 @@ module Bolt
28
36
  string.sub(/\s\z/, '')
29
37
  end
30
38
 
31
- def print_event(event)
39
+ def handle_event(event)
40
+ return unless enabled? || event[:type] == :enable_default_output
41
+
32
42
  case event[:type]
33
43
  when :node_start
34
- print_start(event[:target])
44
+ print_start(event[:target]) if @verbose
35
45
  when :node_result
36
- print_result(event[:result])
46
+ print_result(event[:result]) if @verbose
47
+ when :step_start
48
+ print_step_start(event) if plan_logging?
49
+ when :step_finish
50
+ print_step_finish(event) if plan_logging?
51
+ when :plan_start
52
+ print_plan_start(event)
53
+ when :plan_finish
54
+ print_plan_finish(event)
55
+ when :enable_default_output
56
+ @disable_depth -= 1
57
+ when :disable_default_output
58
+ @disable_depth += 1
37
59
  end
38
60
  end
39
61
 
62
+ def enabled?
63
+ @disable_depth == 0
64
+ end
65
+
66
+ def plan_logging?
67
+ @plan_depth > 0
68
+ end
69
+
40
70
  def print_start(target)
41
71
  @stream.puts(colorize(:green, "Started on #{target.host}..."))
42
72
  end
@@ -52,6 +82,15 @@ module Bolt
52
82
  @stream.puts(colorize(:red, remove_trail(indent(2, result.error_hash['msg']))))
53
83
  end
54
84
 
85
+ if result.is_a?(Bolt::ApplyResult) && @verbose
86
+ result.resource_logs.each do |log|
87
+ # Omit low-level info/debug messages
88
+ next if %w[info debug].include?(log['level'])
89
+ message = format_log(log)
90
+ @stream.puts(indent(2, message))
91
+ end
92
+ end
93
+
55
94
  if result.message
56
95
  @stream.puts(remove_trail(indent(2, result.message)))
57
96
  end
@@ -74,6 +113,52 @@ module Bolt
74
113
  end
75
114
  end
76
115
 
116
+ def format_log(log)
117
+ color = case log['level']
118
+ when 'warn'
119
+ :yellow
120
+ when 'err'
121
+ :red
122
+ end
123
+ source = "#{log['source']}: " if log['source']
124
+ message = "#{log['level'].capitalize}: #{source}#{log['message']}"
125
+ message = colorize(color, message) if color
126
+ message
127
+ end
128
+
129
+ def print_step_start(description:, targets:, **_kwargs)
130
+ target_str = if targets.length > 5
131
+ "#{targets.count} targets"
132
+ else
133
+ targets.map(&:uri).join(', ')
134
+ end
135
+ @stream.puts(colorize(:green, "Starting: #{description} on #{target_str}"))
136
+ end
137
+
138
+ def print_step_finish(description:, result:, duration:, **_kwargs)
139
+ failures = result.error_set.length
140
+ plural = failures == 1 ? '' : 's'
141
+ message = "Finished: #{description} with #{failures} failure#{plural} in #{duration.round(2)} sec"
142
+ @stream.puts(colorize(:green, message))
143
+ end
144
+
145
+ def print_plan_start(event)
146
+ @plan_depth += 1
147
+ # We use this event to both mark the start of a plan _and_ to enable
148
+ # plan logging for `apply`, so only log the message if we were called
149
+ # with a plan
150
+ if event[:plan]
151
+ @stream.puts(colorize(:green, "Starting: plan #{event[:plan]}"))
152
+ end
153
+ end
154
+
155
+ def print_plan_finish(event)
156
+ @plan_depth -= 1
157
+ plan = event[:plan]
158
+ duration = event[:duration]
159
+ @stream.puts(colorize(:green, "Finished: plan #{plan} in #{duration.round(2)} sec"))
160
+ end
161
+
77
162
  def print_summary(results, elapsed_time = nil)
78
163
  ok_set = results.ok_set
79
164
  unless ok_set.empty?
@@ -222,9 +307,8 @@ module Bolt
222
307
  end
223
308
 
224
309
  # @param [Bolt::ResultSet] apply_result A ResultSet object representing the result of a `bolt apply`
225
- def print_apply_result(apply_result)
226
- apply_result.each { |result| print_result(result) }
227
- print_summary(apply_result)
310
+ def print_apply_result(apply_result, elapsed_time)
311
+ print_summary(apply_result, elapsed_time)
228
312
  end
229
313
 
230
314
  # @param [Bolt::PlanResult] plan_result A PlanResult object
@@ -3,11 +3,11 @@
3
3
  module Bolt
4
4
  class Outputter
5
5
  class JSON < Bolt::Outputter
6
- def initialize(color, trace, stream = $stdout)
6
+ def initialize(color, verbose, trace, stream = $stdout)
7
+ super
7
8
  @items_open = false
8
9
  @object_open = false
9
10
  @preceding_item = false
10
- super(color, trace, stream)
11
11
  end
12
12
 
13
13
  def print_head
@@ -17,7 +17,7 @@ module Bolt
17
17
  @object_open = true
18
18
  end
19
19
 
20
- def print_event(event)
20
+ def handle_event(event)
21
21
  case event[:type]
22
22
  when :node_result
23
23
  print_result(event[:result])
@@ -72,7 +72,7 @@ module Bolt
72
72
  print_table('plans' => plans, 'modulepath' => modulepath)
73
73
  end
74
74
 
75
- def print_apply_result(apply_result)
75
+ def print_apply_result(apply_result, _elapsed_time)
76
76
  @stream.puts apply_result.to_json
77
77
  end
78
78
 
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bolt/pal'
4
+
5
+ module Bolt
6
+ class Outputter
7
+ class Logger < Bolt::Outputter
8
+ def initialize(verbose, trace)
9
+ super(false, verbose, trace)
10
+ @logger = Logging.logger[self]
11
+ end
12
+
13
+ def handle_event(event)
14
+ case event[:type]
15
+ when :step_start
16
+ log_step_start(event)
17
+ when :step_finish
18
+ log_step_finish(event)
19
+ when :plan_start
20
+ log_plan_start(event)
21
+ when :plan_finish
22
+ log_plan_finish(event)
23
+ end
24
+ end
25
+
26
+ def log_step_start(description:, targets:, **_kwargs)
27
+ target_str = if targets.length > 5
28
+ "#{targets.count} targets"
29
+ else
30
+ targets.map(&:uri).join(', ')
31
+ end
32
+ @logger.info("Starting: #{description} on #{target_str}")
33
+ end
34
+
35
+ def log_step_finish(description:, result:, duration:, **_kwargs)
36
+ failures = result.error_set.length
37
+ plural = failures == 1 ? '' : 's'
38
+ @logger.info("Finished: #{description} with #{failures} failure#{plural} in #{duration.round(2)} sec")
39
+ end
40
+
41
+ def log_plan_start(event)
42
+ plan = event[:plan]
43
+ @logger.notice("Starting: plan #{plan}")
44
+ end
45
+
46
+ def log_plan_finish(event)
47
+ plan = event[:plan]
48
+ duration = event[:duration]
49
+ @logger.notice("Finished: plan #{plan} in #{duration.round(2)} sec")
50
+ end
51
+ end
52
+ end
53
+ end
@@ -333,10 +333,10 @@ module Bolt
333
333
  end
334
334
  end
335
335
 
336
- def run_task(task_name, targets, params, executor, inventory, description = nil, &eventblock)
336
+ def run_task(task_name, targets, params, executor, inventory, description = nil)
337
337
  in_task_compiler(executor, inventory) do |compiler|
338
- params = params.merge('_bolt_api_call' => true)
339
- compiler.call_function('run_task', task_name, targets, description, params, &eventblock)
338
+ params = params.merge('_bolt_api_call' => true, '_catch_errors' => true)
339
+ compiler.call_function('run_task', task_name, targets, description, params)
340
340
  end
341
341
  end
342
342
 
@@ -77,7 +77,7 @@ module Bolt
77
77
  # We have to do a little extra parsing here, since we only need
78
78
  # with() for eval blocks
79
79
  code = Bolt::Util.to_code(body['eval'])
80
- if @name && code.include?("\n")
80
+ if @name && code.lines.count > 1
81
81
  # A little indented niceness
82
82
  indented = code.gsub(/\n/, "\n ").chomp(" ")
83
83
  result << "with() || {\n #{indented}}"
@@ -1,12 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'bolt/plugin/puppetdb'
4
+ require 'bolt/plugin/terraform'
4
5
 
5
6
  module Bolt
6
7
  class Plugin
7
8
  def self.setup(config, pdb_client)
8
9
  plugins = new(config)
9
10
  plugins.add_plugin(Bolt::Plugin::Puppetdb.new(pdb_client))
11
+ plugins.add_plugin(Bolt::Plugin::Terraform.new)
10
12
  plugins
11
13
  end
12
14
 
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module Bolt
6
+ class Plugin
7
+ class Terraform
8
+ def initialize
9
+ @logger = Logging.logger[self]
10
+ end
11
+
12
+ def name
13
+ 'terraform'
14
+ end
15
+
16
+ def hooks
17
+ ['lookup_targets']
18
+ end
19
+
20
+ def warn_missing_property(name, property)
21
+ @logger.warn("Could not find property #{property} of terraform resource #{name}")
22
+ end
23
+
24
+ def lookup_targets(opts)
25
+ state = load_statefile(opts)
26
+
27
+ resources = state.fetch('modules', {}).flat_map do |mod|
28
+ mod.fetch('resources', {}).map do |name, resource|
29
+ [name, resource.dig('primary', 'attributes')]
30
+ end
31
+ end
32
+
33
+ regex = Regexp.new(opts['resource_type'])
34
+
35
+ resources.select do |name, _resource|
36
+ name.match?(regex)
37
+ end.map do |name, resource|
38
+ unless resource.key?(opts['uri'])
39
+ warn_missing_property(name, opts['uri'])
40
+ next
41
+ end
42
+
43
+ target = { 'uri' => resource[opts['uri']] }
44
+ if opts.key?('name')
45
+ if resource.key?(opts['name'])
46
+ target['name'] = resource[opts['name']]
47
+ else
48
+ warn_missing_property(name, opts['name'])
49
+ end
50
+ end
51
+ if opts.key?('config')
52
+ target['config'] = resolve_config(name, resource, opts['config'])
53
+ end
54
+ target
55
+ end.compact
56
+ end
57
+
58
+ def load_statefile(opts)
59
+ dir = opts['dir']
60
+ filename = opts.fetch('statefile', 'terraform.tfstate')
61
+ statefile = File.expand_path(File.join(dir, filename))
62
+
63
+ JSON.parse(File.read(statefile))
64
+ rescue StandardError => e
65
+ raise Bolt::FileError.new("Could not load Terraform state file #{filename}: #{e}", filename)
66
+ end
67
+
68
+ def resolve_config(name, resource, config_template)
69
+ Bolt::Util.walk_vals(config_template) do |value|
70
+ if value.is_a?(String)
71
+ if resource.key?(value)
72
+ resource[value]
73
+ else
74
+ warn_missing_property(name, value)
75
+ nil
76
+ end
77
+ else
78
+ value
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -5,7 +5,7 @@ require 'bolt/error'
5
5
 
6
6
  module Bolt
7
7
  class Result
8
- attr_reader :target, :value, :type, :object
8
+ attr_reader :target, :value, :action, :object
9
9
 
10
10
  def self.from_exception(target, exception)
11
11
  @exception = exception
@@ -23,7 +23,7 @@ module Bolt
23
23
  Result.new(target, error: error)
24
24
  end
25
25
 
26
- def self.for_command(target, stdout, stderr, exit_code, type, command)
26
+ def self.for_command(target, stdout, stderr, exit_code, action, command)
27
27
  value = {
28
28
  'stdout' => stdout,
29
29
  'stderr' => stderr,
@@ -37,7 +37,7 @@ module Bolt
37
37
  'details' => { 'exit_code' => exit_code }
38
38
  }
39
39
  end
40
- new(target, value: value, type: type, object: command)
40
+ new(target, value: value, action: action, object: command)
41
41
  end
42
42
 
43
43
  def self.for_task(target, stdout, stderr, exit_code, task)
@@ -61,11 +61,11 @@ module Bolt
61
61
  'msg' => msg,
62
62
  'details' => { 'exit_code' => exit_code } }
63
63
  end
64
- new(target, value: value, type: 'task', object: task)
64
+ new(target, value: value, action: 'task', object: task)
65
65
  end
66
66
 
67
67
  def self.for_upload(target, source, destination)
68
- new(target, message: "Uploaded '#{source}' to '#{target.host}:#{destination}'", type: 'upload', object: source)
68
+ new(target, message: "Uploaded '#{source}' to '#{target.host}:#{destination}'", action: 'upload', object: source)
69
69
  end
70
70
 
71
71
  # Satisfies the Puppet datatypes API
@@ -73,10 +73,10 @@ module Bolt
73
73
  new(target, value: value)
74
74
  end
75
75
 
76
- def initialize(target, error: nil, message: nil, value: nil, type: nil, object: nil)
76
+ def initialize(target, error: nil, message: nil, value: nil, action: nil, object: nil)
77
77
  @target = target
78
78
  @value = value || {}
79
- @type = type
79
+ @action = action
80
80
  @object = object
81
81
  @value_set = !value.nil?
82
82
  if error && !error.is_a?(Hash)
@@ -94,7 +94,7 @@ module Bolt
94
94
  # DEPRECATION: node in status hashes is deprecated and should be removed in 2.0
95
95
  { node: @target.name,
96
96
  target: @target.name,
97
- type: type,
97
+ action: action,
98
98
  object: object,
99
99
  status: ok? ? 'success' : 'failure',
100
100
  result: @value }
@@ -128,6 +128,10 @@ module Bolt
128
128
  to_json
129
129
  end
130
130
 
131
+ def to_data
132
+ Bolt::Util.walk_keys(status_hash, &:to_s)
133
+ end
134
+
131
135
  def ok?
132
136
  error_hash.nil?
133
137
  end
@@ -90,6 +90,10 @@ module Bolt
90
90
  @results.map(&:status_hash).to_json(opts)
91
91
  end
92
92
 
93
+ def to_data
94
+ @results.map(&:to_data)
95
+ end
96
+
93
97
  def to_s
94
98
  to_json
95
99
  end