bolt 1.29.1 → 1.30.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.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 319eb2a2f42911eed9c4a7e9aa8e9bd351146f7ab3cdf69b34c9e364a2fbf574
4
- data.tar.gz: ad22b5b96fc923b68d7109fdeba19f4a1046b327d0dcb0a1934bfc7a774425f6
3
+ metadata.gz: f863b5a29cc2286bc59ed3e987545be4e692c3b17e026ea33b4529625677c086
4
+ data.tar.gz: 65a2a7eff22001527871c72fcda6a65eeb6f8a943e1c043c232e1bd873dc9c27
5
5
  SHA512:
6
- metadata.gz: a2e9e0277e1b7ee2cb7eb86120250e62a2ef91068893e7d86e7fb8ce542b3eaf016f0f7ca8a8245202a6c1c64a4a3d1af82c6e10fd826d5bec8df0f9f42c8d65
7
- data.tar.gz: 64d7965e9e75abee048a0c251bcaaf2ce6f9a592ac86bc8f73fc330dba77ff847acaf4574ebdd194c6747d90b3d8496ae85c34658d3c0831c85befbe3a8b0aa9
6
+ metadata.gz: 1a0c09806f427b335ed26848072de21ae37ac53d196329dd5f8bcb5e1458460fc59804272a35336c9fc3196067bd74642baa9ac3a50be884eda99aba646c008b
7
+ data.tar.gz: d0dbfa6c28ab4a7c0f753e91c99ee0bf4379fe9e1843c7124d52555ea4fdc5ed9c7c46d6783206cb1b518f9e463dbe6d693c115c79c261ec7ae0adda99bab0b8
@@ -2,14 +2,17 @@
2
2
 
3
3
  require 'bolt/task'
4
4
 
5
- # Installs the puppet-agent package on targets if needed then collects facts, including any custom
6
- # facts found in Bolt's modulepath.
5
+ # Installs the puppet-agent package on targets if needed, then collects facts,
6
+ # including any custom facts found in Bolt's modulepath. The package is
7
+ # installed using either the configured plugin or the `task` plugin with the
8
+ # `puppet_agent::install` task.
7
9
  #
8
10
  # Agent detection will be skipped if the target includes the 'puppet-agent' feature, either as a
9
11
  # property of its transport (PCP) or by explicitly setting it as a feature in Bolt's inventory.
10
12
  #
11
- # If no agent is detected on the target using the 'puppet_agent::version' task, it's installed
12
- # using 'puppet_agent::install' and the puppet service is stopped/disabled using the 'service' task.
13
+ # If Bolt does not detect an agent on the target using the 'puppet_agent::version' task,
14
+ # it will install the agent using either the configured plugin or the
15
+ # task plugin.
13
16
  #
14
17
  # **NOTE:** Not available in apply block
15
18
  Puppet::Functions.create_function(:apply_prep) do
@@ -24,14 +27,31 @@ Puppet::Functions.create_function(:apply_prep) do
24
27
  @script_compiler ||= Puppet::Pal::ScriptCompiler.new(closure_scope.compiler)
25
28
  end
26
29
 
27
- def run_task(executor, targets, name, args = {})
30
+ def inventory
31
+ Puppet.lookup(:bolt_inventory)
32
+ end
33
+
34
+ def get_task(name, params = {})
28
35
  tasksig = script_compiler.task_signature(name)
29
36
  raise Bolt::Error.new("#{name} could not be found", 'bolt/apply-prep') unless tasksig
30
37
 
31
- task = Bolt::Task.new(tasksig.task_hash)
32
- results = executor.run_task(targets, task, args)
33
- raise Bolt::RunFailure.new(results, 'run_task', task.name) unless results.ok?
34
- results
38
+ errors = []
39
+ unless tasksig.runnable_with?(params) { |msg| errors << msg }
40
+ # This relies on runnable with printing a partial message before the first real error
41
+ raise Bolt::ValidationError, "Invalid parameters for #{errors.join("\n")}"
42
+ end
43
+
44
+ Bolt::Task.new(tasksig.task_hash)
45
+ end
46
+
47
+ # rubocop:disable Naming/AccessorMethodName
48
+ def set_agent_feature(target)
49
+ inventory.set_feature(target, 'puppet-agent')
50
+ end
51
+ # rubocop:enable Naming/AccessorMethodName
52
+
53
+ def run_task(targets, task, args = {})
54
+ executor.run_task(targets, task, args)
35
55
  end
36
56
 
37
57
  # Returns true if the target has the puppet-agent feature defined, either from inventory or transport.
@@ -40,6 +60,10 @@ Puppet::Functions.create_function(:apply_prep) do
40
60
  executor.transport(target.transport).provided_features.include?('puppet-agent') || target.remote?
