bolt 2.44.0 → 3.4.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.

Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/Puppetfile +11 -9
  3. data/bolt-modules/boltlib/lib/puppet/functions/add_facts.rb +1 -1
  4. data/bolt-modules/boltlib/lib/puppet/functions/apply_prep.rb +25 -0
  5. data/bolt-modules/boltlib/lib/puppet/functions/run_command.rb +20 -2
  6. data/bolt-modules/boltlib/lib/puppet/functions/run_plan.rb +2 -2
  7. data/bolt-modules/boltlib/lib/puppet/functions/run_script.rb +44 -5
  8. data/bolt-modules/boltlib/lib/puppet/functions/upload_file.rb +1 -1
  9. data/bolt-modules/boltlib/lib/puppet/functions/wait_until_available.rb +7 -3
  10. data/bolt-modules/file/lib/puppet/functions/file/read.rb +3 -2
  11. data/bolt-modules/prompt/lib/puppet/functions/prompt.rb +20 -2
  12. data/bolt-modules/prompt/lib/puppet/functions/prompt/menu.rb +103 -0
  13. data/lib/bolt/apply_result.rb +1 -1
  14. data/lib/bolt/bolt_option_parser.rb +9 -123
  15. data/lib/bolt/cli.rb +125 -127
  16. data/lib/bolt/config.rb +39 -214
  17. data/lib/bolt/config/options.rb +34 -125
  18. data/lib/bolt/config/transport/local.rb +1 -0
  19. data/lib/bolt/config/transport/lxd.rb +23 -0
  20. data/lib/bolt/config/transport/options.rb +9 -2
  21. data/lib/bolt/executor.rb +20 -5
  22. data/lib/bolt/logger.rb +9 -1
  23. data/lib/bolt/module_installer.rb +2 -2
  24. data/lib/bolt/module_installer/puppetfile.rb +2 -2
  25. data/lib/bolt/module_installer/specs/forge_spec.rb +2 -2
  26. data/lib/bolt/module_installer/specs/git_spec.rb +2 -2
  27. data/lib/bolt/node/output.rb +14 -4
  28. data/lib/bolt/outputter/human.rb +52 -24
  29. data/lib/bolt/outputter/json.rb +16 -16
  30. data/lib/bolt/pal.rb +26 -5
  31. data/lib/bolt/pal/yaml_plan.rb +1 -2
  32. data/lib/bolt/pal/yaml_plan/evaluator.rb +5 -153
  33. data/lib/bolt/pal/yaml_plan/step.rb +91 -52
  34. data/lib/bolt/pal/yaml_plan/step/command.rb +21 -13
  35. data/lib/bolt/pal/yaml_plan/step/download.rb +15 -16
  36. data/lib/bolt/pal/yaml_plan/step/eval.rb +11 -11
  37. data/lib/bolt/pal/yaml_plan/step/message.rb +13 -4
  38. data/lib/bolt/pal/yaml_plan/step/plan.rb +19 -15
  39. data/lib/bolt/pal/yaml_plan/step/resources.rb +82 -21
  40. data/lib/bolt/pal/yaml_plan/step/script.rb +36 -17
  41. data/lib/bolt/pal/yaml_plan/step/task.rb +19 -16
  42. data/lib/bolt/pal/yaml_plan/step/upload.rb +16 -17
  43. data/lib/bolt/pal/yaml_plan/transpiler.rb +3 -3
  44. data/lib/bolt/plan_creator.rb +1 -1
  45. data/lib/bolt/plugin/module.rb +0 -23
  46. data/lib/bolt/plugin/puppet_connect_data.rb +45 -3
  47. data/lib/bolt/project.rb +16 -56
  48. data/lib/bolt/project_manager.rb +5 -4
  49. data/lib/bolt/project_manager/module_migrator.rb +7 -6
  50. data/lib/bolt/result.rb +10 -11
  51. data/lib/bolt/shell.rb +16 -0
  52. data/lib/bolt/shell/bash.rb +61 -31
  53. data/lib/bolt/shell/bash/tmpdir.rb +2 -2
  54. data/lib/bolt/shell/powershell.rb +35 -14
  55. data/lib/bolt/shell/powershell/snippets.rb +37 -150
  56. data/lib/bolt/task.rb +1 -1
  57. data/lib/bolt/transport/base.rb +0 -9
  58. data/lib/bolt/transport/docker.rb +1 -125
  59. data/lib/bolt/transport/docker/connection.rb +86 -161
  60. data/lib/bolt/transport/local.rb +1 -9
  61. data/lib/bolt/transport/lxd.rb +26 -0
  62. data/lib/bolt/transport/lxd/connection.rb +99 -0
  63. data/lib/bolt/transport/orch.rb +13 -5
  64. data/lib/bolt/transport/ssh/connection.rb +1 -1
  65. data/lib/bolt/transport/winrm/connection.rb +1 -1
  66. data/lib/bolt/util.rb +8 -0
  67. data/lib/bolt/version.rb +1 -1
  68. data/lib/bolt_server/transport_app.rb +61 -33
  69. data/lib/bolt_spec/bolt_context.rb +9 -4
  70. data/lib/bolt_spec/plans.rb +1 -109
  71. data/lib/bolt_spec/plans/action_stubs.rb +1 -1
  72. data/lib/bolt_spec/plans/action_stubs/command_stub.rb +8 -1
  73. data/lib/bolt_spec/plans/action_stubs/script_stub.rb +8 -1
  74. data/lib/bolt_spec/plans/mock_executor.rb +4 -0
  75. data/modules/aggregate/plans/count.pp +21 -0
  76. data/modules/aggregate/plans/targets.pp +21 -0
  77. data/modules/puppet_connect/plans/test_input_data.pp +67 -0
  78. data/modules/puppetdb_fact/plans/init.pp +10 -0
  79. metadata +7 -3
  80. data/modules/aggregate/plans/nodes.pp +0 -36
