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
@@ -0,0 +1,102 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module Bolt
|
6
|
+
class Plugin
|
7
|
+
class AwsInventory
|
8
|
+
attr_accessor :client
|
9
|
+
attr_reader :config
|
10
|
+
|
11
|
+
def initialize(config:, **_kwargs)
|
12
|
+
require 'aws-sdk-ec2'
|
13
|
+
@config = config
|
14
|
+
@logger = Logging.logger[self]
|
15
|
+
end
|
16
|
+
|
17
|
+
def name
|
18
|
+
'aws_inventory'
|
19
|
+
end
|
20
|
+
|
21
|
+
def hooks
|
22
|
+
[:resolve_reference]
|
23
|
+
end
|
24
|
+
|
25
|
+
def config_client(opts)
|
26
|
+
return client if client
|
27
|
+
|
28
|
+
options = {}
|
29
|
+
|
30
|
+
if opts.key?('region')
|
31
|
+
options[:region] = opts['region']
|
32
|
+
end
|
33
|
+
if opts.key?('profile')
|
34
|
+
options[:profile] = opts['profile']
|
35
|
+
end
|
36
|
+
|
37
|
+
if config['credentials']
|
38
|
+
creds = File.expand_path(config['credentials'])
|
39
|
+
if File.exist?(creds)
|
40
|
+
options[:credentials] = ::Aws::SharedCredentials.new(path: creds)
|
41
|
+
else
|
42
|
+
raise Bolt::ValidationError, "Cannot load credentials file #{config['credentials']}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
::Aws::EC2::Client.new(options)
|
47
|
+
end
|
48
|
+
|
49
|
+
def resolve_reference(opts)
|
50
|
+
client = config_client(opts)
|
51
|
+
resource = ::Aws::EC2::Resource.new(client: client)
|
52
|
+
|
53
|
+
# Retrieve a list of EC2 instances and create a list of targets
|
54
|
+
# Note: It doesn't seem possible to filter stubbed responses...
|
55
|
+
resource.instances(filters: opts['filters']).map do |instance|
|
56
|
+
next unless instance.state.name == 'running'
|
57
|
+
target = {}
|
58
|
+
|
59
|
+
if opts.key?('uri')
|
60
|
+
uri = lookup(instance, opts['uri'])
|
61
|
+
target['uri'] = uri if uri
|
62
|
+
end
|
63
|
+
if opts.key?('name')
|
64
|
+
real_name = lookup(instance, opts['name'])
|
65
|
+
target['name'] = real_name if real_name
|
66
|
+
end
|
67
|
+
if opts.key?('config')
|
68
|
+
target['config'] = resolve_config(instance, opts['config'])
|
69
|
+
end
|
70
|
+
|
71
|
+
target if target['uri'] || target['name']
|
72
|
+
end.compact
|
73
|
+
end
|
74
|
+
|
75
|
+
# Look for an instance attribute specified in the inventory file
|
76
|
+
def lookup(instance, attribute)
|
77
|
+
value = instance.data.respond_to?(attribute) ? instance.data[attribute] : nil
|
78
|
+
unless value
|
79
|
+
warn_missing_attribute(instance, attribute)
|
80
|
+
end
|
81
|
+
value
|
82
|
+
end
|
83
|
+
|
84
|
+
def warn_missing_attribute(instance, attribute)
|
85
|
+
@logger.warn("Could not find attribute #{attribute} of instance #{instance.instance_id}")
|
86
|
+
end
|
87
|
+
|
88
|
+
# Walk the "template" config mapping provided in the plugin config and
|
89
|
+
# replace all values with the corresponding value from the resource
|
90
|
+
# parameters.
|
91
|
+
def resolve_config(name, config_template)
|
92
|
+
Bolt::Util.walk_vals(config_template) do |value|
|
93
|
+
if value.is_a?(String)
|
94
|
+
lookup(name, value)
|
95
|
+
else
|
96
|
+
value
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -4,13 +4,15 @@ module Bolt
|
|
4
4
|
class Plugin
|
5
5
|
class InstallAgent
|
6
6
|
def hooks
|
7
|
-
%
|
7
|
+
%i[puppet_library]
|
8
8
|
end
|
9
9
|
|
10
10
|
def name
|
11
11
|
'install_agent'
|
12
12
|
end
|
13
13
|
|
14
|
+
def initialize(*args); end
|
15
|
+
|
14
16
|
def puppet_library(_opts, target, apply_prep)
|
15
17
|
install_task = apply_prep.get_task("puppet_agent::install")
|
16
18
|
service_task = apply_prep.get_task("service", 'action' => 'stop', 'name' => 'puppet')
|
@@ -0,0 +1,238 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bolt/task/run'
|
4
|
+
|
5
|
+
module Bolt
|
6
|
+
class Plugin
|
7
|
+
class Module
|
8
|
+
class InvalidPluginData < Bolt::Plugin::PluginError
|
9
|
+
def initialize(plugin, msg)
|
10
|
+
msg = "Invalid Plugin Data for #{plugin}: #{msg}"
|
11
|
+
super(msg, 'bolt/invalid-plugin-data')
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.load(name, modules, opts)
|
16
|
+
mod = modules[name]
|
17
|
+
if mod&.plugin?
|
18
|
+
opts[:mod] = mod
|
19
|
+
plugin = Bolt::Plugin::Module.new(opts)
|
20
|
+
plugin.setup
|
21
|
+
plugin
|
22
|
+
else
|
23
|
+
raise PluginError::Unknown, name
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
attr_reader :config
|
28
|
+
|
29
|
+
def initialize(mod:, context:, config:, **_opts)
|
30
|
+
@module = mod
|
31
|
+
@config = config
|
32
|
+
@context = context
|
33
|
+
end
|
34
|
+
|
35
|
+
# This method interacts with the module on disk so it's separate from initialize
|
36
|
+
def setup
|
37
|
+
@data = load_data
|
38
|
+
@config_schema = process_schema(@data['config'] || {})
|
39
|
+
|
40
|
+
@hook_map = find_hooks(@data['hooks'] || {})
|
41
|
+
|
42
|
+
validate_config(@config, @config_schema)
|
43
|
+
end
|
44
|
+
|
45
|
+
def name
|
46
|
+
@module.name
|
47
|
+
end
|
48
|
+
|
49
|
+
def hooks
|
50
|
+
(@hook_map.keys + [:validate_resolve_reference]).uniq
|
51
|
+
end
|
52
|
+
|
53
|
+
def config?
|
54
|
+
@data.include?('config') && !@data['config'].empty?
|
55
|
+
end
|
56
|
+
|
57
|
+
def load_data
|
58
|
+
JSON.parse(File.read(@module.plugin_data_file))
|
59
|
+
rescue JSON::ParserError => e
|
60
|
+
raise InvalidPluginData.new(e.message, name)
|
61
|
+
end
|
62
|
+
|
63
|
+
def process_schema(schema)
|
64
|
+
raise InvalidPluginData.new('config specification is not an object', name) unless schema.is_a?(Hash)
|
65
|
+
schema.each do |key, val|
|
66
|
+
unless key =~ /\A[a-z][a-z0-9_]*\z/
|
67
|
+
raise InvalidPluginData.new("config specification key, '#{key}', is not allowed", name)
|
68
|
+
end
|
69
|
+
|
70
|
+
unless val.is_a?(Hash) && (val['type'] || '').is_a?(String)
|
71
|
+
raise InvalidPluginData.new("config specification #{val.to_json} is not allowed", name)
|
72
|
+
end
|
73
|
+
|
74
|
+
type_string = val['type'] || 'Any'
|
75
|
+
begin
|
76
|
+
val['pcore_type'] = Puppet::Pops::Types::TypeParser.singleton.parse(type_string)
|
77
|
+
if val['pcore_type'].is_a? Puppet::Pops::Types::PTypeReferenceType
|
78
|
+
raise InvalidPluginData.new("Could not find type '#{type_string}' for #{key}", name)
|
79
|
+
end
|
80
|
+
rescue Puppet::ParseError
|
81
|
+
raise InvalidPluginData.new("Could not parse type '#{type_string}' for #{key}", name)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
schema
|
86
|
+
end
|
87
|
+
|
88
|
+
def validate_config(config, config_schema)
|
89
|
+
config.keys.each do |key|
|
90
|
+
msg = "Config for #{name} plugin contains unexpected key #{key}"
|
91
|
+
raise Bolt::ValidationError, msg unless config_schema.include?(key)
|
92
|
+
end
|
93
|
+
|
94
|
+
config_schema.each do |key, spec|
|
95
|
+
val = config[key]
|
96
|
+
|
97
|
+
unless spec['pcore_type'].instance?(val)
|
98
|
+
raise Bolt::ValidationError, "#{name} plugin expects a #{spec['type']} for key #{key}, got: #{val}"
|
99
|
+
end
|
100
|
+
val.nil?
|
101
|
+
end
|
102
|
+
nil
|
103
|
+
end
|
104
|
+
|
105
|
+
def find_hooks(hook_data)
|
106
|
+
raise InvalidPluginData.new("'hooks' must be a hash", name) unless hook_data.is_a?(Hash)
|
107
|
+
|
108
|
+
hooks = {}
|
109
|
+
# Load hooks specified in the config
|
110
|
+
hook_data.each do |hook_name, hook_spec|
|
111
|
+
unless hook_spec.is_a?(Hash) && hook_spec['task'].is_a?(String)
|
112
|
+
msg = "Unexpected hook specification #{hook_spec.to_json} in #{@name} for hook #{hook_name}"
|
113
|
+
raise InvalidPluginData.new(msg, name)
|
114
|
+
end
|
115
|
+
|
116
|
+
begin
|
117
|
+
task = @context.get_validated_task(hook_spec['task'])
|
118
|
+
rescue Bolt::Error => e
|
119
|
+
msg = if e.kind == 'bolt/unknown-task'
|
120
|
+
"Plugin #{name} specified an unkown task '#{hook_spec['task']}' for a hook"
|
121
|
+
else
|
122
|
+
"Plugin #{name} could not load task '#{hook_spec['task']}': #{e.message}"
|
123
|
+
end
|
124
|
+
raise InvalidPluginData.new(msg, name)
|
125
|
+
end
|
126
|
+
|
127
|
+
hooks[hook_name.to_sym] = { 'task' => task }
|
128
|
+
end
|
129
|
+
|
130
|
+
# Check for tasks for any hooks not already defined
|
131
|
+
(Set.new(KNOWN_HOOKS.map) - hooks.keys).each do |hook_name|
|
132
|
+
task_name = "#{name}::#{hook_name}"
|
133
|
+
begin
|
134
|
+
task = @context.get_validated_task(task_name)
|
135
|
+
rescue Bolt::Error => e
|
136
|
+
raise e unless e.kind == 'bolt/unknown-task'
|
137
|
+
end
|
138
|
+
hooks[hook_name] = { 'task' => task } if task
|
139
|
+
end
|
140
|
+
|
141
|
+
Bolt::Util.symbolize_top_level_keys(hooks)
|
142
|
+
end
|
143
|
+
|
144
|
+
def validate_params(task, params)
|
145
|
+
@context.validate_params(task.name, params)
|
146
|
+
end
|
147
|
+
|
148
|
+
def process_params(task, opts)
|
149
|
+
# opts are passed directly from inventory but all of the _ options are
|
150
|
+
# handled previously. That may not always be the case so filter them
|
151
|
+
# out now.
|
152
|
+
_meta, params = opts.partition { |key, _val| key.start_with?('_') }.map(&:to_h)
|
153
|
+
|
154
|
+
metaparams = {}
|
155
|
+
metaparams['_config'] = config if config?
|
156
|
+
metaparams['_boltdir'] = @context.boltdir
|
157
|
+
|
158
|
+
validate_params(task, params)
|
159
|
+
[params, metaparams]
|
160
|
+
end
|
161
|
+
|
162
|
+
def run_task(task, opts)
|
163
|
+
params, metaparams = process_params(task, opts)
|
164
|
+
params = params.merge(metaparams)
|
165
|
+
|
166
|
+
# There are no executor options to pass now.
|
167
|
+
options = { "_catch_errors" => true }
|
168
|
+
|
169
|
+
result = @context.run_local_task(task,
|
170
|
+
params,
|
171
|
+
options).first
|
172
|
+
|
173
|
+
raise Bolt::Error.new(result.error_hash['msg'], result.error_hash['kind']) unless result.ok
|
174
|
+
result.value
|
175
|
+
end
|
176
|
+
|
177
|
+
def run_hook(hook_name, opts, value = true)
|
178
|
+
hook = @hook_map[hook_name]
|
179
|
+
# This shouldn't happen if the Plugin api is used
|
180
|
+
raise PluginError::UnsupportedHook.new(name, hook_name) unless hook
|
181
|
+
result = run_task(hook['task'], opts)
|
182
|
+
|
183
|
+
if value
|
184
|
+
unless result.include?('value')
|
185
|
+
msg = "Plugin #{name} result did not include a value, got #{result}"
|
186
|
+
raise Bolt::Plugin::PluginError::ExecutionError.new(msg, name, hook_name)
|
187
|
+
end
|
188
|
+
|
189
|
+
result['value']
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def validate_resolve_reference(opts)
|
194
|
+
params = opts.reject { |k, _v| k.start_with?('_') }
|
195
|
+
sig = @hook_map[:resolve_reference]['task']
|
196
|
+
if sig
|
197
|
+
validate_params(sig, params)
|
198
|
+
end
|
199
|
+
|
200
|
+
if @hook_map.include?(:validate_resolve_reference)
|
201
|
+
run_hook(:validate_resolve_reference, opts, false)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
# These are all the same but are defined explicitly for clarity
|
206
|
+
def resolve_reference(opts)
|
207
|
+
run_hook(__method__, opts)
|
208
|
+
end
|
209
|
+
|
210
|
+
def secret_encrypt(opts)
|
211
|
+
run_hook(__method__, opts)
|
212
|
+
end
|
213
|
+
|
214
|
+
def secret_decrypt(opts)
|
215
|
+
run_hook(__method__, opts)
|
216
|
+
end
|
217
|
+
|
218
|
+
def secret_createkeys(opts = {})
|
219
|
+
run_hook(__method__, opts)
|
220
|
+
end
|
221
|
+
|
222
|
+
def puppet_library(opts, target, apply_prep)
|
223
|
+
tasksig = @hook_map[:puppet_library]
|
224
|
+
|
225
|
+
# this also validates
|
226
|
+
params, meta_params = process_params(tasksig, opts)
|
227
|
+
|
228
|
+
# our metaparams are meant for the task not the executor
|
229
|
+
params = params.merge(meta_params)
|
230
|
+
task = Bolt::Task.new(tasksig)
|
231
|
+
|
232
|
+
proc do
|
233
|
+
apply_prep.run_task([target], task, params).first
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
data/lib/bolt/plugin/pkcs7.rb
CHANGED
@@ -6,7 +6,7 @@ require 'fileutils'
|
|
6
6
|
module Bolt
|
7
7
|
class Plugin
|
8
8
|
class Pkcs7 < Bolt::Secret::Base
|
9
|
-
def self.validate_config(config)
|
9
|
+
def self.validate_config(config = {})
|
10
10
|
known_keys = %w[private-key public-key keysize]
|
11
11
|
known_keys.each do |key|
|
12
12
|
unless key.is_a? String
|
@@ -25,17 +25,21 @@ module Bolt
|
|
25
25
|
'pkcs7'
|
26
26
|
end
|
27
27
|
|
28
|
-
def initialize(
|
29
|
-
self.class.validate_config(
|
28
|
+
def initialize(config:, context:, **_opts)
|
29
|
+
self.class.validate_config(config)
|
30
30
|
require 'openssl'
|
31
|
-
@
|
32
|
-
@options =
|
31
|
+
@context = context
|
32
|
+
@options = config || {}
|
33
33
|
@logger = Logging.logger[self]
|
34
34
|
end
|
35
35
|
|
36
|
+
def boltdir
|
37
|
+
@context.boltdir
|
38
|
+
end
|
39
|
+
|
36
40
|
def private_key_path
|
37
41
|
path = @options['private-key'] || 'keys/private_key.pkcs7.pem'
|
38
|
-
path = File.absolute_path(path,
|
42
|
+
path = File.absolute_path(path, boltdir)
|
39
43
|
@logger.debug("Using private-key: #{path}")
|
40
44
|
path
|
41
45
|
end
|
@@ -46,7 +50,7 @@ module Bolt
|
|
46
50
|
|
47
51
|
def public_key_path
|
48
52
|
path = @options['public-key'] || 'keys/public_key.pkcs7.pem'
|
49
|
-
path = File.absolute_path(path,
|
53
|
+
path = File.absolute_path(path, boltdir)
|
50
54
|
@logger.debug("Using public-key: #{path}")
|
51
55
|
path
|
52
56
|
end
|
data/lib/bolt/plugin/prompt.rb
CHANGED
@@ -4,24 +4,21 @@ require 'concurrent/delay'
|
|
4
4
|
module Bolt
|
5
5
|
class Plugin
|
6
6
|
class Prompt
|
7
|
-
def initialize
|
8
|
-
# Might not need this
|
9
|
-
@logger = Logging.logger[self]
|
10
|
-
end
|
7
|
+
def initialize(*_args); end
|
11
8
|
|
12
9
|
def name
|
13
10
|
'prompt'
|
14
11
|
end
|
15
12
|
|
16
13
|
def hooks
|
17
|
-
[
|
14
|
+
[:resolve_reference]
|
18
15
|
end
|
19
16
|
|
20
|
-
def
|
17
|
+
def validate_resolve_reference(opts)
|
21
18
|
raise Bolt::ValidationError, "Prompt requires a 'message'" unless opts['message']
|
22
19
|
end
|
23
20
|
|
24
|
-
def
|
21
|
+
def resolve_reference(opts)
|
25
22
|
STDOUT.print "#{opts['message']}:"
|
26
23
|
value = STDIN.noecho(&:gets).chomp
|
27
24
|
STDOUT.puts
|
data/lib/bolt/plugin/puppetdb.rb
CHANGED
@@ -23,7 +23,7 @@ module Bolt
|
|
23
23
|
end
|
24
24
|
|
25
25
|
def hooks
|
26
|
-
[
|
26
|
+
[:resolve_reference]
|
27
27
|
end
|
28
28
|
|
29
29
|
def warn_missing_fact(certname, fact)
|
@@ -41,7 +41,7 @@ module Bolt
|
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
44
|
-
def
|
44
|
+
def resolve_reference(opts)
|
45
45
|
targets = @puppetdb_client.query_certnames(opts['query'])
|
46
46
|
facts = []
|
47
47
|
|