41
61
  end
42
62
 
63
+ def executor
64
+ Puppet.lookup(:bolt_executor)
65
+ end
66
+
43
67
  def apply_prep(target_spec)
44
68
  unless Puppet[:tasks]
45
69
  raise Puppet::ParseErrorWithIssue
@@ -47,8 +71,6 @@ Puppet::Functions.create_function(:apply_prep) do
47
71
  end
48
72
 
49
73
  applicator = Puppet.lookup(:apply_executor)
50
- executor = Puppet.lookup(:bolt_executor)
51
- inventory = Puppet.lookup(:bolt_inventory)
52
74
 
53
75
  executor.report_function_call(self.class.name)
54
76
 
@@ -61,21 +83,47 @@ Puppet::Functions.create_function(:apply_prep) do
61
83
  agent_targets.each { |target| Puppet.debug "Puppet Agent feature declared for #{target.name}" }
62
84
  unless unknown_targets.empty?
63
85
  # Ensure Puppet is installed
64
- versions = run_task(executor, unknown_targets, 'puppet_agent::version')
86
+ version_task = get_task('puppet_agent::version')
87
+ versions = run_task(unknown_targets, version_task)
88
+ raise Bolt::RunFailure.new(versions, 'run_task', 'puppet_agent::version') unless versions.ok?
65
89
  need_install, installed = versions.partition { |r| r['version'].nil? }
66
90
  installed.each do |r|
67
91
  Puppet.debug "Puppet Agent #{r['version']} installed on #{r.target.name}"
68
- inventory.set_feature(r.target, 'puppet-agent')
92
+ set_agent_feature(r.target)
69
93
  end
70
94
 
71
95
  unless need_install.empty?
72
96
  need_install_targets = need_install.map(&:target)
73
- run_task(executor, need_install_targets, 'puppet_agent::install')
74
- # Service task works best when targets have puppet-agent feature
75
- need_install_targets.each { |target| inventory.set_feature(target, 'puppet-agent') }
76
- # Ensure the Puppet service is stopped after new install
77
- run_task(executor, need_install_targets, 'service', 'action' => 'stop', 'name' => 'puppet')
78
- run_task(executor, need_install_targets, 'service', 'action' => 'disable', 'name' => 'puppet')
97
+ # lazy-load expensive gem code
98
+ require 'concurrent'
99
+ pool = Concurrent::ThreadPoolExecutor.new
100
+
101
+ hooks = need_install_targets.map do |t|
102
+ begin
103
+ opts = t.plugin_hooks&.fetch('puppet_library')
104
+ hook = inventory.plugins.get_hook(opts['plugin'], :puppet_library)
105
+ { 'target' => t,
106
+ 'hook_proc' => hook.call(opts, t, self) }
107
+ rescue StandardError => e
108
+ Bolt::Result.from_exception(t, e)
109
+ end
110
+ end
111
+
112
+ hook_errors, ok_hooks = hooks.partition { |h| h.is_a?(Bolt::Result) }
113
+
114
+ futures = ok_hooks.map do |hash|
115
+ Concurrent::Future.execute(executor: pool) do
116
+ hash['hook_proc'].call
117
+ end
118
+ end
119
+
120
+ results = futures.zip(ok_hooks).map do |f, hash|
121
+ f.value || Bolt::Result.from_exception(hash['target'], f.reason)
122
+ end
123
+ set = Bolt::ResultSet.new(results + hook_errors)
124
+ raise Bolt::RunFailure.new(set.error_set, 'apply_prep') unless set.ok
125
+
126
+ need_install_targets.each { |target| set_agent_feature(target) }
79
127
  end
80
128
  end
81
129
 
@@ -90,6 +138,7 @@ Puppet::Functions.create_function(:apply_prep) do
90
138
  task = applicator.custom_facts_task
91
139
  arguments = { 'plugins' => Puppet::Pops::Types::PSensitiveType::Sensitive.new(plugins) }
92
140
  results = executor.run_task(targets, task, arguments)
141
+ # TODO: Standardize RunFailure type with error above
93
142
  raise Bolt::RunFailure.new(results, 'run_task', task.name) unless results.ok?
94
143
 
95
144
  results.each do |result|
@@ -33,7 +33,7 @@ module Bolt
33
33
  class Config
34
34
  attr_accessor :concurrency, :format, :trace, :log, :puppetdb, :color, :save_rerun,
35
35
  :transport, :transports, :inventoryfile, :compile_concurrency, :boltdir,
36
- :puppetfile_config, :plugins
36
+ :puppetfile_config, :plugins, :plugin_hooks
37
37
  attr_writer :modulepath
38
38
 
