bolt 0.21.4 → 0.21.5
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 +67 -0
- data/bolt-modules/boltlib/lib/puppet/functions/facts.rb +0 -6
- data/bolt-modules/boltlib/lib/puppet/functions/get_targets.rb +0 -6
- data/bolt-modules/boltlib/lib/puppet/functions/vars.rb +0 -6
- data/bolt-modules/boltlib/lib/puppet/functions/without_default_logging.rb +1 -5
- data/lib/bolt/applicator.rb +65 -4
- data/lib/bolt/catalog.rb +17 -1
- data/lib/bolt/config.rb +9 -0
- data/lib/bolt/executor.rb +9 -1
- data/lib/bolt/inventory.rb +16 -4
- data/lib/bolt/inventory/group.rb +42 -19
- data/lib/bolt/result_set.rb +1 -0
- data/lib/bolt/transport/winrm/connection.rb +13 -114
- data/lib/bolt/util.rb +5 -0
- data/lib/bolt/version.rb +1 -1
- data/libexec/apply_catalog.rb +27 -15
- data/libexec/bolt_catalog +12 -0
- data/libexec/custom_facts.rb +44 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1736c46973051ce64d6c27c43c9d34e63c4c6f05f5d5017f2df51796669500e4
|
4
|
+
data.tar.gz: da561ef33b65849c91f625fc465e89d7d7d7709bf6e1493b8fff6d5d954884c6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ff4aafb9c30b0e00fb89482ff864f8a85bf8c33cbdc2a8a6a1a41301029a14f79fa6580009e1621bcd223fc035c3eea44faae7b39b575a325fb61b8d3a99cc9d
|
7
|
+
data.tar.gz: eafccbd22e39d5db8e8e0f2bb70ec7ac02f3bfc18085c30fd826e30e59a9c19be5be5c01530e43e94e8e0396d3911d5e0de53e65647acc68c84b4a4b35a5a131
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
|
5
|
+
Puppet::Functions.create_function(:apply_prep) do
|
6
|
+
dispatch :apply_prep do
|
7
|
+
param 'Boltlib::TargetSpec', :targets
|
8
|
+
end
|
9
|
+
|
10
|
+
def apply_prep(target_spec)
|
11
|
+
applicator = Puppet.lookup(:apply_executor) { nil }
|
12
|
+
executor = Puppet.lookup(:bolt_executor) { nil }
|
13
|
+
inventory = Puppet.lookup(:bolt_inventory) { nil }
|
14
|
+
unless applicator && executor && inventory && Puppet.features.bolt?
|
15
|
+
raise Puppet::ParseErrorWithIssue.from_issue_and_stack(
|
16
|
+
Puppet::Pops::Issues::TASK_MISSING_BOLT, action: _('apply_prep')
|
17
|
+
)
|
18
|
+
end
|
19
|
+
|
20
|
+
executor.report_function_call('apply_prep')
|
21
|
+
|
22
|
+
targets = inventory.get_targets(target_spec)
|
23
|
+
|
24
|
+
executor.log_action('install puppet and gather facts', targets) do
|
25
|
+
executor.without_default_logging do
|
26
|
+
script_compiler = Puppet::Pal::ScriptCompiler.new(closure_scope.compiler)
|
27
|
+
|
28
|
+
# Ensure Puppet is installed
|
29
|
+
version_task = script_compiler.task_signature('puppet_agent::version')
|
30
|
+
raise Bolt::Error.new('puppet_agent::version could not be found', 'bolt/apply-prep') unless version_task
|
31
|
+
versions = executor.run_task(targets, version_task.task, {})
|
32
|
+
raise Bolt::RunFailure.new(versions, 'run_task', version_task.name) unless versions.ok?
|
33
|
+
need_install, installed = versions.partition { |r| r['version'].nil? }
|
34
|
+
installed.each do |r|
|
35
|
+
Puppet.info "Puppet Agent #{r['version']} installed on #{r.target.name}"
|
36
|
+
end
|
37
|
+
|
38
|
+
unless need_install.empty?
|
39
|
+
install_task = script_compiler.task_signature('puppet_agent::install')
|
40
|
+
raise Bolt::Error.new('puppet_agent::install could not be found', 'bolt/apply-prep') unless install_task
|
41
|
+
installed = executor.run_task(need_install.map(&:target), install_task.task, {})
|
42
|
+
raise Bolt::RunFailure.new(installed, 'run_task', install_task.name) unless installed.ok?
|
43
|
+
end
|
44
|
+
targets.each { |target| inventory.set_feature(target, 'puppet-agent') }
|
45
|
+
|
46
|
+
# Gather facts, including custom facts
|
47
|
+
plugins = applicator.build_plugin_tarball do |mod|
|
48
|
+
search_dirs = []
|
49
|
+
search_dirs << mod.plugins if mod.plugins?
|
50
|
+
search_dirs << mod.pluginfacts if mod.pluginfacts?
|
51
|
+
search_dirs
|
52
|
+
end
|
53
|
+
|
54
|
+
task = applicator.custom_facts_task
|
55
|
+
results = executor.run_task(targets, task, 'plugins' => plugins)
|
56
|
+
raise Bolt::RunFailure.new(results, 'run_task', task.name) unless results.ok?
|
57
|
+
|
58
|
+
results.each do |result|
|
59
|
+
inventory.add_facts(result.target, result.value)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Return nothing
|
65
|
+
nil
|
66
|
+
end
|
67
|
+
end
|
@@ -14,12 +14,6 @@ Puppet::Functions.create_function(:facts) do
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def facts(target)
|
17
|
-
unless Puppet[:tasks]
|
18
|
-
raise Puppet::ParseErrorWithIssue.from_issue_and_stack(
|
19
|
-
Puppet::Pops::Issues::TASK_OPERATION_NOT_SUPPORTED_WHEN_COMPILING, operation: 'facts'
|
20
|
-
)
|
21
|
-
end
|
22
|
-
|
23
17
|
inventory = Puppet.lookup(:bolt_inventory) { nil }
|
24
18
|
|
25
19
|
unless inventory
|
@@ -22,12 +22,6 @@ Puppet::Functions.create_function(:get_targets) do
|
|
22
22
|
end
|
23
23
|
|
24
24
|
def get_targets(names)
|
25
|
-
unless Puppet[:tasks]
|
26
|
-
raise Puppet::ParseErrorWithIssue.from_issue_and_stack(
|
27
|
-
Puppet::Pops::Issues::TASK_OPERATION_NOT_SUPPORTED_WHEN_COMPILING, operation: 'get_targets'
|
28
|
-
)
|
29
|
-
end
|
30
|
-
|
31
25
|
inventory = Puppet.lookup(:bolt_inventory) { nil }
|
32
26
|
|
33
27
|
unless inventory && Puppet.features.bolt?
|
@@ -18,12 +18,6 @@ Puppet::Functions.create_function(:vars) do
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def vars(target)
|
21
|
-
unless Puppet[:tasks]
|
22
|
-
raise Puppet::ParseErrorWithIssue.from_issue_and_stack(
|
23
|
-
Puppet::Pops::Issues::TASK_OPERATION_NOT_SUPPORTED_WHEN_COMPILING, operation: 'vars'
|
24
|
-
)
|
25
|
-
end
|
26
|
-
|
27
21
|
inventory = Puppet.lookup(:bolt_inventory) { nil }
|
28
22
|
|
29
23
|
unless inventory
|
@@ -23,12 +23,8 @@ Puppet::Functions.create_function(:without_default_logging) do
|
|
23
23
|
executor = Puppet.lookup(:bolt_executor) { nil }
|
24
24
|
executor.report_function_call('without_default_logging')
|
25
25
|
|
26
|
-
|
27
|
-
executor.plan_logging = false
|
28
|
-
begin
|
26
|
+
executor.without_default_logging do
|
29
27
|
yield
|
30
|
-
ensure
|
31
|
-
executor.plan_logging = old_log
|
32
28
|
end
|
33
29
|
end
|
34
30
|
end
|
data/lib/bolt/applicator.rb
CHANGED
@@ -1,9 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'base64'
|
4
|
+
require 'concurrent'
|
5
|
+
require 'find'
|
3
6
|
require 'json'
|
4
7
|
require 'logging'
|
8
|
+
require 'minitar'
|
5
9
|
require 'open3'
|
6
|
-
require 'concurrent'
|
7
10
|
require 'bolt/util/puppet_log_level'
|
8
11
|
|
9
12
|
module Bolt
|
@@ -19,12 +22,28 @@ module Bolt
|
|
19
22
|
|
20
23
|
@pool = Concurrent::ThreadPoolExecutor.new(max_threads: max_compiles)
|
21
24
|
@logger = Logging.logger[self]
|
25
|
+
@plugin_tarball = Concurrent::Delay.new do
|
26
|
+
build_plugin_tarball do |mod|
|
27
|
+
search_dirs = []
|
28
|
+
search_dirs << mod.plugins if mod.plugins?
|
29
|
+
search_dirs << mod.files if mod.files?
|
30
|
+
search_dirs
|
31
|
+
end
|
32
|
+
end
|
22
33
|
end
|
23
34
|
|
24
35
|
private def libexec
|
25
36
|
@libexec ||= File.join(Gem::Specification.find_by_name('bolt').gem_dir, 'libexec')
|
26
37
|
end
|
27
38
|
|
39
|
+
def custom_facts_task
|
40
|
+
@custom_facts_task ||= begin
|
41
|
+
path = File.join(libexec, 'custom_facts.rb')
|
42
|
+
impl = { 'name' => 'custom_facts.rb', 'path' => path, 'requirements' => [], 'supports_noop' => true }
|
43
|
+
Task.new('custom_facts', [impl], 'stdin')
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
28
47
|
def catalog_apply_task
|
29
48
|
@catalog_apply_task ||= begin
|
30
49
|
path = File.join(libexec, 'apply_catalog.rb')
|
@@ -46,11 +65,11 @@ module Bolt
|
|
46
65
|
facts: @inventory.facts(target),
|
47
66
|
variables: @inventory.vars(target).merge(plan_vars),
|
48
67
|
trusted: trusted.to_h
|
49
|
-
}
|
68
|
+
},
|
69
|
+
inventory: @inventory.data_hash
|
50
70
|
}
|
51
71
|
|
52
72
|
bolt_catalog_exe = File.join(libexec, 'bolt_catalog')
|
53
|
-
|
54
73
|
old_path = ENV['PATH']
|
55
74
|
ENV['PATH'] = "#{RbConfig::CONFIG['bindir']}#{File::PATH_SEPARATOR}#{old_path}"
|
56
75
|
out, err, stat = Open3.capture3('ruby', bolt_catalog_exe, 'compile', stdin_data: catalog_input.to_json)
|
@@ -144,6 +163,8 @@ module Bolt
|
|
144
163
|
type0 = Puppet.lookup(:pal_script_compiler).type('TargetSpec')
|
145
164
|
Puppet::Pal.assert_type(type0, args[0], 'apply targets')
|
146
165
|
|
166
|
+
@executor.report_function_call('apply')
|
167
|
+
|
147
168
|
options = {}
|
148
169
|
if args.count > 1
|
149
170
|
type1 = Puppet.lookup(:pal_script_compiler).type('Hash[String, Data]')
|
@@ -171,7 +192,7 @@ module Bolt
|
|
171
192
|
result_promises = targets.zip(futures).flat_map do |target, future|
|
172
193
|
@executor.queue_execute([target]) do |transport, batch|
|
173
194
|
@executor.with_node_logging("Applying manifest block", batch) do
|
174
|
-
arguments = { 'catalog' => future.value, '_noop' => options['_noop'] }
|
195
|
+
arguments = { 'catalog' => future.value, 'plugins' => plugins, '_noop' => options['_noop'] }
|
175
196
|
raise future.reason if future.rejected?
|
176
197
|
result = transport.batch_task(batch, catalog_apply_task, arguments, options, ¬ify)
|
177
198
|
result = provide_puppet_missing_errors(result)
|
@@ -188,5 +209,45 @@ module Bolt
|
|
188
209
|
end
|
189
210
|
r
|
190
211
|
end
|
212
|
+
|
213
|
+
def plugins
|
214
|
+
@plugin_tarball.value ||
|
215
|
+
raise(Bolt::Error.new("Failed to pack module plugins: #{@plugin_tarball.reason}", 'bolt/plugin-error'))
|
216
|
+
end
|
217
|
+
|
218
|
+
def build_plugin_tarball
|
219
|
+
start_time = Time.now
|
220
|
+
sio = StringIO.new
|
221
|
+
output = Minitar::Output.new(Zlib::GzipWriter.new(sio))
|
222
|
+
|
223
|
+
Puppet.lookup(:current_environment).modules.each do |mod|
|
224
|
+
search_dirs = yield mod
|
225
|
+
|
226
|
+
parent = Pathname.new(mod.path).parent
|
227
|
+
files = Find.find(*search_dirs).select { |file| File.file?(file) }
|
228
|
+
|
229
|
+
files.each do |file|
|
230
|
+
tar_path = Pathname.new(file).relative_path_from(parent)
|
231
|
+
@logger.debug("Packing plugin #{file} to #{tar_path}")
|
232
|
+
stat = File.stat(file)
|
233
|
+
content = File.binread(file)
|
234
|
+
output.tar.add_file_simple(
|
235
|
+
tar_path.to_s,
|
236
|
+
data: content,
|
237
|
+
size: content.size,
|
238
|
+
mode: stat.mode & 0o777,
|
239
|
+
mtime: stat.mtime
|
240
|
+
)
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
duration = Time.now - start_time
|
245
|
+
@logger.debug("Packed plugins in #{duration * 1000} ms")
|
246
|
+
|
247
|
+
output.close
|
248
|
+
Base64.encode64(sio.string)
|
249
|
+
ensure
|
250
|
+
output&.close
|
251
|
+
end
|
191
252
|
end
|
192
253
|
end
|
data/lib/bolt/catalog.rb
CHANGED
@@ -51,6 +51,19 @@ module Bolt
|
|
51
51
|
end
|
52
52
|
end
|
53
53
|
|
54
|
+
def setup_inventory(inventory)
|
55
|
+
config = Bolt::Config.default
|
56
|
+
config.overwrite_transport_data(inventory['config']['transport'],
|
57
|
+
Bolt::Util.symbolize_top_level_keys(inventory['config']['transports']))
|
58
|
+
|
59
|
+
bolt_inventory = Bolt::Inventory.new(inventory['data'],
|
60
|
+
config,
|
61
|
+
Bolt::Util.symbolize_top_level_keys(inventory['target_hash']))
|
62
|
+
|
63
|
+
bolt_inventory.collect_groups
|
64
|
+
bolt_inventory
|
65
|
+
end
|
66
|
+
|
54
67
|
def compile_catalog(request)
|
55
68
|
pal_main = request['code_ast'] || request['code_string']
|
56
69
|
target = request['target']
|
@@ -69,7 +82,10 @@ module Bolt
|
|
69
82
|
node = Puppet.lookup(:pal_current_node)
|
70
83
|
setup_node(node, target["trusted"])
|
71
84
|
|
72
|
-
Puppet.override(pal_main: pal_main,
|
85
|
+
Puppet.override(pal_main: pal_main,
|
86
|
+
bolt_pdb_client: pdb_client,
|
87
|
+
bolt_inventory:
|
88
|
+
setup_inventory(request['inventory'])) do
|
73
89
|
compile_node(node)
|
74
90
|
end
|
75
91
|
end
|
data/lib/bolt/config.rb
CHANGED
@@ -106,6 +106,15 @@ module Bolt
|
|
106
106
|
validate
|
107
107
|
end
|
108
108
|
|
109
|
+
def overwrite_transport_data(transport, transports)
|
110
|
+
@transport = transport
|
111
|
+
@transports = transports
|
112
|
+
end
|
113
|
+
|
114
|
+
def transport_data_get
|
115
|
+
{ transport: @transport, transports: @transports }
|
116
|
+
end
|
117
|
+
|
109
118
|
def deep_clone
|
110
119
|
Bolt::Util.deep_clone(self)
|
111
120
|
end
|
data/lib/bolt/executor.rb
CHANGED
@@ -16,7 +16,7 @@ require 'bolt/puppetdb'
|
|
16
16
|
module Bolt
|
17
17
|
class Executor
|
18
18
|
attr_reader :noop, :transports
|
19
|
-
attr_accessor :run_as
|
19
|
+
attr_accessor :run_as
|
20
20
|
|
21
21
|
def initialize(concurrency = 1,
|
22
22
|
analytics = Bolt::Analytics::NoopClient.new,
|
@@ -244,5 +244,13 @@ module Bolt
|
|
244
244
|
def finish_plan(plan_result)
|
245
245
|
transport('pcp').finish_plan(plan_result)
|
246
246
|
end
|
247
|
+
|
248
|
+
def without_default_logging
|
249
|
+
old_log = @plan_logging
|
250
|
+
@plan_logging = false
|
251
|
+
yield
|
252
|
+
ensure
|
253
|
+
@plan_logging = old_log
|
254
|
+
end
|
247
255
|
end
|
248
256
|
end
|
data/lib/bolt/inventory.rb
CHANGED
@@ -53,16 +53,16 @@ module Bolt
|
|
53
53
|
inventory
|
54
54
|
end
|
55
55
|
|
56
|
-
def initialize(data, config = nil)
|
56
|
+
def initialize(data, config = nil, target_vars: {}, target_facts: {}, target_features: {})
|
57
57
|
@logger = Logging.logger[self]
|
58
58
|
# Config is saved to add config options to targets
|
59
59
|
@config = config || Bolt::Config.default
|
60
60
|
@data = data ||= {}
|
61
61
|
@groups = Group.new(data.merge('name' => 'all'))
|
62
62
|
@group_lookup = {}
|
63
|
-
@target_vars =
|
64
|
-
@target_facts =
|
65
|
-
@target_features =
|
63
|
+
@target_vars = target_vars
|
64
|
+
@target_facts = target_facts
|
65
|
+
@target_features = target_features
|
66
66
|
end
|
67
67
|
|
68
68
|
def validate
|
@@ -123,6 +123,18 @@ module Bolt
|
|
123
123
|
@target_features[target.name] || Set.new
|
124
124
|
end
|
125
125
|
|
126
|
+
def data_hash
|
127
|
+
{
|
128
|
+
data: @data,
|
129
|
+
target_hash: {
|
130
|
+
target_vars: @target_vars,
|
131
|
+
target_facts: @target_facts,
|
132
|
+
target_features: @target_features
|
133
|
+
},
|
134
|
+
config: @config.transport_data_get
|
135
|
+
}
|
136
|
+
end
|
137
|
+
|
126
138
|
#### PRIVATE ####
|
127
139
|
#
|
128
140
|
# For debugging only now
|
data/lib/bolt/inventory/group.rb
CHANGED
@@ -9,30 +9,54 @@ module Bolt
|
|
9
9
|
|
10
10
|
def initialize(data)
|
11
11
|
@logger = Logging.logger[self]
|
12
|
-
@name = data['name']
|
13
|
-
@nodes = {}
|
14
12
|
|
15
|
-
data
|
16
|
-
|
17
|
-
|
18
|
-
|
13
|
+
unless data.is_a?(Hash)
|
14
|
+
raise ValidationError.new("Expected group to be a Hash, not #{data.class}", nil)
|
15
|
+
end
|
16
|
+
|
17
|
+
if data.key?('name')
|
18
|
+
if data['name'].is_a?(String)
|
19
|
+
@name = data['name']
|
20
|
+
else
|
21
|
+
raise ValidationError.new("Group name must be a String, not #{data['name'].inspect}", nil)
|
22
|
+
end
|
23
|
+
else
|
24
|
+
raise ValidationError.new("Group does not have a name", nil)
|
25
|
+
end
|
26
|
+
|
27
|
+
@vars = fetch_value(data, 'vars', Hash)
|
28
|
+
@facts = fetch_value(data, 'facts', Hash)
|
29
|
+
@features = fetch_value(data, 'features', Array)
|
30
|
+
@config = fetch_value(data, 'config', Hash)
|
31
|
+
|
32
|
+
nodes = fetch_value(data, 'nodes', Array)
|
33
|
+
groups = fetch_value(data, 'groups', Array)
|
34
|
+
|
35
|
+
@nodes = {}
|
36
|
+
nodes.each do |node|
|
37
|
+
node = { 'name' => node } if node.is_a? String
|
38
|
+
unless node.is_a?(Hash)
|
39
|
+
raise ValidationError.new("Node entry must be a String or Hash, not #{node.class}", @name)
|
40
|
+
end
|
41
|
+
if @nodes.include? node['name']
|
42
|
+
@logger.warn("Ignoring duplicate node in #{@name}: #{node}")
|
19
43
|
else
|
20
|
-
@nodes[
|
44
|
+
@nodes[node['name']] = node
|
21
45
|
end
|
22
46
|
end
|
23
47
|
|
24
|
-
@
|
25
|
-
@facts = data['facts'] || {}
|
26
|
-
@features = data['features'] || []
|
27
|
-
@config = data['config'] || {}
|
28
|
-
@groups = if data['groups']
|
29
|
-
data['groups'].map { |g| Group.new(g) }
|
30
|
-
else
|
31
|
-
[]
|
32
|
-
end
|
48
|
+
@groups = groups.map { |g| Group.new(g) }
|
33
49
|
|
34
50
|
# this allows arbitrary info for the top level
|
35
|
-
@rest = data.reject { |k, _| %w[name nodes config groups].include? k }
|
51
|
+
@rest = data.reject { |k, _| %w[name nodes config groups vars facts features].include? k }
|
52
|
+
end
|
53
|
+
|
54
|
+
def fetch_value(data, key, type)
|
55
|
+
value = data.fetch(key, type.new)
|
56
|
+
unless value.is_a?(type)
|
57
|
+
raise ValidationError.new("Expected #{key} to be of type #{type}, not #{value.class}", @name)
|
58
|
+
end
|
59
|
+
value
|
36
60
|
end
|
37
61
|
|
38
62
|
def check_deprecated_config(context, name, config)
|
@@ -44,11 +68,10 @@ module Bolt
|
|
44
68
|
end
|
45
69
|
|
46
70
|
def validate(used_names = Set.new, node_names = Set.new, depth = 0)
|
47
|
-
raise ValidationError.new("Group does not have a name", nil) unless @name
|
48
71
|
if used_names.include?(@name)
|
49
72
|
raise ValidationError.new("Tried to redefine group #{@name}", @name)
|
50
73
|
end
|
51
|
-
raise ValidationError.new("Invalid
|
74
|
+
raise ValidationError.new("Invalid group name #{@name}", @name) unless @name =~ /\A[a-z0-9_]+\Z/
|
52
75
|
|
53
76
|
if node_names.include?(@name)
|
54
77
|
raise ValidationError.new("Group #{@name} conflicts with node of the same name", @name)
|
data/lib/bolt/result_set.rb
CHANGED
@@ -109,99 +109,6 @@ $ENV:RUBYLIB
|
|
109
109
|
Add-Type -AssemblyName System.ServiceModel.Web, System.Runtime.Serialization
|
110
110
|
$utf8 = [System.Text.Encoding]::UTF8
|
111
111
|
|
112
|
-
function Invoke-Interpreter
|
113
|
-
{
|
114
|
-
[CmdletBinding()]
|
115
|
-
Param (
|
116
|
-
[Parameter()]
|
117
|
-
[String]
|
118
|
-
$Path,
|
119
|
-
|
120
|
-
[Parameter()]
|
121
|
-
[String]
|
122
|
-
$Arguments,
|
123
|
-
|
124
|
-
[Parameter()]
|
125
|
-
[Int32]
|
126
|
-
$Timeout,
|
127
|
-
|
128
|
-
[Parameter()]
|
129
|
-
[String]
|
130
|
-
$StdinInput = $Null
|
131
|
-
)
|
132
|
-
|
133
|
-
try
|
134
|
-
{
|
135
|
-
if (-not (Get-Command $Path -ErrorAction SilentlyContinue))
|
136
|
-
{
|
137
|
-
throw "Could not find executable '$Path' in ${ENV:PATH} on target node"
|
138
|
-
}
|
139
|
-
|
140
|
-
$startInfo = New-Object System.Diagnostics.ProcessStartInfo($Path, $Arguments)
|
141
|
-
$startInfo.UseShellExecute = $false
|
142
|
-
$startInfo.WorkingDirectory = Split-Path -Parent (Get-Command $Path).Path
|
143
|
-
$startInfo.CreateNoWindow = $true
|
144
|
-
if ($StdinInput) { $startInfo.RedirectStandardInput = $true }
|
145
|
-
$startInfo.RedirectStandardOutput = $true
|
146
|
-
$startInfo.RedirectStandardError = $true
|
147
|
-
|
148
|
-
$stdoutHandler = { if (-not ([String]::IsNullOrEmpty($EventArgs.Data))) { $Host.UI.WriteLine($EventArgs.Data) } }
|
149
|
-
$stderrHandler = { if (-not ([String]::IsNullOrEmpty($EventArgs.Data))) { $Host.UI.WriteErrorLine($EventArgs.Data) } }
|
150
|
-
$invocationId = [Guid]::NewGuid().ToString()
|
151
|
-
|
152
|
-
$process = New-Object System.Diagnostics.Process
|
153
|
-
$process.StartInfo = $startInfo
|
154
|
-
$process.EnableRaisingEvents = $true
|
155
|
-
|
156
|
-
# https://msdn.microsoft.com/en-us/library/system.diagnostics.process.standarderror(v=vs.110).aspx#Anchor_2
|
157
|
-
$stdoutEvent = Register-ObjectEvent -InputObject $process -EventName 'OutputDataReceived' -Action $stdoutHandler
|
158
|
-
$stderrEvent = Register-ObjectEvent -InputObject $process -EventName 'ErrorDataReceived' -Action $stderrHandler
|
159
|
-
$exitedEvent = Register-ObjectEvent -InputObject $process -EventName 'Exited' -SourceIdentifier $invocationId
|
160
|
-
|
161
|
-
$process.Start() | Out-Null
|
162
|
-
|
163
|
-
$process.BeginOutputReadLine()
|
164
|
-
$process.BeginErrorReadLine()
|
165
|
-
|
166
|
-
if ($StdinInput)
|
167
|
-
{
|
168
|
-
$process.StandardInput.WriteLine($StdinInput)
|
169
|
-
$process.StandardInput.Close()
|
170
|
-
}
|
171
|
-
|
172
|
-
# park current thread until the PS event is signaled upon process exit
|
173
|
-
# OR the timeout has elapsed
|
174
|
-
$waitResult = Wait-Event -SourceIdentifier $invocationId -Timeout $Timeout
|
175
|
-
if (! $process.HasExited)
|
176
|
-
{
|
177
|
-
$Host.UI.WriteErrorLine("Process $Path did not complete in $Timeout seconds")
|
178
|
-
return 1
|
179
|
-
}
|
180
|
-
|
181
|
-
return $process.ExitCode
|
182
|
-
}
|
183
|
-
catch
|
184
|
-
{
|
185
|
-
$Host.UI.WriteErrorLine($_)
|
186
|
-
return 1
|
187
|
-
}
|
188
|
-
finally
|
189
|
-
{
|
190
|
-
@($stdoutEvent, $stderrEvent, $exitedEvent) |
|
191
|
-
? { $_ -ne $Null } |
|
192
|
-
% { Unregister-Event -SourceIdentifier $_.Name }
|
193
|
-
|
194
|
-
if ($process -ne $Null)
|
195
|
-
{
|
196
|
-
if (($process.Handle -ne $Null) -and (! $process.HasExited))
|
197
|
-
{
|
198
|
-
try { $process.Kill() } catch { $Host.UI.WriteErrorLine("Failed To Kill Process $Path") }
|
199
|
-
}
|
200
|
-
$process.Dispose()
|
201
|
-
}
|
202
|
-
}
|
203
|
-
}
|
204
|
-
|
205
112
|
function Write-Stream {
|
206
113
|
PARAM(
|
207
114
|
[Parameter(Position=0)] $stream,
|
@@ -408,30 +315,22 @@ PS
|
|
408
315
|
raise
|
409
316
|
end
|
410
317
|
|
411
|
-
|
412
|
-
DEFAULT_EXECUTION_TIMEOUT = 10 * 60
|
413
|
-
|
414
|
-
def execute_process(path = '', arguments = [], stdin = nil,
|
415
|
-
timeout = DEFAULT_EXECUTION_TIMEOUT)
|
318
|
+
def execute_process(path = '', arguments = [], stdin = nil)
|
416
319
|
quoted_args = arguments.map do |arg|
|
417
320
|
"'" + arg.gsub("'", "''") + "'"
|
418
|
-
end.join('
|
419
|
-
|
321
|
+
end.join(' ')
|
322
|
+
|
323
|
+
exec_cmd =
|
324
|
+
if stdin.nil?
|
325
|
+
"& #{path} #{quoted_args}"
|
326
|
+
else
|
327
|
+
"@'\n#{stdin}\n'@ | & #{path} #{quoted_args}"
|
328
|
+
end
|
420
329
|
execute(<<-PS)
|
421
|
-
$
|
422
|
-
|
423
|
-
)
|
424
|
-
|
425
|
-
$invokeArgs = @{
|
426
|
-
Path = "#{path}"
|
427
|
-
Arguments = $quoted_array -Join ' '
|
428
|
-
Timeout = #{timeout}
|
429
|
-
#{stdin.nil? ? '' : "StdinInput = @'\n" + stdin + "\n'@"}
|
430
|
-
}
|
431
|
-
|
432
|
-
# winrm gem checks $? prior to using $LASTEXITCODE
|
433
|
-
# making it necessary to exit with the desired code to propagate status properly
|
434
|
-
exit $(Invoke-Interpreter @invokeArgs)
|
330
|
+
$OutputEncoding = [Console]::OutputEncoding
|
331
|
+
#{exec_cmd}
|
332
|
+
if (-not $? -and ($LASTEXITCODE -eq $null)) { exit 1 }
|
333
|
+
exit $LASTEXITCODE
|
435
334
|
PS
|
436
335
|
end
|
437
336
|
|
data/lib/bolt/util.rb
CHANGED
@@ -117,6 +117,11 @@ module Bolt
|
|
117
117
|
def windows?
|
118
118
|
!!File::ALT_SEPARATOR
|
119
119
|
end
|
120
|
+
|
121
|
+
# Accept hash and return hash with top level keys of type "String" converted to symbols.
|
122
|
+
def symbolize_top_level_keys(hsh)
|
123
|
+
hsh.each_with_object({}) { |(k, v), h| k.is_a?(String) ? h[k.to_sym] = v : h[k] = v }
|
124
|
+
end
|
120
125
|
end
|
121
126
|
end
|
122
127
|
end
|
data/lib/bolt/version.rb
CHANGED
data/libexec/apply_catalog.rb
CHANGED
@@ -4,6 +4,7 @@
|
|
4
4
|
require 'json'
|
5
5
|
require 'puppet'
|
6
6
|
require 'puppet/configurer'
|
7
|
+
require 'puppet/module_tool/tar'
|
7
8
|
require 'tempfile'
|
8
9
|
|
9
10
|
args = JSON.parse(STDIN.read)
|
@@ -39,23 +40,34 @@ Puppet[:skip_tags] = nil
|
|
39
40
|
Puppet[:prerun_command] = nil
|
40
41
|
Puppet[:postrun_command] = nil
|
41
42
|
|
42
|
-
|
43
|
+
Puppet[:default_file_terminus] = :file_server
|
43
44
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
45
|
+
exit_code = 0
|
46
|
+
Dir.mktmpdir do |moduledir|
|
47
|
+
Tempfile.open('plugins.tar.gz') do |plugins|
|
48
|
+
File.binwrite(plugins, Base64.decode64(args['plugins']))
|
49
|
+
Puppet::ModuleTool::Tar.instance.unpack(plugins, moduledir, Etc.getlogin)
|
50
|
+
end
|
49
51
|
|
50
|
-
|
51
|
-
catalog = Puppet::Resource::Catalog.from_data_hash(args['catalog']).to_ral
|
52
|
-
catalog.environment = env.name.to_s
|
53
|
-
catalog.environment_instance = env
|
52
|
+
env = Puppet.lookup(:environments).get('production').override_with(modulepath: [moduledir])
|
54
53
|
|
55
|
-
|
56
|
-
|
57
|
-
|
54
|
+
report = if Puppet::Util::Package.versioncmp(Puppet.version, '5.0.0') > 0
|
55
|
+
Puppet::Transaction::Report.new
|
56
|
+
else
|
57
|
+
Puppet::Transaction::Report.new('apply')
|
58
|
+
end
|
59
|
+
|
60
|
+
Puppet.override(current_environment: env, loaders: Puppet::Pops::Loaders.new(env)) do
|
61
|
+
catalog = Puppet::Resource::Catalog.from_data_hash(args['catalog']).to_ral
|
62
|
+
catalog.environment = env.name.to_s
|
63
|
+
catalog.environment_instance = env
|
58
64
|
|
59
|
-
|
65
|
+
configurer = Puppet::Configurer.new
|
66
|
+
configurer.run(catalog: catalog, report: report, pluginsync: false)
|
67
|
+
end
|
68
|
+
|
69
|
+
puts JSON.pretty_generate(report.to_data_hash)
|
70
|
+
exit_code = report.exit_status != 1
|
71
|
+
end
|
60
72
|
|
61
|
-
exit
|
73
|
+
exit exit_code
|
data/libexec/bolt_catalog
CHANGED
@@ -16,6 +16,18 @@ require 'json'
|
|
16
16
|
# "facts": "Hash of facts to use for the node",
|
17
17
|
# "variables": "Hash of variables to use for compilation",
|
18
18
|
# "trusted": "Hash of trusted data for the node"
|
19
|
+
# },
|
20
|
+
# "inventory": JSON serialized information about inventory {
|
21
|
+
# "data": Data stored in inventory @data instance variable,
|
22
|
+
# "target_hash": {
|
23
|
+
# "target_vars": Vars that may have been updated plan,
|
24
|
+
# "target_facts": Facts that may have been updated in plan,
|
25
|
+
# "target_features": Features that may have been updated in plan,
|
26
|
+
# },
|
27
|
+
# "config": {
|
28
|
+
# "transport": Transport to use,
|
29
|
+
# "transports": Array of transport configs (note that transport keys are stringified)
|
30
|
+
# }
|
19
31
|
# }
|
20
32
|
# }
|
21
33
|
|
@@ -0,0 +1,44 @@
|
|
1
|
+
#! /opt/puppetlabs/puppet/bin/ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'json'
|
5
|
+
require 'puppet'
|
6
|
+
require 'puppet/module_tool/tar'
|
7
|
+
require 'tempfile'
|
8
|
+
|
9
|
+
args = JSON.parse(STDIN.read)
|
10
|
+
|
11
|
+
Dir.mktmpdir do |moduledir|
|
12
|
+
Tempfile.open('plugins.tar.gz') do |plugins|
|
13
|
+
File.binwrite(plugins, Base64.decode64(args['plugins']))
|
14
|
+
Puppet::ModuleTool::Tar.instance.unpack(plugins, moduledir, Etc.getlogin)
|
15
|
+
end
|
16
|
+
|
17
|
+
Puppet.initialize_settings
|
18
|
+
env = Puppet.lookup(:environments).get('production').override_with(modulepath: [moduledir])
|
19
|
+
env.each_plugin_directory do |dir|
|
20
|
+
$LOAD_PATH << dir unless $LOAD_PATH.include?(dir)
|
21
|
+
end
|
22
|
+
|
23
|
+
dirs = []
|
24
|
+
external_dirs = []
|
25
|
+
env.modules.each do |mod|
|
26
|
+
dirs << File.join(mod.plugins, 'facter') if mod.plugins?
|
27
|
+
external_dirs << mod.pluginfacts if mod.pluginfacts?
|
28
|
+
end
|
29
|
+
|
30
|
+
Facter.reset
|
31
|
+
Facter.search(*dirs) unless dirs.empty?
|
32
|
+
Facter.search_external(external_dirs)
|
33
|
+
|
34
|
+
if Puppet.respond_to? :initialize_facts
|
35
|
+
Puppet.initialize_facts
|
36
|
+
else
|
37
|
+
Facter.add(:puppetversion) do
|
38
|
+
setcode { Puppet.version.to_s }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
puts(Facter.to_hash.to_json)
|
43
|
+
end
|
44
|
+
exit 0
|
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: 0.21.
|
4
|
+
version: 0.21.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Puppet
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-08-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: addressable
|
@@ -289,6 +289,7 @@ files:
|
|
289
289
|
- bolt-modules/boltlib/lib/puppet/datatypes/resultset.rb
|
290
290
|
- bolt-modules/boltlib/lib/puppet/datatypes/target.rb
|
291
291
|
- bolt-modules/boltlib/lib/puppet/functions/add_facts.rb
|
292
|
+
- bolt-modules/boltlib/lib/puppet/functions/apply_prep.rb
|
292
293
|
- bolt-modules/boltlib/lib/puppet/functions/facts.rb
|
293
294
|
- bolt-modules/boltlib/lib/puppet/functions/fail_plan.rb
|
294
295
|
- bolt-modules/boltlib/lib/puppet/functions/file_upload.rb
|
@@ -355,6 +356,7 @@ files:
|
|
355
356
|
- lib/bolt_spec/plans/mock_executor.rb
|
356
357
|
- libexec/apply_catalog.rb
|
357
358
|
- libexec/bolt_catalog
|
359
|
+
- libexec/custom_facts.rb
|
358
360
|
- modules/aggregate/lib/puppet/functions/aggregate/count.rb
|
359
361
|
- modules/aggregate/lib/puppet/functions/aggregate/nodes.rb
|
360
362
|
- modules/aggregate/plans/count.pp
|