bolt 1.10.0 → 1.11.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/lib/bolt/cli.rb +2 -6
- data/lib/bolt/outputter/human.rb +14 -0
- data/lib/bolt/outputter/json.rb +8 -0
- data/lib/bolt/pal.rb +4 -0
- data/lib/bolt/transport/orch.rb +2 -1
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt_server/base_config.rb +6 -1
- data/lib/plan_executor/app.rb +20 -12
- data/lib/plan_executor/config.rb +5 -1
- data/lib/plan_executor/executor.rb +21 -48
- data/lib/plan_executor/orch_client.rb +211 -0
- metadata +3 -4
- data/lib/bolt/transport/api.rb +0 -28
- data/lib/bolt/transport/api/connection.rb +0 -70
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 29c062ea69471f56b51b9cef6ea2a3ba820773161753a13145879b7b941d2a32
|
4
|
+
data.tar.gz: 0f237d5aa389dee5e8a908f4fd52268d9fd397af25894a25adeeff0bc96d2ccc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 79a0c7e11a5998175df643ed639f197f3e05c78895713562b220338b8c0df5f2fa1a506d5d4a7e063b38e314d4c046816cf7d4fd084296f313b3193b04b91c12
|
7
|
+
data.tar.gz: b92c3bb6f4e0fcc95895d22907c7c98e7cf5b4fef4385354dfd6a53c09817306d6d99aed1266beb1c0a65cbfb804567599c29c69a08790c3ba1d04ac5e3c7392
|
data/lib/bolt/cli.rb
CHANGED
@@ -343,9 +343,7 @@ module Bolt
|
|
343
343
|
end
|
344
344
|
|
345
345
|
def list_tasks
|
346
|
-
outputter.
|
347
|
-
outputter.print_message("\nUse `bolt task show <task-name>` to view "\
|
348
|
-
"details and parameters for a specific task.")
|
346
|
+
outputter.print_tasks(pal.list_tasks, pal.list_modulepath)
|
349
347
|
end
|
350
348
|
|
351
349
|
def show_plan(plan_name)
|
@@ -353,9 +351,7 @@ module Bolt
|
|
353
351
|
end
|
354
352
|
|
355
353
|
def list_plans
|
356
|
-
outputter.
|
357
|
-
outputter.print_message("\nUse `bolt plan show <plan-name>` to view "\
|
358
|
-
"details and parameters for a specific plan.")
|
354
|
+
outputter.print_plans(pal.list_plans, pal.list_modulepath)
|
359
355
|
end
|
360
356
|
|
361
357
|
def run_plan(plan_name, plan_arguments, nodes, options)
|
data/lib/bolt/outputter/human.rb
CHANGED
@@ -115,6 +115,13 @@ module Bolt
|
|
115
115
|
)
|
116
116
|
end
|
117
117
|
|
118
|
+
def print_tasks(tasks, modulepath)
|
119
|
+
print_table(tasks)
|
120
|
+
print_message("\nMODULEPATH:\n#{modulepath.join(':')}\n"\
|
121
|
+
"\nUse `bolt task show <task-name>` to view "\
|
122
|
+
"details and parameters for a specific task.")
|
123
|
+
end
|
124
|
+
|
118
125
|
# @param [Hash] task A hash representing the task
|
119
126
|
def print_task_info(task)
|
120
127
|
# Building lots of strings...
|
@@ -177,6 +184,13 @@ module Bolt
|
|
177
184
|
@stream.puts(plan_info)
|
178
185
|
end
|
179
186
|
|
187
|
+
def print_plans(plans, modulepath)
|
188
|
+
print_table(plans)
|
189
|
+
print_message("\nMODULEPATH:\n#{modulepath.join(':')}\n"\
|
190
|
+
"\nUse `bolt plan show <plan-name>` to view "\
|
191
|
+
"details and parameters for a specific plan.")
|
192
|
+
end
|
193
|
+
|
180
194
|
# @param [Bolt::ResultSet] apply_result A ResultSet object representing the result of a `bolt apply`
|
181
195
|
def print_apply_result(apply_result)
|
182
196
|
apply_result.each { |result| print_result(result) }
|
data/lib/bolt/outputter/json.rb
CHANGED
@@ -53,6 +53,10 @@ module Bolt
|
|
53
53
|
@stream.puts task.to_json
|
54
54
|
end
|
55
55
|
|
56
|
+
def print_tasks(tasks, modulepath)
|
57
|
+
print_table('tasks' => tasks, 'modulepath' => modulepath)
|
58
|
+
end
|
59
|
+
|
56
60
|
def print_plan_info(plan)
|
57
61
|
path = plan.delete('module')
|
58
62
|
plan['module_dir'] = if path.start_with?(Bolt::PAL::MODULES_PATH)
|
@@ -63,6 +67,10 @@ module Bolt
|
|
63
67
|
@stream.puts plan.to_json
|
64
68
|
end
|
65
69
|
|
70
|
+
def print_plans(plans, modulepath)
|
71
|
+
print_table('plans' => plans, 'modulepath' => modulepath)
|
72
|
+
end
|
73
|
+
|
66
74
|
def print_apply_result(apply_result)
|
67
75
|
@stream.puts apply_result.to_json
|
68
76
|
end
|
data/lib/bolt/pal.rb
CHANGED
data/lib/bolt/transport/orch.rb
CHANGED
@@ -110,7 +110,8 @@ module Bolt
|
|
110
110
|
content = Base64.encode64(content)
|
111
111
|
params = {
|
112
112
|
'content' => content,
|
113
|
-
'arguments' => arguments
|
113
|
+
'arguments' => arguments,
|
114
|
+
'name' => Pathname(script).basename.to_s
|
114
115
|
}
|
115
116
|
callback ||= proc {}
|
116
117
|
results = run_task_job(targets, BOLT_SCRIPT_TASK, params, options, &callback)
|
data/lib/bolt/version.rb
CHANGED
@@ -76,7 +76,12 @@ module BoltServer
|
|
76
76
|
|
77
77
|
def validate
|
78
78
|
required_keys.each do |k|
|
79
|
-
|
79
|
+
# Handled nested config
|
80
|
+
if k.is_a?(Array)
|
81
|
+
next unless @data.dig(*k).nil?
|
82
|
+
else
|
83
|
+
next unless @data[k].nil?
|
84
|
+
end
|
80
85
|
raise Bolt::ValidationError, "You must configure #{k} in #{@config_path}"
|
81
86
|
end
|
82
87
|
|
data/lib/plan_executor/app.rb
CHANGED
@@ -3,10 +3,10 @@
|
|
3
3
|
require 'sinatra'
|
4
4
|
require 'bolt'
|
5
5
|
require 'bolt/error'
|
6
|
-
require 'bolt/executor'
|
7
6
|
require 'bolt/inventory'
|
8
7
|
require 'bolt/pal'
|
9
8
|
require 'bolt/puppetdb'
|
9
|
+
require 'bolt/version'
|
10
10
|
require 'plan_executor/applicator'
|
11
11
|
require 'plan_executor/executor'
|
12
12
|
require 'concurrent'
|
@@ -27,18 +27,24 @@ module PlanExecutor
|
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
30
|
-
def initialize(
|
31
|
-
|
32
|
-
|
30
|
+
def initialize(config)
|
31
|
+
conn_opts = { 'service-url' => config['orchestrator-url'],
|
32
|
+
'cacert' => config['ssl-ca-cert'],
|
33
|
+
'User-Agent' => "Bolt/#{Bolt::VERSION}" }
|
34
|
+
@client = OrchestratorClient.new(conn_opts, false)
|
33
35
|
|
34
|
-
# Create a basic executor, leave concurrency up to Orchestrator.
|
35
|
-
@executor = executor || PlanExecutor::Executor.new(0)
|
36
36
|
# Use an empty inventory until we figure out where this data comes from.
|
37
37
|
@inventory = Bolt::Inventory.new(nil)
|
38
|
-
# TODO: what should max compiles be set to for apply?
|
39
|
-
@pal = Bolt::PAL.new(modulepath, nil)
|
40
38
|
|
41
|
-
|
39
|
+
# PAL is not threadsafe. Part of the work of making the plan executor
|
40
|
+
# functional will be making changes to Puppet that remove the need for
|
41
|
+
# global Puppet state.
|
42
|
+
# https://github.com/puppetlabs/bolt/blob/master/lib/bolt/pal.rb#L166
|
43
|
+
@pal = Bolt::PAL.new(config['modulepath'], nil)
|
44
|
+
|
45
|
+
@schema = JSON.parse(File.read(File.join(__dir__, 'schemas', 'run_plan.json')))
|
46
|
+
@worker = Concurrent::SingleThreadExecutor.new
|
47
|
+
@modulepath = config['modulepath']
|
42
48
|
|
43
49
|
super(nil)
|
44
50
|
end
|
@@ -77,16 +83,18 @@ module PlanExecutor
|
|
77
83
|
body = JSON.parse(request.body.read)
|
78
84
|
error = validate_schema(@schema, body)
|
79
85
|
return [400, error.to_json] unless error.nil?
|
80
|
-
|
81
86
|
name = body['plan_name']
|
82
87
|
# Errors if plan is not found
|
83
88
|
@pal.get_plan_info(name)
|
84
89
|
|
90
|
+
executor = PlanExecutor::Executor.new(body['job_id'], @client)
|
91
|
+
applicator = PlanExecutor::Applicator.new(@inventory, executor, nil)
|
85
92
|
params = body['params']
|
86
93
|
# This provides a wait function, which promise doesn't
|
87
94
|
result = Concurrent::Future.execute(executor: @worker) do
|
88
|
-
|
89
|
-
|
95
|
+
pal_result = @pal.run_plan(name, params, executor, @inventory, puppetdb_client, applicator)
|
96
|
+
executor.finish_plan(pal_result)
|
97
|
+
pal_result
|
90
98
|
end
|
91
99
|
|
92
100
|
[200, { status: 'running' }.to_json]
|
data/lib/plan_executor/config.rb
CHANGED
@@ -7,7 +7,7 @@ require 'bolt/error'
|
|
7
7
|
module PlanExecutor
|
8
8
|
class Config < BoltServer::BaseConfig
|
9
9
|
def config_keys
|
10
|
-
super + %w[modulepath workers]
|
10
|
+
super + %w[modulepath workers orchestrator-url]
|
11
11
|
end
|
12
12
|
|
13
13
|
def defaults
|
@@ -21,6 +21,10 @@ module PlanExecutor
|
|
21
21
|
'plan-executor'
|
22
22
|
end
|
23
23
|
|
24
|
+
def required_keys
|
25
|
+
super + ['orchestrator-url']
|
26
|
+
end
|
27
|
+
|
24
28
|
def load_env_config
|
25
29
|
env_keys.each do |key|
|
26
30
|
transformed_key = "BOLT_#{key.tr('-', '_').upcase}"
|
@@ -7,27 +7,26 @@ require 'logging'
|
|
7
7
|
require 'set'
|
8
8
|
require 'bolt/result'
|
9
9
|
require 'bolt/config'
|
10
|
-
require 'bolt/transport/api'
|
11
|
-
require 'bolt/notifier'
|
12
10
|
require 'bolt/result_set'
|
13
11
|
require 'bolt/puppetdb'
|
12
|
+
require 'plan_executor/orch_client'
|
14
13
|
|
15
14
|
module PlanExecutor
|
16
15
|
class Executor
|
17
|
-
attr_reader :noop, :
|
16
|
+
attr_reader :noop, :logger
|
17
|
+
attr_accessor :transport
|
18
18
|
|
19
|
-
def initialize(noop = nil)
|
19
|
+
def initialize(job_id, client, noop = nil)
|
20
20
|
@logger = Logging.logger[self]
|
21
21
|
@plan_logging = false
|
22
22
|
@noop = noop
|
23
23
|
@logger.debug { "Started" }
|
24
|
-
@
|
25
|
-
@transport = Bolt::Transport::Api.new
|
24
|
+
@transport = PlanExecutor::OrchClient.new(job_id, client, @logger)
|
26
25
|
end
|
27
26
|
|
28
27
|
# This handles running the job, catching errors, and turning the result
|
29
28
|
# into a result set
|
30
|
-
def
|
29
|
+
def as_resultset(targets)
|
31
30
|
result_array = begin
|
32
31
|
yield
|
33
32
|
rescue StandardError => e
|
@@ -38,7 +37,7 @@ module PlanExecutor
|
|
38
37
|
Bolt::ResultSet.new(result_array)
|
39
38
|
end
|
40
39
|
|
41
|
-
#
|
40
|
+
# BOLT-1098
|
42
41
|
def log_action(description, targets)
|
43
42
|
# When running a plan, info messages like starting a task are promoted to notice.
|
44
43
|
log_method = @plan_logging ? :notice : :info
|
@@ -62,6 +61,7 @@ module PlanExecutor
|
|
62
61
|
results
|
63
62
|
end
|
64
63
|
|
64
|
+
# BOLT-1098
|
65
65
|
def log_plan(plan_name)
|
66
66
|
log_method = @plan_logging ? :notice : :info
|
67
67
|
@logger.send(log_method, "Starting: plan #{plan_name}")
|
@@ -78,60 +78,48 @@ module PlanExecutor
|
|
78
78
|
results
|
79
79
|
end
|
80
80
|
|
81
|
-
def run_command(targets, command, options = {}
|
81
|
+
def run_command(targets, command, options = {})
|
82
82
|
description = options.fetch('_description', "command '#{command}'")
|
83
83
|
log_action(description, targets) do
|
84
|
-
|
85
|
-
|
86
|
-
results = execute(targets) do
|
87
|
-
@transport.batch_command(targets, command, options, ¬ify)
|
84
|
+
results = as_resultset(targets) do
|
85
|
+
@transport.run_command(targets, command, options)
|
88
86
|
end
|
89
87
|
|
90
|
-
@notifier.shutdown
|
91
88
|
results
|
92
89
|
end
|
93
90
|
end
|
94
91
|
|
95
|
-
def run_script(targets, script, arguments, options = {}
|
92
|
+
def run_script(targets, script, arguments, options = {})
|
96
93
|
description = options.fetch('_description', "script #{script}")
|
97
94
|
log_action(description, targets) do
|
98
|
-
|
99
|
-
|
100
|
-
results = execute(targets) do
|
101
|
-
@transport.batch_script(targets, script, arguments, options, ¬ify)
|
95
|
+
results = as_resultset(targets) do
|
96
|
+
@transport.run_script(targets, script, arguments, options)
|
102
97
|
end
|
103
98
|
|
104
|
-
@notifier.shutdown
|
105
99
|
results
|
106
100
|
end
|
107
101
|
end
|
108
102
|
|
109
|
-
def run_task(targets, task, arguments, options = {}
|
103
|
+
def run_task(targets, task, arguments, options = {})
|
110
104
|
description = options.fetch('_description', "task #{task.name}")
|
111
105
|
log_action(description, targets) do
|
112
|
-
notify = proc { |event| @notifier.notify(callback, event) if callback }
|
113
|
-
|
114
106
|
arguments['_task'] = task.name
|
115
107
|
|
116
|
-
results =
|
117
|
-
@transport.
|
108
|
+
results = as_resultset(targets) do
|
109
|
+
@transport.run_task(targets, task, arguments, options)
|
118
110
|
end
|
119
111
|
|
120
|
-
@notifier.shutdown
|
121
112
|
results
|
122
113
|
end
|
123
114
|
end
|
124
115
|
|
125
|
-
def upload_file(targets, source, destination, options = {}
|
116
|
+
def upload_file(targets, source, destination, options = {})
|
126
117
|
description = options.fetch('_description', "file upload from #{source} to #{destination}")
|
127
118
|
log_action(description, targets) do
|
128
|
-
|
129
|
-
|
130
|
-
results = execute(targets) do
|
131
|
-
@transport.batch_upload(targets, source, destination, options, ¬ify)
|
119
|
+
results = as_resultset(targets) do
|
120
|
+
@transport.file_upload(targets, source, destination, options)
|
132
121
|
end
|
133
122
|
|
134
|
-
@notifier.shutdown
|
135
123
|
results
|
136
124
|
end
|
137
125
|
end
|
@@ -144,7 +132,7 @@ module PlanExecutor
|
|
144
132
|
retry_interval: 1)
|
145
133
|
log_action(description, targets) do
|
146
134
|
begin
|
147
|
-
wait_until(wait_time, retry_interval) { @transport.
|
135
|
+
wait_until(wait_time, retry_interval) { @transport.connected?(targets) }
|
148
136
|
targets.map { |target| Bolt::Result.new(target) }
|
149
137
|
rescue TimeoutError => e
|
150
138
|
targets.map { |target| Bolt::Result.from_exception(target, e) }
|
@@ -160,21 +148,6 @@ module PlanExecutor
|
|
160
148
|
end
|
161
149
|
end
|
162
150
|
|
163
|
-
# Plan context doesn't make sense for most transports but it is tightly
|
164
|
-
# coupled with the orchestrator transport since the transport behaves
|
165
|
-
# differently when a plan is running. In order to limit how much this
|
166
|
-
# pollutes the transport API we only handle the orchestrator transport here.
|
167
|
-
# Since we callt this function without resolving targets this will result
|
168
|
-
# in the orchestrator transport always being initialized during plan runs.
|
169
|
-
# For now that's ok.
|
170
|
-
#
|
171
|
-
# In the future if other transports need this or if we want a plan stack
|
172
|
-
# we'll need to refactor.
|
173
|
-
def start_plan(plan_context)
|
174
|
-
@transport.plan_context = plan_context
|
175
|
-
@plan_logging = true
|
176
|
-
end
|
177
|
-
|
178
151
|
def finish_plan(plan_result)
|
179
152
|
@transport.finish_plan(plan_result)
|
180
153
|
end
|
@@ -0,0 +1,211 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PlanExecutor
|
4
|
+
class OrchClient
|
5
|
+
attr_reader :plan_job, :client
|
6
|
+
|
7
|
+
BOLT_COMMAND_TASK = Struct.new(:name).new('bolt_shim::command').freeze
|
8
|
+
BOLT_SCRIPT_TASK = Struct.new(:name).new('bolt_shim::script').freeze
|
9
|
+
BOLT_UPLOAD_TASK = Struct.new(:name).new('bolt_shim::upload').freeze
|
10
|
+
|
11
|
+
def initialize(plan_job, client, logger)
|
12
|
+
@plan_job = plan_job
|
13
|
+
@client = client
|
14
|
+
@logger = logger
|
15
|
+
@client_url = client.config['service-url']
|
16
|
+
logger.debug("Creating orchestrator client for #{@client_url}")
|
17
|
+
@environment = 'production'
|
18
|
+
end
|
19
|
+
|
20
|
+
def finish_plan(plan_result)
|
21
|
+
result = @client.command.plan_finish(
|
22
|
+
plan_job: @plan_job,
|
23
|
+
result: plan_result.value || '',
|
24
|
+
status: plan_result.status
|
25
|
+
)
|
26
|
+
rescue StandardError => e
|
27
|
+
@logger.debug("Failed to finish plan on #{@client_url}: #{e.message}\nResult: #{result.to_json}")
|
28
|
+
end
|
29
|
+
|
30
|
+
def run_task_job(targets, task, arguments, options)
|
31
|
+
# unpack any Sensitive data
|
32
|
+
arguments = unwrap_sensitive_args(arguments)
|
33
|
+
results = send_request(targets, task, arguments, options)
|
34
|
+
|
35
|
+
process_run_results(targets, results)
|
36
|
+
rescue OrchestratorClient::ApiError => e
|
37
|
+
targets.map do |target|
|
38
|
+
Bolt::Result.new(target, error: e.data)
|
39
|
+
end
|
40
|
+
rescue StandardError => e
|
41
|
+
targets.map do |target|
|
42
|
+
Bolt::Result.from_exception(target, e)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def send_request(targets, task, arguments, options = {})
|
47
|
+
description = options['_description']
|
48
|
+
body = { task: task.name,
|
49
|
+
environment: @environment,
|
50
|
+
noop: arguments['_noop'],
|
51
|
+
params: arguments.reject { |k, _| k.start_with?('_') },
|
52
|
+
scope: {
|
53
|
+
nodes: targets.map(&:host)
|
54
|
+
} }
|
55
|
+
body[:description] = description if description
|
56
|
+
body[:plan_job] = @plan_job if @plan_job
|
57
|
+
|
58
|
+
@client.run_task(body)
|
59
|
+
end
|
60
|
+
|
61
|
+
def process_run_results(targets, results)
|
62
|
+
targets_by_name = Hash[targets.map(&:host).zip(targets)]
|
63
|
+
results.map do |node_result|
|
64
|
+
target = targets_by_name[node_result['name']]
|
65
|
+
state = node_result['state']
|
66
|
+
result = node_result['result']
|
67
|
+
|
68
|
+
# If it's finished or already has a proper error simply pass it to the
|
69
|
+
# the result otherwise make sure an error is generated
|
70
|
+
if state == 'finished' || (result && result['_error'])
|
71
|
+
Bolt::Result.new(target, value: result)
|
72
|
+
elsif state == 'skipped'
|
73
|
+
Bolt::Result.new(
|
74
|
+
target,
|
75
|
+
value: { '_error' => {
|
76
|
+
'kind' => 'puppetlabs.tasks/skipped-node',
|
77
|
+
'msg' => "Node #{target.host} was skipped",
|
78
|
+
'details' => {}
|
79
|
+
} }
|
80
|
+
)
|
81
|
+
else
|
82
|
+
# Make a generic error with a unkown exit_code
|
83
|
+
Bolt::Result.for_task(target, result.to_json, '', 'unknown')
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def run_task(targets, task, arguments, options = {})
|
89
|
+
run_task_job(targets, task, arguments, options)
|
90
|
+
end
|
91
|
+
|
92
|
+
def run_command(targets, command, options = {})
|
93
|
+
params = { 'command' => command }
|
94
|
+
run_task_job(targets, BOLT_COMMAND_TASK, params, options)
|
95
|
+
end
|
96
|
+
|
97
|
+
def run_script(targets, script, arguments, options = {})
|
98
|
+
content = File.open(script, &:read)
|
99
|
+
content = Base64.encode64(content)
|
100
|
+
params = {
|
101
|
+
'content' => content,
|
102
|
+
'arguments' => arguments
|
103
|
+
}
|
104
|
+
callback ||= proc {}
|
105
|
+
results = run_task_job(targets, BOLT_SCRIPT_TASK, params, options, &callback)
|
106
|
+
results.map! { |result| unwrap_bolt_result(result.target, result) }
|
107
|
+
results.each do |result|
|
108
|
+
callback.call(type: :node_result, result: result)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def pack(directory)
|
113
|
+
start_time = Time.now
|
114
|
+
io = StringIO.new
|
115
|
+
output = Minitar::Output.new(Zlib::GzipWriter.new(io))
|
116
|
+
Find.find(directory) do |file|
|
117
|
+
next unless File.file?(file)
|
118
|
+
|
119
|
+
tar_path = Pathname.new(file).relative_path_from(Pathname.new(directory))
|
120
|
+
@logger.debug("Packing #{file} to #{tar_path}")
|
121
|
+
stat = File.stat(file)
|
122
|
+
content = File.binread(file)
|
123
|
+
output.tar.add_file_simple(
|
124
|
+
tar_path.to_s,
|
125
|
+
data: content,
|
126
|
+
size: content.size,
|
127
|
+
mode: stat.mode & 0o777,
|
128
|
+
mtime: stat.mtime
|
129
|
+
)
|
130
|
+
end
|
131
|
+
|
132
|
+
duration = Time.now - start_time
|
133
|
+
@logger.debug("Packed upload in #{duration * 1000} ms")
|
134
|
+
|
135
|
+
output.close
|
136
|
+
io.string
|
137
|
+
ensure
|
138
|
+
# Closes both tar and sgz.
|
139
|
+
output&.close
|
140
|
+
end
|
141
|
+
|
142
|
+
def file_upload(targets, source, destination, options = {})
|
143
|
+
stat = File.stat(source)
|
144
|
+
content = if stat.directory?
|
145
|
+
pack(source)
|
146
|
+
else
|
147
|
+
File.open(source, &:read)
|
148
|
+
end
|
149
|
+
content = Base64.encode64(content)
|
150
|
+
mode = File.stat(source).mode
|
151
|
+
params = {
|
152
|
+
'path' => destination,
|
153
|
+
'content' => content,
|
154
|
+
'mode' => mode,
|
155
|
+
'directory' => stat.directory?
|
156
|
+
}
|
157
|
+
results = run_task_job(targets, BOLT_UPLOAD_TASK, params, options)
|
158
|
+
results.map! do |result|
|
159
|
+
if result.error_hash
|
160
|
+
result
|
161
|
+
else
|
162
|
+
Bolt::Result.for_upload(result.target, source, destination)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
# Unwraps any Sensitive data in an arguments Hash, so the plain-text is passed
|
168
|
+
# to the Task/Script.
|
169
|
+
#
|
170
|
+
# This works on deeply nested data structures composed of Hashes, Arrays, and
|
171
|
+
# and plain-old data types (int, string, etc).
|
172
|
+
def unwrap_sensitive_args(arguments)
|
173
|
+
# Skip this if Puppet isn't loaded
|
174
|
+
return arguments unless defined?(Puppet::Pops::Types::PSensitiveType::Sensitive)
|
175
|
+
|
176
|
+
case arguments
|
177
|
+
when Array
|
178
|
+
# iterate over the array, unwrapping all elements
|
179
|
+
arguments.map { |x| unwrap_sensitive_args(x) }
|
180
|
+
when Hash
|
181
|
+
# iterate over the arguments hash and unwrap all keys and values
|
182
|
+
arguments.each_with_object({}) { |(k, v), h|
|
183
|
+
h[unwrap_sensitive_args(k)] = unwrap_sensitive_args(v)
|
184
|
+
}
|
185
|
+
when Puppet::Pops::Types::PSensitiveType::Sensitive
|
186
|
+
# this value is Sensitive, unwrap it
|
187
|
+
unwrap_sensitive_args(arguments.unwrap)
|
188
|
+
else
|
189
|
+
# unknown data type, just return it
|
190
|
+
arguments
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
# run_task generates a result that makes sense for a generic task which
|
195
|
+
# needs to be unwrapped to extract stdout/stderr/exitcode.
|
196
|
+
#
|
197
|
+
def unwrap_bolt_result(target, result)
|
198
|
+
if result.error_hash
|
199
|
+
# something went wrong return the failure
|
200
|
+
return result
|
201
|
+
end
|
202
|
+
|
203
|
+
Bolt::Result.for_command(target, result.value['stdout'], result.value['stderr'], result.value['exit_code'])
|
204
|
+
end
|
205
|
+
|
206
|
+
def connected?(targets)
|
207
|
+
nodes = @client.post('inventory', nodes: targets.map(&:host))
|
208
|
+
nodes['items'].all? { |node| node['connected'] }
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
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: 1.
|
4
|
+
version: 1.11.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Puppet
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-02-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: addressable
|
@@ -357,8 +357,6 @@ files:
|
|
357
357
|
- lib/bolt/task.rb
|
358
358
|
- lib/bolt/task/puppet_server.rb
|
359
359
|
- lib/bolt/task/remote.rb
|
360
|
-
- lib/bolt/transport/api.rb
|
361
|
-
- lib/bolt/transport/api/connection.rb
|
362
360
|
- lib/bolt/transport/base.rb
|
363
361
|
- lib/bolt/transport/docker.rb
|
364
362
|
- lib/bolt/transport/docker/connection.rb
|
@@ -396,6 +394,7 @@ files:
|
|
396
394
|
- lib/plan_executor/applicator.rb
|
397
395
|
- lib/plan_executor/config.rb
|
398
396
|
- lib/plan_executor/executor.rb
|
397
|
+
- lib/plan_executor/orch_client.rb
|
399
398
|
- lib/plan_executor/schemas/run_plan.json
|
400
399
|
- libexec/apply_catalog.rb
|
401
400
|
- libexec/bolt_catalog
|
data/lib/bolt/transport/api.rb
DELETED
@@ -1,28 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'bolt/transport/api/connection'
|
4
|
-
|
5
|
-
# A copy of the orchestrator transport which uses the api connection class
|
6
|
-
# in order to bypass calling 'start_plan'
|
7
|
-
module Bolt
|
8
|
-
module Transport
|
9
|
-
class Api < Orch
|
10
|
-
def initialize(*args)
|
11
|
-
super
|
12
|
-
end
|
13
|
-
|
14
|
-
def get_connection(conn_opts)
|
15
|
-
key = Bolt::Transport::Api::Connection.get_key(conn_opts)
|
16
|
-
unless (conn = @connections[key])
|
17
|
-
@connections[key] = Bolt::Transport::Api::Connection.new(conn_opts, logger)
|
18
|
-
conn = @connections[key]
|
19
|
-
end
|
20
|
-
conn
|
21
|
-
end
|
22
|
-
|
23
|
-
def batches(targets)
|
24
|
-
targets.group_by { |target| Bolt::Transport::Api::Connection.get_key(target.options) }.values
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
@@ -1,70 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# This is a copy of the orchestrator transport connection, but without the
|
4
|
-
# 'start_plan' call in the init function which is handled by the orchestrator
|
5
|
-
# client
|
6
|
-
module Bolt
|
7
|
-
module Transport
|
8
|
-
class Api < Orch
|
9
|
-
class Connection
|
10
|
-
attr_reader :logger, :key
|
11
|
-
|
12
|
-
CONTEXT_KEYS = Set.new(%i[plan_name description params]).freeze
|
13
|
-
|
14
|
-
def self.get_key(opts)
|
15
|
-
[
|
16
|
-
opts['service-url'],
|
17
|
-
opts['task-environment'],
|
18
|
-
opts['token-file']
|
19
|
-
].join('-')
|
20
|
-
end
|
21
|
-
|
22
|
-
def initialize(opts, logger)
|
23
|
-
@logger = logger
|
24
|
-
@key = self.class.get_key(opts)
|
25
|
-
client_keys = %w[service-url token-file cacert]
|
26
|
-
client_opts = client_keys.each_with_object({}) do |k, acc|
|
27
|
-
acc[k] = opts[k] if opts.include?(k)
|
28
|
-
end
|
29
|
-
client_opts['User-Agent'] = "Bolt/#{VERSION}"
|
30
|
-
logger.debug("Creating orchestrator client for #{client_opts}")
|
31
|
-
|
32
|
-
@client = OrchestratorClient.new(client_opts, true)
|
33
|
-
@environment = opts["task-environment"]
|
34
|
-
end
|
35
|
-
|
36
|
-
def finish_plan(plan_result)
|
37
|
-
if @plan_job
|
38
|
-
@client.command.plan_finish(
|
39
|
-
plan_job: @plan_job,
|
40
|
-
result: plan_result.value || '',
|
41
|
-
status: plan_result.status
|
42
|
-
)
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
def build_request(targets, task, arguments, description = nil)
|
47
|
-
body = { task: task.name,
|
48
|
-
environment: @environment,
|
49
|
-
noop: arguments['_noop'],
|
50
|
-
params: arguments.reject { |k, _| k.start_with?('_') },
|
51
|
-
scope: {
|
52
|
-
nodes: targets.map(&:host)
|
53
|
-
} }
|
54
|
-
body[:description] = description if description
|
55
|
-
body[:plan_job] = @plan_job if @plan_job
|
56
|
-
body
|
57
|
-
end
|
58
|
-
|
59
|
-
def run_task(targets, task, arguments, options)
|
60
|
-
body = build_request(targets, task, arguments, options['_description'])
|
61
|
-
@client.run_task(body)
|
62
|
-
end
|
63
|
-
|
64
|
-
def query_inventory(targets)
|
65
|
-
@client.post('inventory', nodes: targets.map(&:host))
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|