@@ -5,30 +5,38 @@ module Bolt
5
5
  class YamlPlan
6
6
  class Step
7
7
  class Command < Step
8
- def self.allowed_keys
9
- super + Set['command']
8
+ def self.option_keys
9
+ Set['catch_errors', 'env_vars', 'run_as']
10
10
  end
11
11
 
12
12
  def self.required_keys
13
- Set['targets']
13
+ Set['command', 'targets']
14
14
  end
15
15
 
16
- def initialize(step_body)
16
+ def self.validate_step_keys(body, number)
17
17
  super
18
- @command = step_body['command']
18
+
19
+ if body.key?('env_vars') && ![Hash, String].include?(body['env_vars'].class)
20
+ raise StepError.new('env_vars key must be a hash or evaluable string', body['name'], number)
21
+ end
19
22
  end
20
23
 
21
- def transpile
22
- code = String.new(" ")
23
- code << "$#{@name} = " if @name
24
+ # Returns an array of arguments to pass to the step's function call
25
+ #
26
+ private def format_args(body)
27
+ opts = format_options(body)
24
28
 
25
- fn = 'run_command'
26
- args = [@command, @targets]
27
- args << @description if @description
29
+ args = [body['command'], body['targets']]
30
+ args << body['description'] if body['description']
31
+ args << opts if opts.any?
28
32
 
29
- code << function_call(fn, args)
33
+ args
34
+ end
30
35
 
31
- code << "\n"
36
+ # Returns the function corresponding to the step
37
+ #
38
+ private def function
39
+ 'run_command'
32
40
  end
33
41
  end
34
42
  end
@@ -5,31 +5,30 @@ module Bolt
5
5
  class YamlPlan
6
6
  class Step
7
7
  class Download < Step
8
- def self.allowed_keys
9
- super + Set['download', 'destination']
8
+ def self.option_keys
9
+ Set['catch_errors', 'run_as']
10
10
  end
11
11
 
12
12
  def self.required_keys
13
13
  Set['download', 'destination', 'targets']
14
14
  end
15
15
 
16
- def initialize(step_body)
17
- super
18
- @source = step_body['download']
19
- @destination = step_body['destination']
20
- end
21
-
22
- def transpile
23
- code = String.new(" ")
24
- code << "$#{@name} = " if @name
16
+ # Returns an array of arguments to pass to the step's function call
17
+ #
18
+ private def format_args(body)
19
+ opts = format_options(body)
25
20
 
26
- fn = 'download_file'
27
- args = [@source, @destination, @targets]
28
- args << @description if @description
21
+ args = [body['download'], body['destination'], body['targets']]
22
+ args << body['description'] if body['description']
23
+ args << opts if opts.any?
29
24
 
30
- code << function_call(fn, args)
25
+ args
26
+ end
31
27
 
32
- code << "\n"
28
+ # Returns the function corresponding to the step
29
+ #
30
+ private def function
31
+ 'download_file'
33
32
  end
34
33
  end
35
34
  end
@@ -5,28 +5,28 @@ module Bolt
5
5
  class YamlPlan
6
6
  class Step
7
7
  class Eval < Step
