bolt 2.5.0 → 2.6.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: 35079d3ff8276e6ebe7e1164e7336ef77b17d64c0f3c3df4a257b71fb3c6c599
4
- data.tar.gz: ccc148bcec686d950741595e99dbcacd88825685d3dfb7641850d9ff54df02a6
3
+ metadata.gz: ca1602020625348ee5ba03e246feeb9263ca10fdcda10e13bba36eb674dcdfba
4
+ data.tar.gz: bdde3554a2406f427f57d02f2e0063e592b7ceba235656c406e9f1ece3899c75
5
5
  SHA512:
6
- metadata.gz: 9f101955f9562b6e8365a277079fdaff192350c51e5505a36f5fad1be0736deb4f6ca9c458758177c8e5907cf9bbfb709e87bf3a6877075269250b98fd076b03
7
- data.tar.gz: dfa5f451c469a9f787f4ed4be798af368463ee9d9f463c683a8c458ba6a552e5e161ddb13c99bc6e819cb95987ce73fca9b5194215a3863432e3e3cac82c709b
6
+ metadata.gz: 13bcc53364488a1f37ebc19821c6429bcbcc055a543f9b581352b03c403793579290af88be0b92416506c3ddd9f23e775c7d9b9a6d2369aed3c38b0ef162da8f
7
+ data.tar.gz: ea09c6b92cc08d1cd893c136775070df2f6d79097663c58a96212bfdcf23984fe39fd5690d706f4802a61cd384bfe8139e446914506938605ba6d4771f272d63
data/Puppetfile CHANGED
@@ -30,10 +30,11 @@ mod 'puppetlabs-ruby_task_helper', '0.5.1'
30
30
  mod 'puppetlabs-ruby_plugin_helper', '0.1.0'
31
31
 
32
32
  # Plugin modules
33
+ mod 'puppetlabs-aws_inventory', '0.5.0'
33
34
  mod 'puppetlabs-azure_inventory', '0.3.0'
35
+ mod 'puppetlabs-gcloud_inventory', '0.1.0'
34
36
  mod 'puppetlabs-terraform', '0.5.0'
35
37
  mod 'puppetlabs-vault', '0.3.0'
36
- mod 'puppetlabs-aws_inventory', '0.5.0'
37
38
  mod 'puppetlabs-yaml', '0.2.0'
38
39
 
39
40
  # If we don't list these modules explicitly, r10k will purge them
@@ -15,7 +15,6 @@ require 'bolt/transport/ssh'
15
15
  require 'bolt/transport/winrm'
16
16
  require 'bolt/transport/orch'
17
17
  require 'bolt/transport/local'
18
- require 'bolt/transport/local_windows'
19
18
  require 'bolt/transport/docker'
20
19
  require 'bolt/transport/remote'
21
20
 
@@ -24,7 +23,7 @@ module Bolt
24
23
  ssh: Bolt::Transport::SSH,
25
24
  winrm: Bolt::Transport::WinRM,
26
25
  pcp: Bolt::Transport::Orch,
27
- local: Bolt::Util.windows? ? Bolt::Transport::LocalWindows : Bolt::Transport::Local,
26
+ local: Bolt::Transport::Local,
28
27
  docker: Bolt::Transport::Docker,
29
28
  remote: Bolt::Transport::Remote
30
29
  }.freeze
@@ -24,14 +24,14 @@ module Bolt
24
24
 
25
25
  def task_step(scope, step)
26
26
  task = step['task']
27
- target = step['target']
27
+ targets = step['targets'] || step['target']
28
28
  description = step['description']
29
29
  params = step['parameters'] || {}
30
30
 
31
31
  args = if description
32
- [task, target, description, params]
32
+ [task, targets, description, params]
33
33
  else
34
- [task, target, params]
34
+ [task, targets, params]
35
35
  end
36
36
 
37
37
  scope.call_function('run_task', args)
@@ -48,15 +48,15 @@ module Bolt
48
48
 
49
49
  def script_step(scope, step)
50
50
  script = step['script']
51
- target = step['target']
51
+ targets = step['targets'] || step['target']
52
52
  description = step['description']
53
53
  arguments = step['arguments'] || []
54
54
 
55
55
  options = { 'arguments' => arguments }
56
56
  args = if description
57
- [script, target, description, options]
57
+ [script, targets, description, options]
58
58
  else
59
- [script, target, options]
59
+ [script, targets, options]
60
60
  end
61
61
 
62
62
  scope.call_function('run_script', args)
