bolt 2.34.0 → 2.35.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/catch_errors.rb +1 -3
- data/bolt-modules/boltlib/lib/puppet/functions/download_file.rb +17 -6
- data/bolt-modules/boltlib/lib/puppet/functions/parallelize.rb +56 -0
- data/bolt-modules/boltlib/lib/puppet/functions/run_command.rb +24 -6
- data/bolt-modules/boltlib/lib/puppet/functions/run_script.rb +27 -8
- data/bolt-modules/boltlib/lib/puppet/functions/run_task.rb +21 -1
- data/bolt-modules/boltlib/lib/puppet/functions/run_task_with.rb +18 -1
- data/bolt-modules/boltlib/lib/puppet/functions/upload_file.rb +24 -6
- data/lib/bolt/cli.rb +9 -6
- data/lib/bolt/config/options.rb +4 -4
- data/lib/bolt/config/transport/options.rb +1 -1
- data/lib/bolt/error.rb +24 -0
- data/lib/bolt/executor.rb +87 -1
- data/lib/bolt/outputter/human.rb +2 -2
- data/lib/bolt/pal.rb +18 -6
- data/lib/bolt/pal/yaml_plan.rb +7 -0
- data/lib/bolt/puppetdb/client.rb +3 -2
- data/lib/bolt/puppetdb/config.rb +3 -1
- data/lib/bolt/transport/orch.rb +0 -5
- data/lib/bolt/transport/orch/connection.rb +10 -3
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt/yarn.rb +23 -0
- data/lib/bolt_server/transport_app.rb +38 -7
- data/lib/bolt_spec/plans/mock_executor.rb +4 -1
- 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: d05af3f780d500b91be547a73123d24c2da041a8ef60ba14114921358b120800
|
4
|
+
data.tar.gz: e8534835e69d1e5e57a353ff89a7d2667cf6d0894e8a07c1e5812282ed85b2cd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 97b3faac5a1e423dcebf8e4fa92f55bbc36bd1c9735fc38332db40a2bb0db0e1ddc8e1e815aa29f31f9eb3601972a8d6e07b39d0c5dc6cd2762ea87a91ddec92
|
7
|
+
data.tar.gz: 15963b1bb77ee04a1db4f54996964b32e599e4a0cc422dc77892151b221c1a17df9e6a1c74f160a30e9146402d48819b07b3b9d2ee9c96d7e763bba97389ccc8
|
@@ -44,9 +44,7 @@ Puppet::Functions.create_function(:catch_errors) do
|
|
44
44
|
yield
|
45
45
|
rescue Puppet::PreformattedError => e
|
46
46
|
if e.cause.is_a?(Bolt::Error)
|
47
|
-
if error_types.nil?
|
48
|
-
e.cause.to_puppet_error
|
49
|
-
elsif error_types.include?(e.cause.to_h['kind'])
|
47
|
+
if error_types.nil? || error_types.include?(e.cause.to_h['kind'])
|
50
48
|
e.cause.to_puppet_error
|
51
49
|
else
|
52
50
|
raise e
|
@@ -110,14 +110,25 @@ Puppet::Functions.create_function(:download_file, Puppet::Functions::InternalFun
|
|
110
110
|
targets = inventory.get_targets(targets)
|
111
111
|
if targets.empty?
|
112
112
|
call_function('debug', "Simulating file download of '#{source}' - no targets given - no action taken")
|
113
|
-
|
113
|
+
Bolt::ResultSet.new([])
|
114
114
|
else
|
115
|
-
r = executor.
|
116
|
-
|
115
|
+
r = if executor.in_parallel
|
116
|
+
require 'concurrent'
|
117
|
+
require 'fiber'
|
118
|
+
future = Concurrent::Future.execute do
|
119
|
+
executor.download_file(targets, source, destination, options, Puppet::Pops::PuppetStack.top_of_stack)
|
120
|
+
end
|
121
|
+
|
122
|
+
Fiber.yield('unfinished') while future.incomplete?
|
123
|
+
future.value || future.reason
|
124
|
+
else
|
125
|
+
executor.download_file(targets, source, destination, options, Puppet::Pops::PuppetStack.top_of_stack)
|
126
|
+
end
|
117
127
|
|
118
|
-
|
119
|
-
|
128
|
+
if !r.ok && !options[:catch_errors]
|
129
|
+
raise Bolt::RunFailure.new(r, 'download_file', source)
|
130
|
+
end
|
131
|
+
r
|
120
132
|
end
|
121
|
-
r
|
122
133
|
end
|
123
134
|
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bolt/yarn'
|
4
|
+
|
5
|
+
# Map a code block onto an array, where each array element executes in parallel.
|
6
|
+
# This function is experimental.
|
7
|
+
#
|
8
|
+
# > **Note:** Not available in apply block.
|
9
|
+
Puppet::Functions.create_function(:parallelize, Puppet::Functions::InternalFunction) do
|
10
|
+
# Map a block onto an array, where each array element executes in parallel.
|
11
|
+
# This function is experimental.
|
12
|
+
# @param data The array to apply the block to.
|
13
|
+
# @return [Array] An array of PlanResult objects. Each input from the input
|
14
|
+
# array returns a corresponding PlanResult object.
|
15
|
+
# @example Execute two tasks on multiple targets. Once the task finishes on one
|
16
|
+
# target, that target can move to the next step without waiting for the task
|
17
|
+
# to finish on the second target.
|
18
|
+
# $targets = get_targets(["host1", "host2"])
|
19
|
+
# $result = parallelize ($targets) |$t| {
|
20
|
+
# run_task('a', $t)
|
21
|
+
# run_task('b', $t)
|
22
|
+
# }
|
23
|
+
dispatch :parallelize do
|
24
|
+
scope_param
|
25
|
+
param 'Array[Any]', :data
|
26
|
+
block_param 'Callable[Any]', :block
|
27
|
+
return_type 'Array[Boltlib::PlanResult]'
|
28
|
+
end
|
29
|
+
|
30
|
+
def parallelize(scope, data, &block)
|
31
|
+
unless Puppet[:tasks]
|
32
|
+
raise Puppet::ParseErrorWithIssue
|
33
|
+
.from_issue_and_stack(Bolt::PAL::Issues::PLAN_OPERATION_NOT_SUPPORTED_WHEN_COMPILING, action: 'parallelize')
|
34
|
+
end
|
35
|
+
|
36
|
+
executor = Puppet.lookup(:bolt_executor)
|
37
|
+
executor.report_function_call(self.class.name)
|
38
|
+
|
39
|
+
skein = data.each_with_index.map do |object, index|
|
40
|
+
executor.create_yarn(scope, block, object, index)
|
41
|
+
end
|
42
|
+
|
43
|
+
result = executor.round_robin(skein)
|
44
|
+
|
45
|
+
failed_indices = result.each_index.select do |i|
|
46
|
+
result[i].is_a?(Bolt::Error)
|
47
|
+
end
|
48
|
+
|
49
|
+
# TODO: Inner catch errors block?
|
50
|
+
if failed_indices.any?
|
51
|
+
raise Bolt::ParallelFailure.new(result, failed_indices)
|
52
|
+
end
|
53
|
+
|
54
|
+
result
|
55
|
+
end
|
56
|
+
end
|
@@ -67,14 +67,32 @@ Puppet::Functions.create_function(:run_command) do
|
|
67
67
|
|
68
68
|
if targets.empty?
|
69
69
|
call_function('debug', "Simulating run_command('#{command}') - no targets given - no action taken")
|
70
|
-
|
70
|
+
Bolt::ResultSet.new([])
|
71
71
|
else
|
72
|
-
r = executor.
|
73
|
-
|
72
|
+
r = if executor.in_parallel
|
73
|
+
require 'concurrent'
|
74
|
+
require 'fiber'
|
75
|
+
future = Concurrent::Future.execute do
|
76
|
+
executor.run_command(targets,
|
77
|
+
command,
|
78
|
+
options,
|
79
|
+
Puppet::Pops::PuppetStack.top_of_stack)
|
80
|
+
end
|
81
|
+
|
82
|
+
Fiber.yield('unfinished') while future.incomplete?
|
83
|
+
future.value || future.reason
|
84
|
+
else
|
85
|
+
executor.run_command(targets,
|
86
|
+
command,
|
87
|
+
options,
|
88
|
+
Puppet::Pops::PuppetStack.top_of_stack)
|
89
|
+
end
|
90
|
+
|
91
|
+
if !r.ok && !options[:catch_errors]
|
92
|
+
raise Bolt::RunFailure.new(r, 'run_command', command)
|
93
|
+
end
|
74
94
|
|
75
|
-
|
76
|
-
raise Bolt::RunFailure.new(r, 'run_command', command)
|
95
|
+
r
|
77
96
|
end
|
78
|
-
r
|
79
97
|
end
|
80
98
|
end
|
@@ -84,15 +84,34 @@ Puppet::Functions.create_function(:run_script, Puppet::Functions::InternalFuncti
|
|
84
84
|
# Ensure that given targets are all Target instances)
|
85
85
|
targets = inventory.get_targets(targets)
|
86
86
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
87
|
+
if targets.empty?
|
88
|
+
Bolt::ResultSet.new([])
|
89
|
+
else
|
90
|
+
r = if executor.in_parallel
|
91
|
+
require 'concurrent'
|
92
|
+
require 'fiber'
|
93
|
+
future = Concurrent::Future.execute do
|
94
|
+
executor.run_script(targets,
|
95
|
+
found,
|
96
|
+
arguments,
|
97
|
+
options,
|
98
|
+
Puppet::Pops::PuppetStack.top_of_stack)
|
99
|
+
end
|
92
100
|
|
93
|
-
|
94
|
-
|
101
|
+
Fiber.yield('unfinished') while future.incomplete?
|
102
|
+
future.value || future.reason
|
103
|
+
else
|
104
|
+
executor.run_script(targets,
|
105
|
+
found,
|
106
|
+
arguments,
|
107
|
+
options,
|
108
|
+
Puppet::Pops::PuppetStack.top_of_stack)
|
109
|
+
end
|
110
|
+
|
111
|
+
if !r.ok && !options[:catch_errors]
|
112
|
+
raise Bolt::RunFailure.new(r, 'run_script', script)
|
113
|
+
end
|
114
|
+
r
|
95
115
|
end
|
96
|
-
r
|
97
116
|
end
|
98
117
|
end
|
@@ -133,7 +133,27 @@ Puppet::Functions.create_function(:run_task) do
|
|
133
133
|
if targets.empty?
|
134
134
|
Bolt::ResultSet.new([])
|
135
135
|
else
|
136
|
-
result = executor.
|
136
|
+
result = if executor.in_parallel
|
137
|
+
require 'concurrent'
|
138
|
+
require 'fiber'
|
139
|
+
future = Concurrent::Future.execute do
|
140
|
+
executor.run_task(targets,
|
141
|
+
task,
|
142
|
+
params,
|
143
|
+
options,
|
144
|
+
Puppet::Pops::PuppetStack.top_of_stack)
|
145
|
+
end
|
146
|
+
|
147
|
+
Fiber.yield('unfinished') while future.incomplete?
|
148
|
+
future.value || future.reason
|
149
|
+
else
|
150
|
+
executor.run_task(targets,
|
151
|
+
task,
|
152
|
+
params,
|
153
|
+
options,
|
154
|
+
Puppet::Pops::PuppetStack.top_of_stack)
|
155
|
+
end
|
156
|
+
|
137
157
|
if !result.ok && !options[:catch_errors]
|
138
158
|
raise Bolt::RunFailure.new(result, 'run_task', task_name)
|
139
159
|
end
|
@@ -180,7 +180,24 @@ Puppet::Functions.create_function(:run_task_with) do
|
|
180
180
|
else
|
181
181
|
# Combine the results from the task run with any failing results that were
|
182
182
|
# generated earlier when creating the target mapping
|
183
|
-
task_result = executor.
|
183
|
+
task_result = if executor.in_parallel
|
184
|
+
require 'concurrent'
|
185
|
+
require 'fiber'
|
186
|
+
future = Concurrent::Future.execute do
|
187
|
+
executor.run_task_with(target_mapping,
|
188
|
+
task,
|
189
|
+
options,
|
190
|
+
Puppet::Pops::PuppetStack.top_of_stack)
|
191
|
+
end
|
192
|
+
|
193
|
+
Fiber.yield('unfinished') while future.incomplete?
|
194
|
+
future.value || future.reason
|
195
|
+
else
|
196
|
+
executor.run_task_with(target_mapping,
|
197
|
+
task,
|
198
|
+
options,
|
199
|
+
Puppet::Pops::PuppetStack.top_of_stack)
|
200
|
+
end
|
184
201
|
result = Bolt::ResultSet.new(task_result.results + error_set)
|
185
202
|
|
186
203
|
if !result.ok && !options[:catch_errors]
|
@@ -81,14 +81,32 @@ Puppet::Functions.create_function(:upload_file, Puppet::Functions::InternalFunct
|
|
81
81
|
targets = inventory.get_targets(targets)
|
82
82
|
if targets.empty?
|
83
83
|
call_function('debug', "Simulating file upload of '#{found}' - no targets given - no action taken")
|
84
|
-
|
84
|
+
Bolt::ResultSet.new([])
|
85
85
|
else
|
86
|
-
r = executor.
|
87
|
-
|
86
|
+
r = if executor.in_parallel
|
87
|
+
require 'concurrent'
|
88
|
+
require 'fiber'
|
89
|
+
future = Concurrent::Future.execute do
|
90
|
+
executor.upload_file(targets,
|
91
|
+
found,
|
92
|
+
destination,
|
93
|
+
options,
|
94
|
+
Puppet::Pops::PuppetStack.top_of_stack)
|
95
|
+
end
|
88
96
|
|
89
|
-
|
90
|
-
|
97
|
+
Fiber.yield('unfinished') while future.incomplete?
|
98
|
+
future.value || future.reason
|
99
|
+
else
|
100
|
+
executor.upload_file(targets,
|
101
|
+
found,
|
102
|
+
destination,
|
103
|
+
options,
|
104
|
+
Puppet::Pops::PuppetStack.top_of_stack)
|
105
|
+
end
|
106
|
+
if !r.ok && !options[:catch_errors]
|
107
|
+
raise Bolt::RunFailure.new(r, 'upload_file', source)
|
108
|
+
end
|
109
|
+
r
|
91
110
|
end
|
92
|
-
r
|
93
111
|
end
|
94
112
|
end
|
data/lib/bolt/cli.rb
CHANGED
@@ -575,10 +575,15 @@ module Bolt
|
|
575
575
|
outputter.print_task_info(pal.get_task(task_name))
|
576
576
|
end
|
577
577
|
|
578
|
+
# Filters a list of content by matching substring.
|
579
|
+
#
|
580
|
+
private def filter_content(content, filter)
|
581
|
+
return content unless content && filter
|
582
|
+
content.select { |name,| name.include?(filter) }
|
583
|
+
end
|
584
|
+
|
578
585
|
def list_tasks
|
579
|
-
tasks = pal.list_tasks
|
580
|
-
tasks.select! { |task| task.first.include?(options[:filter]) } if options[:filter]
|
581
|
-
tasks.select! { |task| config.project.tasks.include?(task.first) } unless config.project.tasks.nil?
|
586
|
+
tasks = filter_content(pal.list_tasks(filter_content: true), options[:filter])
|
582
587
|
outputter.print_tasks(tasks, pal.user_modulepath)
|
583
588
|
end
|
584
589
|
|
@@ -587,9 +592,7 @@ module Bolt
|
|
587
592
|
end
|
588
593
|
|
589
594
|
def list_plans
|
590
|
-
plans = pal.list_plans
|
591
|
-
plans.select! { |plan| plan.first.include?(options[:filter]) } if options[:filter]
|
592
|
-
plans.select! { |plan| config.project.plans.include?(plan.first) } unless config.project.plans.nil?
|
595
|
+
plans = filter_content(pal.list_plans(filter_content: true), options[:filter])
|
593
596
|
outputter.print_plans(plans, pal.user_modulepath)
|
594
597
|
end
|
595
598
|
|
data/lib/bolt/config/options.rb
CHANGED
@@ -283,7 +283,7 @@ module Bolt
|
|
283
283
|
_example: "myproject"
|
284
284
|
},
|
285
285
|
"plans" => {
|
286
|
-
description: "A list of plan names
|
286
|
+
description: "A list of plan names and glob patterns to filter the project's plans by. This option is used "\
|
287
287
|
"to limit the visibility of plans for users of the project. For example, project authors "\
|
288
288
|
"might want to limit the visibility of plans that are bundled with Bolt or plans that should "\
|
289
289
|
"only be run as part of another plan. When this option is not configured, all plans are "\
|
@@ -291,7 +291,7 @@ module Bolt
|
|
291
291
|
"list.",
|
292
292
|
type: Array,
|
293
293
|
_plugin: false,
|
294
|
-
_example: ["myproject", "myproject::foo", "myproject::bar"]
|
294
|
+
_example: ["myproject", "myproject::foo", "myproject::bar", "myproject::deploy::*"]
|
295
295
|
},
|
296
296
|
"plugin_hooks" => {
|
297
297
|
description: "A map of [plugin hooks](writing_plugins.md#hooks) and which plugins a hook should use. "\
|
@@ -402,7 +402,7 @@ module Bolt
|
|
402
402
|
_default: true
|
403
403
|
},
|
404
404
|
"tasks" => {
|
405
|
-
description: "A list of task names
|
405
|
+
description: "A list of task names and glob patterns to filter the project's tasks by. This option is used "\
|
406
406
|
"to limit the visibility of tasks for users of the project. For example, project authors "\
|
407
407
|
"might want to limit the visibility of tasks that are bundled with Bolt or plans that should "\
|
408
408
|
"only be run as part of a larger workflow. When this option is not configured, all tasks "\
|
@@ -413,7 +413,7 @@ module Bolt
|
|
413
413
|
type: String
|
414
414
|
},
|
415
415
|
_plugin: false,
|
416
|
-
_example: ["myproject", "myproject::foo", "myproject::bar"]
|
416
|
+
_example: ["myproject", "myproject::foo", "myproject::bar", "myproject::deploy_*"]
|
417
417
|
},
|
418
418
|
"trusted-external-command" => {
|
419
419
|
description: "The path to an executable on the Bolt controller that can produce external trusted facts. "\
|
@@ -357,7 +357,7 @@ module Bolt
|
|
357
357
|
description: "The URL of the host used for API requests.",
|
358
358
|
format: "uri",
|
359
359
|
_plugin: true,
|
360
|
-
_example: "https://api.example.com
|
360
|
+
_example: "https://api.example.com:8143"
|
361
361
|
},
|
362
362
|
"shell-command" => {
|
363
363
|
type: String,
|
data/lib/bolt/error.rb
CHANGED
@@ -90,6 +90,20 @@ module Bolt
|
|
90
90
|
end
|
91
91
|
end
|
92
92
|
|
93
|
+
class ParallelFailure < Bolt::Error
|
94
|
+
def initialize(results, failed_indices)
|
95
|
+
details = {
|
96
|
+
'action' => 'parallelize',
|
97
|
+
'failed_indices' => failed_indices,
|
98
|
+
'results' => results
|
99
|
+
}
|
100
|
+
message = "Plan aborted: parallel block failed on #{failed_indices.length} target"
|
101
|
+
message += "s" unless failed_indices.length == 1
|
102
|
+
super(message, 'bolt/parallel-failure', details)
|
103
|
+
@error_code = 2
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
93
107
|
class PlanFailure < Error
|
94
108
|
def initialize(*args)
|
95
109
|
super(*args)
|
@@ -131,6 +145,16 @@ module Bolt
|
|
131
145
|
end
|
132
146
|
end
|
133
147
|
|
148
|
+
class InvalidParallelResult < Error
|
149
|
+
def initialize(result_str, file, line)
|
150
|
+
super("Parallel block returned an invalid result: #{result_str}",
|
151
|
+
'bolt/invalid-plan-result',
|
152
|
+
{ 'file' => file,
|
153
|
+
'line' => line,
|
154
|
+
'result_string' => result_str })
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
134
158
|
class ValidationError < Bolt::Error
|
135
159
|
def initialize(msg)
|
136
160
|
super(msg, 'bolt/validation-error')
|
data/lib/bolt/executor.rb
CHANGED
@@ -17,6 +17,7 @@ require 'bolt/transport/orch'
|
|
17
17
|
require 'bolt/transport/local'
|
18
18
|
require 'bolt/transport/docker'
|
19
19
|
require 'bolt/transport/remote'
|
20
|
+
require 'bolt/yarn'
|
20
21
|
|
21
22
|
module Bolt
|
22
23
|
TRANSPORTS = {
|
@@ -29,7 +30,7 @@ module Bolt
|
|
29
30
|
}.freeze
|
30
31
|
|
31
32
|
class Executor
|
32
|
-
attr_reader :noop, :transports
|
33
|
+
attr_reader :noop, :transports, :in_parallel
|
33
34
|
attr_accessor :run_as
|
34
35
|
|
35
36
|
def initialize(concurrency = 1,
|
@@ -60,6 +61,7 @@ module Bolt
|
|
60
61
|
|
61
62
|
@noop = noop
|
62
63
|
@run_as = nil
|
64
|
+
@in_parallel = false
|
63
65
|
@pool = if concurrency > 0
|
64
66
|
Concurrent::ThreadPoolExecutor.new(name: 'exec', max_threads: concurrency)
|
65
67
|
else
|
@@ -84,6 +86,14 @@ module Bolt
|
|
84
86
|
self
|
85
87
|
end
|
86
88
|
|
89
|
+
def unsubscribe(subscriber, types = nil)
|
90
|
+
if types.nil? || types.sort == @subscribers[subscriber]&.sort
|
91
|
+
@subscribers.delete(subscriber)
|
92
|
+
elsif @subscribers[subscriber].is_a?(Array)
|
93
|
+
@subscribers[subscriber] = @subscribers[subscriber] - types
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
87
97
|
def publish_event(event)
|
88
98
|
@subscribers.each do |subscriber, types|
|
89
99
|
# If types isn't set or if the subscriber is subscribed to
|
@@ -359,6 +369,82 @@ module Bolt
|
|
359
369
|
plan.call_by_name_with_scope(scope, params, true)
|
360
370
|
end
|
361
371
|
|
372
|
+
def create_yarn(scope, block, object, index)
|
373
|
+
fiber = Fiber.new do
|
374
|
+
# Create the new scope
|
375
|
+
newscope = Puppet::Parser::Scope.new(scope.compiler)
|
376
|
+
local = Puppet::Parser::Scope::LocalScope.new
|
377
|
+
|
378
|
+
# Compress the current scopes into a single vars hash to add to the new scope
|
379
|
+
current_scope = scope.effective_symtable(true)
|
380
|
+
until current_scope.nil?
|
381
|
+
current_scope.instance_variable_get(:@symbols)&.each_pair { |k, v| local[k] = v }
|
382
|
+
current_scope = current_scope.parent
|
383
|
+
end
|
384
|
+
newscope.push_ephemerals([local])
|
385
|
+
|
386
|
+
begin
|
387
|
+
result = catch(:return) do
|
388
|
+
args = { block.parameters[0][1].to_s => object }
|
389
|
+
block.closure.call_by_name_with_scope(newscope, args, true)
|
390
|
+
end
|
391
|
+
|
392
|
+
# If we got a return from the block, get it's value
|
393
|
+
# Otherwise the result is the last line from the block
|
394
|
+
result = result.value if result.is_a?(Puppet::Pops::Evaluator::Return)
|
395
|
+
|
396
|
+
# Validate the result is a PlanResult
|
397
|
+
unless Puppet::Pops::Types::TypeParser.singleton.parse('Boltlib::PlanResult').instance?(result)
|
398
|
+
raise Bolt::InvalidParallelResult.new(result.to_s, *Puppet::Pops::PuppetStack.top_of_stack)
|
399
|
+
end
|
400
|
+
|
401
|
+
result
|
402
|
+
rescue Puppet::PreformattedError => e
|
403
|
+
if e.cause.is_a?(Bolt::Error)
|
404
|
+
e.cause
|
405
|
+
else
|
406
|
+
raise e
|
407
|
+
end
|
408
|
+
end
|
409
|
+
end
|
410
|
+
|
411
|
+
Bolt::Yarn.new(fiber, index)
|
412
|
+
end
|
413
|
+
|
414
|
+
def handle_event(event)
|
415
|
+
case event[:type]
|
416
|
+
when :node_result
|
417
|
+
@thread_completed = true
|
418
|
+
end
|
419
|
+
end
|
420
|
+
|
421
|
+
def round_robin(skein)
|
422
|
+
subscribe(self, [:node_result])
|
423
|
+
results = Array.new(skein.length)
|
424
|
+
@in_parallel = true
|
425
|
+
|
426
|
+
until skein.empty?
|
427
|
+
@thread_completed = false
|
428
|
+
r = nil
|
429
|
+
|
430
|
+
skein.each do |yarn|
|
431
|
+
if yarn.alive?
|
432
|
+
r = yarn.resume
|
433
|
+
else
|
434
|
+
results[yarn.index] = yarn.value
|
435
|
+
skein.delete(yarn)
|
436
|
+
end
|
437
|
+
end
|
438
|
+
|
439
|
+
next unless r == 'unfinished'
|
440
|
+
sleep(0.1) until @thread_completed || skein.empty?
|
441
|
+
end
|
442
|
+
|
443
|
+
@in_parallel = false
|
444
|
+
unsubscribe(self, [:node_result])
|
445
|
+
results
|
446
|
+
end
|
447
|
+
|
362
448
|
class TimeoutError < RuntimeError; end
|
363
449
|
|
364
450
|
def wait_until_available(targets,
|
data/lib/bolt/outputter/human.rb
CHANGED
@@ -215,7 +215,7 @@ module Bolt
|
|
215
215
|
|
216
216
|
def print_tasks(tasks, modulepath)
|
217
217
|
command = Bolt::Util.powershell? ? 'Get-BoltTask -Task <TASK NAME>' : 'bolt task show <TASK NAME>'
|
218
|
-
print_table(tasks)
|
218
|
+
tasks.any? ? print_table(tasks) : print_message('No available tasks')
|
219
219
|
print_message("\nMODULEPATH:\n#{modulepath.join(File::PATH_SEPARATOR)}\n"\
|
220
220
|
"\nUse '#{command}' to view "\
|
221
221
|
"details and parameters for a specific task.")
|
@@ -299,7 +299,7 @@ module Bolt
|
|
299
299
|
|
300
300
|
def print_plans(plans, modulepath)
|
301
301
|
command = Bolt::Util.powershell? ? 'Get-BoltPlan -Name <PLAN NAME>' : 'bolt plan show <PLAN NAME>'
|
302
|
-
print_table(plans)
|
302
|
+
plans.any? ? print_table(plans) : print_message('No available plans')
|
303
303
|
print_message("\nMODULEPATH:\n#{modulepath.join(File::PATH_SEPARATOR)}\n"\
|
304
304
|
"\nUse '#{command}' to view "\
|
305
305
|
"details and parameters for a specific plan.")
|
data/lib/bolt/pal.rb
CHANGED
@@ -26,7 +26,7 @@ module Bolt
|
|
26
26
|
details[:line] = err.line if defined?(err.line)
|
27
27
|
details[:column] = err.pos if defined?(err.pos)
|
28
28
|
|
29
|
-
error.add_filelineno(details)
|
29
|
+
error.add_filelineno(details.compact)
|
30
30
|
error
|
31
31
|
end
|
32
32
|
|
@@ -286,15 +286,26 @@ module Bolt
|
|
286
286
|
raise Bolt::PAL::PALError, "Failed to parse manifest: #{e}"
|
287
287
|
end
|
288
288
|
|
289
|
-
|
289
|
+
# Filters content by a list of names and glob patterns specified in project
|
290
|
+
# configuration.
|
291
|
+
def filter_content(content, patterns)
|
292
|
+
return content unless content && patterns
|
293
|
+
|
294
|
+
content.select do |name,|
|
295
|
+
patterns.any? { |pattern| File.fnmatch?(pattern, name, File::FNM_EXTGLOB) }
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
def list_tasks(filter_content: false)
|
290
300
|
in_bolt_compiler do |compiler|
|
291
|
-
tasks = compiler.list_tasks
|
292
|
-
tasks.map(&:name).sort.each_with_object([]) do |task_name, data|
|
301
|
+
tasks = compiler.list_tasks.map(&:name).sort.each_with_object([]) do |task_name, data|
|
293
302
|
task_sig = compiler.task_signature(task_name)
|
294
303
|
unless task_sig.task_hash['metadata']['private']
|
295
304
|
data << [task_name, task_sig.task_hash['metadata']['description']]
|
296
305
|
end
|
297
306
|
end
|
307
|
+
|
308
|
+
filter_content ? filter_content(tasks, @project&.tasks) : tasks
|
298
309
|
end
|
299
310
|
end
|
300
311
|
|
@@ -346,14 +357,15 @@ module Bolt
|
|
346
357
|
Bolt::Task.from_task_signature(task)
|
347
358
|
end
|
348
359
|
|
349
|
-
def list_plans
|
360
|
+
def list_plans(filter_content: false)
|
350
361
|
in_bolt_compiler do |compiler|
|
351
362
|
errors = []
|
352
363
|
plans = compiler.list_plans(nil, errors).map { |plan| [plan.name] }.sort
|
353
364
|
errors.each do |error|
|
354
365
|
@logger.warn(error.details['original_error'])
|
355
366
|
end
|
356
|
-
|
367
|
+
|
368
|
+
filter_content ? filter_content(plans, @project&.plans) : plans
|
357
369
|
end
|
358
370
|
end
|
359
371
|
|
data/lib/bolt/pal/yaml_plan.rb
CHANGED
@@ -45,6 +45,13 @@ module Bolt
|
|
45
45
|
used_names = Set.new(@parameters.map(&:name))
|
46
46
|
|
47
47
|
@steps = plan['steps'].each_with_index.map do |step, index|
|
48
|
+
unless step.is_a?(Hash)
|
49
|
+
raise Bolt::Error.new(
|
50
|
+
"Parse error in step number #{index + 1}: Plan step must be an object with valid step keys.",
|
51
|
+
'bolt/invalid-plan'
|
52
|
+
)
|
53
|
+
end
|
54
|
+
|
48
55
|
# Step keys also aren't allowed to be code and neither is the value of "name"
|
49
56
|
stringified_step = Bolt::Util.walk_keys(step) { |key| stringify(key) }
|
50
57
|
stringified_step['name'] = stringify(stringified_step['name']) if stringified_step.key?('name')
|
data/lib/bolt/puppetdb/client.rb
CHANGED
@@ -2,7 +2,6 @@
|
|
2
2
|
|
3
3
|
require 'json'
|
4
4
|
require 'logging'
|
5
|
-
require 'uri'
|
6
5
|
|
7
6
|
module Bolt
|
8
7
|
module PuppetDB
|
@@ -108,13 +107,15 @@ module Bolt
|
|
108
107
|
end
|
109
108
|
|
110
109
|
def uri
|
110
|
+
require 'addressable/uri'
|
111
|
+
|
111
112
|
@current_url ||= (@config.server_urls - @bad_urls).first
|
112
113
|
unless @current_url
|
113
114
|
msg = "Failed to connect to all PuppetDB server_urls: #{@config.server_urls.to_a.join(', ')}."
|
114
115
|
raise Bolt::PuppetDBError, msg
|
115
116
|
end
|
116
117
|
|
117
|
-
uri = URI.parse(@current_url)
|
118
|
+
uri = Addressable::URI.parse(@current_url)
|
118
119
|
uri.port ||= 8081
|
119
120
|
uri
|
120
121
|
end
|
data/lib/bolt/puppetdb/config.rb
CHANGED
@@ -89,6 +89,8 @@ module Bolt
|
|
89
89
|
|
90
90
|
def uri
|
91
91
|
return @uri if @uri
|
92
|
+
require 'addressable/uri'
|
93
|
+
|
92
94
|
uri = case @settings['server_urls']
|
93
95
|
when String
|
94
96
|
@settings['server_urls']
|
@@ -100,7 +102,7 @@ module Bolt
|
|
100
102
|
raise Bolt::PuppetDBError, "server_urls must be a string or array"
|
101
103
|
end
|
102
104
|
|
103
|
-
@uri = URI.parse(uri)
|
105
|
+
@uri = Addressable::URI.parse(uri)
|
104
106
|
@uri.port ||= 8081
|
105
107
|
@uri
|
106
108
|
end
|
data/lib/bolt/transport/orch.rb
CHANGED
@@ -10,11 +10,6 @@ require 'bolt/transport/orch/connection'
|
|
10
10
|
module Bolt
|
11
11
|
module Transport
|
12
12
|
class Orch < Base
|
13
|
-
CONF_FILE = if ENV['HOME'].nil?
|
14
|
-
'/etc/puppetlabs/client-tools/orchestrator.conf'
|
15
|
-
else
|
16
|
-
File.expand_path('~/.puppetlabs/client-tools/orchestrator.conf')
|
17
|
-
end
|
18
13
|
BOLT_COMMAND_TASK = Struct.new(:name).new('bolt_shim::command').freeze
|
19
14
|
BOLT_SCRIPT_TASK = Struct.new(:name).new('bolt_shim::script').freeze
|
20
15
|
BOLT_UPLOAD_TASK = Struct.new(:name).new('bolt_shim::upload').freeze
|
@@ -17,13 +17,20 @@ module Bolt
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def initialize(opts, plan_context, logger)
|
20
|
+
require 'addressable/uri'
|
21
|
+
|
20
22
|
@logger = logger
|
21
23
|
@key = self.class.get_key(opts)
|
22
|
-
|
23
|
-
|
24
|
-
|
24
|
+
client_opts = opts.slice('token-file', 'cacert', 'job-poll-interval', 'job-poll-timeout')
|
25
|
+
|
26
|
+
if opts['service-url']
|
27
|
+
uri = Addressable::URI.parse(opts['service-url'])
|
28
|
+
uri&.port ||= 8143
|
29
|
+
client_opts['service-url'] = uri.to_s
|
25
30
|
end
|
31
|
+
|
26
32
|
client_opts['User-Agent'] = "Bolt/#{VERSION}"
|
33
|
+
|
27
34
|
%w[token-file cacert].each do |f|
|
28
35
|
client_opts[f] = File.expand_path(client_opts[f]) if client_opts[f]
|
29
36
|
end
|
data/lib/bolt/version.rb
CHANGED
data/lib/bolt/yarn.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'fiber'
|
4
|
+
|
5
|
+
module Bolt
|
6
|
+
class Yarn
|
7
|
+
attr_reader :fiber, :value, :index
|
8
|
+
|
9
|
+
def initialize(fiber, index)
|
10
|
+
@fiber = fiber
|
11
|
+
@index = index
|
12
|
+
@value = nil
|
13
|
+
end
|
14
|
+
|
15
|
+
def alive?
|
16
|
+
fiber.alive?
|
17
|
+
end
|
18
|
+
|
19
|
+
def resume
|
20
|
+
@value = fiber.resume
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -48,6 +48,10 @@ module BoltServer
|
|
48
48
|
# See the `orchestrator.bolt.codedir` tk config setting.
|
49
49
|
DEFAULT_BOLT_CODEDIR = '/opt/puppetlabs/server/data/orchestration-services/code'
|
50
50
|
|
51
|
+
MISSING_PROJECT_REF_RESPONSE = [
|
52
|
+
400, Bolt::ValidationError.new('`project_ref` is a required argument').to_json
|
53
|
+
].freeze
|
54
|
+
|
51
55
|
def initialize(config)
|
52
56
|
@config = config
|
53
57
|
@schemas = Hash[REQUEST_SCHEMAS.map do |basename|
|
@@ -261,13 +265,16 @@ module BoltServer
|
|
261
265
|
end
|
262
266
|
end
|
263
267
|
|
264
|
-
def
|
265
|
-
|
266
|
-
project_dir
|
267
|
-
|
268
|
+
def config_from_project(project_ref)
|
269
|
+
project_dir = File.join(@config['projects-dir'], project_ref)
|
270
|
+
raise Bolt::ValidationError, "`project_ref`: #{project_dir} does not exist" unless Dir.exist?(project_dir)
|
271
|
+
project = Bolt::Project.create_project(project_dir)
|
272
|
+
Bolt::Config.from_project(project, { log: { 'bolt-debug.log' => 'disable' } })
|
273
|
+
end
|
274
|
+
|
275
|
+
def in_bolt_project(project_ref)
|
268
276
|
@pal_mutex.synchronize do
|
269
|
-
|
270
|
-
bolt_config = Bolt::Config.from_project(project, { log: { 'bolt-debug.log' => 'disable' } })
|
277
|
+
bolt_config = config_from_project(project_ref)
|
271
278
|
modulepath_object = Bolt::Config::Modulepath.new(
|
272
279
|
bolt_config.modulepath,
|
273
280
|
boltlib_path: [PE_BOLTLIB_PATH, Bolt::Config::Modulepath::BOLTLIB_PATH]
|
@@ -508,6 +515,7 @@ module BoltServer
|
|
508
515
|
#
|
509
516
|
# @param project_ref [String] the project to fetch the plan from
|
510
517
|
get '/project_plans/:module_name/:plan_name' do
|
518
|
+
return MISSING_PROJECT_REF_RESPONSE if params['project_ref'].nil?
|
511
519
|
in_bolt_project(params['project_ref']) do |context|
|
512
520
|
plan_info = pe_plan_info(context[:pal], params[:module_name], params[:plan_name])
|
513
521
|
plan_info = allowed_helper(plan_info, context[:config].project.plans)
|
@@ -532,6 +540,7 @@ module BoltServer
|
|
532
540
|
#
|
533
541
|
# @param bolt_project_ref [String] the reference to the bolt-project directory to load task metadata from
|
534
542
|
get '/project_tasks/:module_name/:task_name' do
|
543
|
+
return MISSING_PROJECT_REF_RESPONSE if params['project_ref'].nil?
|
535
544
|
in_bolt_project(params['project_ref']) do |context|
|
536
545
|
ps_parameters = {
|
537
546
|
'project' => params['project_ref']
|
@@ -570,6 +579,7 @@ module BoltServer
|
|
570
579
|
#
|
571
580
|
# @param project_ref [String] the project to fetch the list of plans from
|
572
581
|
get '/project_plans' do
|
582
|
+
return MISSING_PROJECT_REF_RESPONSE if params['project_ref'].nil?
|
573
583
|
in_bolt_project(params['project_ref']) do |context|
|
574
584
|
plans_response = plan_list(context[:pal])
|
575
585
|
|
@@ -603,6 +613,7 @@ module BoltServer
|
|
603
613
|
#
|
604
614
|
# @param project_ref [String] the project to fetch the list of tasks from
|
605
615
|
get '/project_tasks' do
|
616
|
+
return MISSING_PROJECT_REF_RESPONSE if params['project_ref'].nil?
|
606
617
|
in_bolt_project(params['project_ref']) do |context|
|
607
618
|
tasks_response = task_list(context[:pal])
|
608
619
|
|
@@ -621,6 +632,7 @@ module BoltServer
|
|
621
632
|
#
|
622
633
|
# @param project_ref [String] the project_ref to fetch the file metadatas from
|
623
634
|
get '/project_file_metadatas/:module_name/*' do
|
635
|
+
return MISSING_PROJECT_REF_RESPONSE if params['project_ref'].nil?
|
624
636
|
in_bolt_project(params['project_ref']) do |context|
|
625
637
|
file = params[:splat].first
|
626
638
|
metadatas = file_metadatas(context[:pal], params[:module_name], file)
|
@@ -630,13 +642,32 @@ module BoltServer
|
|
630
642
|
[400, e.message]
|
631
643
|
end
|
632
644
|
|
645
|
+
# Returns a list of targets parsed from a Project inventory
|
646
|
+
#
|
647
|
+
# @param project_ref [String] the project_ref to compute the inventory from
|
648
|
+
get '/project_inventory_targets' do
|
649
|
+
return MISSING_PROJECT_REF_RESPONSE if params['project_ref'].nil?
|
650
|
+
bolt_config = config_from_project(params['project_ref'])
|
651
|
+
if bolt_config.inventoryfile && bolt_config.project.inventory_file.to_s != bolt_config.inventoryfile
|
652
|
+
raise Bolt::ValidationError, "Project inventory must be defined in the " \
|
653
|
+
"inventory.yaml file at the root of the project directory"
|
654
|
+
end
|
655
|
+
plugins = Bolt::Plugin.setup(bolt_config, nil)
|
656
|
+
inventory = Bolt::Inventory.from_config(bolt_config, plugins)
|
657
|
+
target_list = inventory.get_targets('all').map { |targ| targ.to_h.merge({ 'transport' => targ.transport }) }
|
658
|
+
|
659
|
+
[200, target_list.to_json]
|
660
|
+
rescue Bolt::Error => e
|
661
|
+
[500, e.to_json]
|
662
|
+
end
|
663
|
+
|
633
664
|
error 404 do
|
634
665
|
err = Bolt::Error.new("Could not find route #{request.path}",
|
635
666
|
'boltserver/not-found')
|
636
667
|
[404, err.to_json]
|
637
668
|
end
|
638
669
|
|
639
|
-
error
|
670
|
+
error StandardError do
|
640
671
|
e = env['sinatra.error']
|
641
672
|
err = Bolt::Error.new("500: Unknown error: #{e.message}",
|
642
673
|
'boltserver/server-error')
|
@@ -17,12 +17,13 @@ module BoltSpec
|
|
17
17
|
|
18
18
|
# Nothing on the executor is 'public'
|
19
19
|
class MockExecutor
|
20
|
-
attr_reader :noop, :error_message
|
20
|
+
attr_reader :noop, :error_message, :in_parallel
|
21
21
|
attr_accessor :run_as, :transport_features, :execute_any_plan
|
22
22
|
|
23
23
|
def initialize(modulepath)
|
24
24
|
@noop = false
|
25
25
|
@run_as = nil
|
26
|
+
@in_parallel = false
|
26
27
|
@error_message = nil
|
27
28
|
@allow_apply = false
|
28
29
|
@modulepath = [modulepath].flatten.map { |path| File.absolute_path(path) }
|
@@ -133,6 +134,7 @@ module BoltSpec
|
|
133
134
|
# through to another conditional statement
|
134
135
|
doub = @plan_doubles[plan_name] || @plan_doubles[:default]
|
135
136
|
|
137
|
+
# rubocop:disable Lint/DuplicateBranch
|
136
138
|
# High level:
|
137
139
|
# - If we've explicitly allowed execution of the plan (normally the main plan
|
138
140
|
# passed into BoltSpec::Plan::run_plan()), then execute it
|
@@ -165,6 +167,7 @@ module BoltSpec
|
|
165
167
|
@error_message = "Unexpected call to 'run_plan(#{plan_name}, #{params_str})'"
|
166
168
|
raise UnexpectedInvocation, @error_message
|
167
169
|
end
|
170
|
+
# rubocop:enable Lint/DuplicateBranch
|
168
171
|
result
|
169
172
|
end
|
170
173
|
|
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: 2.
|
4
|
+
version: 2.35.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Puppet
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-11-
|
11
|
+
date: 2020-11-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: addressable
|
@@ -404,6 +404,7 @@ files:
|
|
404
404
|
- bolt-modules/boltlib/lib/puppet/functions/get_resources.rb
|
405
405
|
- bolt-modules/boltlib/lib/puppet/functions/get_target.rb
|
406
406
|
- bolt-modules/boltlib/lib/puppet/functions/get_targets.rb
|
407
|
+
- bolt-modules/boltlib/lib/puppet/functions/parallelize.rb
|
407
408
|
- bolt-modules/boltlib/lib/puppet/functions/puppetdb_fact.rb
|
408
409
|
- bolt-modules/boltlib/lib/puppet/functions/puppetdb_query.rb
|
409
410
|
- bolt-modules/boltlib/lib/puppet/functions/remove_from_group.rb
|
@@ -555,6 +556,7 @@ files:
|
|
555
556
|
- lib/bolt/util.rb
|
556
557
|
- lib/bolt/util/puppet_log_level.rb
|
557
558
|
- lib/bolt/version.rb
|
559
|
+
- lib/bolt/yarn.rb
|
558
560
|
- lib/bolt_server/acl.rb
|
559
561
|
- lib/bolt_server/base_config.rb
|
560
562
|
- lib/bolt_server/config.rb
|