bolt 2.7.0 → 2.8.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/Puppetfile +2 -1
- data/bolt-modules/boltlib/lib/puppet/functions/run_task_with.rb +192 -0
- data/lib/bolt/applicator.rb +0 -1
- data/lib/bolt/apply_inventory.rb +1 -1
- data/lib/bolt/apply_target.rb +8 -0
- data/lib/bolt/bolt_option_parser.rb +9 -2
- data/lib/bolt/cli.rb +29 -18
- data/lib/bolt/config.rb +23 -23
- data/lib/bolt/config/transport/base.rb +3 -3
- data/lib/bolt/config/transport/orch.rb +2 -2
- data/lib/bolt/config/transport/ssh.rb +1 -1
- data/lib/bolt/config/transport/winrm.rb +1 -1
- data/lib/bolt/executor.rb +16 -0
- data/lib/bolt/pal.rb +30 -13
- data/lib/bolt/plugin.rb +2 -2
- data/lib/bolt/plugin/module.rb +40 -7
- data/lib/bolt/project.rb +128 -0
- data/lib/bolt/puppetdb/config.rb +6 -6
- data/lib/bolt/secret.rb +20 -4
- data/lib/bolt/shell/bash.rb +11 -4
- data/lib/bolt/target.rb +4 -0
- data/lib/bolt/transport/base.rb +24 -8
- data/lib/bolt/transport/orch.rb +4 -0
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt_spec/bolt_context.rb +2 -2
- data/lib/bolt_spec/plans.rb +1 -1
- data/lib/bolt_spec/run.rb +8 -8
- metadata +6 -7
- data/lib/bolt/boltdir.rb +0 -54
- data/lib/bolt/plugin/pkcs7.rb +0 -104
- data/lib/bolt/secret/base.rb +0 -41
@@ -9,12 +9,12 @@ module Bolt
|
|
9
9
|
class Base
|
10
10
|
attr_reader :input
|
11
11
|
|
12
|
-
def initialize(data = {},
|
12
|
+
def initialize(data = {}, project = nil)
|
13
13
|
assert_hash_or_config(data)
|
14
14
|
@input = data
|
15
15
|
@resolved = !Bolt::Util.references?(input)
|
16
16
|
@config = resolved? ? Bolt::Util.deep_merge(defaults, filter(input)) : defaults
|
17
|
-
@
|
17
|
+
@project = project
|
18
18
|
|
19
19
|
validate if resolved?
|
20
20
|
end
|
@@ -62,7 +62,7 @@ module Bolt
|
|
62
62
|
Bolt::Util.deep_merge(acc, layer_data)
|
63
63
|
end
|
64
64
|
|
65
|
-
self.class.new(merged, @
|
65
|
+
self.class.new(merged, @project)
|
66
66
|
end
|
67
67
|
|
68
68
|
# Resolve any references in the input data, then remerge it with the defaults
|
@@ -32,12 +32,12 @@ module Bolt
|
|
32
32
|
super
|
33
33
|
|
34
34
|
if @config['cacert']
|
35
|
-
@config['cacert'] = File.expand_path(@config['cacert'], @
|
35
|
+
@config['cacert'] = File.expand_path(@config['cacert'], @project)
|
36
36
|
Bolt::Util.validate_file('cacert', @config['cacert'])
|
37
37
|
end
|
38
38
|
|
39
39
|
if @config['token-file']
|
40
|
-
@config['token-file'] = File.expand_path(@config['token-file'], @
|
40
|
+
@config['token-file'] = File.expand_path(@config['token-file'], @project)
|
41
41
|
Bolt::Util.validate_file('token-file', @config['token-file'])
|
42
42
|
end
|
43
43
|
end
|
data/lib/bolt/executor.rb
CHANGED
@@ -278,6 +278,22 @@ module Bolt
|
|
278
278
|
end
|
279
279
|
end
|
280
280
|
|
281
|
+
def run_task_with(target_mapping, task, options = {})
|
282
|
+
targets = target_mapping.keys
|
283
|
+
description = options.fetch(:description, "task #{task.name}")
|
284
|
+
|
285
|
+
log_action(description, targets) do
|
286
|
+
options[:run_as] = run_as if run_as && !options.key?(:run_as)
|
287
|
+
target_mapping.each_value { |arguments| arguments['_task'] = task.name }
|
288
|
+
|
289
|
+
batch_execute(targets) do |transport, batch|
|
290
|
+
with_node_logging("Running task #{task.name}'", batch) do
|
291
|
+
transport.batch_task_with(batch, task, target_mapping, options, &method(:publish_event))
|
292
|
+
end
|
293
|
+
end
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
281
297
|
def upload_file(targets, source, destination, options = {})
|
282
298
|
description = options.fetch(:description, "file upload from #{source} to #{destination}")
|
283
299
|
log_action(description, targets) do
|
data/lib/bolt/pal.rb
CHANGED
@@ -40,7 +40,7 @@ module Bolt
|
|
40
40
|
attr_reader :modulepath
|
41
41
|
|
42
42
|
def initialize(modulepath, hiera_config, resource_types, max_compiles = Etc.nprocessors,
|
43
|
-
trusted_external = nil, apply_settings = {})
|
43
|
+
trusted_external = nil, apply_settings = {}, project = nil)
|
44
44
|
# Nothing works without initialized this global state. Reinitializing
|
45
45
|
# is safe and in practice only happens in tests
|
46
46
|
self.class.load_puppet
|
@@ -52,6 +52,7 @@ module Bolt
|
|
52
52
|
@apply_settings = apply_settings
|
53
53
|
@max_compiles = max_compiles
|
54
54
|
@resource_types = resource_types
|
55
|
+
@project = project
|
55
56
|
|
56
57
|
@logger = Logging.logger[self]
|
57
58
|
if modulepath && !modulepath.empty?
|
@@ -114,7 +115,7 @@ module Bolt
|
|
114
115
|
compiler.evaluate_string('type PlanResult = Boltlib::PlanResult')
|
115
116
|
end
|
116
117
|
|
117
|
-
# Register all resource types defined in $
|
118
|
+
# Register all resource types defined in $Project/.resource_types as well as
|
118
119
|
# the built in types registered with the runtime_3_init method.
|
119
120
|
def register_resource_types(loaders)
|
120
121
|
static_loader = loaders.static_loader
|
@@ -136,19 +137,34 @@ module Bolt
|
|
136
137
|
# TODO: If we always call this inside a bolt_executor we can remove this here
|
137
138
|
setup
|
138
139
|
r = Puppet::Pal.in_tmp_environment('bolt', modulepath: @modulepath, facts: {}) do |pal|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
Puppet.
|
140
|
+
Puppet.override(bolt_project: @project,
|
141
|
+
yaml_plan_instantiator: Bolt::PAL::YamlPlan::Loader) do
|
142
|
+
pal.with_script_compiler do |compiler|
|
143
|
+
alias_types(compiler)
|
144
|
+
register_resource_types(Puppet.lookup(:loaders)) if @resource_types
|
145
|
+
begin
|
144
146
|
yield compiler
|
147
|
+
rescue Bolt::Error => e
|
148
|
+
e
|
149
|
+
rescue Puppet::DataBinding::LookupError => e
|
150
|
+
if e.issue_code == :HIERA_UNDEFINED_VARIABLE
|
151
|
+
message = "Interpolations are not supported in lookups outside of an apply block: #{e.message}"
|
152
|
+
PALError.new(message)
|
153
|
+
else
|
154
|
+
PALError.from_preformatted_error(e)
|
155
|
+
end
|
156
|
+
rescue Puppet::PreformattedError => e
|
157
|
+
if e.issue_code == :UNKNOWN_VARIABLE &&
|
158
|
+
%w[facts trusted server_facts settings].include?(e.arguments[:name])
|
159
|
+
message = "Evaluation Error: Variable '#{e.arguments[:name]}' is not available in the current scope "\
|
160
|
+
"unless explicitly defined. (file: #{e.file}, line: #{e.line}, column: #{e.pos})"
|
161
|
+
PALError.new(message)
|
162
|
+
else
|
163
|
+
PALError.from_preformatted_error(e)
|
164
|
+
end
|
165
|
+
rescue StandardError => e
|
166
|
+
PALError.from_preformatted_error(e)
|
145
167
|
end
|
146
|
-
rescue Bolt::Error => e
|
147
|
-
e
|
148
|
-
rescue Puppet::PreformattedError => e
|
149
|
-
PALError.from_preformatted_error(e)
|
150
|
-
rescue StandardError => e
|
151
|
-
PALError.from_preformatted_error(e)
|
152
168
|
end
|
153
169
|
end
|
154
170
|
end
|
@@ -215,6 +231,7 @@ module Bolt
|
|
215
231
|
Puppet.initialize_settings(cli)
|
216
232
|
Puppet::GettextConfig.create_default_text_domain
|
217
233
|
Puppet[:trusted_external_command] = @trusted_external
|
234
|
+
Puppet.settings[:hiera_config] = @hiera_config
|
218
235
|
self.class.configure_logging
|
219
236
|
yield
|
220
237
|
end
|
data/lib/bolt/plugin.rb
CHANGED
@@ -115,7 +115,7 @@ module Bolt
|
|
115
115
|
end
|
116
116
|
|
117
117
|
def boltdir
|
118
|
-
@config.
|
118
|
+
@config.project.path
|
119
119
|
end
|
120
120
|
end
|
121
121
|
|
@@ -142,7 +142,7 @@ module Bolt
|
|
142
142
|
plugins
|
143
143
|
end
|
144
144
|
|
145
|
-
RUBY_PLUGINS = %w[task
|
145
|
+
RUBY_PLUGINS = %w[task prompt env_var].freeze
|
146
146
|
BUILTIN_PLUGINS = %w[task terraform pkcs7 prompt vault aws_inventory puppetdb azure_inventory
|
147
147
|
yaml env_var gcloud_inventory].freeze
|
148
148
|
DEFAULT_PLUGIN_HOOKS = { 'puppet_library' => { 'plugin' => 'puppet_agent', 'stop_service' => true } }.freeze
|
data/lib/bolt/plugin/module.rb
CHANGED
@@ -30,6 +30,10 @@ module Bolt
|
|
30
30
|
@module = mod
|
31
31
|
@config = config
|
32
32
|
@context = context
|
33
|
+
|
34
|
+
if @module.name == 'pkcs7'
|
35
|
+
@config = handle_deprecated_pkcs7_keys(@config)
|
36
|
+
end
|
33
37
|
end
|
34
38
|
|
35
39
|
# This method interacts with the module on disk so it's separate from initialize
|
@@ -155,7 +159,21 @@ module Bolt
|
|
155
159
|
# handled previously. That may not always be the case so filter them
|
156
160
|
# out now.
|
157
161
|
meta, params = opts.partition { |key, _val| key.start_with?('_') }.map(&:to_h)
|
158
|
-
|
162
|
+
|
163
|
+
if task.module_name == 'pkcs7'
|
164
|
+
params = handle_deprecated_pkcs7_keys(params)
|
165
|
+
end
|
166
|
+
|
167
|
+
# Reject parameters from config that are not accepted by the task and
|
168
|
+
# merge in parameter defaults
|
169
|
+
params = if task.parameters
|
170
|
+
task.parameter_defaults
|
171
|
+
.merge(config.slice(*task.parameters.keys))
|
172
|
+
.merge(params)
|
173
|
+
else
|
174
|
+
config.merge(params)
|
175
|
+
end
|
176
|
+
|
159
177
|
validate_params(task, params)
|
160
178
|
|
161
179
|
meta['_boltdir'] = @context.boltdir.to_s
|
@@ -163,6 +181,23 @@ module Bolt
|
|
163
181
|
[params, meta]
|
164
182
|
end
|
165
183
|
|
184
|
+
# Raises a deprecation warning if the pkcs7 plugin is using deprecated keys and
|
185
|
+
# modifies the keys so they are the correct format
|
186
|
+
def handle_deprecated_pkcs7_keys(params)
|
187
|
+
if (params.key?('private-key') || params.key?('public-key')) && !@deprecation_warning_issued
|
188
|
+
@deprecation_warning_issued = true
|
189
|
+
|
190
|
+
message = "pkcs7 keys 'private-key' and 'public-key' have been deprecated and will be "\
|
191
|
+
"removed in a future version of Bolt; use 'private_key' and 'public_key' instead."
|
192
|
+
Logging.logger[self].warn(message)
|
193
|
+
end
|
194
|
+
|
195
|
+
params['private_key'] = params.delete('private-key') if params.key?('private-key')
|
196
|
+
params['public_key'] = params.delete('public-key') if params.key?('public-key')
|
197
|
+
|
198
|
+
params
|
199
|
+
end
|
200
|
+
|
166
201
|
def extract_task_parameter_schema
|
167
202
|
# Get the intersection of expected types (using Set)
|
168
203
|
type_set = @hook_map.each_with_object({}) do |(_hook, task), acc|
|
@@ -220,13 +255,11 @@ module Bolt
|
|
220
255
|
end
|
221
256
|
|
222
257
|
def validate_resolve_reference(opts)
|
223
|
-
|
224
|
-
|
225
|
-
params = merged.reject { |k, _v| k.start_with?('_') }
|
258
|
+
task = @hook_map[:resolve_reference]['task']
|
259
|
+
params, _metaparams = process_params(task, opts)
|
226
260
|
|
227
|
-
|
228
|
-
|
229
|
-
validate_params(sig, params)
|
261
|
+
if task
|
262
|
+
validate_params(task, params)
|
230
263
|
end
|
231
264
|
|
232
265
|
if @hook_map.include?(:validate_resolve_reference)
|
data/lib/bolt/project.rb
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'pathname'
|
4
|
+
require 'bolt/pal'
|
5
|
+
|
6
|
+
module Bolt
|
7
|
+
class Project
|
8
|
+
BOLTDIR_NAME = 'Boltdir'
|
9
|
+
PROJECT_SETTINGS = {
|
10
|
+
"name" => "The name of the project",
|
11
|
+
"plans" => "An array of plan names to whitelist. Whitelisted plans are included in `bolt plan show` output",
|
12
|
+
"tasks" => "An array of task names to whitelist. Whitelisted plans are included in `bolt task show` output"
|
13
|
+
}.freeze
|
14
|
+
|
15
|
+
attr_reader :path, :config_file, :inventory_file, :modulepath, :hiera_config,
|
16
|
+
:puppetfile, :rerunfile, :type, :resource_types
|
17
|
+
|
18
|
+
def self.default_project
|
19
|
+
Project.new(File.join('~', '.puppetlabs', 'bolt'), 'user')
|
20
|
+
end
|
21
|
+
|
22
|
+
# Search recursively up the directory hierarchy for the Project. Look for a
|
23
|
+
# directory called Boltdir or a file called bolt.yaml (for a control repo
|
24
|
+
# type Project). Otherwise, repeat the check on each directory up the
|
25
|
+
# hierarchy, falling back to the default if we reach the root.
|
26
|
+
def self.find_boltdir(dir)
|
27
|
+
dir = Pathname.new(dir)
|
28
|
+
if (dir + BOLTDIR_NAME).directory?
|
29
|
+
new(dir + BOLTDIR_NAME, 'embedded')
|
30
|
+
elsif (dir + 'bolt.yaml').file?
|
31
|
+
new(dir, 'local')
|
32
|
+
elsif dir.root?
|
33
|
+
default_project
|
34
|
+
else
|
35
|
+
find_boltdir(dir.parent)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def initialize(path, type = 'option')
|
40
|
+
@path = Pathname.new(path).expand_path
|
41
|
+
@config_file = @path + 'bolt.yaml'
|
42
|
+
@inventory_file = @path + 'inventory.yaml'
|
43
|
+
@modulepath = [(@path + 'modules').to_s, (@path + 'site-modules').to_s, (@path + 'site').to_s]
|
44
|
+
@hiera_config = @path + 'hiera.yaml'
|
45
|
+
@puppetfile = @path + 'Puppetfile'
|
46
|
+
@rerunfile = @path + '.rerun.json'
|
47
|
+
@resource_types = @path + '.resource_types'
|
48
|
+
@type = type
|
49
|
+
|
50
|
+
@project_file = @path + 'project.yaml'
|
51
|
+
@data = Bolt::Util.read_optional_yaml_hash(File.expand_path(@project_file), 'project') || {}
|
52
|
+
validate if load_as_module?
|
53
|
+
end
|
54
|
+
|
55
|
+
def to_s
|
56
|
+
@path.to_s
|
57
|
+
end
|
58
|
+
|
59
|
+
# This API is used to prepend the project as a module to Puppet's internal
|
60
|
+
# module_references list. CHANGE AT YOUR OWN RISK
|
61
|
+
def to_h
|
62
|
+
{ path: @path, name: name }
|
63
|
+
end
|
64
|
+
|
65
|
+
def eql?(other)
|
66
|
+
path == other.path
|
67
|
+
end
|
68
|
+
alias == eql?
|
69
|
+
|
70
|
+
def load_as_module?
|
71
|
+
@project_file.file?
|
72
|
+
end
|
73
|
+
|
74
|
+
def name
|
75
|
+
# If the project is in mymod/Boltdir/project.yaml, use mymod as the project name
|
76
|
+
dirname = @path.basename.to_s == 'Boltdir' ? @path.parent.basename.to_s : @path.basename.to_s
|
77
|
+
pname = @data['name'] || dirname
|
78
|
+
pname.include?('-') ? pname.split('-', 2)[1] : pname
|
79
|
+
end
|
80
|
+
|
81
|
+
def tasks
|
82
|
+
@data['tasks']
|
83
|
+
end
|
84
|
+
|
85
|
+
def plans
|
86
|
+
@data['plans']
|
87
|
+
end
|
88
|
+
|
89
|
+
def project_directory_name?(name)
|
90
|
+
# it must match an installed project name according to forge validator
|
91
|
+
name =~ /^[a-z][a-z0-9_]*$/
|
92
|
+
end
|
93
|
+
|
94
|
+
def project_namespaced_name?(name)
|
95
|
+
# it must match the full project name according to forge validator
|
96
|
+
name =~ /^[a-zA-Z0-9]+[-][a-z][a-z0-9_]*$/
|
97
|
+
end
|
98
|
+
|
99
|
+
def validate
|
100
|
+
n = @data['name']
|
101
|
+
if n && !project_directory_name?(n) && !project_namespaced_name?(n)
|
102
|
+
raise Bolt::ValidationError, <<~ERROR_STRING
|
103
|
+
Invalid project name '#{n}' in project.yaml; project names must match either:
|
104
|
+
An installed project name (ex. projectname) matching the expression /^[a-z][a-z0-9_]*$/ -or-
|
105
|
+
A namespaced project name (ex. author-projectname) matching the expression /^[a-zA-Z0-9]+[-][a-z][a-z0-9_]*$/
|
106
|
+
ERROR_STRING
|
107
|
+
elsif !project_directory_name?(name) && !project_namespaced_name?(name)
|
108
|
+
raise Bolt::ValidationError, <<~ERROR_STRING
|
109
|
+
Invalid project name '#{name}'; project names must match either:
|
110
|
+
A project name (ex. projectname) matching the expression /^[a-z][a-z0-9_]*$/ -or-
|
111
|
+
A namespaced project name (ex. author-projectname) matching the expression /^[a-zA-Z0-9]+[-][a-z][a-z0-9_]*$/
|
112
|
+
|
113
|
+
Configure project name in <project_dir>/project.yaml
|
114
|
+
ERROR_STRING
|
115
|
+
# If the project name is the same as one of the built-in modules raise a warning
|
116
|
+
elsif Dir.children(Bolt::PAL::BOLTLIB_PATH).include?(name)
|
117
|
+
raise Bolt::ValidationError, "The project '#{name}' will not be loaded. The project name conflicts "\
|
118
|
+
"with a built-in Bolt module of the same name."
|
119
|
+
end
|
120
|
+
|
121
|
+
%w[tasks plans].each do |conf|
|
122
|
+
unless @data.fetch(conf, []).is_a?(Array)
|
123
|
+
raise Bolt::ValidationError, "'#{conf}' in project.yaml must be an array"
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
data/lib/bolt/puppetdb/config.rb
CHANGED
@@ -22,7 +22,7 @@ module Bolt
|
|
22
22
|
File.expand_path(File.join(Dir::COMMON_APPDATA, 'PuppetLabs/client-tools/puppetdb.conf'))
|
23
23
|
end
|
24
24
|
|
25
|
-
def self.load_config(filename, options,
|
25
|
+
def self.load_config(filename, options, project_path = nil)
|
26
26
|
config = {}
|
27
27
|
global_path = Bolt::Util.windows? ? default_windows_config : DEFAULT_CONFIG[:global]
|
28
28
|
if filename
|
@@ -46,12 +46,12 @@ module Bolt
|
|
46
46
|
end
|
47
47
|
|
48
48
|
config = config.fetch('puppetdb', {})
|
49
|
-
new(config.merge(options),
|
49
|
+
new(config.merge(options), project_path)
|
50
50
|
end
|
51
51
|
|
52
|
-
def initialize(settings,
|
52
|
+
def initialize(settings, project_path = nil)
|
53
53
|
@settings = settings
|
54
|
-
@
|
54
|
+
@project_path = project_path
|
55
55
|
expand_paths
|
56
56
|
end
|
57
57
|
|
@@ -71,8 +71,8 @@ module Bolt
|
|
71
71
|
def expand_paths
|
72
72
|
%w[cacert cert key token].each do |file|
|
73
73
|
next unless @settings[file]
|
74
|
-
@settings[file] = if @
|
75
|
-
File.expand_path(@settings[file], @
|
74
|
+
@settings[file] = if @project_path
|
75
|
+
File.expand_path(@settings[file], @project_path)
|
76
76
|
else
|
77
77
|
File.expand_path(@settings[file])
|
78
78
|
end
|
data/lib/bolt/secret.rb
CHANGED
@@ -1,17 +1,33 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'bolt/plugin'
|
4
|
+
|
3
5
|
module Bolt
|
4
6
|
class Secret
|
7
|
+
KNOWN_KEYS = {
|
8
|
+
'createkeys' => %w[keysize private_key public_key],
|
9
|
+
'encrypt' => %w[public_key],
|
10
|
+
'decrypt' => %w[private_key public_key]
|
11
|
+
}.freeze
|
12
|
+
|
5
13
|
def self.execute(plugins, outputter, options)
|
6
|
-
|
14
|
+
name = options[:plugin] || 'pkcs7'
|
15
|
+
plugin = plugins.by_name(name)
|
16
|
+
|
17
|
+
unless plugin
|
18
|
+
raise Bolt::Plugin::PluginError::Unknown, name
|
19
|
+
end
|
20
|
+
|
7
21
|
case options[:action]
|
8
22
|
when 'createkeys'
|
9
|
-
|
23
|
+
opts = { 'force' => options[:force] }.compact
|
24
|
+
result = plugins.get_hook(name, :secret_createkeys).call(opts)
|
25
|
+
outputter.print_message(result)
|
10
26
|
when 'encrypt'
|
11
|
-
encrypted = plugins.get_hook(
|
27
|
+
encrypted = plugins.get_hook(name, :secret_encrypt).call('plaintext_value' => options[:object])
|
12
28
|
outputter.print_message(encrypted)
|
13
29
|
when 'decrypt'
|
14
|
-
decrypted = plugins.get_hook(
|
30
|
+
decrypted = plugins.get_hook(name, :secret_decrypt).call('encrypted_value' => options[:object])
|
15
31
|
outputter.print_message(decrypted)
|
16
32
|
end
|
17
33
|
|