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 +4 -4
- data/bolt-modules/boltlib/lib/puppet/functions/apply_prep.rb +68 -19
- data/lib/bolt/config.rb +3 -1
- data/lib/bolt/executor.rb +0 -3
- data/lib/bolt/inventory.rb +34 -3
- data/lib/bolt/inventory/group.rb +8 -2
- data/lib/bolt/inventory/group2.rb +11 -4
- data/lib/bolt/inventory/inventory2.rb +32 -2
- data/lib/bolt/outputter/human.rb +18 -4
- data/lib/bolt/plugin.rb +2 -0
- data/lib/bolt/plugin/install_agent.rb +26 -0
- data/lib/bolt/plugin/task.rb +8 -0
- data/lib/bolt/target.rb +8 -0
- data/lib/bolt/transport/ssh.rb +1 -2
- data/lib/bolt/transport/ssh/connection.rb +39 -9
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt_server/transport_app.rb +2 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f863b5a29cc2286bc59ed3e987545be4e692c3b17e026ea33b4529625677c086
|
4
|
+
data.tar.gz: 65a2a7eff22001527871c72fcda6a65eeb6f8a943e1c043c232e1bd873dc9c27
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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,
|
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
|
12
|
-
#
|
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
|
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
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
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|
|
data/lib/bolt/config.rb
CHANGED
@@ -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)
|
data/lib/bolt/executor.rb
CHANGED
@@ -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
|
|
data/lib/bolt/inventory.rb
CHANGED
@@ -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
|
-
|
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,
|
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
|
data/lib/bolt/inventory/group.rb
CHANGED
@@ -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,
|
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,
|
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
|
-
# {
|
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
|
-
|
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
|
data/lib/bolt/outputter/human.rb
CHANGED
@@ -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
|
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
|
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(
|
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(
|
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
|
data/lib/bolt/plugin.rb
CHANGED
@@ -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
|
data/lib/bolt/plugin/task.rb
CHANGED
@@ -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
|
data/lib/bolt/target.rb
CHANGED
@@ -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)
|
data/lib/bolt/transport/ssh.rb
CHANGED
@@ -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
|
-
|
29
|
-
@user = @target.user ||
|
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
|
-
|
66
|
-
|
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
|
-
|
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
|
71
|
-
|
75
|
+
elsif target.options['host-key-check']
|
76
|
+
net_ssh_verifier(:always)
|
72
77
|
else
|
73
|
-
|
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
|
data/lib/bolt/version.rb
CHANGED
@@ -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
|
-
|
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.
|
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-
|
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
|