bolt 3.23.1 → 3.26.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 67f08b10db67ef79aadf22a542f6564aa502743f53619f41c40aff4761e2e0c0
4
- data.tar.gz: 81d8b927c1349299ba568358a2f155d624fb28f7b65ae5985bcfc8b6fe24eb6b
3
+ metadata.gz: '0591d13a9aa93f3c96585985ffdf1619c29f3f13130cbde98e1b24a1a357b84b'
4
+ data.tar.gz: 3fe46914478ad07fab90348eb0464a5e45f5c10fa5f921baf3fa089f8d17cad3
5
5
  SHA512:
6
- metadata.gz: a3e66f8d5adc727228b09e212de34c7de796c556a0d91153e1599cbf619b30b98c9358ea3683c2e00650375ecac8745fdf7a79977a42d6761967ce98c5c2ac71
7
- data.tar.gz: a6dedd218bc2db8e5365906366a1cbeef5e99b02f606c80677081eccc71172fa7f15f3798630847239228feafc861278938044e98b2107429c8e34cea7143f9c
6
+ metadata.gz: a12e686a2ce4a3a15f22e2d45f8210eb6211c910e49e59377e37165554ae5f776c4827dfdb93984d8acdd6295cb7c31f49a090a4239ecc2150bff27f2aeb2bbc
7
+ data.tar.gz: 5f6ccddd83c3026b1a34be7ab439d800f82cb997f179ab9967ead53c87b5141f2419dce5ae0a29ad17fc4257b294856e39aa61916fca4cbb907919520ad8fbf3
data/Puppetfile CHANGED
@@ -1,12 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- forge "http://forge.puppetlabs.com"
3
+ forge 'https://forge.puppetlabs.com'
4
4
 
5
5
  moduledir File.join(File.dirname(__FILE__), 'modules')
6
6
 
7
7
  # Core modules used by 'apply'
8
8
  mod 'puppetlabs-service', '2.2.0'
9
- mod 'puppetlabs-puppet_agent', '4.11.0'
9
+ mod 'puppetlabs-puppet_agent', '4.12.1'
10
10
  mod 'puppetlabs-facts', '1.4.0'
11
11
 
12
12
  # Core types and providers for Puppet 6
@@ -23,13 +23,16 @@ mod 'puppetlabs-zone_core', '1.0.3'
23
23
 
24
24
  # Useful additional modules
25
25
  mod 'puppetlabs-package', '2.2.0'
26
- mod 'puppetlabs-powershell_task_helper', '0.1.0'
27
26
  mod 'puppetlabs-puppet_conf', '1.3.0'
28
- mod 'puppetlabs-python_task_helper', '0.5.0'
29
- mod 'puppetlabs-reboot', '4.1.0'
27
+ mod 'puppetlabs-reboot', '4.2.0'
28
+ mod 'puppetlabs-stdlib', '8.4.0'
29
+
30
+ # Task helpers
31
+ mod 'puppetlabs-powershell_task_helper', '0.1.0'
30
32
  mod 'puppetlabs-ruby_task_helper', '0.6.1'
31
33
  mod 'puppetlabs-ruby_plugin_helper', '0.2.0'
32
- mod 'puppetlabs-stdlib', '8.2.0'
34
+ mod 'puppetlabs-python_task_helper', '0.5.0'
35
+ mod 'puppetlabs-bash_task_helper', '2.0.0'
33
36
 
34
37
  # Plugin modules
35
38
  mod 'puppetlabs-aws_inventory', '0.7.0'
@@ -12,11 +12,20 @@
12
12
  # keys](applying_manifest_blocks.md#result-keys).
13
13
  # @param target
14
14
  # The target the result is from.