39
39
  TRANSPORT_OPTIONS = %i[password run-as sudo-password extensions
@@ -71,6 +71,7 @@ module Bolt
71
71
  @save_rerun = true
72
72
  @puppetfile_config = {}
73
73
  @plugins = {}
74
+ @plugin_hooks = { 'puppet_library' => { 'plugin' => 'install_agent' } }
74
75
 
75
76
  # add an entry for the default console logger
76
77
  @log = { 'console' => {} }
@@ -161,6 +162,7 @@ module Bolt
161
162
 
162
163
  # Plugins are only settable from config not inventory so we can overwrite
163
164
  @plugins = data['plugins'] if data.key?('plugins')
165
+ @plugin_hooks.merge!(data['plugin_hooks']) if data.key?('plugin_hooks')
164
166
 
165
167
  %w[concurrency format puppetdb color transport].each do |key|
166
168
  send("#{key}=", data[key]) if data.key?(key)
@@ -16,12 +16,9 @@ module Bolt
16
16
  attr_reader :noop, :transports
17
17
  attr_accessor :run_as
18
18
 
19
- # FIXME: There must be a better way
20
- # https://makandracards.com/makandra/36011-ruby-do-not-mix-optional-and-keyword-arguments
21
19
  def initialize(concurrency = 1,
22
20
  analytics = Bolt::Analytics::NoopClient.new,
23
21
  noop = false)
24
-
25
22
  # lazy-load expensive gem code
26
23
  require 'concurrent'
27
24
 
@@ -63,7 +63,7 @@ module Bolt
63
63
  version = (data || {}).delete('version') { 1 }
64
64
  case version
65
65
  when 1
66
- new(data, config)
66
+ new(data, config, plugins: plugins)
67
67
  when 2
68
68
  Bolt::Inventory::Inventory2.new(data, config, plugins: plugins)
69
69
  else
@@ -71,7 +71,10 @@ module Bolt
71
71
  end
72
72
  end
73
73
 
74
- def initialize(data, config = nil, target_vars: {}, target_facts: {}, target_features: {})
74
+ attr_reader :plugins, :config
75
+
76
+ def initialize(data, config = nil, plugins: nil, target_vars: {},
77
+ target_facts: {}, target_features: {}, target_plugin_hooks: {})
75
78
  @logger = Logging.logger[self]
76
79
  # Config is saved to add config options to targets
77
80
  @config = config || Bolt::Config.default
@@ -81,6 +84,8 @@ module Bolt
81
84
  @target_vars = target_vars
82
85
  @target_facts = target_facts
83
86
  @target_features = target_features
87
+ @plugins = plugins
88
+ @target_plugin_hooks = target_plugin_hooks
84
89
 
85
90
  @groups.resolve_aliases(@groups.node_aliases)
86
91
  collect_groups
@@ -107,6 +112,10 @@ module Bolt
107
112
  @groups.node_names
108
113
  end
109
114
 
115
+ def plugin_hooks(target)
116
+ @target_plugin_hooks[target.name] || {}
117
+ end
118
+
110
119
  def get_targets(targets)
111
120
  targets = expand_targets(targets)
112
121
  targets = if targets.is_a? Array
@@ -213,6 +222,9 @@ module Bolt
213
222
  set_vars_from_hash(target.name, data['vars']) unless @target_vars[target.name]
214
223
  set_facts(target.name, data['facts']) unless @target_facts[target.name]
215
224
  data['features']&.each { |feature| set_feature(target, feature) } unless @target_features[target.name]
225
+ unless @target_plugin_hooks[target.name]
226
+ set_plugin_hooks(target.name, @config.plugin_hooks.merge(data['plugin_hooks'] || {}))
227
+ end
216
228
 
217
229
  # Use Config object to ensure config section is treated consistently with config file
218
230
  conf = @config.deep_clone
@@ -298,6 +310,14 @@ module Bolt
298
310
  end
299
311
  private :set_facts
300
312
 
313
+ def set_plugin_hooks(tname, hash)
314
+ if hash
315
+ @target_plugin_hooks[tname] ||= {}
316
+ @target_plugin_hooks[tname].merge!(hash)
317
+ end
318
+ end
319
+ private :set_plugin_hooks
320
+
301
321
  def add_node(current_group, target, desired_group, track = { 'all' => nil })
302
322
  if current_group.name == desired_group
303
323
  # Group to add to is found
@@ -305,13 +325,23 @@ module Bolt
305
325
  # Add target to nodes hash
306
326
  current_group.nodes[t_name] = { 'name' => t_name }.merge(target.options)
307
327
  # Inherit facts, vars, and features from hierarchy
308
- current_group_data = { facts: current_group.facts, vars: current_group.vars, features: current_group.features }
328
+ current_group_data = { facts: current_group.facts,
329
+ vars: current_group.vars,
330
+ features: current_group.features,
331
+ plugin_hooks: current_group.plugin_hooks }
309
332
  data = inherit_data(track, current_group.name, current_group_data)
310
333
  set_facts(t_name, @target_facts[t_name] ? data[:facts].merge(@target_facts[t_name]) : data[:facts])
311
334
  set_vars_from_hash(t_name, @target_vars[t_name] ? data[:vars].merge(@target_vars[t_name]) : data[:vars])
312
335
  data[:features].each do |feature|
313
336
  set_feature(target, feature)
314
337
  end
338
+ hook_data = @config.plugin_hooks.merge(data[:plugin_hooks])
339
+ hash = if @target_plugin_hooks[t_name]
340
+ hook_data.merge(@target_plugin_hooks[t_name])
341
+ else
342
+ hook_data
343
+ end
344
+ set_plugin_hooks(t_name, hash)
315
345
  return true
316
346
  end
317
347
  # Recurse on children Groups if not desired_group
@@ -327,6 +357,7 @@ module Bolt
327
357
  data[:facts] = track[name].facts.merge(data[:facts])
328
358
  data[:vars] = track[name].vars.merge(data[:vars])
329
359
  data[:features].concat(track[name].features)
360
+ data[:plugin_hooks] = track[name].plugin_hooks.merge(data[:plugin_hooks])
330
361
  inherit_data(track, track[name].name, data)
331
362
  end
332
363
  data
@@ -5,12 +5,13 @@ module Bolt
5
5
  # Group is a specific implementation of Inventory based on nested
6
6
  # structured data.
7
7
  class Group
8
- attr_accessor :name, :nodes, :aliases, :name_or_alias, :groups, :config, :rest, :facts, :vars, :features
8
+ attr_accessor :name, :nodes, :aliases, :name_or_alias, :groups,
9
+ :config, :rest, :facts, :vars, :features, :plugin_hooks
9
10
 
10
11
  # Regex used to validate group names and target aliases.
11
12
  NAME_REGEX = /\A[a-z0-9_][a-z0-9_-]*\Z/.freeze
12
13
 
13
- DATA_KEYS = %w[name config facts vars features].freeze
14
+ DATA_KEYS = %w[name config facts vars features plugin_hooks].freeze
14
15
  NODE_KEYS = DATA_KEYS + ['alias']
15
16
  GROUP_KEYS = DATA_KEYS + %w[groups nodes]
16
17
  CONFIG_KEYS = Bolt::TRANSPORTS.keys.map(&:to_s) + ['transport']
@@ -33,6 +34,7 @@ module Bolt
33
34
  @vars = fetch_value(data, 'vars', Hash)
34
35
  @facts = fetch_value(data, 'facts', Hash)
35
36
  @features = fetch_value(data, 'features', Array)
37
+ @plugin_hooks = fetch_value(data, 'plugin_hooks', Hash)
36
38
  @config = fetch_value(data, 'config', Hash)
37
39
 
38
40
  unless (unexpected_keys = @config.keys - CONFIG_KEYS).empty?
@@ -198,6 +200,7 @@ module Bolt
198
200
  'vars' => data['vars'] || {},
199
201
  'facts' => data['facts'] || {},
200
202
  'features' => data['features'] || [],
203
+ 'plugin_hooks' => data['plugin_hooks'] || {},
201
204
  # groups come from group_data
202
205
  'groups' => [] }