@@ -64,10 +64,10 @@ module Bolt
64
64
 
65
65
  def command_step(scope, step)
66
66
  command = step['command']
67
- target = step['target']
67
+ targets = step['targets'] || step['target']
68
68
  description = step['description']
69
69
 
70
- args = [command, target]
70
+ args = [command, targets]
71
71
  args << description if description
72
72
  scope.call_function('run_command', args)
73
73
  end
@@ -75,10 +75,10 @@ module Bolt
75
75
  def upload_step(scope, step)
76
76
  source = step['source']
77
77
  destination = step['destination']
78
- target = step['target']
78
+ targets = step['targets'] || step['target']
79
79
  description = step['description']
80
80
 
81
- args = [source, destination, target]
81
+ args = [source, destination, targets]
82
82
  args << description if description
83
83
  scope.call_function('upload_file', args)
84
84
  end
@@ -88,11 +88,13 @@ module Bolt
88
88
  end
89
89
 
90
90
  def resources_step(scope, step)
91
+ targets = step['targets'] || step['target']
92
+
91
93
  # TODO: Only call apply_prep when needed
92
- scope.call_function('apply_prep', step['target'])
94
+ scope.call_function('apply_prep', targets)
93
95
  manifest = generate_manifest(step['resources'])
94
96
 
95
- apply_manifest(scope, step['target'], manifest)
97
+ apply_manifest(scope, targets, manifest)
96
98
  end
97
99
 
98
100
  def generate_manifest(resources)
@@ -127,16 +129,22 @@ module Bolt
127
129
  MANIFEST
128
130
  end
129
131
 
130
- def apply_manifest(scope, target, manifest)
132
+ def apply_manifest(scope, targets, manifest)
131
133
  ast = @evaluator.parse_string(manifest)
132
134
  apply_block = ast.body.body
133
135
  applicator = Puppet.lookup(:apply_executor)
134
- applicator.apply([target], apply_block, scope)
136
+ applicator.apply([targets], apply_block, scope)
135
137
  end
136
138
 
137
139
  # This is the method that Puppet calls to evaluate the plan. The name
138
140
  # makes more sense for .pp plans.
139
141
  def evaluate_block_with_bindings(closure_scope, args_hash, plan)
142
+ if plan.steps.any? { |step| step.body.key?('target') }
143
+ msg = "The 'target' parameter for YAML plan steps is deprecated and will be removed "\
144
+ "in a future version of Bolt. Use the 'targets' parameter instead."
145
+ @logger.warn(msg)
146
+ end
147
+
140
148
  plan_result = closure_scope.with_local_scope(args_hash) do |scope|
141
149
  plan.steps.each do |step|
142
150
  step_result = dispatch_step(scope, step)
@@ -6,13 +6,12 @@ module Bolt
6
6
  class PAL
7
7
  class YamlPlan
8
8
  class Step
9
- attr_reader :name, :type, :body, :target
9
+ attr_reader :name, :type, :body, :targets
10
10
 
11
11
  def self.allowed_keys
12
- Set['name', 'description', 'target']
12
+ Set['name', 'description', 'target', 'targets']
13
13
  end
14
14
 
15
- COMMON_STEP_KEYS = %w[name description target].freeze
16
15
  STEP_KEYS = %w[command script task plan source destination eval resources].freeze
17
16
 
18
17
  def self.create(step_body, step_number)
@@ -38,7 +37,7 @@ module Bolt
38
37
  def initialize(step_body)
39
38
  @name = step_body['name']
40
39
  @description = step_body['description']
41
- @target = step_body['target']
40
+ @targets = step_body['targets'] || step_body['target']
42
41
  @body = step_body
43
42
  end
44
43
 
@@ -82,6 +81,14 @@ module Bolt
82
81
 
83
82
  # Ensure all required keys are present
84
83
  missing_keys = required_keys - body.keys
84
+
85
+ # Handle cases where steps with a required 'targets' key are using the deprecated
86
+ # 'target' key instead.
87
+ # TODO: Remove this when 'target' is removed
88
+ if body.include?('target')
89
+ missing_keys -= ['targets']
90
+ end
91
+
85
92
  if missing_keys.any?
86
93
  error_message = "The #{step_type.inspect} step requires: #{missing_keys.to_a.inspect} key(s)"
87
94
  err = step_error(error_message, body['name'], step_number)
@@ -10,7 +10,7 @@ module Bolt
10
10
  end
11
11
 
12
12
  def self.required_keys
13
- Set['target']
13
+ Set['targets']
14
14
  end