15
+ # @param error
16
+ # An Error object constructed from the `_error` field of the result's value.
17
+ # @param catalog
18
+ # The Puppet catalog used to configure the target. The catalog describes the
19
+ # desired state of the target. The catalog is masked with the `Sensitive` data
20
+ # type to protect any sensitive information in the catalog from being printed
21
+ # to the console or logs. Using this function automatically unwraps the
22
+ # catalog. For more information about catalogs and the catalog compilation
23
+ # process, see [Catalog
24
+ # compilation](https://puppet.com/docs/puppet/latest/subsystem_catalog_compilation.html).
25
+
15
26
  #
16
27
  # @!method action
17
28
  # The action performed. `ApplyResult.action` always returns the string `apply`.
18
- # @!method error
19
- # Returns an Error object constructed from the `_error` field of the result's value.
20
29
  # @!method message
21
30
  # The `_output` field of the result's value.
22
31
  # @!method ok
@@ -30,10 +39,11 @@ Puppet::DataTypes.create_type('ApplyResult') do
30
39
  interface <<-PUPPET
31
40
  attributes => {
32
41
  'report' => Hash[String[1], Data],
33
- 'target' => Target
42
+ 'target' => Target,
43
+ 'error' => Optional[Error],
44
+ 'catalog' => Optional[Hash]
34
45
  },