203
206
  end
@@ -208,6 +211,7 @@ module Bolt
208
211
  'vars' => @vars,
209
212
  'facts' => @facts,
210
213
  'features' => @features,
214
+ 'plugin_hooks' => @plugin_hooks,
211
215
  'groups' => [@name] }
212
216
  end
213
217
 
@@ -216,6 +220,7 @@ module Bolt
216
220
  'vars' => {},
217
221
  'facts' => {},
218
222
  'features' => [],
223
+ 'plugin_hooks' => {},
219
224
  'groups' => [] }
220
225
  end
221
226
 
@@ -232,6 +237,7 @@ module Bolt
232
237
  'vars' => data1['vars'].merge(data2['vars']),
233
238
  'facts' => Bolt::Util.deep_merge(data1['facts'], data2['facts']),
234
239
  'features' => data1['features'] | data2['features'],
240
+ 'plugin_hooks' => data1['plugin_hooks'].merge(data2['plugin_hooks']),
235
241
  'groups' => data2['groups'] + data1['groups']
236
242
  }
237
243
  end
@@ -5,13 +5,14 @@ require 'bolt/inventory/group'
5
5
  module Bolt
6
6
  class Inventory
7
7
  class Group2
8
- attr_accessor :name, :targets, :aliases, :name_or_alias, :groups, :config, :rest, :facts, :vars, :features
8
+ attr_accessor :name, :targets, :aliases, :name_or_alias, :groups,
9
+ :config, :rest, :facts, :vars, :features, :plugin_hooks
9
10
 