8
- def self.allowed_keys
9
- super + Set['eval']
10
- end
11
-
12
8
  def self.required_keys
13
- Set.new
9
+ Set['eval']
14
10
  end
15
11
 
16
- def initialize(step_body)
17
- super
18
- @eval = step_body['eval']
12
+ # Evaluates the step
13
+ #
14
+ def evaluate(scope, evaluator)
15
+ evaluated = evaluator.evaluate_code_blocks(scope, body)
16
+ evaluated['eval']
19
17
  end
20
18
 
19
+ # Transpiles the step into the plan language
20
+ #
21
21
  def transpile
22
22
  code = String.new(" ")
23
- code << "$#{@name} = " if @name
23
+ code << "$#{body['name']} = " if body['name']
24
24
 
25
- code_body = Bolt::Util.to_code(@eval)
25
+ code_body = Bolt::Util.to_code(body['eval']) || 'undef'
26
26
 
27
27
  # If we're trying to assign the result of a multi-line eval to a name
28
28
  # variable, we need to wrap it in `with()`.
29
- if @name && code_body.lines.count > 1
29
+ if body['name'] && code_body.lines.count > 1
30
30
  indented = code_body.gsub(/\n/, "\n ").chomp(" ")
31
31
  code << "with() || {\n #{indented}}"
32
32
  else
@@ -13,14 +13,23 @@ module Bolt
13
13
  Set['message']
14
14
  end
15
15
 
16
- def initialize(step_body)
17
- super
18
- @message = step_body['message']
16
+ # Returns an array of arguments to pass to the step's function call
17
+ #
18
+ private def format_args(body)
19
+ [body['message']]
19
20
  end
20
21
 
22
+ # Returns the function corresponding to the step
23
+ #
24
+ private def function
25
+ 'out::message'
26
+ end
27
+
28
+ # Transpiles the step into the plan language
29
+ #
21
30
  def transpile
22
31
  code = String.new(" ")
23
- code << function_call('out::message', [@message])
32
+ code << function_call(function, format_args(body))
24
33
  code << "\n"
25
34
  end
26
35
  end
@@ -6,30 +6,34 @@ module Bolt
6
6
  class Step
7
7
  class Plan < Step
8
8
  def self.allowed_keys
9
- super + Set['plan', 'parameters']
9
+ super + Set['parameters']
10
10
  end
11
11
 
12
- def self.required_keys
13
- Set.new
12
+ def self.option_keys
13
+ Set['catch_errors', 'run_as']
14
14
  end
15
15
 
16
- def initialize(step_body)
17
- super
18
- @plan = step_body['plan']
19
- @parameters = step_body.fetch('parameters', {})
16
+ def self.required_keys
17
+ Set['plan']
20
18
  end
21
19
 
22
- def transpile
23
- code = String.new(" ")
24
- code << "$#{@name} = " if @name
20
+ # Returns an array of arguments to pass to the step's function call
21
+ #
22
+ private def format_args(body)
23
+ opts = format_options(body)
24
+ params = (body['parameters'] || {}).merge(opts)
25
25
 
26
- fn = 'run_plan'
27
- args = [@plan]
28
- args << @parameters unless @parameters.empty?
26
+ args = [body['plan']]
27
+ args << body['targets'] if body['targets']
28
+ args << params if params.any?
29
29
 
30
- code << function_call(fn, args)
30
+ args
31
+ end
31
32
 
32
- code << "\n"
33
+ # Returns the function corresponding to the step
34
+ #
35
+ private def function
36
+ 'run_plan'
33
37
  end
34
38
  end
35
39
  end
@@ -5,18 +5,81 @@ module Bolt
5
5
  class YamlPlan
6
6
  class Step
7
7
  class Resources < Step
8
- def self.allowed_keys
9
- super + Set['resources']
8
+ def self.option_keys
9
+ Set['catch_errors', 'description', 'noop', 'run_as']
10
10
  end
11
11
 
12
12
  def self.required_keys
13
- Set['targets']
13
+ Set['resources', 'targets']
14
14
  end
15
15
 
16
- def initialize(step_body)
16
+ def initialize(body)
17
17
  super