35
46
  functions => {
36
- error => Callable[[], Optional[Error]],
37
47
  ok => Callable[[], Boolean],
38
48
  message => Callable[[], Optional[String]],
39
49
  action => Callable[[], String],
@@ -49,7 +49,7 @@ module Bolt
49
49
  code
50
50
  end
51
51
 
52
- targets = inventory.get_targets(targets)
52
+ targets = inventory.get_targets(targets, ext_glob: true)
53
53
 
54
54
  Puppet[:tasks] = false
55
55
  ast = pal.parse_manifest(manifest_code, manifest)
@@ -90,7 +90,7 @@ module Bolt
90
90
  # @return [Bolt::ResultSet]
91
91
  #
92
92
  def run_command(command, targets, env_vars: nil)
93
- targets = inventory.get_targets(targets)
93
+ targets = inventory.get_targets(targets, ext_glob: true)
94
94
 
95
95
  with_benchmark do
96
96
  executor.run_command(targets, command, env_vars: env_vars)
@@ -106,7 +106,7 @@ module Bolt
106
106
  #
107
107
  def download_file(source, destination, targets)
108
108
  destination = File.expand_path(destination, Dir.pwd)
109
- targets = inventory.get_targets(targets)
109
+ targets = inventory.get_targets(targets, ext_glob: true)
110
110
 
111
111
  with_benchmark do
112
112
  executor.download_file(targets, source, destination)
@@ -122,7 +122,7 @@ module Bolt
122
122
  #
123
123
  def upload_file(source, destination, targets)
124
124
  source = find_file(source)
125
- targets = inventory.get_targets(targets)
125
+ targets = inventory.get_targets(targets, ext_glob: true)
126
126
 
127
127
  Bolt::Util.validate_file('source file', source, true)
128
128
 
@@ -225,7 +225,7 @@ module Bolt
225
225
 
226
226
  with_benchmark do
227
227
  pal.lookup(key,
228
- inventory.get_targets(targets),
228
+ inventory.get_targets(targets, ext_glob: true),
229
229
  inventory,
230
230
  executor,
231
231
  plan_vars: vars)
@@ -351,6 +351,7 @@ module Bolt
351
351
  # @return [Bolt::PlanResult]
352
352
  #
353
353
  def run_plan(plan, targets, params: {})
354
+ plan_params = pal.get_plan_info(plan)['parameters']
354
355
  if targets && targets.any?
355
356
  if params['nodes'] || params['targets']
356
357
  key = params.include?('nodes') ? 'nodes' : 'targets'
@@ -360,7 +361,6 @@ module Bolt
360
361
  "in the JSON data passed in the --params option"
361
362
  end
362
363
 
363
- plan_params = pal.get_plan_info(plan)['parameters']
364
364
  target_param = plan_params.dig('targets', 'type') =~ /TargetSpec/
365
365
  node_param = plan_params.include?('nodes')
366
366
 
@@ -375,13 +375,17 @@ module Bolt
375
375
  end
376
376
  end
377
377
 
378
- plan_context = { plan_name: plan, params: params }
378
+ sensitive_params = params.keys.select { |param| plan_params.dig(param, 'sensitive') }
379
+
380
+ plan_context = { plan_name: plan, params: params, sensitive: sensitive_params }
379
381
 
380
382
  executor.start_plan(plan_context)
381
383
  result = pal.run_plan(plan, params, executor, inventory, plugins.puppetdb_client)
382
384
  executor.finish_plan(result)
383
385
 
384
386
  result
387
+ rescue Bolt::Error => e
388
+ Bolt::PlanResult.new(e, 'failure')
385
389
  end
386
390
 
387
391
  # Show plan information.
@@ -620,7 +624,10 @@ module Bolt
620
624
  Bolt::Util.validate_file('script', script)
621
625
 
622
626
  with_benchmark do
623
- executor.run_script(inventory.get_targets(targets), script, arguments, env_vars: env_vars)
627
+ executor.run_script(inventory.get_targets(targets, ext_glob: true),
628
+ script,
629
+ arguments,
630
+ env_vars: env_vars)
624
631
  end
625
632
  end
626
633
 
@@ -676,7 +683,7 @@ module Bolt
676
683
  # @return [Bolt::ResultSet]
677
684
  #
678
685
  def run_task(task, targets, params: {})
679
- targets = inventory.get_targets(targets)
686
+ targets = inventory.get_targets(targets, ext_glob: true)
680
687
 
681
688
  with_benchmark do
682
689
  pal.run_task(task, targets, params, executor, inventory)
@@ -775,7 +782,7 @@ module Bolt
775
782
  # Retrieve the known group and target names. This needs to be done before
776
783
  # updating targets, as that will add adhoc targets to the inventory.
777
784
  known_names = inventory.target_names
778
- targets = inventory.get_targets(targets)
785
+ targets = inventory.get_targets(targets, ext_glob: true)
779
786
 
780
787
  inventory_targets, adhoc_targets = targets.partition do |target|
781
788
  known_names.include?(target.name)
@@ -280,7 +280,6 @@ module Bolt
280
280
  result
281
281
  end
282
282
  else
283
-
284
283
  arguments = {
285
284
  'catalog' => Puppet::Pops::Types::PSensitiveType::Sensitive.new(catalog),
286
285
  'plugins' => Puppet::Pops::Types::PSensitiveType::Sensitive.new(plugins),
@@ -291,7 +290,7 @@ module Bolt
291
290
 
292
291
  callback = proc do |event|
293
292
  if event[:type] == :node_result
294
- event = event.merge(result: ApplyResult.from_task_result(event[:result]))
293
+ event = event.merge(result: ApplyResult.from_task_result(event[:result], catalog))
295
294
  end
296
295
  @executor.publish_event(event)
297
296
  end
@@ -299,7 +298,7 @@ module Bolt
299
298
  options[:run_as] = @executor.run_as if @executor.run_as && !options.key?(:run_as)
300
299
 
301
300
  results = transport.batch_task(batch, catalog_apply_task, arguments, options, &callback)
302
- Array(results).map { |result| ApplyResult.from_task_result(result) }
301
+ Array(results).map { |result| ApplyResult.from_task_result(result, catalog) }
303
302
  end
304
303
  end
305
304
  end
@@ -65,23 +65,30 @@ module Bolt
65
65
  end
66
66
  end
67
67
 
68
- def self.from_task_result(result)
68
+ def self.from_task_result(result, catalog = nil)
69
69
  if (puppet_missing = puppet_missing_error(result))
70
70
  new(result.target,
71
71
  error: puppet_missing,
72
- report: result.value.reject { |k| k == '_error' })
72
+ report: result.value.reject { |k| k == '_error' },
73
+ catalog: catalog)
73
74
  elsif !result.ok?
74
- new(result.target, error: result.error_hash)
75
+ new(result.target,
76
+ error: result.error_hash,
77
+ catalog: catalog)
75
78
  elsif (invalid_report = invalid_report_error(result))
76
79
  new(result.target,
77
80
  error: invalid_report,
78
- report: result.value.reject { |k| %w[_error _output].include?(k) })
81
+ report: result.value.reject { |k| %w[_error _output].include?(k) },
82
+ catalog: catalog)
79
83
  elsif (resource_error = resource_error(result))