15
15
 
16
16
  def initialize(step_body)
@@ -23,7 +23,7 @@ module Bolt
23
23
  code << "$#{@name} = " if @name
24
24
 
25
25
  fn = 'run_command'
26
- args = [@command, @target]
26
+ args = [@command, @targets]
27
27
  args << @description if @description
28
28
 
29
29
  code << function_call(fn, args)
@@ -10,7 +10,7 @@ module Bolt
10
10
  end
11
11
 
12
12
  def self.required_keys
13
- Set['target']
13
+ Set['targets']
14
14
  end
15
15
 
16
16
  def initialize(step_body)
@@ -68,14 +68,14 @@ module Bolt
68
68
 
69
69
  code.print " "
70
70
  fn = 'apply_prep'
71
- args = [@target]
71
+ args = [@targets]
72
72
  code << function_call(fn, args)
73
73
  code.print "\n"
74
74
 
75
75
  code.print " "
76
76
  code.print "$#{@name} = " if @name
77
77
 
78
- code.puts "apply(#{Bolt::Util.to_code(@target)}) {"
78
+ code.puts "apply(#{Bolt::Util.to_code(@targets)}) {"
79
79
 
80
80
  declarations = @normalized_resources.map do |resource|
81
81
  type = resource['type'].is_a?(EvaluableString) ? resource['type'].value : resource['type']
@@ -10,7 +10,7 @@ module Bolt
10
10
  end
11
11
 
12
12
  def self.required_keys
13
- Set['target']
13
+ Set['targets']
14
14
  end
15
15
 
16
16
  def initialize(step_body)
@@ -28,7 +28,7 @@ module Bolt
28
28
  options['arguments'] = @arguments unless @arguments.empty?
29
29
 
30
30
  fn = 'run_script'
31
- args = [@script, @target]
31
+ args = [@script, @targets]
32
32
  args << @description if @description
33
33
  args << options unless options.empty?
34
34
 
@@ -10,7 +10,7 @@ module Bolt
10
10
  end
11
11
 
12
12
  def self.required_keys
13
- Set['target']
13
+ Set['targets']
14
14
  end
15
15
 
16
16
  def initialize(step_body)
@@ -24,7 +24,7 @@ module Bolt
24
24
  code << "$#{@name} = " if @name
25
25
 
26
26
  fn = 'run_task'
27
- args = [@task, @target]
27
+ args = [@task, @targets]
28
28
  args << @description if @description
29
29
  args << @parameters unless @parameters.empty?
30
30
 
@@ -10,7 +10,7 @@ module Bolt
10
10
  end
11
11
 
12
12
  def self.required_keys
13
- Set['target', 'source', 'destination']
13
+ Set['destination', 'source', 'targets']
14
14
  end
15
15
 
16
16
  def initialize(step_body)
@@ -24,7 +24,7 @@ module Bolt
24
24
  code << "$#{@name} = " if @name
25
25
 
26
26
  fn = 'upload_file'
27
- args = [@source, @destination, @target]
27
+ args = [@source, @destination, @targets]
28
28
  args << @description if @description
29
29
 
30
30
  code << function_call(fn, args)
@@ -143,7 +143,8 @@ module Bolt
143
143
  end
144
144
 
145
145
  RUBY_PLUGINS = %w[task pkcs7 prompt env_var].freeze
146
- BUILTIN_PLUGINS = %w[task terraform pkcs7 prompt vault aws_inventory puppetdb azure_inventory yaml env_var].freeze
146
+ BUILTIN_PLUGINS = %w[task terraform pkcs7 prompt vault aws_inventory puppetdb azure_inventory
147
+ yaml env_var gcloud_inventory].freeze
147
148
  DEFAULT_PLUGIN_HOOKS = { 'puppet_library' => { 'plugin' => 'puppet_agent', 'stop_service' => true } }.freeze
148
149
 
149
150
  attr_reader :pal, :plugin_context
@@ -86,3 +86,4 @@ module Bolt
86
86
  end
87
87
 
88
88
  require 'bolt/shell/bash'