10
11
  # THESE are duplicates with the old groups for now.
11
12
  # Regex used to validate group names and target aliases.
12
13
  NAME_REGEX = /\A[a-z0-9_][a-z0-9_-]*\Z/.freeze
13
14
 
14
- DATA_KEYS = %w[name config facts vars features].freeze
15
+ DATA_KEYS = %w[name config facts vars features plugin_hooks].freeze
15
16
  NODE_KEYS = DATA_KEYS + %w[alias uri]
16
17
  GROUP_KEYS = DATA_KEYS + %w[groups targets]
17
18
  CONFIG_KEYS = Bolt::TRANSPORTS.keys.map(&:to_s) + ['transport']
@@ -23,7 +24,7 @@ module Bolt
23
24
  raise ValidationError.new("Group does not have a name", nil) unless data.key?('name')
24
25
  @plugins = plugins
25
26
 
26
- %w[name vars features facts].each do |key|
27
+ %w[name vars features facts plugin_hooks].each do |key|
27
28
  validate_config_plugin(data[key], key, nil)
28
29
  end
29
30
 
@@ -45,6 +46,7 @@ module Bolt
45
46
  @vars = fetch_value(data, 'vars', Hash)
46
47
  @facts = fetch_value(data, 'facts', Hash)
47
48
  @features = fetch_value(data, 'features', Array)
49
+ @plugin_hooks = fetch_value(data, 'plugin_hooks', Hash)
48
50
 
49
51
  @config = config_only_plugin(fetch_value(data, 'config', Hash))
50
52
 
@@ -134,6 +136,7 @@ module Bolt
134
136
  'vars' => data['vars'] || {},
135
137
  'facts' => data['facts'] || {},
136
138
  'features' => data['features'] || [],
139
+ 'plugin_hooks' => data['plugin_hooks'] || {},
137
140
  # This allows us to determine if a target was found?
138
141
  'name' => data['name'] || nil,
139
142
  'uri' => data['uri'] || nil,
@@ -240,6 +243,7 @@ module Bolt
240
243
  'vars' => data1['vars'].merge(data2['vars']),
241
244
  'facts' => Bolt::Util.deep_merge(data1['facts'], data2['facts']),
242
245
  'features' => data1['features'] | data2['features'],
246
+ 'plugin_hooks' => data1['plugin_hooks'].merge(data2['plugin_hooks']),
243
247
  'groups' => data2['groups'] + data1['groups']
244
248
  }
245
249
  end
@@ -346,7 +350,8 @@ module Bolt
346
350
  end
347
351
 
348
352
  # The data functions below expect and return nil or a hash of the schema
349
- # { 'config' => Hash , 'vars' => Hash, 'facts' => Hash, 'features' => Array, groups => Array }
353
+ # {'config' => Hash, 'vars' => Hash, 'facts' => Hash, 'features' => Array,
354
+ # 'plugin_hooks' => Hash, 'groups' => Array}
350
355
  def data_for(target_name)
351
356
  data_merge(group_collect(target_name), target_collect(target_name))
352
357
  end
@@ -356,6 +361,7 @@ module Bolt
356
361
  'vars' => @vars,
357
362
  'facts' => @facts,
358
363
  'features' => @features,
364
+ 'plugin_hooks' => @plugin_hooks,
359
365
  'groups' => [@name] }
360
366
  end
361
367
 
@@ -364,6 +370,7 @@ module Bolt
364
370
  'vars' => {},
365
371
  'facts' => {},
366
372
  'features' => [],
373
+ 'plugin_hooks' => {},
367
374
  'groups' => [] }
368
375
  end
369
376
 
@@ -12,16 +12,22 @@ module Bolt
12
12
  end
13
13
  end
14
14
 
15
- def initialize(data, config = nil, plugins: nil, target_vars: {}, target_facts: {}, target_features: {})
15
+ attr_reader :plugins, :config
16
+
17
+ def initialize(data, config = nil, plugins: nil, target_vars: {},
18
+ target_facts: {}, target_features: {},
19
+ target_plugin_hooks: {})
16
20
  @logger = Logging.logger[self]
17
21
  # Config is saved to add config options to targets
18
22
  @config = config || Bolt::Config.default
19
23
  @data = data || {}
20
24
  @groups = Group2.new(@data.merge('name' => 'all'), plugins)
25
+ @plugins = plugins
21
26
  @group_lookup = {}
22
27
  @target_vars = target_vars
23
28
  @target_facts = target_facts
24
29
  @target_features = target_features