80
84
  new(result.target,
81
85
  error: resource_error,
82
- report: result.value.reject { |k| k == '_error' })
86
+ report: result.value.reject { |k| k == '_error' },
87
+ catalog: catalog)
83
88
  else
84
- new(result.target, report: result.value)
89
+ new(result.target,
90
+ report: result.value,
91
+ catalog: catalog)
85
92
  end
86
93
  end
87
94
 
@@ -89,16 +96,21 @@ module Bolt
89
96
  def _pcore_init_hash
90
97
  { 'target' => @target,
91
98
  'error' => value['_error'],
92
- 'report' => value['report'] }
99
+ 'report' => value['report'],
100
+ 'catalog' => catalog }
93
101
  end
94
102
 
95
- def initialize(target, error: nil, report: nil)
103
+ def initialize(target, error: nil, report: nil, catalog: nil)
96
104
  @target = target
97
105
  @value = {}
98
106
  @action = 'apply'
99
107
  @value['report'] = report if report
100
108
  @value['_error'] = error if error
101
109
  @value['_output'] = metrics_message if metrics_message
110
+
111
+ if catalog
112
+ @value['_sensitive'] = Puppet::Pops::Types::PSensitiveType::Sensitive.new({ 'catalog' => catalog })
113
+ end
102
114
  end
103
115
 
104
116
  def event_metrics
@@ -131,6 +143,10 @@ module Bolt
131
143
  @value['report']
132
144
  end
133
145
 
146
+ def catalog
147
+ sensitive.unwrap['catalog'] if sensitive
148
+ end
149
+
134
150
  def generic_value
135
151
  {}
136
152
  end
@@ -9,6 +9,7 @@ module Bolt
9
9
  class LXD < Base
10
10
  OPTIONS = %w[
11
11
  cleanup
12
+ interpreters
12
13
  remote
13
14
  tmpdir
14
15
  ].concat(RUN_AS_OPTIONS).sort.freeze
@@ -17,6 +18,14 @@ module Bolt
17
18
  'cleanup' => true,
18
19
  'remote' => 'local'
19
20
  }.freeze
21
+
22
+ private def validate
23
+ super
24
+
25
+ if @config['interpreters']
26
+ @config['interpreters'] = normalize_interpreters(@config['interpreters'])
27
+ end
28
+ end
20
29
  end
21
30
  end
22
31
  end
@@ -10,8 +10,10 @@ module Bolt
10
10
  class Group
11
11
  attr_accessor :name, :groups
12
12
 
13
- # Regex used to validate group names and target aliases.
14
- NAME_REGEX = /\A[a-z0-9_][a-z0-9_-]*\Z/.freeze
13
+ # Illegal characters that are not permitted in group names or aliases.
14
+ # These characters are delimiters for target and group names and allowing
15
+ # them would cause unexpected behavior.
16
+ ILLEGAL_CHARS = /[\s,]/.freeze
15
17
 
16
18
  # NOTE: All keys should have a corresponding schema property in schemas/bolt-inventory.schema.json
17
19
  DATA_KEYS = %w[config facts vars features plugin_hooks].freeze
@@ -41,7 +43,10 @@ module Bolt
41
43
  @name = @plugins.resolve_references(input['name'])