89
+ require 'bolt/shell/powershell'
@@ -0,0 +1,263 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bolt/shell/powershell/snippets'
4
+
5
+ module Bolt
6
+ class Shell
7
+ class Powershell < Shell
8
+ DEFAULT_EXTENSIONS = Set.new(%w[.ps1 .rb .pp])
9
+ PS_ARGS = %w[-NoProfile -NonInteractive -NoLogo -ExecutionPolicy Bypass -File].freeze
10
+
11
+ def initialize(target, conn)
12
+ super
13
+
14
+ extensions = [target.options['extensions'] || []].flatten.map { |ext| ext[0] != '.' ? '.' + ext : ext }
15
+ extensions += target.options['interpreters'].keys if target.options['interpreters']
16
+ @extensions = DEFAULT_EXTENSIONS + extensions
17
+ end
18
+
19
+ def provided_features
20
+ ['powershell']
21
+ end
22
+
23
+ def default_input_method(executable)
24
+ powershell_file?(executable) ? 'powershell' : 'both'
25
+ end
26
+
27
+ def powershell_file?(path)
28
+ File.extname(path).downcase == '.ps1'
29
+ end
30
+
31
+ def validate_extensions(ext)
32
+ unless @extensions.include?(ext)
33
+ raise Bolt::Node::FileError.new("File extension #{ext} is not enabled, "\
34
+ "to run it please add to 'winrm: extensions'", 'FILETYPE_ERROR')
35
+ end
36
+ end
37
+
38
+ def process_from_extension(path)
39
+ case Pathname(path).extname.downcase
40
+ when '.rb'
41
+ [
42
+ 'ruby.exe',
43
+ %W[-S "#{path}"]
44
+ ]
45
+ when '.ps1'
46
+ [
47
+ 'powershell.exe',
48
+ [*PS_ARGS, path]
49
+ ]
50
+ when '.pp'
51
+ [
52
+ 'puppet.bat',
53
+ %W[apply "#{path}"]
54
+ ]
55
+ else
56
+ # Run the script via cmd, letting Windows extension handling determine how
57
+ [
58
+ 'cmd.exe',
59
+ %W[/c "#{path}"]
60
+ ]
61
+ end
62
+ end
63
+
64
+ def escape_arguments(arguments)
65
+ arguments.map do |arg|
66
+ if arg =~ / /
67
+ "\"#{arg}\""
68
+ else
69
+ arg
70
+ end
71
+ end
72
+ end
73
+
74
+ def set_env(arg, val)
75
+ "[Environment]::SetEnvironmentVariable('#{arg}', @'\n#{val}\n'@)"
76
+ end
77
+
78
+ def quote_string(string)
79
+ "'" + string.gsub("'", "''") + "'"
80
+ end
81
+
82
+ def write_executable(dir, file, filename = nil)
83
+ filename ||= File.basename(file)
84
+ validate_extensions(File.extname(filename))
85
+ destination = "#{dir}\\#{filename}"
86
+ conn.copy_file(file, destination)
87
+ destination
88
+ end
89
+
90
+ def execute_process(path, arguments, stdin = nil)
91
+ quoted_args = arguments.map { |arg| quote_string(arg) }.join(' ')
92
+
93
+ quoted_path = if path =~ /^'.*'$/ || path =~ /^".*"$/
94
+ path
95
+ else
96
+ quote_string(path)
97
+ end
98
+ exec_cmd =
99
+ if stdin.nil?
100
+ "& #{quoted_path} #{quoted_args}"
101
+ else
102
+ <<~STR
103
+ $command_stdin = @'
104
+ #{stdin}
105
+ '@
106
+
107
+ $command_stdin | & #{quoted_path} #{quoted_args}
108
+ STR
109
+ end
110
+ Snippets.execute_process(exec_cmd)
111
+ end
112
+
113
+ def mkdirs(dirs)
114
+ mkdir_command = "mkdir -Force #{dirs.uniq.sort.join(',')}"
115
+ result = execute(mkdir_command)
116
+ if result.exit_code != 0
117
+ message = "Could not create directories: #{result.stderr.string}"
118
+ raise Bolt::Node::FileError.new(message, 'MKDIR_ERROR')
119
+ end
120
+ end
121
+
122
+ def make_tempdir
123
+ find_parent = target.options['tmpdir'] ? "\"#{target.options['tmpdir']}\"" : '[System.IO.Path]::GetTempPath()'
124
+ result = execute(Snippets.make_tempdir(find_parent))
125
+ if result.exit_code != 0
126
+ raise Bolt::Node::FileError.new("Could not make tempdir: #{result.stderr.string}", 'TEMPDIR_ERROR')
127
+ end
128
+ result.stdout.string.chomp
129
+ end
130
+
131
+ def rmdir(dir)
132
+ execute(Snippets.rmdir(dir))
133
+ end
134
+
135
+ def with_tempdir
136
+ dir = make_tempdir
137
+ yield dir
138
+ ensure
139
+ rmdir(dir)
140
+ end
141
+
142
+ def run_ps_task(task_path, arguments, input_method)
143
+ # NOTE: cannot redirect STDIN to a .ps1 script inside of PowerShell
144
+ # must create new powershell.exe process like other interpreters
145
+ # fortunately, using PS with stdin input_method should never happen
146
+ if input_method == 'powershell'
147
+ Snippets.ps_task(task_path, arguments)
148
+ else
149
+ Snippets.try_catch(task_path)
150
+ end
151
+ end
152
+
153
+ def upload(source, destination, _options = {})
154
+ conn.copy_file(source, destination)
155
+ Bolt::Result.for_upload(target, source, destination)
156
+ end
157
+
158
+ def run_command(command, _options = {})
159
+ output = execute(command)
160
+ Bolt::Result.for_command(target,
161
+ output.stdout.string,
162
+ output.stderr.string,
163
+ output.exit_code,
164
+ 'command', command)
165
+ end
166
+
167
+ def run_script(script, arguments, _options = {})
168
+ # unpack any Sensitive data
169
+ arguments = unwrap_sensitive_args(arguments)
170
+ with_tempdir do |dir|
171
+ script_path = write_executable(dir, script)
172
+ command = if powershell_file?(script_path)
173
+ Snippets.run_script(arguments, script_path)
174
+ else
175
+ path, args = *process_from_extension(script_path)
176
+ args += escape_arguments(arguments)
177
+ execute_process(path, args)
178
+ end
179
+ output = execute(command)
180
+ Bolt::Result.for_command(target,
181
+ output.stdout.string,
182
+ output.stderr.string,
183
+ output.exit_code,
184
+ 'script', script)
185
+ end
186
+ end
187
+
188
+ def run_task(task, arguments, _options = {})
189
+ implementation = select_implementation(target, task)
190
+ executable = implementation['path']
191
+ input_method = implementation['input_method']
192
+ extra_files = implementation['files']
193
+ input_method ||= powershell_file?(executable) ? 'powershell' : 'both'
194
+
195
+ # unpack any Sensitive data
196
+ arguments = unwrap_sensitive_args(arguments)
197
+ with_tempdir do |dir|
198
+ if extra_files.empty?
199
+ task_dir = dir
200
+ else
201
+ # TODO: optimize upload of directories
202
+ arguments['_installdir'] = dir
203
+ task_dir = File.join(dir, task.tasks_dir)
204
+ mkdirs([task_dir] + extra_files.map { |file| File.join(dir, File.dirname(file['name'])) })
205
+ extra_files.each do |file|
206
+ conn.copy_file(file['path'], File.join(dir, file['name']))
207
+ end
208
+ end
209
+
210
+ task_path = write_executable(task_dir, executable)
211
+
212
+ if Bolt::Task::STDIN_METHODS.include?(input_method)
213
+ stdin = JSON.dump(arguments)
214
+ end
215
+
216
+ command = if powershell_file?(task_path) && stdin.nil?
217
+ run_ps_task(task_path, arguments, input_method)
218
+ else
219
+ if (interpreter = select_interpreter(task_path, target.options['interpreters']))
220
+ path = interpreter
221
+ args = [task_path]
222
+ else
223
+ path, args = *process_from_extension(task_path)
224
+ end
225
+ execute_process(path, args, stdin)
226
+ end
227
+
228
+ env_assignments = if Bolt::Task::ENVIRONMENT_METHODS.include?(input_method)
229
+ envify_params(arguments).map do |(arg, val)|
230
+ set_env(arg, val)
231
+ end
232
+ else
233
+ []
234
+ end
235
+
236
+ output = execute([Snippets.shell_init, *env_assignments, command].join("\n"))
237
+
238
+ Bolt::Result.for_task(target, output.stdout.string,
239
+ output.stderr.string,
240
+ output.exit_code,
241
+ task.name)
242
+ end
243
+ end
244
+
245
+ def execute(command)
246
+ inp, out, err, t = conn.execute(command)
247
+
248
+ result = Bolt::Node::Output.new
249
+ inp.close
250
+ out.binmode
251
+ err.binmode
252
+ stdout = Thread.new { result.stdout << out.read }
253
+ stderr = Thread.new { result.stderr << err.read }
254
+
255
+ stdout.join
256
+ stderr.join
257
+ result.exit_code = t.value.respond_to?(:exitstatus) ? t.value.exitstatus : t.value
258
+
259
+ result
260
+ end
261
+ end
262
+ end
263
+ end