bolt 1.1.0 → 1.2.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/applicator.rb +6 -0
- data/lib/bolt/bolt_option_parser.rb +17 -0
- data/lib/bolt/catalog.rb +2 -2
- data/lib/bolt/cli.rb +65 -22
- data/lib/bolt/error.rb +2 -2
- data/lib/bolt/inventory/group.rb +10 -10
- data/lib/bolt/outputter/human.rb +6 -0
- data/lib/bolt/outputter/json.rb +4 -0
- data/lib/bolt/pal.rb +10 -0
- data/lib/bolt/target.rb +4 -1
- data/lib/bolt/task/puppet_server.rb +27 -0
- data/lib/bolt/task.rb +22 -8
- data/lib/bolt/transport/base.rb +0 -4
- data/lib/bolt/transport/local.rb +15 -7
- data/lib/bolt/transport/ssh.rb +13 -22
- data/lib/bolt/transport/winrm.rb +13 -22
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt_server/acl.rb +39 -0
- data/lib/bolt_server/config.rb +105 -0
- data/lib/bolt_server/file_cache.rb +177 -0
- data/lib/{bolt_ext → bolt_server}/schemas/ssh-run_task.json +0 -0
- data/lib/{bolt_ext → bolt_server}/schemas/task.json +24 -14
- data/lib/{bolt_ext → bolt_server}/schemas/winrm-run_task.json +0 -0
- data/lib/bolt_server/transport_app.rb +105 -0
- data/lib/bolt_spec/run.rb +15 -1
- data/libexec/bolt_catalog +1 -1
- metadata +24 -8
- data/lib/bolt_ext/server.rb +0 -101
- data/lib/bolt_ext/server_acl.rb +0 -37
- data/lib/bolt_ext/server_config.rb +0 -88
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ca6e259cbc835ea3d8b4f5f67d2b199faf19adfeb2a2d9b0b999582b919a863a
|
4
|
+
data.tar.gz: 2152f34ff150aaaf2d1c0a6fb114d401ef3ddf5c2da5a739e8f2fa22ab5565cf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 69fe10cdd01be7f4965a8e6cca73aa9c65f449b94bb35909399839ca4c506107b611df43f3a0e27fe9fce896d3e63261f3dc90ce329d257583ff427e6c712eb3
|
7
|
+
data.tar.gz: 400b7ef4c9b01ad6c5464481ac73f5c3b7774e25aa6780f1aa404e3e6504091c57828d9a0bdaf7a3f4effecd08ff1b35cae4dd8e5b7dd6b6329267805ce0ef77
|
data/lib/bolt/applicator.rb
CHANGED
@@ -133,7 +133,13 @@ module Bolt
|
|
133
133
|
%w[trusted server_facts facts].each { |k| plan_vars.delete(k) }
|
134
134
|
|
135
135
|
targets = @inventory.get_targets(args[0])
|
136
|
+
|
136
137
|
ast = Puppet::Pops::Serialization::ToDataConverter.convert(apply_body, rich_data: true, symbol_to_string: true)
|
138
|
+
|
139
|
+
apply_ast(ast, targets, options, plan_vars)
|
140
|
+
end
|
141
|
+
|
142
|
+
def apply_ast(ast, targets, options, plan_vars = {})
|
137
143
|
notify = proc { |_| nil }
|
138
144
|
|
139
145
|
r = @executor.log_action('apply catalog', targets) do
|
@@ -30,6 +30,7 @@ Available subcommands:
|
|
30
30
|
bolt plan show Show list of available plans
|
31
31
|
bolt plan show <plan> Show details for plan
|
32
32
|
bolt plan run <plan> [params] Run a Puppet task plan
|
33
|
+
bolt apply <manifest> Apply Puppet manifest code
|
33
34
|
bolt puppetfile install Install modules from a Puppetfile into a Boltdir
|
34
35
|
|
35
36
|
Run `bolt <subcommand> --help` to view specific examples.
|
@@ -107,6 +108,13 @@ Install modules into the local Boltdir
|
|
107
108
|
Available options are:
|
108
109
|
HELP
|
109
110
|
|
111
|
+
APPLY_HELP = <<-HELP
|
112
|
+
Usage: bolt apply <manifest.pp> [options]
|
113
|
+
|
114
|
+
#{examples('apply site.pp', 'apply a manifest on')}
|
115
|
+
bolt apply site.pp --nodes foo.example.com,bar.example.com
|
116
|
+
HELP
|
117
|
+
|
110
118
|
# A helper mixin for OptionParser::Switch instances which will allow
|
111
119
|
# us to show/hide particular switch in the help message produced by
|
112
120
|
# the OptionParser#help method on demand.
|
@@ -150,6 +158,10 @@ Available options are:
|
|
150
158
|
"Parameters to a task or plan as json, a json file '@<file>', or on stdin '-'") do |params|
|
151
159
|
@options[:task_options] = parse_params(params)
|
152
160
|
end
|
161
|
+
@execute = define('-e', '--execute CODE',
|
162
|
+
"Puppet manifest code to apply to the targets") do |code|
|
163
|
+
@options[:code] = code
|
164
|
+
end.extend(SwitchHider)
|
153
165
|
|
154
166
|
separator 'Authentication:'
|
155
167
|
define('-u', '--user USER', 'User to authenticate as') do |user|
|
@@ -270,6 +282,8 @@ Available options are:
|
|
270
282
|
def update
|
271
283
|
# show the --nodes and --query switches by default
|
272
284
|
@nodes.hide = @query.hide = false
|
285
|
+
# Don't show the --execute switch except for `apply`
|
286
|
+
@execute.hide = true
|
273
287
|
|
274
288
|
# Update the banner according to the subcommand
|
275
289
|
self.banner = case @options[:subcommand]
|
@@ -287,6 +301,9 @@ Available options are:
|
|
287
301
|
FILE_HELP
|
288
302
|
when 'puppetfile'
|
289
303
|
PUPPETFILE_HELP
|
304
|
+
when 'apply'
|
305
|
+
@execute.hide = false
|
306
|
+
APPLY_HELP
|
290
307
|
else
|
291
308
|
BANNER
|
292
309
|
end
|
data/lib/bolt/catalog.rb
CHANGED
@@ -33,11 +33,11 @@ module Bolt
|
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
36
|
-
def generate_ast(code)
|
36
|
+
def generate_ast(code, filename = nil)
|
37
37
|
with_puppet_settings do
|
38
38
|
Puppet::Pal.in_tmp_environment("bolt_parse") do |pal|
|
39
39
|
pal.with_catalog_compiler do |compiler|
|
40
|
-
ast = compiler.parse_string(code)
|
40
|
+
ast = compiler.parse_string(code, filename)
|
41
41
|
Puppet::Pops::Serialization::ToDataConverter.convert(ast,
|
42
42
|
rich_data: true,
|
43
43
|
symbol_to_string: true)
|
data/lib/bolt/cli.rb
CHANGED
@@ -9,7 +9,6 @@ require 'json'
|
|
9
9
|
require 'io/console'
|
10
10
|
require 'logging'
|
11
11
|
require 'optparse'
|
12
|
-
require 'r10k/action/puppetfile/install'
|
13
12
|
require 'bolt/analytics'
|
14
13
|
require 'bolt/bolt_option_parser'
|
15
14
|
require 'bolt/config'
|
@@ -20,19 +19,19 @@ require 'bolt/logger'
|
|
20
19
|
require 'bolt/outputter'
|
21
20
|
require 'bolt/puppetdb'
|
22
21
|
require 'bolt/pal'
|
23
|
-
require 'bolt/r10k_log_proxy'
|
24
22
|
require 'bolt/target'
|
25
23
|
require 'bolt/version'
|
26
24
|
|
27
25
|
module Bolt
|
28
26
|
class CLIExit < StandardError; end
|
29
27
|
class CLI
|
30
|
-
COMMANDS = { 'command'
|
31
|
-
'script'
|
32
|
-
'task'
|
33
|
-
'plan'
|
34
|
-
'file'
|
35
|
-
'puppetfile' => %w[install]
|
28
|
+
COMMANDS = { 'command' => %w[run],
|
29
|
+
'script' => %w[run],
|
30
|
+
'task' => %w[show run],
|
31
|
+
'plan' => %w[show run],
|
32
|
+
'file' => %w[upload],
|
33
|
+
'puppetfile' => %w[install],
|
34
|
+
'apply' => %w[] }.freeze
|
36
35
|
|
37
36
|
attr_reader :config, :options
|
38
37
|
|
@@ -80,7 +79,10 @@ module Bolt
|
|
80
79
|
|
81
80
|
# This section handles parsing non-flag options which are
|
82
81
|
# subcommand specific rather then part of the config
|
83
|
-
options[:
|
82
|
+
actions = COMMANDS[options[:subcommand]]
|
83
|
+
if actions && !actions.empty?
|
84
|
+
options[:action] = remaining.shift
|
85
|
+
end
|
84
86
|
options[:object] = remaining.shift
|
85
87
|
|
86
88
|
task_options, remaining = remaining.partition { |s| s =~ /.+=/ }
|
@@ -140,16 +142,18 @@ module Bolt
|
|
140
142
|
"#{COMMANDS.keys.join(', ')}"
|
141
143
|
end
|
142
144
|
|
143
|
-
if options[:action].nil?
|
144
|
-
raise Bolt::CLIError,
|
145
|
-
"Expected an action of the form 'bolt #{options[:subcommand]} <action>'"
|
146
|
-
end
|
147
|
-
|
148
145
|
actions = COMMANDS[options[:subcommand]]
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
146
|
+
if actions.any?
|
147
|
+
if options[:action].nil?
|
148
|
+
raise Bolt::CLIError,
|
149
|
+
"Expected an action of the form 'bolt #{options[:subcommand]} <action>'"
|
150
|
+
end
|
151
|
+
|
152
|
+
unless actions.include?(options[:action])
|
153
|
+
raise Bolt::CLIError,
|
154
|
+
"Expected action '#{options[:action]}' to be one of " \
|
155
|
+
"#{actions.join(', ')}"
|
156
|
+
end
|
153
157
|
end
|
154
158
|
|
155
159
|
if options[:subcommand] != 'file' && options[:subcommand] != 'script' &&
|
@@ -181,9 +185,18 @@ module Bolt
|
|
181
185
|
raise Bolt::CLIError, "Only one of '--boltdir' or '--configfile' may be specified"
|
182
186
|
end
|
183
187
|
|
184
|
-
if options[:noop] &&
|
188
|
+
if options[:noop] &&
|
189
|
+
!(options[:subcommand] == 'task' && options[:action] == 'run') && options[:subcommand] != 'apply'
|
185
190
|
raise Bolt::CLIError,
|
186
|
-
"Option '--noop' may only be specified when running a task"
|
191
|
+
"Option '--noop' may only be specified when running a task or applying manifest code"
|
192
|
+
end
|
193
|
+
|
194
|
+
if options[:subcommand] == 'apply' && (options[:object] && options[:code])
|
195
|
+
raise Bolt::CLIError, "--execute is unsupported when specifying a manifest file"
|
196
|
+
end
|
197
|
+
|
198
|
+
if options[:subcommand] == 'apply' && (!options[:object] && !options[:code])
|
199
|
+
raise Bolt::CLIError, "a manifest file or --execute is required"
|
187
200
|
end
|
188
201
|
end
|
189
202
|
|
@@ -254,10 +267,17 @@ module Bolt
|
|
254
267
|
options[:task_options] = pal.parse_params(options[:subcommand], options[:object], options[:task_options])
|
255
268
|
end
|
256
269
|
|
257
|
-
|
270
|
+
case options[:subcommand]
|
271
|
+
when 'plan'
|
258
272
|
code = run_plan(options[:object], options[:task_options], options[:nodes], options)
|
259
|
-
|
273
|
+
when 'puppetfile'
|
260
274
|
code = install_puppetfile(@config.puppetfile, @config.modulepath)
|
275
|
+
when 'apply'
|
276
|
+
if options[:object]
|
277
|
+
validate_file('manifest', options[:object])
|
278
|
+
options[:code] = File.read(File.expand_path(options[:object]))
|
279
|
+
end
|
280
|
+
code = apply_manifest(options[:code], options[:targets], options[:object], options[:noop])
|
261
281
|
else
|
262
282
|
executor = Bolt::Executor.new(config.concurrency, @analytics, options[:noop], bundled_content: bundled_content)
|
263
283
|
targets = options[:targets]
|
@@ -364,7 +384,30 @@ module Bolt
|
|
364
384
|
result.ok? ? 0 : 1
|
365
385
|
end
|
366
386
|
|
387
|
+
def apply_manifest(code, targets, filename = nil, noop = false)
|
388
|
+
ast = pal.parse_manifest(code, filename)
|
389
|
+
|
390
|
+
executor = Bolt::Executor.new(config.concurrency, @analytics, noop, bundled_content: bundled_content)
|
391
|
+
# Call start_plan just to enable plan_logging
|
392
|
+
executor.start_plan(nil)
|
393
|
+
|
394
|
+
pal.in_plan_compiler(executor, inventory, puppetdb_client) do |compiler|
|
395
|
+
compiler.call_function('apply_prep', targets)
|
396
|
+
end
|
397
|
+
|
398
|
+
results = pal.with_bolt_executor(executor, inventory, puppetdb_client) do
|
399
|
+
Puppet.lookup(:apply_executor).apply_ast(ast, targets, '_catch_errors' => true, '_noop' => noop)
|
400
|
+
end
|
401
|
+
|
402
|
+
outputter.print_apply_result(results)
|
403
|
+
|
404
|
+
results.ok ? 0 : 1
|
405
|
+
end
|
406
|
+
|
367
407
|
def install_puppetfile(puppetfile, modulepath)
|
408
|
+
require 'r10k/action/puppetfile/install'
|
409
|
+
require 'bolt/r10k_log_proxy'
|
410
|
+
|
368
411
|
if puppetfile.exist?
|
369
412
|
moduledir = modulepath.first.to_s
|
370
413
|
r10k_config = {
|
data/lib/bolt/error.rb
CHANGED
@@ -17,7 +17,7 @@ module Bolt
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def to_h
|
20
|
-
h = { 'kind' =>
|
20
|
+
h = { 'kind' => kind,
|
21
21
|
'msg' => message,
|
22
22
|
'details' => details }
|
23
23
|
h['issue_code'] = issue_code if issue_code
|
@@ -53,7 +53,7 @@ module Bolt
|
|
53
53
|
def initialize(result_set, action, object)
|
54
54
|
details = {
|
55
55
|
'action' => action,
|
56
|
-
'object' =>
|
56
|
+
'object' => object,
|
57
57
|
'result_set' => result_set
|
58
58
|
}
|
59
59
|
message = "Plan aborted: #{action} '#{object}' failed on #{result_set.error_set.length} nodes"
|
data/lib/bolt/inventory/group.rb
CHANGED
@@ -118,19 +118,19 @@ module Bolt
|
|
118
118
|
end
|
119
119
|
|
120
120
|
def group_data
|
121
|
-
{ 'config'
|
122
|
-
'vars'
|
123
|
-
'facts'
|
121
|
+
{ 'config' => @config,
|
122
|
+
'vars' => @vars,
|
123
|
+
'facts' => @facts,
|
124
124
|
'features' => @features,
|
125
|
-
'groups'
|
125
|
+
'groups' => [@name] }
|
126
126
|
end
|
127
127
|
|
128
128
|
def empty_data
|
129
|
-
{ 'config'
|
130
|
-
'vars'
|
131
|
-
'facts'
|
129
|
+
{ 'config' => {},
|
130
|
+
'vars' => {},
|
131
|
+
'facts' => {},
|
132
132
|
'features' => [],
|
133
|
-
'groups'
|
133
|
+
'groups' => [] }
|
134
134
|
end
|
135
135
|
|
136
136
|
def data_merge(data1, data2)
|
@@ -143,8 +143,8 @@ module Bolt
|
|
143
143
|
# Shallow merge instead of deep merge so that vars with a hash value
|
144
144
|
# are assigned a new hash, rather than merging the existing value
|
145
145
|
# with the value meant to replace it
|
146
|
-
'vars'
|
147
|
-
'facts'
|
146
|
+
'vars' => data1['vars'].merge(data2['vars']),
|
147
|
+
'facts' => Bolt::Util.deep_merge(data1['facts'], data2['facts']),
|
148
148
|
'features' => data1['features'] | data2['features'],
|
149
149
|
'groups' => data2['groups'] + data1['groups']
|
150
150
|
}
|
data/lib/bolt/outputter/human.rb
CHANGED
@@ -177,6 +177,12 @@ module Bolt
|
|
177
177
|
@stream.puts(plan_info)
|
178
178
|
end
|
179
179
|
|
180
|
+
# @param [Bolt::ResultSet] apply_result A ResultSet object representing the result of a `bolt apply`
|
181
|
+
def print_apply_result(apply_result)
|
182
|
+
apply_result.each { |result| print_result(result) }
|
183
|
+
print_summary(apply_result)
|
184
|
+
end
|
185
|
+
|
180
186
|
# @param [Bolt::PlanResult] plan_result A PlanResult object
|
181
187
|
def print_plan_result(plan_result)
|
182
188
|
value = plan_result.value
|
data/lib/bolt/outputter/json.rb
CHANGED
@@ -63,6 +63,10 @@ module Bolt
|
|
63
63
|
@stream.puts plan.to_json
|
64
64
|
end
|
65
65
|
|
66
|
+
def print_apply_result(apply_result)
|
67
|
+
@stream.puts apply_result.to_json
|
68
|
+
end
|
69
|
+
|
66
70
|
def print_plan_result(result)
|
67
71
|
# Ruby JSON patches most objects to have a to_json method.
|
68
72
|
@stream.puts result.to_json
|
data/lib/bolt/pal.rb
CHANGED
@@ -57,6 +57,7 @@ module Bolt
|
|
57
57
|
|
58
58
|
# Puppet logging is global so this is class method to avoid confusion
|
59
59
|
def self.configure_logging
|
60
|
+
Puppet::Util::Log.destinations.clear
|
60
61
|
Puppet::Util::Log.newdestination(Logging.logger['Puppet'])
|
61
62
|
# Defer all log level decisions to the Logging library by telling Puppet
|
62
63
|
# to log everything
|
@@ -176,6 +177,15 @@ module Bolt
|
|
176
177
|
end
|
177
178
|
end
|
178
179
|
|
180
|
+
# Parses a snippet of Puppet manifest code and returns the AST represented
|
181
|
+
# in JSON.
|
182
|
+
def parse_manifest(code, filename)
|
183
|
+
raw_ast = Puppet::Pops::Parser::EvaluatingParser.new.parse_string(code, filename)
|
184
|
+
Puppet::Pops::Serialization::ToDataConverter.convert(raw_ast, rich_data: true, symbol_to_string: true)
|
185
|
+
rescue Puppet::Error => e
|
186
|
+
raise Bolt::PAL::PALError, "Failed to parse manifest: #{e}"
|
187
|
+
end
|
188
|
+
|
179
189
|
def list_tasks
|
180
190
|
in_bolt_compiler do |compiler|
|
181
191
|
tasks = compiler.list_tasks
|
data/lib/bolt/target.rb
CHANGED
@@ -8,6 +8,8 @@ module Bolt
|
|
8
8
|
attr_reader :uri, :options
|
9
9
|
attr_writer :inventory
|
10
10
|
|
11
|
+
PRINT_OPTS ||= %w[host user port protocol].freeze
|
12
|
+
|
11
13
|
# Satisfies the Puppet datatypes API
|
12
14
|
def self.from_asserted_hash(hash)
|
13
15
|
new(hash['uri'], hash['options'])
|
@@ -79,7 +81,8 @@ module Bolt
|
|
79
81
|
end
|
80
82
|
|
81
83
|
def to_s
|
82
|
-
|
84
|
+
opts = @options.select { |k, _| PRINT_OPTS.include? k }
|
85
|
+
"Target('#{@uri}', #{opts})"
|
83
86
|
end
|
84
87
|
|
85
88
|
def host
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bolt/task'
|
4
|
+
|
5
|
+
module Bolt
|
6
|
+
class Task
|
7
|
+
class PuppetServer < Bolt::Task
|
8
|
+
def initialize(task_data, file_cache)
|
9
|
+
@file_cache = file_cache
|
10
|
+
task_data = update_file_data(task_data)
|
11
|
+
super(task_data)
|
12
|
+
end
|
13
|
+
|
14
|
+
# puppetserver file entries have 'filename' rather then 'name'
|
15
|
+
def update_file_data(task_data)
|
16
|
+
task_data['files'].each { |f| f['name'] = f['filename'] }
|
17
|
+
task_data
|
18
|
+
end
|
19
|
+
|
20
|
+
# Compute local path and download files from puppetserver as needed
|
21
|
+
def file_path(file_name)
|
22
|
+
file = file_map[file_name]
|
23
|
+
file['path'] ||= @file_cache.update_file(file)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/bolt/task.rb
CHANGED
@@ -32,26 +32,39 @@ module Bolt
|
|
32
32
|
metadata['supports_noop']
|
33
33
|
end
|
34
34
|
|
35
|
+
def module_name
|
36
|
+
name.split('::').first
|
37
|
+
end
|
38
|
+
|
39
|
+
def tasks_dir
|
40
|
+
File.join(module_name, 'tasks')
|
41
|
+
end
|
42
|
+
|
35
43
|
def file_map
|
36
|
-
@file_map ||= files.each_with_object({}) { |file, hsh| hsh[file['name']] = file
|
44
|
+
@file_map ||= files.each_with_object({}) { |file, hsh| hsh[file['name']] = file }
|
37
45
|
end
|
38
46
|
private :file_map
|
39
47
|
|
48
|
+
# This provides a method we can override in subclasses if the 'path' needs
|
49
|
+
# to be fetched or computed.
|
50
|
+
def file_path(file_name)
|
51
|
+
file_map[file_name]['path']
|
52
|
+
end
|
53
|
+
|
40
54
|
# Returns a hash of implementation name, path to executable, input method (if defined),
|
41
55
|
# and any additional files (name and path)
|
42
56
|
def select_implementation(target, additional_features = [])
|
43
|
-
raise 'select_implementation only supported with multiple files' if files.nil? || files.empty?
|
44
|
-
|
45
57
|
impl = if (impls = metadata['implementations'])
|
46
58
|
available_features = target.features + additional_features
|
47
59
|
impl = impls.find { |imp| Set.new(imp['requirements']).subset?(available_features) }
|
48
60
|
raise "No suitable implementation of #{name} for #{target.name}" unless impl
|
49
61
|
impl = impl.dup
|
50
|
-
impl['path'] =
|
62
|
+
impl['path'] = file_path(impl['name'])
|
51
63
|
impl.delete('requirements')
|
52
64
|
impl
|
53
65
|
else
|
54
|
-
files.first
|
66
|
+
name = files.first['name']
|
67
|
+
{ 'name' => name, 'path' => file_path(name) }
|
55
68
|
end
|
56
69
|
|
57
70
|
inmethod = impl['input_method'] || metadata['input_method']
|
@@ -60,15 +73,16 @@ module Bolt
|
|
60
73
|
mfiles = impl.fetch('files', []) + metadata.fetch('files', [])
|
61
74
|
dirnames, filenames = mfiles.partition { |file| file.end_with?('/') }
|
62
75
|
impl['files'] = filenames.map do |file|
|
63
|
-
path =
|
76
|
+
path = file_path(file)
|
64
77
|
raise "No file found for reference #{file}" if path.nil?
|
65
78
|
{ 'name' => file, 'path' => path }
|
66
79
|
end
|
67
80
|
|
68
81
|
unless dirnames.empty?
|
69
82
|
files.each do |file|
|
70
|
-
|
71
|
-
|
83
|
+
name = file['name']
|
84
|
+
if dirnames.any? { |dirname| name.start_with?(dirname) }
|
85
|
+
impl['files'] << { 'name' => name, 'path' => file_path(name) }
|
72
86
|
end
|
73
87
|
end
|
74
88
|
end
|
data/lib/bolt/transport/base.rb
CHANGED
@@ -162,10 +162,6 @@ module Bolt
|
|
162
162
|
targets.map { |target| [target] }
|
163
163
|
end
|
164
164
|
|
165
|
-
def from_api?(task)
|
166
|
-
!task.file.nil?
|
167
|
-
end
|
168
|
-
|
169
165
|
# Transports should override this method with their own implementation of running a command.
|
170
166
|
def run_command(*_args)
|
171
167
|
raise NotImplementedError, "run_command() must be implemented by the transport class"
|
data/lib/bolt/transport/local.rb
CHANGED
@@ -85,17 +85,25 @@ module Bolt
|
|
85
85
|
input_method = implementation['input_method'] || 'both'
|
86
86
|
extra_files = implementation['files']
|
87
87
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
88
|
+
in_tmpdir(target.options['tmpdir']) do |dir|
|
89
|
+
if extra_files.empty?
|
90
|
+
script = File.join(dir, File.basename(executable))
|
91
|
+
else
|
92
|
+
arguments['_installdir'] = dir
|
93
|
+
script_dest = File.join(dir, task.tasks_dir)
|
94
|
+
FileUtils.mkdir_p([script_dest] + extra_files.map { |file| File.join(dir, File.dirname(file['name'])) })
|
95
|
+
|
96
|
+
script = File.join(script_dest, File.basename(executable))
|
94
97
|
extra_files.each do |file|
|
95
|
-
|
98
|
+
dest = File.join(dir, file['name'])
|
99
|
+
copy_file(file['path'], dest)
|
100
|
+
File.chmod(0o750, dest)
|
96
101
|
end
|
97
102
|
end
|
98
103
|
|
104
|
+
copy_file(executable, script)
|
105
|
+
File.chmod(0o750, script)
|
106
|
+
|
99
107
|
# unpack any Sensitive data, write it to a separate variable because
|
100
108
|
# we log 'arguments' below
|
101
109
|
unwrapped_arguments = unwrap_sensitive_args(arguments)
|
data/lib/bolt/transport/ssh.rb
CHANGED
@@ -122,17 +122,10 @@ module Bolt
|
|
122
122
|
end
|
123
123
|
|
124
124
|
def run_task(target, task, arguments, options = {})
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
extra_files = []
|
130
|
-
else
|
131
|
-
implementation = task.select_implementation(target, PROVIDED_FEATURES)
|
132
|
-
executable = implementation['path']
|
133
|
-
input_method = implementation['input_method']
|
134
|
-
extra_files = implementation['files']
|
135
|
-
end
|
125
|
+
implementation = task.select_implementation(target, PROVIDED_FEATURES)
|
126
|
+
executable = implementation['path']
|
127
|
+
input_method = implementation['input_method']
|
128
|
+
extra_files = implementation['files']
|
136
129
|
input_method ||= 'both'
|
137
130
|
|
138
131
|
# unpack any Sensitive data
|
@@ -144,22 +137,20 @@ module Bolt
|
|
144
137
|
execute_options = {}
|
145
138
|
|
146
139
|
conn.with_remote_tempdir do |dir|
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
conn.write_remote_executable(dir, executable)
|
151
|
-
end
|
152
|
-
|
153
|
-
unless extra_files.empty?
|
140
|
+
if extra_files.empty?
|
141
|
+
task_dir = dir
|
142
|
+
else
|
154
143
|
# TODO: optimize upload of directories
|
155
|
-
|
156
|
-
|
157
|
-
dir.mkdirs(extra_files.map { |file| File.
|
144
|
+
arguments['_installdir'] = dir.to_s
|
145
|
+
task_dir = File.join(dir.to_s, task.tasks_dir)
|
146
|
+
dir.mkdirs([task.tasks_dir] + extra_files.map { |file| File.dirname(file['name']) })
|
158
147
|
extra_files.each do |file|
|
159
|
-
conn.write_remote_file(file['path'], File.join(
|
148
|
+
conn.write_remote_file(file['path'], File.join(dir.to_s, file['name']))
|
160
149
|
end
|
161
150
|
end
|
162
151
|
|
152
|
+
remote_task_path = conn.write_remote_executable(task_dir, executable)
|
153
|
+
|
163
154
|
if STDIN_METHODS.include?(input_method)
|
164
155
|
stdin = JSON.dump(arguments)
|
165
156
|
end
|
data/lib/bolt/transport/winrm.rb
CHANGED
@@ -108,39 +108,30 @@ catch
|
|
108
108
|
end
|
109
109
|
|
110
110
|
def run_task(target, task, arguments, _options = {})
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
extra_files = []
|
116
|
-
else
|
117
|
-
implementation = task.select_implementation(target, PROVIDED_FEATURES)
|
118
|
-
executable = implementation['path']
|
119
|
-
input_method = implementation['input_method']
|
120
|
-
extra_files = implementation['files']
|
121
|
-
end
|
111
|
+
implementation = task.select_implementation(target, PROVIDED_FEATURES)
|
112
|
+
executable = implementation['path']
|
113
|
+
input_method = implementation['input_method']
|
114
|
+
extra_files = implementation['files']
|
122
115
|
input_method ||= powershell_file?(executable) ? 'powershell' : 'both'
|
123
116
|
|
124
117
|
# unpack any Sensitive data
|
125
118
|
arguments = unwrap_sensitive_args(arguments)
|
126
119
|
with_connection(target) do |conn|
|
127
120
|
conn.with_remote_tempdir do |dir|
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
conn.write_remote_executable(dir, executable)
|
132
|
-
end
|
133
|
-
|
134
|
-
unless extra_files.empty?
|
121
|
+
if extra_files.empty?
|
122
|
+
task_dir = dir
|
123
|
+
else
|
135
124
|
# TODO: optimize upload of directories
|
136
|
-
|
137
|
-
|
138
|
-
conn.mkdirs(extra_files.map { |file| File.join(
|
125
|
+
arguments['_installdir'] = dir
|
126
|
+
task_dir = File.join(dir, task.tasks_dir)
|
127
|
+
conn.mkdirs([task_dir] + extra_files.map { |file| File.join(dir, File.dirname(file['name'])) })
|
139
128
|
extra_files.each do |file|
|
140
|
-
conn.write_remote_file(file['path'], File.join(
|
129
|
+
conn.write_remote_file(file['path'], File.join(dir, file['name']))
|
141
130
|
end
|
142
131
|
end
|
143
132
|
|
133
|
+
remote_task_path = conn.write_remote_executable(task_dir, executable)
|
134
|
+
|
144
135
|
if STDIN_METHODS.include?(input_method)
|
145
136
|
stdin = JSON.dump(arguments)
|
146
137
|
end
|
data/lib/bolt/version.rb
CHANGED
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails/auth/rack'
|
4
|
+
|
5
|
+
module BoltServer
|
6
|
+
class ACL < Rails::Auth::ErrorPage::Middleware
|
7
|
+
class X509Matcher
|
8
|
+
def initialize(options)
|
9
|
+
@options = options.freeze
|
10
|
+
end
|
11
|
+
|
12
|
+
def match(env)
|
13
|
+
certificate = Rails::Auth::X509::Certificate.new(env['puma.peercert'])
|
14
|
+
# This can be extended fairly easily to search OpenSSL::X509::Certificate#extensions for subjectAltNames.
|
15
|
+
@options.all? { |name, value| certificate[name] == value }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(app, whitelist)
|
20
|
+
acls = []
|
21
|
+
whitelist.each do |entry|
|
22
|
+
acls << {
|
23
|
+
'resources' => [
|
24
|
+
{
|
25
|
+
'method' => 'ALL',
|
26
|
+
'path' => '/.*'
|
27
|
+
}
|
28
|
+
],
|
29
|
+
'allow_x509_subject' => {
|
30
|
+
'cn' => entry
|
31
|
+
}
|
32
|
+
}
|
33
|
+
end
|
34
|
+
acl = Rails::Auth::ACL.new(acls, matchers: { allow_x509_subject: X509Matcher })
|
35
|
+
mid = Rails::Auth::ACL::Middleware.new(app, acl: acl)
|
36
|
+
super(mid, page_body: 'Access denied')
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|