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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b5c70a9312d9fa6f384b140fec6cbc3c173906e9b428e0d3b19faaab91cf6590
4
- data.tar.gz: 10fe1a0b13b4bd489cd41a9b3971d6e5a8f797fced7ddd1faeaf33c09c9f3e28
3
+ metadata.gz: e2159fe8eb6cf9a69b3ff093bf61d45f00ac4dbdfab37c412165da8c642c0909
4
+ data.tar.gz: 34e56147d83f6894dbc6baa73ad571213d1964c86af61bfff9c4b8a75a248923
5
5
  SHA512:
6
- metadata.gz: 26f36949b058636604697ba4804e1aeb3068c94dd49890c19f336bb9afabe3256f5383661e876881d51b95c667d6c9f4f6bf149566d9d06e54c21456a6de1301
7
- data.tar.gz: cc48faf53683fac36548175e42466a80330580942b48f3bdfd2fae060e1e91eef67b289c1e751e58bde2be3ab459bd52a5a2460e3c108468b77fc82ff4c37800
6
+ metadata.gz: 05b79d3c523be35edb9230222301ab4328fe3907a3ab1199d4f6dd3e2037b30182dbfb08436079e728f92d9beabf8890911acfdca99551755cc5dd45062dccf0
7
+ data.tar.gz: 3fd5a1a1a54b338ca99550f3f83720a50eba7a61195fff12fedffc336fd84533f23d836d1d6efca083cbba27b60f223f6c2cb2536a71515acb8569aa223beaf2
@@ -70,9 +70,7 @@ Puppet::Functions.create_function(:run_plan, Puppet::Functions::InternalFunction
70
70
 
71
71
  # TODO: Why would we not have a private_environment_loader?
72
72
  unless loader && (func = loader.load(:plan, plan_name))
73
- raise Puppet::ParseErrorWithIssue.from_issue_and_stack(
74
- Puppet::Pops::Issues.issue(:UNKNOWN_PLAN) { Bolt::Error.unknown_plan(plan_name) }
75
- )
73
+ raise Bolt::Error.unknown_plan(plan_name)
76
74
  end
77
75
 
78
76
  if (run_as = named_args['_run_as'])
@@ -77,7 +77,7 @@ Puppet::Functions.create_function(:run_task) do
77
77
  # TODO: use the compiler injection once PUP-8237 lands
78
78
  task_signature = Puppet::Pal::ScriptCompiler.new(closure_scope.compiler).task_signature(task_name)
79
79
  if task_signature.nil?
80
- raise with_stack(:UNKNOWN_TASK, Bolt::Error.unknown_task(task_name))
80
+ raise Bolt::Error.unknown_task(task_name)
81
81
  end
82
82
 
83
83
  task_signature.runnable_with?(use_args) do |mismatch_message|
@@ -62,7 +62,7 @@ module Bolt
62
62
  { flags: ACTION_OPTS + %w[tmpdir],
63
63
  banner: SCRIPT_HELP }
64
64
  when 'secret'
65
- { flags: OPTIONS[:global] + OPTIONS[:global_config_setters],
65
+ { flags: OPTIONS[:global] + OPTIONS[:global_config_setters] + %w[plugin],
66
66
  banner: SECRET_HELP }
67
67
  when 'task'
68
68
  case action
@@ -261,6 +261,7 @@ module Bolt
261
261
  HELP
262
262
 
263
263
  SECRET_HELP = <<~SECRET_HELP
264
+ Usage: bolt secret <action> <value>
264
265
  Manage secrets for inventory and hiera data.
265
266
 
266
267
  Available actions are:
@@ -446,6 +447,10 @@ module Bolt
446
447
  define('--debug', 'Display debug logging') do |_|
447
448
  @options[:debug] = true
448
449
  end
450
+
451
+ define('--plugin PLUGIN', 'Select the plugin to use') do |plug|
452
+ @options[:plugin] = plug
453
+ end
449
454
  end
450
455
 
451
456
  def remove_excluded_opts(option_list)
data/lib/bolt/cli.rb CHANGED
@@ -244,7 +244,7 @@ module Bolt
244
244
  end
245
245
 
246
246
  def plugins
247
- @plugins ||= Bolt::Plugin.setup(config, puppetdb_client, analytics)
247
+ @plugins ||= Bolt::Plugin.setup(config, pal, puppetdb_client, analytics)
248
248
  end
249
249
 
250
250
  def query_puppetdb_nodes(query)
data/lib/bolt/error.rb CHANGED
@@ -33,11 +33,15 @@ module Bolt
33
33
  end
34
34
 
35
35
  def self.unknown_task(task)
36
- "Could not find a task named \"#{task}\". For a list of available tasks, run \"bolt task show\""
36
+ new("Could not find a task named \"#{task}\". For a list of available tasks, run \"bolt task show\"",
37
+ 'bolt/unknown-task')
37
38
  end
38
39
 
39
40
  def self.unknown_plan(plan)
40
- "Could not find a plan named \"#{plan}\". For a list of available plans, run \"bolt plan show\""
41
+ new(
42
+ "Could not find a plan named \"#{plan}\". For a list of available plans, run \"bolt plan show\"",
43
+ 'bolt/unknown-plan'
44
+ )
41
45
  end
42
46
  end
43
47
 
@@ -102,13 +102,13 @@ module Bolt
102
102
  if value.is_a?(Hash) && value.include?('_plugin')
103
103
  plugin_name = value['_plugin']
104
104
  begin
105
- hook = @plugins.get_hook(plugin_name, :inventory_config)
105
+ hook = @plugins.get_hook(plugin_name, :resolve_reference)
106
106
  rescue Bolt::Plugin::PluginError => e
107
107
  raise ValidationError.new(e.message, @name)
108
108
  end
109
109
 
110
110
  begin
111
- validate_proc = @plugins.get_hook(plugin_name, :validate_inventory_config)
111
+ validate_proc = @plugins.get_hook(plugin_name, :validate_resolve_reference)
112
112
  rescue Bolt::Plugin::PluginError
113
113
  validate_proc = proc { |*args| }
114
114
  end
@@ -119,7 +119,7 @@ module Bolt
119
119
  begin
120
120
  hook.call(value)
121
121
  rescue StandardError => e
122
- loc = "inventory_config in #{@name}"
122
+ loc = "resolve_reference in #{@name}"
123
123
  raise Bolt::Plugin::PluginError::ExecutionError.new(e.message, plugin_name, loc)
124
124
  end
125
125
  end
@@ -213,7 +213,7 @@ module Bolt
213
213
 
214
214
  def lookup_targets(lookup)
215
215
  begin
216
- hook = @plugins.get_hook(lookup['_plugin'], :inventory_targets)
216
+ hook = @plugins.get_hook(lookup['_plugin'], :resolve_reference)
217
217
  rescue Bolt::Plugin::PluginError => e
218
218
  raise ValidationError.new(e.message, @name)
219
219
  end
@@ -221,7 +221,7 @@ module Bolt
221
221
  begin
222
222
  targets = hook.call(lookup)
223
223
  rescue StandardError => e
224
- loc = "inventory_targets in #{@name}"
224
+ loc = "resolve_reference in #{@name}"
225
225
  raise Bolt::Plugin::PluginError::ExecutionError.new(e.message, lookup['_plugin'], loc)
226
226
  end
227
227
 
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Is this Bolt::Pobs::Module?
4
+ module Bolt
5
+ class Module
6
+ MODULE_NAME_REGEX = /\A[a-z][a-z0-9_]*\z/.freeze
7
+
8
+ def self.discover(modulepath)
9
+ modulepath.each_with_object({}) do |path, mods|
10
+ next unless File.exist?(path) && File.directory?(path)
11
+ Dir.children(path)
12
+ .map { |dir| File.join(path, dir) }
13
+ .select { |dir| File.directory?(dir) }
14
+ .each do |dir|
15
+ module_name = File.basename(dir)
16
+ if module_name =~ MODULE_NAME_REGEX
17
+ # Puppet will load some objects from shadowed modules but this won't
18
+ mods[module_name] ||= Bolt::Module.new(module_name, dir)
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ attr_reader :name, :path
25
+
26
+ def initialize(name, path)
27
+ @name = name
28
+ @path = path
29
+ end
30
+
31
+ def plugin_data_file
32
+ File.join(path, 'bolt_plugin.json')
33
+ end
34
+
35
+ def plugin?
36
+ File.exist?(plugin_data_file)
37
+ end
38
+ end
39
+ end
data/lib/bolt/pal.rb CHANGED
@@ -41,12 +41,9 @@ module Bolt
41
41
 
42
42
  def initialize(modulepath, hiera_config, max_compiles = Etc.nprocessors)
43
43
  # Nothing works without initialized this global state. Reinitializing
44
- # is safe and in practice only happen in tests
44
+ # is safe and in practice only happens in tests
45
45
  self.class.load_puppet
46
46
 
47
- # This makes sure we don't accidentally create puppet dirs
48
- with_puppet_settings { |_| nil }
49
-
50
47
  @original_modulepath = modulepath
51
48
  @modulepath = [BOLTLIB_PATH, *modulepath, MODULES_PATH]
52
49
  @hiera_config = hiera_config
@@ -56,6 +53,8 @@ module Bolt
56
53
  if modulepath && !modulepath.empty?
57
54
  @logger.info("Loading modules from #{@modulepath.join(File::PATH_SEPARATOR)}")
58
55
  end
56
+
57
+ @loaded = false
59
58
  end
60
59
 
61
60
  # Puppet logging is global so this is class method to avoid confusion
@@ -91,6 +90,17 @@ module Bolt
91
90
  Bolt::ResultSet.include_iterable
92
91
  end
93
92
 
93
+ def setup
94
+ unless @loaded
95
+ # This is slow so don't do it until we have to
96
+ Bolt::PAL.load_puppet
97
+
98
+ # Make sure we don't create the puppet directories
99
+ with_puppet_settings { |_| nil }
100
+ @loaded = true
101
+ end
102
+ end
103
+
94
104
  # Create a top-level alias for TargetSpec and PlanResult so that users don't have to
95
105
  # namespace it with Boltlib, which is just an implementation detail. This
96
106
  # allows them to feel like a built-in type in bolt, rather than
@@ -104,6 +114,8 @@ module Bolt
104
114
  # exceptions thrown by the block and re-raises them ensuring they are
105
115
  # Bolt::Errors since the script compiler block will squash all exceptions.
106
116
  def in_bolt_compiler
117
+ # TODO: If we always call this inside a bolt_executor we can remove this here
118
+ setup
107
119
  r = Puppet::Pal.in_tmp_environment('bolt', modulepath: @modulepath, facts: {}) do |pal|
108
120
  pal.with_script_compiler do |compiler|
109
121
  alias_types(compiler)
@@ -129,6 +141,7 @@ module Bolt
129
141
  end
130
142
 
131
143
  def with_bolt_executor(executor, inventory, pdb_client = nil, applicator = nil, &block)
144
+ setup
132
145
  opts = {
133
146
  bolt_executor: executor,
134
147
  bolt_inventory: inventory,
@@ -188,6 +201,7 @@ module Bolt
188
201
  # Parses a snippet of Puppet manifest code and returns the AST represented
189
202
  # in JSON.
190
203
  def parse_manifest(code, filename)
204
+ setup
191
205
  Puppet::Pops::Parser::EvaluatingParser.new.parse_string(code, filename)
192
206
  rescue Puppet::Error => e
193
207
  raise Bolt::PAL::PALError, "Failed to parse manifest: #{e}"
@@ -250,7 +264,7 @@ module Bolt
250
264
  task = task_signature(task_name)
251
265
 
252
266
  if task.nil?
253
- raise Bolt::Error.new(Bolt::Error.unknown_task(task_name), 'bolt/unknown-task')
267
+ raise Bolt::Error.unknown_task(task_name)
254
268
  end
255
269
 
256
270
  task.task_hash.reject { |k, _| k == 'parameters' }
@@ -296,7 +310,7 @@ module Bolt
296
310
  end
297
311
 
298
312
  if plan_info.nil?
299
- raise Bolt::Error.new(Bolt::Error.unknown_plan(plan_name), 'bolt/unknown-plan')
313
+ raise Bolt::Error.unknown_plan(plan_name)
300
314
  end
301
315
  plan_info
302
316
  end
data/lib/bolt/plugin.rb CHANGED
@@ -1,20 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'bolt/inventory'
4
+ require 'bolt/executor'
5
+ require 'bolt/module'
6
+ require 'bolt/pal'
3
7
  require 'bolt/plugin/puppetdb'
4
- require 'bolt/plugin/terraform'
5
- require 'bolt/plugin/pkcs7'
6
- require 'bolt/plugin/prompt'
7
- require 'bolt/plugin/task'
8
- require 'bolt/plugin/aws'
9
- require 'bolt/plugin/vault'
10
- require 'bolt/plugin/install_agent'
11
8
 
12
9
  module Bolt
13
10
  class Plugin
11
+ KNOWN_HOOKS = %i[
12
+ puppet_library
13
+ resolve_reference
14
+ secret_encrypt
15
+ secret_decrypt
16
+ secret_createkeys
17
+ validate_resolve_reference
18
+ ].freeze
19
+
14
20
  class PluginError < Bolt::Error
15
21
  class ExecutionError < PluginError
16
22
  def initialize(msg, plugin_name, location)
17
- mess = "Error executing plugin: #{plugin_name} from #{location}: #{msg}"
23
+ mess = "Error executing plugin #{plugin_name} from #{location}: #{msg}"
18
24
  super(mess, 'bolt/plugin-error')
19
25
  end
20
26
  end
@@ -32,40 +38,180 @@ module Bolt
32
38
  end
33
39
  end
34
40
 
35
- def self.setup(config, pdb_client, analytics)
36
- plugins = new(config, analytics)
41
+ class PluginContext
42
+ def initialize(config, pal)
43
+ @pal = pal
44
+ @config = config
45
+ end
46
+
47
+ def serial_executor
48
+ @serial_executor ||= Bolt::Executor.new(1)
49
+ end
50
+ private :serial_executor
51
+
52
+ def empty_inventory
53
+ @empty_inventory ||= Bolt::Inventory.new({}, @config)
54
+ end
55
+ private :empty_inventory
56
+
57
+ def with_a_compiler
58
+ # If we're already inside a pal compiler block use that compiler
59
+ # This may blow up if you try to load a task in catalog pal. Should we
60
+ # guard against that?
61
+ compiler = nil
62
+ if defined?(Puppet)
63
+ begin
64
+ compiler = Puppet.lookup(:pal_compiler)
65
+ rescue Puppet::Context::UndefinedBindingError; end # rubocop:disable Lint/HandleExceptions
66
+ end
67
+
68
+ if compiler
69
+ yield compiler
70
+ else
71
+ @pal.in_bolt_compiler do |temp_compiler|
72
+ yield temp_compiler
73
+ end
74
+ end
75
+ end
76
+ private :with_a_compiler
77
+
78
+ def get_validated_task(task_name, params = nil)
79
+ with_a_compiler do |compiler|
80
+ tasksig = compiler.task_signature(task_name)
81
+
82
+ raise Bolt::Error.unknown_task(task_name) unless tasksig
83
+
84
+ Bolt::Task::Run.validate_params(tasksig, params) if params
85
+ Bolt::Task.new(tasksig.task_hash)
86
+ end
87
+ end
88
+
89
+ def validate_params(task_name, params)
90
+ with_a_compiler do |compiler|
91
+ tasksig = compiler.task_signature(task_name)
92
+
93
+ raise Bolt::Error.new("#{task_name} could not be found", 'bolt/plugin-error') unless tasksig
94
+
95
+ Bolt::Task::Run.validate_params(tasksig, params)
96
+ end
97
+ nil
98
+ end
99
+
100
+ # By passing `_` keys in params the caller can send metaparams directly to the task
101
+ # _catch_errors must be passed as an executor option not a param
102
+ def run_local_task(task, params, options)
103
+ # Make sure we're in a compiler to use the sensitive type
104
+ with_a_compiler do |_comp|
105
+ params = Bolt::Task::Run.wrap_sensitive(task, params)
106
+ Bolt::Task::Run.run_task(
107
+ task,
108
+ empty_inventory.get_targets('localhost'),
109
+ params,
110
+ options,
111
+ serial_executor
112
+ )
113
+ end
114
+ end
115
+
116
+ def boltdir
117
+ @config.boltdir.path
118
+ end
119
+ end
120
+
121
+ def self.setup(config, pal, pdb_client, analytics)
122
+ plugins = new(config, pal, analytics)
123
+ # PDB is special do we want to expose the default client to the context?
37
124
  plugins.add_plugin(Bolt::Plugin::Puppetdb.new(pdb_client))
38
- plugins.add_plugin(Bolt::Plugin::Terraform.new)
39
- plugins.add_plugin(Bolt::Plugin::Prompt.new)
40
- plugins.add_plugin(Bolt::Plugin::Pkcs7.new(config.boltdir.path, config.plugins['pkcs7'] || {}))
41
- plugins.add_plugin(Bolt::Plugin::Task.new(config))
42
- plugins.add_plugin(Bolt::Plugin::Aws::EC2.new(config.plugins['aws'] || {}))
43
- plugins.add_plugin(Bolt::Plugin::Vault.new(config.plugins['vault'] || {}))
44
- plugins.add_plugin(Bolt::Plugin::InstallAgent.new)
125
+
126
+ plugins.add_ruby_plugin('Bolt::Plugin::AwsInventory')
127
+ plugins.add_ruby_plugin('Bolt::Plugin::InstallAgent')
128
+ plugins.add_ruby_plugin('Bolt::Plugin::Task')
129
+ plugins.add_ruby_plugin('Bolt::Plugin::Terraform')
130
+ plugins.add_ruby_plugin('Bolt::Plugin::Pkcs7')
131
+ plugins.add_ruby_plugin('Bolt::Plugin::Prompt')
132
+ plugins.add_ruby_plugin('Bolt::Plugin::Vault')
133
+
45
134
  plugins
46
135
  end
47
136
 
48
- def initialize(config, analytics)
137
+ BUILTIN_PLUGINS = %w[task terraform pkcs7 prompt vault aws_inventory puppetdb].freeze
138
+
139
+ attr_reader :pal, :plugin_context
140
+
141
+ def initialize(config, pal, analytics)
49
142
  @config = config
50
143
  @analytics = analytics
144
+ @plugin_context = PluginContext.new(config, pal)
51
145
  @plugins = {}
146
+ @unknown = Set.new
147
+ end
148
+
149
+ def modules
150
+ @modules ||= Bolt::Module.discover(@config.modulepath)
52
151
  end
53
152
 
153
+ # Generally this is private. Puppetdb is special though
54
154
  def add_plugin(plugin)
55
155
  @plugins[plugin.name] = plugin
56
156
  end
57
157
 
158
+ def add_ruby_plugin(cls_name)
159
+ snake_name = Bolt::Util.class_name_to_file_name(cls_name)
160
+ require snake_name
161
+ cls = Kernel.const_get(cls_name)
162
+ plugin_name = snake_name.split('/').last
163
+ opts = {
164
+ context: @plugin_context,
165
+ config: config_for_plugin(plugin_name)
166
+ }
167
+
168
+ plugin = cls.new(**opts)
169
+ add_plugin(plugin)
170
+ end
171
+
172
+ def add_module_plugin(plugin_name)
173
+ opts = {
174
+ context: @plugin_context,
175
+ config: config_for_plugin(plugin_name)
176
+ }
177
+
178
+ plugin = Bolt::Plugin::Module.load(plugin_name, modules, opts)
179
+ add_plugin(plugin)
180
+ end
181
+
182
+ def add_from_config
183
+ @config.plugins.keys.each do |plugin_name|
184
+ by_name(plugin_name)
185
+ end
186
+ end
187
+
188
+ def config_for_plugin(plugin_name)
189
+ @config.plugins[plugin_name] || {}
190
+ end
191
+
58
192
  def get_hook(plugin_name, hook)
59
193
  plugin = by_name(plugin_name)
60
194
  raise PluginError::Unknown, plugin_name unless plugin
61
- raise PluginError::UnsupportedHook.new(plugin_name, hook) unless plugin.respond_to?(hook)
62
- @analytics.report_bundled_content("Plugin #{hook}", plugin_name)
195
+ raise PluginError::UnsupportedHook.new(plugin_name, hook) unless plugin.hooks.include?(hook)
196
+ @analytics.report_bundled_content("Plugin #{hook}", plugin_name) if BUILTIN_PLUGINS.include?(plugin_name)
63
197
 
64
198
  plugin.method(hook)
65
199
  end
66
200
 
201
+ # Calling by_name or get_hook will load any module based plugin automatically
67
202
  def by_name(plugin_name)
68
- @plugins[plugin_name]
203
+ return @plugins[plugin_name] if @plugins.include?(plugin_name)
204
+ begin
205
+ unless @unknown.include?(plugin_name)
206
+ add_module_plugin(plugin_name)
207
+ end
208
+ rescue PluginError::Unknown
209
+ @unknown << plugin_name
210
+ nil
211
+ end
69
212
  end
70
213
  end
71
214
  end
215
+
216
+ # references PluginError
217
+ require 'bolt/plugin/module'