42
44
 
43
45
  raise ValidationError.new("Group name must be a String, not #{@name.inspect}", nil) unless @name.is_a?(String)
44
- raise ValidationError.new("Invalid group name #{@name}", @name) unless @name =~ NAME_REGEX
46
+
47
+ if (illegal_char = @name.match(ILLEGAL_CHARS))
48
+ raise ValidationError.new("Illegal character '#{illegal_char}' in group name '#{@name}'", @name)
49
+ end
45
50
 
46
51
  validate_group_input(input)
47
52
 
@@ -167,7 +172,9 @@ module Bolt
167
172
 
168
173
  def insert_alia(target_name, aliases)
169
174
  aliases.each do |alia|
170
- raise ValidationError.new("Invalid alias #{alia}", @name) unless alia =~ NAME_REGEX
175
+ if (illegal_char = alia.match(ILLEGAL_CHARS))
176
+ raise ValidationError.new("Illegal character '#{illegal_char}' in alias '#{alia}'", @name)
177
+ end
171
178
 
172
179
  if (found = @aliases[alia])
173
180
  raise ValidationError.new(alias_conflict(alia, found, target_name), @name)
@@ -8,6 +8,12 @@ module Bolt
8
8
  class Inventory
9
9
  attr_reader :plugins, :source, :targets, :transport
10
10
 