30
+ @target_plugin_hooks = target_plugin_hooks
25
31
  @groups.resolve_aliases(@groups.target_aliases, @groups.target_names)
26
32
  collect_groups
27
33
  end
@@ -103,6 +109,10 @@ module Bolt
103
109
  @target_features[target.name] || Set.new
104
110
  end
105
111
 
112
+ def plugin_hooks(target)
113
+ @target_plugin_hooks[target.name] || {}
114
+ end
115
+
106
116
  def data_hash
107
117
  {
108
118
  data: {},
@@ -153,6 +163,9 @@ module Bolt
153
163
  set_vars_from_hash(target.name, data['vars']) unless @target_vars[target.name]
154
164
  set_facts(target.name, data['facts']) unless @target_facts[target.name]
155
165
  data['features']&.each { |feature| set_feature(target, feature) } unless @target_features[target.name]
166
+ unless @target_plugin_hooks[target.name]
167
+ set_plugin_hooks(target.name, @config.plugin_hooks.merge(data['plugin_hooks'] || {}))
168
+ end
156
169
  data['config'] = config_plugin(data['config'])
157
170
 
158
171
  # Use Config object to ensure config section is treated consistently with config file
@@ -234,6 +247,14 @@ module Bolt
234
247
  end
235
248
  private :set_facts
236
249
 
250
+ def set_plugin_hooks(tname, hash)
251
+ if hash
252
+ @target_plugin_hooks[tname] ||= {}
253
+ @target_plugin_hooks[tname].merge!(hash)
254
+ end
255
+ end
256
+ private :set_plugin_hooks
257
+
237
258
  def add_target(current_group, target, desired_group, track = { 'all' => nil })
238
259
  if current_group.name == desired_group
239
260
  # Group to add to is found
@@ -246,13 +267,21 @@ module Bolt
246
267
  # Inherit facts, vars, and features from hierarchy
247
268
  current_group_data = { facts: current_group.facts,
248
269
  vars: current_group.vars,
249
- features: current_group.features }
270
+ features: current_group.features,
271
+ plugin_hooks: current_group.plugin_hooks }
250
272
  data = inherit_data(track, current_group.name, current_group_data)
251
273
  set_facts(t_name, @target_facts[t_name] ? data[:facts].merge(@target_facts[t_name]) : data[:facts])
252
274
  set_vars_from_hash(t_name, @target_vars[t_name] ? data[:vars].merge(@target_vars[t_name]) : data[:vars])
253
275
  data[:features].each do |feature|
254
276
  set_feature(target, feature)
255
277
  end
278
+ hook_data = @config.plugin_hooks.merge(data[:plugin_hooks])
279
+ hash = if @target_plugin_hooks[t_name]
280
+ hook_data.merge(@target_plugin_hooks[t_name])
281
+ else
282
+ hook_data
283
+ end
284
+ set_plugin_hooks(t_name, hash)
256
285
  return true
257
286
  end
258
287
  # Recurse on children Groups if not desired_group
@@ -268,6 +297,7 @@ module Bolt
268
297
  data[:facts] = track[name].facts.merge(data[:facts])
269
298
  data[:vars] = track[name].vars.merge(data[:vars])
270
299
  data[:features].concat(track[name].features)
300
+ data[:plugin_hooks] = track[name].plugin_hooks.merge(data[:plugin_hooks])
271
301
  inherit_data(track, track[name].name, data)
272
302
  end
273
303
  data
@@ -161,7 +161,7 @@ module Bolt
161
161
  @plan_depth -= 1
162
162
  plan = event[:plan]
163
163
  duration = event[:duration]
164
- @stream.puts(colorize(:green, "Finished: plan #{plan} in #{duration.round(2)} sec"))
164
+ @stream.puts(colorize(:green, "Finished: plan #{plan} in #{duration_to_string(duration)}"))
165
165
  end
166
166
 
167
167
  def print_summary(results, elapsed_time = nil)
@@ -185,7 +185,7 @@ module Bolt
185
185
  total_msg = format('Ran on %<size>d node%<plural>s',
186
186
  size: results.size,
187
187
  plural: results.size == 1 ? '' : 's')
188
- total_msg += format(' in %<elapsed>.2f seconds', elapsed: elapsed_time) unless elapsed_time.nil?
188
+ total_msg << " in #{duration_to_string(elapsed_time)}" unless elapsed_time.nil?
189
189
  @stream.puts total_msg
190
190
  end
191
191
 
@@ -209,7 +209,7 @@ module Bolt
209
209
 
210
210
  def print_tasks(tasks, modulepath)
211
211
  print_table(tasks)
