bolt 1.30.1 → 1.31.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 +1 -3
- data/bolt-modules/boltlib/lib/puppet/functions/run_task.rb +1 -1
- data/lib/bolt/bolt_option_parser.rb +6 -1
- data/lib/bolt/cli.rb +1 -1
- data/lib/bolt/error.rb +6 -2
- data/lib/bolt/inventory/group2.rb +5 -5
- data/lib/bolt/module.rb +39 -0
- data/lib/bolt/pal.rb +20 -6
- data/lib/bolt/plugin.rb +167 -21
- data/lib/bolt/plugin/aws_inventory.rb +102 -0
- data/lib/bolt/plugin/install_agent.rb +3 -1
- data/lib/bolt/plugin/module.rb +238 -0
- data/lib/bolt/plugin/pkcs7.rb +11 -7
- data/lib/bolt/plugin/prompt.rb +4 -7
- data/lib/bolt/plugin/puppetdb.rb +2 -2
- data/lib/bolt/plugin/task.rb +16 -57
- data/lib/bolt/plugin/terraform.rb +3 -3
- data/lib/bolt/plugin/vault.rb +3 -3
- data/lib/bolt/secret.rb +4 -3
- data/lib/bolt/secret/base.rb +11 -7
- data/lib/bolt/task/run.rb +55 -0
- data/lib/bolt/transport/orch/connection.rb +3 -1
- data/lib/bolt/transport/winrm.rb +4 -0
- data/lib/bolt/transport/winrm/connection.rb +2 -1
- data/lib/bolt/util.rb +6 -0
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt_server/file_cache.rb +10 -6
- data/lib/bolt_server/schemas/action-run_script.json +44 -0
- data/lib/bolt_server/transport_app.rb +25 -2
- metadata +7 -3
- data/lib/bolt/plugin/aws.rb +0 -103
data/lib/bolt/plugin/task.rb
CHANGED
@@ -4,41 +4,27 @@ module Bolt
|
|
4
4
|
class Plugin
|
5
5
|
class Task
|
6
6
|
def hooks
|
7
|
-
%
|
7
|
+
%i[validate_resolve_reference puppet_library resolve_reference]
|
8
8
|
end
|
9
9
|
|
10
10
|
def name
|
11
11
|
'task'
|
12
12
|
end
|
13
13
|
|
14
|
-
|
15
|
-
#
|
16
|
-
def initialize(config)
|
17
|
-
@config = config
|
18
|
-
end
|
19
|
-
|
20
|
-
attr_reader :config
|
14
|
+
attr_accessor :pal, :executor, :inventory
|
21
15
|
|
22
|
-
def
|
23
|
-
|
24
|
-
@pal ||= Bolt::PAL.new(config.modulepath, config.hiera_config)
|
16
|
+
def initialize(context:, **_opts)
|
17
|
+
@context = context
|
25
18
|
end
|
26
19
|
|
27
|
-
def
|
28
|
-
|
29
|
-
|
30
|
-
end
|
20
|
+
def run_task(opts)
|
21
|
+
params = opts['parameters'] || {}
|
22
|
+
options = { '_catch_errors' => true }
|
31
23
|
|
32
|
-
|
33
|
-
|
34
|
-
end
|
24
|
+
raise Bolt::ValidationError, "Task plugin requires that the 'task' is specified" unless opts['task']
|
25
|
+
task = @context.get_validated_task(opts['task'], params)
|
35
26
|
|
36
|
-
|
37
|
-
result = pal.run_task(opts['task'],
|
38
|
-
'localhost',
|
39
|
-
opts['parameters'] || {},
|
40
|
-
executor,
|
41
|
-
inventory).first
|
27
|
+
result = @context.run_local_task(task, params, options).first
|
42
28
|
|
43
29
|
raise Bolt::Error.new(result.error_hash['msg'], result.error_hash['kind']) if result.error_hash
|
44
30
|
result
|
@@ -46,45 +32,18 @@ module Bolt
|
|
46
32
|
|
47
33
|
def validate_options(opts)
|
48
34
|
raise Bolt::ValidationError, "Task plugin requires that the 'task' is specified" unless opts['task']
|
49
|
-
|
50
|
-
task = pal.task_signature(opts['task'])
|
51
|
-
|
52
|
-
raise Bolt::ValidationError, "Could not find task #{opts['task']}" unless task
|
53
|
-
|
54
|
-
errors = []
|
55
|
-
unless task.runnable_with?(opts['parameters'] || {}) { |msg| errors << msg }
|
56
|
-
# This relies on runnable with printing a partial message before the first real error
|
57
|
-
raise Bolt::ValidationError, "Invalid parameters for #{errors.join("\n")}"
|
58
|
-
end
|
35
|
+
@context.get_validated_task(opts['task'], opts['parameters'] || {})
|
59
36
|
end
|
60
|
-
alias
|
37
|
+
alias validate_resolve_reference validate_options
|
61
38
|
|
62
|
-
def
|
39
|
+
def resolve_reference(opts)
|
63
40
|
result = run_task(opts)
|
64
41
|
|
65
|
-
unless result.value.include?('
|
66
|
-
raise Bolt::ValidationError, "Task result did not return '
|
67
|
-
end
|
68
|
-
|
69
|
-
result['config']
|
70
|
-
end
|
71
|
-
|
72
|
-
def inventory_targets(opts)
|
73
|
-
raise Bolt::ValidationError, "Task plugin requires that the 'task' is specified" unless opts['task']
|
74
|
-
|
75
|
-
result = run_task(opts)
|
76
|
-
|
77
|
-
targets = result['targets']
|
78
|
-
unless targets.is_a?(Array)
|
79
|
-
raise Bolt::ValidationError, "Task result did not return a targets array: #{result.value}"
|
80
|
-
end
|
81
|
-
|
82
|
-
unless targets.all? { |t| t.is_a?(Hash) }
|
83
|
-
msg = "All targets returned by an inventory targets task must be hashes, got: #{targets}"
|
84
|
-
raise Bolt::ValidationError, msg
|
42
|
+
unless result.value.include?('value')
|
43
|
+
raise Bolt::ValidationError, "Task result did not return 'value': #{result.value}"
|
85
44
|
end
|
86
45
|
|
87
|
-
|
46
|
+
result['value']
|
88
47
|
end
|
89
48
|
|
90
49
|
def puppet_library(opts, target, apply_prep)
|
@@ -9,7 +9,7 @@ module Bolt
|
|
9
9
|
'config', 'backend']
|
10
10
|
REQ_KEYS = Set['dir', 'resource_type']
|
11
11
|
|
12
|
-
def initialize
|
12
|
+
def initialize(*_args)
|
13
13
|
@logger = Logging.logger[self]
|
14
14
|
end
|
15
15
|
|
@@ -18,7 +18,7 @@ module Bolt
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def hooks
|
21
|
-
[
|
21
|
+
[:resolve_reference]
|
22
22
|
end
|
23
23
|
|
24
24
|
def warn_missing_property(name, property)
|
@@ -41,7 +41,7 @@ module Bolt
|
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
44
|
-
def
|
44
|
+
def resolve_reference(opts)
|
45
45
|
validate_options(opts)
|
46
46
|
|
47
47
|
state = load_statefile(opts)
|
data/lib/bolt/plugin/vault.rb
CHANGED
@@ -55,16 +55,16 @@ module Bolt
|
|
55
55
|
end
|
56
56
|
|
57
57
|
def hooks
|
58
|
-
[
|
58
|
+
[:resolve_reference]
|
59
59
|
end
|
60
60
|
|
61
|
-
def initialize(config)
|
61
|
+
def initialize(config:, **_opts)
|
62
62
|
validate_config(config)
|
63
63
|
@config = config
|
64
64
|
@logger = Logging.logger[self]
|
65
65
|
end
|
66
66
|
|
67
|
-
def
|
67
|
+
def resolve_reference(opts)
|
68
68
|
validate_options(opts)
|
69
69
|
|
70
70
|
header = {
|
data/lib/bolt/secret.rb
CHANGED
@@ -3,14 +3,15 @@
|
|
3
3
|
module Bolt
|
4
4
|
class Secret
|
5
5
|
def self.execute(plugins, outputter, options)
|
6
|
+
plugin = options[:plugin] || 'pkcs7'
|
6
7
|
case options[:action]
|
7
8
|
when 'createkeys'
|
8
|
-
plugins.get_hook(
|
9
|
+
plugins.get_hook(plugin, :secret_createkeys).call
|
9
10
|
when 'encrypt'
|
10
|
-
encrypted = plugins.get_hook(
|
11
|
+
encrypted = plugins.get_hook(plugin, :secret_encrypt).call('plaintext_value' => options[:object])
|
11
12
|
outputter.print_message(encrypted)
|
12
13
|
when 'decrypt'
|
13
|
-
decrypted = plugins.get_hook(
|
14
|
+
decrypted = plugins.get_hook(plugin, :secret_decrypt).call('encrypted_value' => options[:object])
|
14
15
|
outputter.print_message(decrypted)
|
15
16
|
end
|
16
17
|
|
data/lib/bolt/secret/base.rb
CHANGED
@@ -4,7 +4,7 @@ module Bolt
|
|
4
4
|
class Secret
|
5
5
|
class Base
|
6
6
|
def hooks
|
7
|
-
%
|
7
|
+
%i[resolve_reference secret_encrypt secret_decrypt secret_createkeys validate_resolve_reference]
|
8
8
|
end
|
9
9
|
|
10
10
|
def encode(raw)
|
@@ -23,18 +23,22 @@ module Bolt
|
|
23
23
|
end
|
24
24
|
|
25
25
|
def secret_encrypt(opts)
|
26
|
-
encrypted = encrypt_value(opts['
|
26
|
+
encrypted = encrypt_value(opts['plaintext_value'])
|
27
27
|
encode(encrypted)
|
28
28
|
end
|
29
29
|
|
30
30
|
def secret_decrypt(opts)
|
31
|
-
raw, _plugin = decode(opts['
|
31
|
+
raw, _plugin = decode(opts['encrypted_value'])
|
32
32
|
decrypt_value(raw)
|
33
33
|
end
|
34
|
-
alias
|
35
|
-
|
36
|
-
def
|
37
|
-
|
34
|
+
alias resolve_reference secret_decrypt
|
35
|
+
|
36
|
+
def validate_resolve_reference(opts)
|
37
|
+
# TODO: Remove deprecation warning
|
38
|
+
if opts.include?('encrypted-value')
|
39
|
+
raise Bolt::ValidationError, "The 'encrypted-value' key is deprecated migrate to to 'encrypted_value'"
|
40
|
+
end
|
41
|
+
decode(opts['encrypted_value'])
|
38
42
|
end
|
39
43
|
end
|
40
44
|
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bolt
|
4
|
+
class Task
|
5
|
+
module Run
|
6
|
+
module_function
|
7
|
+
|
8
|
+
# TODO: we should probably use a Bolt::Task for this
|
9
|
+
def validate_params(task_signature, params)
|
10
|
+
task_signature.runnable_with?(params) do |mismatch_message|
|
11
|
+
raise Bolt::ValidationError, mismatch_message
|
12
|
+
end || (raise Bolt::ValidationError, 'Task parameters do not match')
|
13
|
+
|
14
|
+
unless Puppet::Pops::Types::TypeFactory.data.instance?(params)
|
15
|
+
# generate a helpful error message about the type-mismatch between the type Data
|
16
|
+
# and the actual type of use_args
|
17
|
+
use_args_t = Puppet::Pops::Types::TypeCalculator.infer_set(params)
|
18
|
+
desc = Puppet::Pops::Types::TypeMismatchDescriber.singleton.describe_mismatch(
|
19
|
+
'Task parameters are not of type Data. run_task()',
|
20
|
+
Puppet::Pops::Types::TypeFactory.data, use_args_t
|
21
|
+
)
|
22
|
+
raise Bolt::ValidationError, desc
|
23
|
+
end
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
|
27
|
+
def wrap_sensitive(task, params)
|
28
|
+
if (spec = task.metadata['parameters'])
|
29
|
+
params.each_with_object({}) do |(param, val), wrapped|
|
30
|
+
wrapped[param] = if spec.dig(param, 'sensitive')
|
31
|
+
Puppet::Pops::Types::PSensitiveType::Sensitive.new(val)
|
32
|
+
else
|
33
|
+
val
|
34
|
+
end
|
35
|
+
end
|
36
|
+
else
|
37
|
+
params
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def run_task(task, targets, params, options, executor)
|
42
|
+
if targets.empty?
|
43
|
+
Bolt::ResultSet.new([])
|
44
|
+
else
|
45
|
+
result = executor.run_task(targets, task, params, options)
|
46
|
+
|
47
|
+
if !result.ok && !options['_catch_errors']
|
48
|
+
raise Bolt::RunFailure.new(result, 'run_task', task.name)
|
49
|
+
end
|
50
|
+
result
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -24,8 +24,10 @@ module Bolt
|
|
24
24
|
acc[k] = opts[k] if opts.include?(k)
|
25
25
|
end
|
26
26
|
client_opts['User-Agent'] = "Bolt/#{VERSION}"
|
27
|
+
%w[token-file cacert].each do |f|
|
28
|
+
client_opts[f] = File.expand_path(client_opts[f]) if client_opts[f]
|
29
|
+
end
|
27
30
|
logger.debug("Creating orchestrator client for #{client_opts}")
|
28
|
-
|
29
31
|
@client = OrchestratorClient.new(client_opts, true)
|
30
32
|
@plan_job = start_plan(plan_context)
|
31
33
|
logger.debug("Started plan #{@plan_job}")
|
data/lib/bolt/transport/winrm.rb
CHANGED
@@ -42,6 +42,10 @@ module Bolt
|
|
42
42
|
raise Bolt::ValidationError, 'SMB file transfers are not allowed with SSL enabled'
|
43
43
|
end
|
44
44
|
|
45
|
+
if ssl_flag && (ca_path = options['cacert'])
|
46
|
+
Bolt::Util.validate_file('cacert', ca_path)
|
47
|
+
end
|
48
|
+
|
45
49
|
ssl_verify_flag = options['ssl-verify']
|
46
50
|
unless !!ssl_verify_flag == ssl_verify_flag
|
47
51
|
raise Bolt::ValidationError, 'ssl-verify option must be a Boolean true or false'
|
@@ -41,13 +41,14 @@ module Bolt
|
|
41
41
|
|
42
42
|
transport = :kerberos if target.options['realm']
|
43
43
|
endpoint = "#{scheme}://#{target.host}:#{@port}/wsman"
|
44
|
+
cacert = target.options['cacert'] && target.options['ssl'] ? File.expand_path(target.options['cacert']) : nil
|
44
45
|
options = { endpoint: endpoint,
|
45
46
|
# https://github.com/WinRb/WinRM/issues/270
|
46
47
|
user: target.options['realm'] ? 'dummy' : @user,
|
47
48
|
password: target.options['realm'] ? 'dummy' : target.password,
|
48
49
|
retry_limit: 1,
|
49
50
|
transport: transport,
|
50
|
-
ca_trust_path:
|
51
|
+
ca_trust_path: cacert,
|
51
52
|
realm: target.options['realm'],
|
52
53
|
no_ssl_peer_verification: !target.options['ssl-verify'] }
|
53
54
|
|
data/lib/bolt/util.rb
CHANGED
@@ -184,6 +184,12 @@ module Bolt
|
|
184
184
|
File.stat(File.expand_path(path))
|
185
185
|
end
|
186
186
|
|
187
|
+
def class_name_to_file_name(cls_name)
|
188
|
+
# Note this turns Bolt::CLI -> 'bolt/cli' not 'bolt/c_l_i'
|
189
|
+
# this won't handle Bolt::Inventory2Foo
|
190
|
+
cls_name.gsub(/([a-z])([A-Z])/, '\1_\2').gsub('::', '/').downcase
|
191
|
+
end
|
192
|
+
|
187
193
|
def validate_file(type, path, allow_dir = false)
|
188
194
|
stat = file_stat(path)
|
189
195
|
|
data/lib/bolt/version.rb
CHANGED
@@ -27,17 +27,21 @@ module BoltServer
|
|
27
27
|
executor: Concurrent::SingleThreadExecutor.new,
|
28
28
|
purge_interval: PURGE_INTERVAL,
|
29
29
|
purge_timeout: PURGE_TIMEOUT,
|
30
|
-
purge_ttl: PURGE_TTL
|
30
|
+
purge_ttl: PURGE_TTL,
|
31
|
+
cache_dir_mutex: Concurrent::ReadWriteLock.new,
|
32
|
+
do_purge: true)
|
31
33
|
@executor = executor
|
32
34
|
@cache_dir = config['cache-dir']
|
33
35
|
@config = config
|
34
36
|
@logger = Logging.logger[self]
|
35
|
-
@cache_dir_mutex =
|
37
|
+
@cache_dir_mutex = cache_dir_mutex
|
36
38
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
39
|
+
if do_purge
|
40
|
+
@purge = Concurrent::TimerTask.new(execution_interval: purge_interval,
|
41
|
+
timeout_interval: purge_timeout,
|
42
|
+
run_now: true) { expire(purge_ttl) }
|
43
|
+
@purge.execute
|
44
|
+
end
|
41
45
|
end
|
42
46
|
|
43
47
|
def tmppath
|
@@ -0,0 +1,44 @@
|
|
1
|
+
{
|
2
|
+
"$schema": "http://json-schema.org/draft-04/schema#",
|
3
|
+
"title": "run_script request",
|
4
|
+
"description": "POST <transport>/run_script request schema",
|
5
|
+
"type": "object",
|
6
|
+
"properties": {
|
7
|
+
"script": {
|
8
|
+
"type": "object",
|
9
|
+
"properties": {
|
10
|
+
"filename": {
|
11
|
+
"type": "string"
|
12
|
+
},
|
13
|
+
"uri": {
|
14
|
+
"type": "object",
|
15
|
+
"properties": {
|
16
|
+
"path": {
|
17
|
+
"type": "string"
|
18
|
+
},
|
19
|
+
"params": {
|
20
|
+
"type": "object"
|
21
|
+
}
|
22
|
+
},
|
23
|
+
"required": [
|
24
|
+
"path",
|
25
|
+
"params"
|
26
|
+
]
|
27
|
+
},
|
28
|
+
"sha256": {
|
29
|
+
"type": "string"
|
30
|
+
}
|
31
|
+
},
|
32
|
+
"required": [
|
33
|
+
"filename",
|
34
|
+
"uri",
|
35
|
+
"sha256"
|
36
|
+
]
|
37
|
+
},
|
38
|
+
"arguments": {
|
39
|
+
"type": "string"
|
40
|
+
},
|
41
|
+
"target": { "$ref": "partial:target-any" }
|
42
|
+
},
|
43
|
+
"required": ["script", "target"]
|
44
|
+
}
|
@@ -23,6 +23,7 @@ module BoltServer
|
|
23
23
|
action-check_node_connections
|
24
24
|
action-run_command
|
25
25
|
action-run_task
|
26
|
+
action-run_script
|
26
27
|
action-upload_file
|
27
28
|
transport-ssh
|
28
29
|
transport-winrm
|
@@ -139,7 +140,7 @@ module BoltServer
|
|
139
140
|
# but this is to be on the safe side.
|
140
141
|
parent = File.dirname(path)
|
141
142
|
FileUtils.mkdir_p(parent)
|
142
|
-
@file_cache.download_file(path, sha256, uri)
|
143
|
+
@file_cache.serial_execute { @file_cache.download_file(path, sha256, uri) }
|
143
144
|
elsif kind == 'directory'
|
144
145
|
# Create directory in cache so we can move files in.
|
145
146
|
FileUtils.mkdir_p(path)
|
@@ -148,7 +149,28 @@ module BoltServer
|
|
148
149
|
'boltserver/schema-error').to_json]
|
149
150
|
end
|
150
151
|
end
|
151
|
-
|
152
|
+
# We need to special case the scenario where only one file was
|
153
|
+
# included in the request to download. Otherwise, the call to upload_file
|
154
|
+
# will attempt to upload with a directory as a source and potentially a
|
155
|
+
# filename as a destination on the host. In that case the end result will
|
156
|
+
# be the file downloaded to a directory with the same name as the source
|
157
|
+
# filename, rather than directly to the filename set in the destination.
|
158
|
+
upload_source = if files.size == 1 && files[0]['kind'] == 'file'
|
159
|
+
File.join(cache_dir, files[0]['relative_path'])
|
160
|
+
else
|
161
|
+
cache_dir
|
162
|
+
end
|
163
|
+
[@executor.upload_file(target, upload_source, destination), nil]
|
164
|
+
end
|
165
|
+
|
166
|
+
def run_script(target, body)
|
167
|
+
error = validate_schema(@schemas["action-run_script"], body)
|
168
|
+
return [], error unless error.nil?
|
169
|
+
|
170
|
+
# Download the file onto the machine.
|
171
|
+
file_location = @file_cache.update_file(body['script'])
|
172
|
+
|
173
|
+
[@executor.run_script(target, file_location, body['arguments'])]
|
152
174
|
end
|
153
175
|
|
154
176
|
get '/' do
|
@@ -174,6 +196,7 @@ module BoltServer
|
|
174
196
|
check_node_connections
|
175
197
|
run_command
|
176
198
|
run_task
|
199
|
+
run_script
|
177
200
|
upload_file
|
178
201
|
].freeze
|
179
202
|
|