18
- @resources = step_body['resources']
19
- @normalized_resources = normalize_resources(@resources)
18
+ @body['resources'] = normalize_resources(@body['resources'])
19
+ end
20
+
21
+ # Returns an array of arguments to pass to the apply function call
22
+ #
23
+ private def format_args(body)
24
+ opts = format_options(body)
25
+
26
+ args = [body['targets']]
27
+ args << opts if opts.any?
28
+
29
+ args
30
+ end
31
+
32
+ def evaluate(scope, evaluator)
33
+ evaluated = evaluator.evaluate_code_blocks(scope, body)
34
+
35
+ scope.call_function('apply_prep', evaluated['targets'])
36
+
37
+ apply_args = format_args(evaluated)
38
+ manifest = generate_manifest(evaluated['resources'])
39
+ apply_manifest(scope, apply_args, manifest)
40
+ end
41
+
42
+ # Generates a manifest from the resources
43
+ #
44
+ private def generate_manifest(resources)
45
+ # inspect returns the Ruby representation of the resource hashes,
46
+ # which happens to be the same as the Puppet representation
47
+ puppet_resources = resources.inspect
48
+
49
+ # Because the :tasks setting globally controls which mode the parser
50
+ # is in, we need to make this snippet of non-tasks manifest code
51
+ # parseable in tasks mode. The way to do that is by putting it in an
52
+ # apply statement and taking the body.
53
+ <<~MANIFEST
54
+ apply('placeholder') {
55
+ $resources = #{puppet_resources}
56
+ $resources.each |$res| {
57
+ Resource[$res['type']] { $res['title']:
58
+ * => $res['parameters'],
59
+ }
60
+ }
61
+
62
+ # Add relationships if there is more than one resource
63
+ if $resources.length > 1 {
64
+ ($resources.length - 1).each |$index| {
65
+ $lhs = $resources[$index]
66
+ $rhs = $resources[$index+1]
67
+ $lhs_resource = Resource[$lhs['type'] , $lhs['title']]
68
+ $rhs_resource = Resource[$rhs['type'] , $rhs['title']]
69
+ $lhs_resource -> $rhs_resource
70
+ }
71
+ }
72
+ }
73
+ MANIFEST
74
+ end
75
+
76
+ # Applies the manifest block on the targets
77
+ #
78
+ private def apply_manifest(scope, args, manifest)
79
+ ast = self.class.parse_code_string(manifest)
80
+ apply_block = ast.body.body
81
+ applicator = Puppet.lookup(:apply_executor)
82
+ applicator.apply(args, apply_block, scope)
20
83
  end
21
84
 
22
85
  def self.validate(body, step_number)
@@ -26,26 +89,28 @@ module Bolt
26
89
  if resource['type'] || resource['title']
27
90
  if !resource['type']
28
91
  err = "Resource declaration must include type key if title key is set"
29
- raise step_error(err, body['name'], step_number)
92
+ raise StepError.new(err, body['name'], step_number)
30
93
  elsif !resource['title']
31
94
  err = "Resource declaration must include title key if type key is set"
32
- raise step_error(err, body['name'], step_number)
95
+ raise StepError.new(err, body['name'], step_number)
33
96
  end
34
97
  else
35
98
  type_keys = (resource.keys - ['parameters'])
36
99
  if type_keys.empty?
37
100
  err = "Resource declaration is missing a type"
38
- raise step_error(err, body['name'], step_number)
101
+ raise StepError.new(err, body['name'], step_number)
39
102
  elsif type_keys.length > 1
40
103
  err = "Resource declaration has ambiguous type: could be #{type_keys.join(' or ')}"
41
- raise step_error(err, body['name'], step_number)
104
+ raise StepError.new(err, body['name'], step_number)
42
105
  end
43
106
  end
44
107
  end
45
108
  end
46
109
 
110
+ # Normalizes the resources so they are in a format compatible with apply blocks
47
111
  # What if this comes from a code block?
48
- def normalize_resources(resources)
112
+ #
113
+ private def normalize_resources(resources)
49
114
  resources.map do |resource|
50
115
  if resource['type'] && resource['title']
51
116
  type = resource['type']
@@ -59,25 +124,21 @@ module Bolt
59
124
  end
60
125
  end
61
126
 
62
- def body
63
- @body.merge('resources' => @normalized_resources)
64
- end
65
-
66
127
  def transpile
67
128
  code = StringIO.new
68
129
 
69
130
  code.print " "
70
- fn = 'apply_prep'
71
- args = [@targets]
72
- code << function_call(fn, args)
131
+ code << function_call('apply_prep', [body['targets']])
73
132
  code.print "\n"
74
133
 
75
134
  code.print " "
76
- code.print "$#{@name} = " if @name
135
+ code.print "$#{body['name']} = " if body['name']
136
+
137
+ code << function_call('apply', format_args(body))
77
138
 
78
- code.puts "apply(#{Bolt::Util.to_code(@targets)}) {"
139
+ code.print " {\n"
79
140
 