212
- print_message("\nMODULEPATH:\n#{modulepath.join(':')}\n"\
212
+ print_message("\nMODULEPATH:\n#{modulepath.join(File::PATH_SEPARATOR)}\n"\
213
213
  "\nUse `bolt task show <task-name>` to view "\
214
214
  "details and parameters for a specific task.")
215
215
  end
@@ -278,7 +278,7 @@ module Bolt
278
278
 
279
279
  def print_plans(plans, modulepath)
280
280
  print_table(plans)
281
- print_message("\nMODULEPATH:\n#{modulepath.join(':')}\n"\
281
+ print_message("\nMODULEPATH:\n#{modulepath.join(File::PATH_SEPARATOR)}\n"\
282
282
  "\nUse `bolt plan show <plan-name>` to view "\
283
283
  "details and parameters for a specific plan.")
284
284
  end
@@ -366,6 +366,20 @@ module Bolt
366
366
  def print_message(message)
367
367
  @stream.puts(message)
368
368
  end
369
+
370
+ def duration_to_string(duration)
371
+ hrs = (duration / 3600).floor
372
+ mins = ((duration % 3600) / 60).floor
373
+ secs = (duration % 60)
374
+ if hrs > 0
375
+ "#{hrs} hr, #{mins} min, #{secs.round} sec"
376
+ elsif mins > 0
377
+ "#{mins} min, #{secs.round} sec"
378
+ else
379
+ # Include 2 decimal places if the duration is under a minute
380
+ "#{secs.round(2)} sec"
381
+ end
382
+ end
369
383
  end
370
384
  end
371
385
  end
@@ -7,6 +7,7 @@ require 'bolt/plugin/prompt'
7
7
  require 'bolt/plugin/task'
8
8
  require 'bolt/plugin/aws'
9
9
  require 'bolt/plugin/vault'
10
+ require 'bolt/plugin/install_agent'
10
11
 
11
12
  module Bolt
12
13
  class Plugin
@@ -40,6 +41,7 @@ module Bolt
40
41
  plugins.add_plugin(Bolt::Plugin::Task.new(config))
41
42
  plugins.add_plugin(Bolt::Plugin::Aws::EC2.new(config.plugins['aws'] || {}))
42
43
  plugins.add_plugin(Bolt::Plugin::Vault.new(config.plugins['vault'] || {}))
44
+ plugins.add_plugin(Bolt::Plugin::InstallAgent.new)
43
45
  plugins
44
46
  end
45
47
 
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bolt
4
+ class Plugin
5
+ class InstallAgent
6
+ def hooks
7
+ %w[puppet_library]
8
+ end
9
+
10
+ def name
11
+ 'install_agent'
12
+ end
13
+
14
+ def puppet_library(_opts, target, apply_prep)
15
+ install_task = apply_prep.get_task("puppet_agent::install")
16
+ service_task = apply_prep.get_task("service", 'action' => 'stop', 'name' => 'puppet')
17
+ proc do
18
+ apply_prep.run_task([target], install_task).first
19
+ apply_prep.set_agent_feature(target)
20
+ apply_prep.run_task([target], service_task, 'action' => 'stop', 'name' => 'puppet').first
21
+ apply_prep.run_task([target], service_task, 'action' => 'disable', 'name' => 'puppet').first
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -86,6 +86,14 @@ module Bolt
86
86
 
87
87
  targets
88
88
  end
89
+
90
+ def puppet_library(opts, target, apply_prep)
91
+ params = opts['parameters'] || {}
92
+ task = apply_prep.get_task(opts['task'], params)
93
+ proc do
94
+ apply_prep.run_task([target], task, params).first
95
+ end
96
+ end
89
97
  end
90
98
  end
91
99
  end
@@ -88,6 +88,14 @@ module Bolt
88
88
  end
89
89
  end
90
90
 
91
+ def plugin_hooks
92
+ if @inventory
93
+ @inventory.plugin_hooks(self)
94
+ else
95
+ {}
96
+ end
97
+ end
98
+
91
99
  # TODO: WHAT does equality mean here?
92
100
  # should we just compare names? is there something else that is meaninful?
93
101
  def eql?(other)
@@ -16,7 +16,6 @@ module Bolt
16
16
  def self.default_options
17
17
  {
18
18
  'connect-timeout' => 10,
19
- 'host-key-check' => true,
20
19
  'tty' => false,
21
20
  'load-config' => true,
22
21
  'disconnect-timeout' => 5
@@ -31,7 +30,7 @@ module Bolt
31
30
  validate_sudo_options(options)
32
31
 
33
32
  host_key = options['host-key-check']
34
- unless !!host_key == host_key
33
+ unless host_key.nil? || !!host_key == host_key
35
34
  raise Bolt::ValidationError, 'host-key-check option must be a Boolean true or false'
36
35
  end
37
36
 
@@ -25,9 +25,10 @@ module Bolt
25
25
  @target = target
26
26
  @load_config = target.options['load-config']
27
27
 
28
- ssh_user = @load_config ? Net::SSH::Config.for(target.host)[:user] : nil
29
- @user = @target.user || ssh_user || Etc.getlogin
28
+ ssh_config = @load_config ? Net::SSH::Config.for(target.host) : {}
29
+ @user = @target.user || ssh_config[:user] || Etc.getlogin
30
30
  @run_as = nil
31
+ @strict_host_key_checking = ssh_config[:strict_host_key_checking]
31
32
 
32
33
  @logger = Logging.logger[@target.host]
33
34
  @transport_logger = transport_logger
@@ -61,16 +62,20 @@ module Bolt
61
62
  options[:password] = target.password if target.password
62
63
  # Support both net-ssh 4 and 5. We use 5 in packaging, but Beaker pins to 4 so we
63
64
  # want the gem to be compatible with version 4.
64
- options[:verify_host_key] = if target.options['host-key-check']
65
- if defined?(Net::SSH::Verifiers::Always)
66
- Net::SSH::Verifiers::Always.new
65
+ options[:verify_host_key] = if target.options['host-key-check'].nil?
66
+ # Fall back to SSH behavior. This variable will only be set in net-ssh 5.3+.
67
+ if @strict_host_key_checking.nil? || @strict_host_key_checking
68
+ net_ssh_verifier(:always)
67
69
  else
68
- Net::SSH::Verifiers::Secure.new
70
+ # SSH's behavior with StrictHostKeyChecking=no: adds new keys to known_hosts.
71
+ # If known_hosts points to /dev/null, then equivalent to :never where it
72
+ # accepts any key beacuse they're all new.
73
+ net_ssh_verifier(:accept_new_or_tunnel_local)
69
74
  end
70
- elsif defined?(Net::SSH::Verifiers::Never)
71
- Net::SSH::Verifiers::Never.new
75
+ elsif target.options['host-key-check']
76
+ net_ssh_verifier(:always)
72
77
  else
73
- Net::SSH::Verifiers::Null.new
78
+ net_ssh_verifier(:never)
74
79
  end
75
80
  options[:timeout] = target.options['connect-timeout'] if target.options['connect-timeout']
76
81
 
@@ -267,6 +272,31 @@ module Bolt
267
272
  make_executable(remote_path)
268
273
  remote_path
269
274
  end
275
+
276
+ # This handles renaming Net::SSH verifiers between version 4.x and 5.x
277
+ # of the gem
278
+ def net_ssh_verifier(verifier)
279
+ case verifier
280
+ when :always
281
+ if defined?(Net::SSH::Verifiers::Always)
282
+ Net::SSH::Verifiers::Always.new
283
+ else
284
+ Net::SSH::Verifiers::Secure.new
285
+ end
286
+ when :never
287
+ if defined?(Net::SSH::Verifiers::Never)
288
+ Net::SSH::Verifiers::Never.new
289
+ else
290
+ Net::SSH::Verifiers::Null.new
291
+ end
292
+ when :accept_new_or_tunnel_local
293
+ if defined?(Net::SSH::Verifiers::AcceptNewOrLocalTunnel)
294
+ Net::SSH::Verifiers::AcceptNewOrLocalTunnel.new
295
+ else
296
+ Net::SSH::Verifiers::Lenient.new
297
+ end
298
+ end
299
+ end
270
300
  end
271
301
  end
272
302
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bolt
4
- VERSION = '1.29.1'
4
+ VERSION = '1.30.0'
5
5
  end
@@ -103,7 +103,8 @@ module BoltServer
103
103
  error = validate_schema(@schemas["transport-ssh"], body)
104
104
  return [400, error.to_json] unless error.nil?
105
105
 
106
- opts = body['target'].clone
106
+ defaults = { 'host-key-check' => false }
107
+ opts = defaults.merge(body['target'])
107
108
  if opts['private-key-content']
108
109
  opts['private-key'] = { 'key-data' => opts['private-key-content'] }
109
110
  opts.delete('private-key-content')
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bolt
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.29.1
4
+ version: 1.30.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Puppet
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-08-21 00:00:00.000000000 Z
11
+ date: 2019-09-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: addressable
@@ -397,6 +397,7 @@ files:
397
397
  - lib/bolt/plan_result.rb
398
398
  - lib/bolt/plugin.rb
399
399
  - lib/bolt/plugin/aws.rb
400
+ - lib/bolt/plugin/install_agent.rb
400
401
  - lib/bolt/plugin/pkcs7.rb
401
402
  - lib/bolt/plugin/prompt.rb
402
403
  - lib/bolt/plugin/puppetdb.rb