bolt 1.14.0 → 1.15.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of bolt might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/bolt-modules/boltlib/lib/puppet/functions/run_plan.rb +8 -1
- data/bolt-modules/system/lib/puppet/functions/system/env.rb +1 -1
- data/lib/bolt/analytics.rb +7 -1
- data/lib/bolt/applicator.rb +3 -1
- data/lib/bolt/apply_result.rb +1 -0
- data/lib/bolt/config.rb +2 -1
- data/lib/bolt/executor.rb +20 -2
- data/lib/bolt/inventory/group.rb +25 -3
- data/lib/bolt/notifier.rb +5 -4
- data/lib/bolt/pal.rb +10 -2
- data/lib/bolt/pal/yaml_plan.rb +163 -0
- data/lib/bolt/pal/yaml_plan/evaluator.rb +163 -0
- data/lib/bolt/pal/yaml_plan/loader.rb +86 -0
- data/lib/bolt/result.rb +12 -14
- data/lib/bolt/task.rb +15 -2
- data/lib/bolt/task/puppet_server.rb +9 -6
- data/lib/bolt/transport/docker.rb +3 -3
- data/lib/bolt/transport/docker/connection.rb +3 -1
- data/lib/bolt/transport/local.rb +11 -3
- data/lib/bolt/transport/orch.rb +17 -11
- data/lib/bolt/transport/remote.rb +2 -2
- data/lib/bolt/transport/ssh.rb +12 -3
- data/lib/bolt/transport/ssh/connection.rb +10 -11
- data/lib/bolt/transport/winrm.rb +12 -3
- data/lib/bolt/transport/winrm/connection.rb +3 -1
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt_server/file_cache.rb +4 -1
- data/lib/bolt_server/transport_app.rb +1 -1
- data/lib/bolt_spec/plans/action_stubs/command_stub.rb +1 -1
- data/lib/bolt_spec/plans/action_stubs/script_stub.rb +1 -1
- data/lib/bolt_spec/run.rb +62 -0
- data/lib/plan_executor/app.rb +3 -1
- metadata +7 -5
- data/lib/bolt/task/remote.rb +0 -25
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 271a7fea2ecaf024054a9ae2266bc352b19a9969300c882f9622341582585d48
|
4
|
+
data.tar.gz: d10c047f108f62efdfe1e6ac1a35bc7b3303332f0ca0bd50c55635857d41648e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7d6355d7246bfa1305c02e13edf5fd7873584b70306e505f48ee1470babf6aa282b9b5f141ee5af6703b60a589f937c7997110ad21a2d6705926835a8b42b415
|
7
|
+
data.tar.gz: 4a1e5f3bf454ce08b084c0310b98c71119305f3c34fbeca552dffe3f6029cc16e7c410e2490605a985552e9603d7a9e653509b8a71679f3b2bb5e0a56e13951d
|
@@ -59,6 +59,11 @@ Puppet::Functions.create_function(:run_plan, Puppet::Functions::InternalFunction
|
|
59
59
|
executor.run_as = run_as
|
60
60
|
end
|
61
61
|
|
62
|
+
closure = func.class.dispatcher.dispatchers[0]
|
63
|
+
if closure.model.is_a?(Bolt::PAL::YamlPlan)
|
64
|
+
executor.report_yaml_plan(closure.model.body)
|
65
|
+
end
|
66
|
+
|
62
67
|
# wrap plan execution in logging messages
|
63
68
|
executor.log_plan(plan_name) do
|
64
69
|
result = nil
|
@@ -66,7 +71,9 @@ Puppet::Functions.create_function(:run_plan, Puppet::Functions::InternalFunction
|
|
66
71
|
# If the plan does not throw :return by calling the return function it's result is
|
67
72
|
# undef/nil
|
68
73
|
result = catch(:return) do
|
69
|
-
|
74
|
+
scope.with_global_scope do |global_scope|
|
75
|
+
closure.call_by_name_with_scope(global_scope, params, true)
|
76
|
+
end
|
70
77
|
nil
|
71
78
|
end&.value
|
72
79
|
# Validate the result is a PlanResult
|
data/lib/bolt/analytics.rb
CHANGED
@@ -21,7 +21,9 @@ module Bolt
|
|
21
21
|
target_nodes: :cd4,
|
22
22
|
output_format: :cd5,
|
23
23
|
statement_count: :cd6,
|
24
|
-
resource_mean: :cd7
|
24
|
+
resource_mean: :cd7,
|
25
|
+
plan_steps: :cd8,
|
26
|
+
return_type: :cd9
|
25
27
|
}.freeze
|
26
28
|
|
27
29
|
def self.build_client
|
@@ -62,6 +64,10 @@ module Bolt
|
|
62
64
|
attr_reader :user_id
|
63
65
|
|
64
66
|
def initialize(user_id)
|
67
|
+
# lazy-load expensive gem code
|
68
|
+
require 'concurrent/configuration'
|
69
|
+
require 'concurrent/future'
|
70
|
+
|
65
71
|
@logger = Logging.logger[self]
|
66
72
|
@http = HTTPClient.new
|
67
73
|
@user_id = user_id
|
data/lib/bolt/applicator.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'base64'
|
4
|
-
require 'concurrent'
|
5
4
|
require 'find'
|
6
5
|
require 'json'
|
7
6
|
require 'logging'
|
@@ -15,6 +14,9 @@ require 'bolt/util/puppet_log_level'
|
|
15
14
|
module Bolt
|
16
15
|
class Applicator
|
17
16
|
def initialize(inventory, executor, modulepath, plugin_dirs, pdb_client, hiera_config, max_compiles)
|
17
|
+
# lazy-load expensive gem code
|
18
|
+
require 'concurrent'
|
19
|
+
|
18
20
|
@inventory = inventory
|
19
21
|
@executor = executor
|
20
22
|
@modulepath = modulepath
|
data/lib/bolt/apply_result.rb
CHANGED
data/lib/bolt/config.rb
CHANGED
data/lib/bolt/executor.rb
CHANGED
@@ -3,7 +3,6 @@
|
|
3
3
|
# Used for $ERROR_INFO. This *must* be capitalized!
|
4
4
|
require 'English'
|
5
5
|
require 'json'
|
6
|
-
require 'concurrent'
|
7
6
|
require 'logging'
|
8
7
|
require 'set'
|
9
8
|
require 'bolt/analytics'
|
@@ -25,10 +24,13 @@ module Bolt
|
|
25
24
|
noop = nil,
|
26
25
|
bundled_content: nil,
|
27
26
|
load_config: true)
|
27
|
+
|
28
|
+
# lazy-load expensive gem code
|
29
|
+
require 'concurrent'
|
30
|
+
|
28
31
|
@analytics = analytics
|
29
32
|
@bundled_content = bundled_content
|
30
33
|
@logger = Logging.logger[self]
|
31
|
-
@plan_logging = false
|
32
34
|
@load_config = load_config
|
33
35
|
|
34
36
|
@transports = Bolt::TRANSPORTS.each_with_object({}) do |(key, val), coll|
|
@@ -199,6 +201,22 @@ module Bolt
|
|
199
201
|
@analytics&.event('Apply', 'ast', data)
|
200
202
|
end
|
201
203
|
|
204
|
+
def report_yaml_plan(plan)
|
205
|
+
steps = plan.steps.count
|
206
|
+
return_type = case plan.return
|
207
|
+
when Bolt::PAL::YamlPlan::EvaluableString
|
208
|
+
'expression'
|
209
|
+
when nil
|
210
|
+
nil
|
211
|
+
else
|
212
|
+
'value'
|
213
|
+
end
|
214
|
+
|
215
|
+
@analytics&.event('Plan', 'yaml', plan_steps: steps, return_type: return_type)
|
216
|
+
rescue StandardError => e
|
217
|
+
@logger.debug { "Failed to submit analytics event: #{e.message}" }
|
218
|
+
end
|
219
|
+
|
202
220
|
def with_node_logging(description, batch)
|
203
221
|
@logger.info("#{description} on #{batch.map(&:uri)}")
|
204
222
|
result = yield
|
data/lib/bolt/inventory/group.rb
CHANGED
@@ -10,6 +10,11 @@ module Bolt
|
|
10
10
|
# Regex used to validate group names and target aliases.
|
11
11
|
NAME_REGEX = /\A[a-z0-9_][a-z0-9_-]*\Z/.freeze
|
12
12
|
|
13
|
+
DATA_KEYS = %w[name config facts vars features].freeze
|
14
|
+
NODE_KEYS = DATA_KEYS + ['alias']
|
15
|
+
GROUP_KEYS = DATA_KEYS + %w[groups nodes]
|
16
|
+
CONFIG_KEYS = Bolt::TRANSPORTS.keys.map(&:to_s) + ['transport']
|
17
|
+
|
13
18
|
def initialize(data)
|
14
19
|
@logger = Logging.logger[self]
|
15
20
|
|
@@ -20,11 +25,21 @@ module Bolt
|
|
20
25
|
raise ValidationError.new("Group name must be a String, not #{@name.inspect}", nil) unless @name.is_a?(String)
|
21
26
|
raise ValidationError.new("Invalid group name #{@name}", @name) unless @name =~ NAME_REGEX
|
22
27
|
|
28
|
+
unless (unexpected_keys = data.keys - GROUP_KEYS).empty?
|
29
|
+
msg = "Found unexpected key(s) #{unexpected_keys.join(', ')} in group #{@name}"
|
30
|
+
@logger.warn(msg)
|
31
|
+
end
|
32
|
+
|
23
33
|
@vars = fetch_value(data, 'vars', Hash)
|
24
34
|
@facts = fetch_value(data, 'facts', Hash)
|
25
35
|
@features = fetch_value(data, 'features', Array)
|
26
36
|
@config = fetch_value(data, 'config', Hash)
|
27
37
|
|
38
|
+
unless (unexpected_keys = @config.keys - CONFIG_KEYS).empty?
|
39
|
+
msg = "Found unexpected key(s) #{unexpected_keys.join(', ')} in config for group #{@name}"
|
40
|
+
@logger.warn(msg)
|
41
|
+
end
|
42
|
+
|
28
43
|
nodes = fetch_value(data, 'nodes', Array)
|
29
44
|
groups = fetch_value(data, 'groups', Array)
|
30
45
|
|
@@ -43,6 +58,16 @@ module Bolt
|
|
43
58
|
raise ValidationError.new("Node #{node} does not have a name", @name) unless node['name']
|
44
59
|
@nodes[node['name']] = node
|
45
60
|
|
61
|
+
unless (unexpected_keys = node.keys - NODE_KEYS).empty?
|
62
|
+
msg = "Found unexpected key(s) #{unexpected_keys.join(', ')} in node #{node['name']}"
|
63
|
+
@logger.warn(msg)
|
64
|
+
end
|
65
|
+
config_keys = node['config']&.keys || []
|
66
|
+
unless (unexpected_keys = config_keys - CONFIG_KEYS).empty?
|
67
|
+
msg = "Found unexpected key(s) #{unexpected_keys.join(', ')} in config for node #{node['name']}"
|
68
|
+
@logger.warn(msg)
|
69
|
+
end
|
70
|
+
|
46
71
|
next unless node.include?('alias')
|
47
72
|
|
48
73
|
aliases = node['alias']
|
@@ -67,9 +92,6 @@ module Bolt
|
|
67
92
|
@name_or_alias = nodes.select { |node| node.is_a?(String) }
|
68
93
|
|
69
94
|
@groups = groups.map { |g| Group.new(g) }
|
70
|
-
|
71
|
-
# this allows arbitrary info for the top level
|
72
|
-
@rest = data.reject { |k, _| %w[name nodes config groups vars facts features].include? k }
|
73
95
|
end
|
74
96
|
|
75
97
|
private def fetch_value(data, key, type)
|
data/lib/bolt/notifier.rb
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'concurrent'
|
4
|
-
|
5
3
|
module Bolt
|
6
4
|
class Notifier
|
7
|
-
def initialize(executor =
|
8
|
-
|
5
|
+
def initialize(executor = nil)
|
6
|
+
# lazy-load expensive gem code
|
7
|
+
require 'concurrent'
|
8
|
+
|
9
|
+
@executor = executor || Concurrent::SingleThreadExecutor.new
|
9
10
|
end
|
10
11
|
|
11
12
|
def notify(callback, event)
|
data/lib/bolt/pal.rb
CHANGED
@@ -81,6 +81,7 @@ module Bolt
|
|
81
81
|
|
82
82
|
require 'bolt/pal/logging'
|
83
83
|
require 'bolt/pal/issues'
|
84
|
+
require 'bolt/pal/yaml_plan/loader'
|
84
85
|
|
85
86
|
# Now that puppet is loaded we can include puppet mixins in data types
|
86
87
|
Bolt::ResultSet.include_iterable
|
@@ -103,7 +104,9 @@ module Bolt
|
|
103
104
|
pal.with_script_compiler do |compiler|
|
104
105
|
alias_types(compiler)
|
105
106
|
begin
|
106
|
-
|
107
|
+
Puppet.override(yaml_plan_instantiator: Bolt::PAL::YamlPlan::Loader) do
|
108
|
+
yield compiler
|
109
|
+
end
|
107
110
|
rescue Bolt::Error => err
|
108
111
|
err
|
109
112
|
rescue Puppet::PreformattedError => err
|
@@ -247,7 +250,12 @@ module Bolt
|
|
247
250
|
|
248
251
|
def list_plans
|
249
252
|
in_bolt_compiler do |compiler|
|
250
|
-
|
253
|
+
errors = []
|
254
|
+
plans = compiler.list_plans(nil, errors).map { |plan| [plan.name] }.sort
|
255
|
+
errors.each do |error|
|
256
|
+
@logger.warn(error.details['original_error'])
|
257
|
+
end
|
258
|
+
plans
|
251
259
|
end
|
252
260
|
end
|
253
261
|
|
@@ -0,0 +1,163 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bolt
|
4
|
+
class PAL
|
5
|
+
class YamlPlan
|
6
|
+
Parameter = Struct.new(:name, :value, :type_expr) do
|
7
|
+
def captures_rest
|
8
|
+
false
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :name, :parameters, :steps, :return
|
13
|
+
|
14
|
+
def initialize(name, plan)
|
15
|
+
# Top-level plan keys aren't allowed to be Puppet code, so force them
|
16
|
+
# all to strings.
|
17
|
+
plan = Bolt::Util.walk_keys(plan) { |key| stringify(key) }
|
18
|
+
|
19
|
+
@name = name.freeze
|
20
|
+
|
21
|
+
# Nothing in parameters is allowed to be code, since no variables are defined yet
|
22
|
+
params_hash = stringify(plan.fetch('parameters', {}))
|
23
|
+
|
24
|
+
# Munge parameters into an array of Parameter objects, which is what
|
25
|
+
# the Puppet API expects
|
26
|
+
@parameters = params_hash.map do |param, definition|
|
27
|
+
definition ||= {}
|
28
|
+
type = Puppet::Pops::Types::TypeParser.singleton.parse(definition['type']) if definition.key?('type')
|
29
|
+
Parameter.new(param, definition['default'], type)
|
30
|
+
end.freeze
|
31
|
+
|
32
|
+
@steps = plan['steps']&.map do |step|
|
33
|
+
# Step keys also aren't allowed to be code and neither is the value of "name"
|
34
|
+
stringified_step = Bolt::Util.walk_keys(step) { |key| stringify(key) }
|
35
|
+
stringified_step['name'] = stringify(stringified_step['name']) if stringified_step.key?('name')
|
36
|
+
stringified_step
|
37
|
+
end.freeze
|
38
|
+
|
39
|
+
@return = plan['return']
|
40
|
+
|
41
|
+
validate
|
42
|
+
end
|
43
|
+
|
44
|
+
VAR_NAME_PATTERN = /\A[a-z_][a-z0-9_]*\z/.freeze
|
45
|
+
|
46
|
+
def validate
|
47
|
+
unless @steps.is_a?(Array)
|
48
|
+
raise Bolt::Error.new("Plan must specify an array of steps", "bolt/invalid-plan")
|
49
|
+
end
|
50
|
+
|
51
|
+
used_names = Set.new
|
52
|
+
|
53
|
+
# Parameters come in a hash, so they must be unique
|
54
|
+
@parameters.each do |param|
|
55
|
+
unless param.name.is_a?(String) && param.name.match?(VAR_NAME_PATTERN)
|
56
|
+
raise Bolt::Error.new("Invalid parameter name #{param.name.inspect}", "bolt/invalid-plan")
|
57
|
+
end
|
58
|
+
|
59
|
+
used_names << param.name
|
60
|
+
end
|
61
|
+
|
62
|
+
@steps.each do |step|
|
63
|
+
next unless step.key?('name')
|
64
|
+
|
65
|
+
unless step['name'].is_a?(String) && step['name'].match?(VAR_NAME_PATTERN)
|
66
|
+
raise Bolt::Error.new("Invalid step name #{step['name'].inspect}", "bolt/invalid-plan")
|
67
|
+
end
|
68
|
+
|
69
|
+
if used_names.include?(step['name'])
|
70
|
+
msg = "Step name #{step['name'].inspect} matches an existing parameter or step name"
|
71
|
+
raise Bolt::Error.new(msg, "bolt/invalid-plan")
|
72
|
+
end
|
73
|
+
|
74
|
+
used_names << step['name']
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def body
|
79
|
+
self
|
80
|
+
end
|
81
|
+
|
82
|
+
# Turn all "potential" strings in the object into actual strings.
|
83
|
+
# Because we interpret bare strings as potential Puppet code, even in
|
84
|
+
# places where Puppet code isn't allowed (like some hash keys), we need
|
85
|
+
# to be able to force them back into regular strings, as if we had
|
86
|
+
# parsed them normally.
|
87
|
+
def stringify(value)
|
88
|
+
case value
|
89
|
+
when Array
|
90
|
+
value.map { |element| stringify(element) }
|
91
|
+
when Hash
|
92
|
+
value.each_with_object({}) do |(k, v), o|
|
93
|
+
o[stringify(k)] = stringify(v)
|
94
|
+
end
|
95
|
+
when EvaluableString
|
96
|
+
value.value
|
97
|
+
else
|
98
|
+
value
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def return_type
|
103
|
+
Puppet::Pops::Types::TypeParser.singleton.parse('Boltlib::PlanResult')
|
104
|
+
end
|
105
|
+
|
106
|
+
# This class wraps a value parsed from YAML which may be Puppet code.
|
107
|
+
# That includes double-quoted strings and string literals, each of which
|
108
|
+
# subclasses this parent class in order to implement its own evaluation
|
109
|
+
# logic.
|
110
|
+
class EvaluableString
|
111
|
+
attr_reader :value
|
112
|
+
def initialize(value)
|
113
|
+
@value = value
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# This class represents a double-quoted YAML string, which is interpreted
|
118
|
+
# as though it were a double-quoted Puppet string (with associated
|
119
|
+
# variable interpolations)
|
120
|
+
class DoubleQuotedString < EvaluableString
|
121
|
+
def evaluate(scope, evaluator)
|
122
|
+
# "inspect" allows us to get back a double-quoted string literal with
|
123
|
+
# special characters escaped. This is based on the assumption that
|
124
|
+
# YAML, Ruby and Puppet all support similar escape sequences.
|
125
|
+
parse_result = evaluator.parse_string(@value.inspect)
|
126
|
+
|
127
|
+
scope.with_local_scope({}) do
|
128
|
+
evaluator.evaluate(scope, parse_result)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# This represents a literal snippet of Puppet code
|
134
|
+
class CodeLiteral < EvaluableString
|
135
|
+
def evaluate(scope, evaluator)
|
136
|
+
parse_result = evaluator.parse_string(@value)
|
137
|
+
|
138
|
+
scope.with_local_scope({}) do
|
139
|
+
evaluator.evaluate(scope, parse_result)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# This class stores a bare YAML string, which is fuzzily interpreted as
|
145
|
+
# either Puppet code or a literal string, depending on whether it starts
|
146
|
+
# with a variable reference.
|
147
|
+
class BareString < EvaluableString
|
148
|
+
def evaluate(scope, evaluator)
|
149
|
+
if @value.start_with?('$')
|
150
|
+
# Try to parse the string as Puppet code. If it's invalid code,
|
151
|
+
# return the original string.
|
152
|
+
parse_result = evaluator.parse_string(@value)
|
153
|
+
scope.with_local_scope({}) do
|
154
|
+
evaluator.evaluate(scope, parse_result)
|
155
|
+
end
|
156
|
+
else
|
157
|
+
@value
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bolt/pal/yaml_plan'
|
4
|
+
|
5
|
+
module Bolt
|
6
|
+
class PAL
|
7
|
+
class YamlPlan
|
8
|
+
class Evaluator
|
9
|
+
def initialize(analytics = Bolt::Analytics::NoopClient.new)
|
10
|
+
@logger = Logging.logger[self]
|
11
|
+
@analytics = analytics
|
12
|
+
@evaluator = Puppet::Pops::Parser::EvaluatingParser.new
|
13
|
+
end
|
14
|
+
|
15
|
+
STEP_KEYS = %w[task command eval script source plan].freeze
|
16
|
+
|
17
|
+
def dispatch_step(scope, step)
|
18
|
+
step = evaluate_code_blocks(scope, step)
|
19
|
+
|
20
|
+
step_type, *extra_keys = STEP_KEYS.select { |key| step.key?(key) }
|
21
|
+
if !step_type || extra_keys.any?
|
22
|
+
unsupported_step(scope, step)
|
23
|
+
end
|
24
|
+
|
25
|
+
case step_type
|
26
|
+
when 'task'
|
27
|
+
task_step(scope, step)
|
28
|
+
when 'command'
|
29
|
+
command_step(scope, step)
|
30
|
+
when 'plan'
|
31
|
+
plan_step(scope, step)
|
32
|
+
when 'script'
|
33
|
+
script_step(scope, step)
|
34
|
+
when 'source'
|
35
|
+
upload_file_step(scope, step)
|
36
|
+
when 'eval'
|
37
|
+
eval_step(scope, step)
|
38
|
+
else
|
39
|
+
# This shouldn't be able to happen since this case statement should
|
40
|
+
# match the STEP_KEYS list, but raise an error *just in case*,
|
41
|
+
# instead of silently skipping the step.
|
42
|
+
unsupported_step(scope, step)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def task_step(scope, step)
|
47
|
+
task = step['task']
|
48
|
+
target = step['target']
|
49
|
+
description = step['description']
|
50
|
+
params = step['parameters'] || {}
|
51
|
+
raise "Can't run a task without specifying a target" unless target
|
52
|
+
|
53
|
+
args = if description
|
54
|
+
[task, target, description, params]
|
55
|
+
else
|
56
|
+
[task, target, params]
|
57
|
+
end
|
58
|
+
|
59
|
+
scope.call_function('run_task', args)
|
60
|
+
end
|
61
|
+
|
62
|
+
def plan_step(scope, step)
|
63
|
+
plan = step['plan']
|
64
|
+
parameters = step['parameters'] || {}
|
65
|
+
|
66
|
+
args = [plan, parameters]
|
67
|
+
|
68
|
+
scope.call_function('run_plan', args)
|
69
|
+
end
|
70
|
+
|
71
|
+
def script_step(scope, step)
|
72
|
+
script = step['script']
|
73
|
+
target = step['target']
|
74
|
+
description = step['description']
|
75
|
+
arguments = step['arguments'] || []
|
76
|
+
raise "Can't run a script without specifying a target" unless target
|
77
|
+
|
78
|
+
options = { 'arguments' => arguments }
|
79
|
+
args = if description
|
80
|
+
[script, target, description, options]
|
81
|
+
else
|
82
|
+
[script, target, options]
|
83
|
+
end
|
84
|
+
|
85
|
+
scope.call_function('run_script', args)
|
86
|
+
end
|
87
|
+
|
88
|
+
def command_step(scope, step)
|
89
|
+
command = step['command']
|
90
|
+
target = step['target']
|
91
|
+
description = step['description']
|
92
|
+
raise "Can't run a command without specifying a target" unless target
|
93
|
+
|
94
|
+
args = [command, target]
|
95
|
+
args << description if description
|
96
|
+
scope.call_function('run_command', args)
|
97
|
+
end
|
98
|
+
|
99
|
+
def upload_file_step(scope, step)
|
100
|
+
source = step['source']
|
101
|
+
destination = step['destination']
|
102
|
+
target = step['target']
|
103
|
+
description = step['description']
|
104
|
+
raise "Can't upload a file without specifying a target" unless target
|
105
|
+
raise "Can't upload a file without specifying a destination" unless destination
|
106
|
+
|
107
|
+
args = [source, destination, target]
|
108
|
+
args << description if description
|
109
|
+
scope.call_function('upload_file', args)
|
110
|
+
end
|
111
|
+
|
112
|
+
def eval_step(_scope, step)
|
113
|
+
step['eval']
|
114
|
+
end
|
115
|
+
|
116
|
+
def unsupported_step(_scope, step)
|
117
|
+
raise Bolt::Error.new("Unsupported plan step", "bolt/unsupported-step", step: step)
|
118
|
+
end
|
119
|
+
|
120
|
+
# This is the method that Puppet calls to evaluate the plan. The name
|
121
|
+
# makes more sense for .pp plans.
|
122
|
+
def evaluate_block_with_bindings(closure_scope, args_hash, plan)
|
123
|
+
plan_result = closure_scope.with_local_scope(args_hash) do |scope|
|
124
|
+
plan.steps.each do |step|
|
125
|
+
step_result = dispatch_step(scope, step)
|
126
|
+
|
127
|
+
scope.setvar(step['name'], step_result) if step.key?('name')
|
128
|
+
end
|
129
|
+
|
130
|
+
evaluate_code_blocks(scope, plan.return)
|
131
|
+
end
|
132
|
+
|
133
|
+
throw :return, Puppet::Pops::Evaluator::Return.new(plan_result, nil, nil)
|
134
|
+
end
|
135
|
+
|
136
|
+
# Recursively evaluate any EvaluableString instances in the object.
|
137
|
+
def evaluate_code_blocks(scope, value)
|
138
|
+
# XXX We should establish a local scope here probably
|
139
|
+
case value
|
140
|
+
when Array
|
141
|
+
value.map { |element| evaluate_code_blocks(scope, element) }
|
142
|
+
when Hash
|
143
|
+
value.each_with_object({}) do |(k, v), o|
|
144
|
+
key = k.is_a?(EvaluableString) ? k.value : k
|
145
|
+
o[key] = evaluate_code_blocks(scope, v)
|
146
|
+
end
|
147
|
+
when EvaluableString
|
148
|
+
value.evaluate(scope, @evaluator)
|
149
|
+
else
|
150
|
+
value
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
# Occasionally the Closure will ask us to evaluate what it assumes are
|
155
|
+
# AST objects. Because we've sidestepped the AST, they aren't, so just
|
156
|
+
# return the values as already evaluated.
|
157
|
+
def evaluate(value, _scope)
|
158
|
+
value
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|