11
+ # Getting targets from the inventory using '--targets' supports extended glob pattern
12
+ # matching. In this case, use the extended regex so Bolt only uses commas outside
13
+ # brackets and braces as delimiters.
14
+ EXTENDED_TARGET_REGEX = /[[:space:],]+(?=[^\]}]*(?:[\[{]|$))/.freeze
15
+ TARGET_REGEX = /[[:space:],]+/.freeze
16
+
11
17
  class WildcardError < Bolt::Error
12
18
  def initialize(target)
13
19
  super("Found 0 targets matching wildcard pattern #{target}", 'bolt.inventory/wildcard-error')
@@ -85,8 +91,8 @@ module Bolt
85
91
  # alias for analytics
86
92
  alias node_names target_names
87
93
 
88
- def get_targets(targets)
89
- target_array = expand_targets(targets)
94
+ def get_targets(targets, ext_glob: false)
95
+ target_array = expand_targets(targets, ext_glob: ext_glob)
90
96
  if target_array.is_a? Array
91
97
  target_array.flatten.uniq(&:name)
92
98
  else
@@ -107,41 +113,63 @@ module Bolt
107
113
  groups.group_collect(target_name)
108
114
  end
109
115
 
116
+ # Does a target match the glob-style wildcard?
117
+ # Ignore case; use extended globs ({a,b}) when running from the CLI.
118
+ def match_wildcard?(wildcard, target_name, ext_glob: false)
119
+ if ext_glob
120
+ File.fnmatch(wildcard, target_name, File::FNM_CASEFOLD | File::FNM_EXTGLOB)
121
+ else
122
+ regexp = Regexp.new("^#{Regexp.escape(wildcard).gsub('\*', '.*?')}$", Regexp::IGNORECASE)
123
+ target_name =~ regexp
124
+ end
125
+ end
126
+
110
127
  # If target is a group name, expand it to the members of that group.
111
- # Else match against targets in inventory by name or alias.
128
+ # Else match against groups and targets in inventory by name or alias.
112
129
  # If a wildcard string, error if no matches are found.
113
130
  # Else fall back to [target] if no matches are found.
114
- def resolve_name(target)
131
+ def resolve_name(target, ext_glob: false)
115
132
  if (group = group_lookup[target])
116
- group.all_targets
133
+ group.all_targets.to_a
117
134
  else
118
- # Try to wildcard match targets in inventory
119
- # Ignore case because hostnames are generally case-insensitive
120
- regexp = Regexp.new("^#{Regexp.escape(target).gsub('\*', '.*?')}$", Regexp::IGNORECASE)
135
+ targets = []
136
+
137
+ # Find groups that match the glob
138
+ group_lookup.each do |name, grp|
139
+ next unless match_wildcard?(target, name, ext_glob: ext_glob)
140
+ targets += grp.all_targets.to_a
141
+ end
142
+
143
+ # Find target names that match the glob
144
+ targets += groups.all_targets.select { |targ| match_wildcard?(target, targ, ext_glob: ext_glob) }
121
145
 
122
- targets = groups.all_targets.select { |targ| targ =~ regexp }
123
- targets += groups.target_aliases.select { |target_alias, _target| target_alias =~ regexp }.values
146
+ # Find target aliases that match the glob
147
+ targets += groups.target_aliases
148
+ .select { |tgt_alias, _| match_wildcard?(target, tgt_alias, ext_glob: ext_glob) }
149
+ .values
124
150
 
125
151
  if targets.empty?
126
152
  raise(WildcardError, target) if target.include?('*')
127
153
  [target]
128
154
  else
129
- targets
155
+ targets.uniq
130
156
  end
131
157
  end
132
158
  end
133
159
  private :resolve_name
134
160
 
135
- def expand_targets(targets)
161
+ def expand_targets(targets, ext_glob: false)
136
162
  case targets
137
163
  when Bolt::Target
138
164
  targets
139
165
  when Array
140
- targets.map { |tish| expand_targets(tish) }
166
+ targets.map { |tish| expand_targets(tish, ext_glob: ext_glob) }
141
167
  when String
142
168
  # Expand a comma-separated list
143
- targets.split(/[[:space:],]+/).reject(&:empty?).map do |name|
144
- ts = resolve_name(name)
169
+ # Regex magic below is required to workaround `{foo,bar}` glob syntax
170
+ regex = ext_glob ? EXTENDED_TARGET_REGEX : TARGET_REGEX
171
+ targets.split(regex).reject(&:empty?).map do |name|
172
+ ts = resolve_name(name, ext_glob: ext_glob)
145
173
  ts.map do |t|
146
174
  # If the target doesn't exist, evaluate it from the inventory.
147
175
  # Then return a Bolt::Target.
@@ -8,6 +8,11 @@ module Bolt
8
8
  class Target
9
9
  attr_reader :name, :uri, :safe_name, :target_alias, :resources
10
10
 
11
+ # Illegal characters that are not permitted in target names.
12
+ # These characters are delimiters for target and group names and allowing
13
+ # them would cause unexpected behavior.
14
+ ILLEGAL_CHARS = /[\s,]/.freeze
15
+
11
16
  def initialize(target_data, inventory)
12
17
  unless target_data['name'] || target_data['uri']
13
18
  raise Bolt::Inventory::ValidationError.new("Target must have either a name or uri", nil)
@@ -150,6 +155,10 @@ module Bolt
150
155
  raise Bolt::Inventory::ValidationError.new("Target name must be ASCII characters: #{@name}", nil)
151
156
  end
152
157
 
158
+ if (illegal_char = @name.match(ILLEGAL_CHARS))
159
+ raise ValidationError.new("Illegal character '#{illegal_char}' in target name '#{@name}'", nil)
160
+ end
161
+
153
162
  unless transport.nil? || Bolt::TRANSPORTS.include?(transport.to_sym)
154
163
  raise Bolt::UnknownTransportError.new(transport, uri)
155
164
  end
@@ -49,7 +49,7 @@ module Bolt
49
49
  spec_searcher_configuration: spec_searcher_config(config)
50
50
  )
51
51
  rescue StandardError => e
52
- raise Bolt::Error.new(e.message, 'bolt/module-resolver-error')
52
+ raise Bolt::Error.new("Unable to resolve modules: #{e.message}", 'bolt/module-resolver-error')
53
53
  end
54
54
 
55
55
  # Create the Puppetfile object.