80
- declarations = @normalized_resources.map do |resource|
141
+ declarations = body['resources'].map do |resource|
81
142
  type = resource['type'].is_a?(EvaluableString) ? resource['type'].value : resource['type']
82
143
  title = Bolt::Util.to_code(resource['title'])
83
144
  parameters = resource['parameters'].transform_values do |val|
@@ -6,35 +6,54 @@ module Bolt
6
6
  class Step
7
7
  class Script < Step
8
8
  def self.allowed_keys
9
- super + Set['script', 'parameters', 'arguments']
9
+ super + Set['arguments', 'pwsh_params']
10
+ end
11
+
12
+ def self.option_keys
13
+ Set['catch_errors', 'env_vars', 'run_as']
10
14
  end
11
15
 
12
16
  def self.required_keys
13
- Set['targets']
17
+ Set['script', 'targets']
14
18
  end
15
19
 
16
- def initialize(step_body)
20
+ def self.validate_step_keys(body, number)
17
21
  super
18
- @script = step_body['script']
19
- @parameters = step_body.fetch('parameters', {})
20
- @arguments = step_body.fetch('arguments', [])
22
+
23
+ if body.key?('arguments') && !body['arguments'].nil? && !body['arguments'].is_a?(Array)
24
+ raise StepError.new('arguments key must be an array', body['name'], number)
25
+ end
26
+
27
+ if body.key?('pwsh_params') && !body['pwsh_params'].nil? && !body['pwsh_params'].is_a?(Hash)
28
+ raise StepError.new('pwsh_params key must be a hash', body['name'], number)
29
+ end
30
+
31
+ if body.key?('env_vars') && ![Hash, String].include?(body['env_vars'].class)
32
+ raise StepError.new('env_vars key must be a hash or evaluable string', body['name'], number)
33
+ end
21
34
  end
22
35
 
23
- def transpile
24
- code = String.new(" ")
25
- code << "$#{@name} = " if @name
36
+ # Returns an array of arguments to pass to the step's function call
37
+ #
38
+ private def format_args(body)
39
+ args = body['arguments'] || []
40
+ pwsh_params = body['pwsh_params'] || {}
26
41
 
27
- options = @parameters.dup
28
- options['arguments'] = @arguments unless @arguments.empty?
42
+ opts = format_options(body)
43
+ opts = opts.merge('arguments' => args) if args.any?
44
+ opts = opts.merge('pwsh_params' => pwsh_params) if pwsh_params.any?
29
45
 
30
- fn = 'run_script'
31
- args = [@script, @targets]
32
- args << @description if @description
33
- args << options unless options.empty?
46
+ args = [body['script'], body['targets']]
47
+ args << body['description'] if body['description']
48
+ args << opts if opts.any?
34
49
 
35
- code << function_call(fn, args)
50
+ args
51
+ end
36
52
 
37
- code << "\n"
53
+ # Returns the function corresponding to the step
54
+ #
55
+ private def function
56
+ 'run_script'
38
57
  end
39
58
  end
40
59
  end
@@ -6,31 +6,34 @@ module Bolt
6
6
  class Step
7
7
  class Task < Step
8
8
  def self.allowed_keys
9
- super + Set['task', 'parameters']
9
+ super + Set['parameters']
10
10
  end
11
11
 
12
- def self.required_keys
13
- Set['targets']
12
+ def self.option_keys
13
+ Set['catch_errors', 'noop', 'run_as']
14
14
  end
15
15
 
16
- def initialize(step_body)
17
- super
18
- @task = step_body['task']
19
- @parameters = step_body.fetch('parameters', {})
16
+ def self.required_keys
17
+ Set['targets', 'task']
20
18
  end
21
19
 
22
- def transpile
23
- code = String.new(" ")
24
- code << "$#{@name} = " if @name
20
+ # Returns an array of arguments to pass to the step's function call
21
+ #
22
+ private def format_args(body)
23
+ opts = format_options(body)
24
+ params = (body['parameters'] || {}).merge(opts)
25
25
 
26
- fn = 'run_task'
27
- args = [@task, @targets]
28
- args << @description if @description
29
- args << @parameters unless @parameters.empty?
26
+ args = [body['task'], body['targets']]
27
+ args << body['description'] if body['description']
28
+ args << params if params.any?
30
29
 
31
- code << function_call(fn, args)
30
+ args
31
+ end
32
32
 
33
- code << "\n"
33
+ # Returns the function corresponding to the step
34
+ #
35
+ private def function
36
+ 'run_task'
34
37
  end
35
38
  end
36
39
  end