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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 81d6e90b40ddb51ff8c5b4817ba930417ec7aec118a63e8b585b38d6a5555040
4
- data.tar.gz: 3e99e5ba914908058503d1feb9bd8fe5331474288d863ec158a798142b0f41be
3
+ metadata.gz: 1736c46973051ce64d6c27c43c9d34e63c4c6f05f5d5017f2df51796669500e4
4
+ data.tar.gz: da561ef33b65849c91f625fc465e89d7d7d7709bf6e1493b8fff6d5d954884c6
5
5
  SHA512:
6
- metadata.gz: 9e7d9c78f78df1dffa0e4288cefa10216182091c16b70ace73b6e1a766a4ebff7fc6cedb403b9794851c844b9050c63a13f1beb137a87e5f53af2c9b2de7af54
7
- data.tar.gz: 1edc8a0cb84632e703a9c2b0e50d6478cdbb2e5a56e0bb7bc06e9395e6dba1bcbc7232423eca87afef58f904c5922af782041079070b819f08d5228dbb3c029b
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
- old_log = executor.plan_logging
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
@@ -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, &notify)
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
@@ -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, bolt_pdb_client: pdb_client) do
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
@@ -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
@@ -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, :plan_logging
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
@@ -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
@@ -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['nodes']&.each do |n|
16
- n = { 'name' => n } if n.is_a? String
17
- if @nodes.include? n['name']
18
- @logger.warn("Ignoring duplicate node in #{@name}: #{n}")
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[n['name']] = n
44
+ @nodes[node['name']] = node
21
45
  end
22
46
  end
23
47
 
24
- @vars = data['vars'] || {}
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 Group name #{@name}", @name) unless @name =~ /\A[a-z0-9_]+\Z/
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)
@@ -27,6 +27,7 @@ module Bolt
27
27
 
28
28
  def each
29
29
  @results.each { |r| yield r }
30
+ self
30
31
  end
31
32
 
32
33
  def result_hash
@@ -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
- # 10 minutes in seconds
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
- $quoted_array = @(
422
- #{quoted_args}
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
 
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bolt
4
- VERSION = '0.21.4'
4
+ VERSION = '0.21.5'
5
5
  end
@@ -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
- env = Puppet.lookup(:environments).get('production')
43
+ Puppet[:default_file_terminus] = :file_server
43
44
 
44
- report = if Puppet::Util::Package.versioncmp(Puppet.version, '5.0.0') > 0
45
- Puppet::Transaction::Report.new
46
- else
47
- Puppet::Transaction::Report.new('apply')
48
- end
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
- Puppet.override(current_environment: env, loaders: Puppet::Pops::Loaders.new(env)) do
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
- configurer = Puppet::Configurer.new
56
- configurer.run(catalog: catalog, report: report, pluginsync: false)
57
- end
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
- puts JSON.pretty_generate(report.to_data_hash)
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 report.exit_status != 1
73
+ exit exit_code
@@ -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
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-07-26 00:00:00.000000000 Z
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