Class: Bolt::Plugin

Inherits:
Object
  • Object
show all
Defined in:
lib/bolt/plugin.rb,
lib/bolt/plugin/task.rb,
lib/bolt/plugin/cache.rb,
lib/bolt/plugin/module.rb,
lib/bolt/plugin/prompt.rb,
lib/bolt/plugin/env_var.rb,
lib/bolt/plugin/puppetdb.rb,
lib/bolt/plugin/puppet_connect_data.rb

Defined Under Namespace

Classes: Cache, EnvVar, Module, PluginContext, PluginError, Prompt, PuppetConnectData, Puppetdb, Task

Constant Summary collapse

KNOWN_HOOKS =
%i[
  puppet_library
  resolve_reference
  secret_encrypt
  secret_decrypt
  secret_createkeys
  validate_resolve_reference
].freeze
RUBY_PLUGINS =
%w[task prompt env_var puppetdb puppet_connect_data].freeze
BUILTIN_PLUGINS =
%w[task terraform pkcs7 prompt vault aws_inventory puppetdb azure_inventory
yaml env_var gcloud_inventory].freeze
DEFAULT_PLUGIN_HOOKS =
{ 'puppet_library' => { 'plugin' => 'puppet_agent', 'stop_service' => true } }.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config, pal, analytics = Bolt::Analytics::NoopClient.new, load_plugins: true) ⇒ Plugin

Returns a new instance of Plugin.



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/bolt/plugin.rb', line 138

def initialize(config, pal, analytics = Bolt::Analytics::NoopClient.new, load_plugins: true)
  @config = config
  @analytics = analytics
  @plugin_context = PluginContext.new(config, pal, self)
  @plugins = {}
  @pal = pal
  @load_plugins = load_plugins
  @unknown = Set.new
  @resolution_stack = []
  @unresolved_plugin_configs = config.plugins.dup
  # The puppetdb plugin config comes from the puppetdb section, not from
  # the plugins section
  if @unresolved_plugin_configs.key?('puppetdb')
    msg = "Configuration for the PuppetDB plugin must be in the 'puppetdb' config section, not 'plugins'"
    raise Bolt::Error.new(msg, 'bolt/plugin-error')
  end
  @unresolved_plugin_configs['puppetdb'] = config.puppetdb.merge('default'   => config.default_puppetdb,
                                                                 'instances' => config.puppetdb_instances)
end

Instance Attribute Details

#palObject (readonly)

Returns the value of attribute pal.



135
136
137
# File 'lib/bolt/plugin.rb', line 135

def pal
  @pal
end

#plugin_contextObject (readonly)

Returns the value of attribute plugin_context.



135
136
137
# File 'lib/bolt/plugin.rb', line 135

def plugin_context
  @plugin_context
end

#plugin_hooksHash[String, Hash]

Returns a map of configured plugin hooks. Any unresolved plugin references are resolved.

Returns:

  • (Hash[String, Hash])


163
164
165
# File 'lib/bolt/plugin.rb', line 163

def plugin_hooks
  @plugin_hooks ||= DEFAULT_PLUGIN_HOOKS.merge(resolve_references(@config.plugin_hooks))
end

Instance Method Details

#add_module_plugin(plugin_name) ⇒ Object



189
190
191
192
193
194
195
196
197
198
199
200
201
202
# File 'lib/bolt/plugin.rb', line 189

def add_module_plugin(plugin_name)
  opts = {
    context: @plugin_context,
    # Make sure that the plugin's config is validated _before_ the unknown-plugin
    # and loading-disabled checks. This way, we can fail early on invalid plugin
    # config instead of _after_ loading the modulepath (which can be expensive).
    config: config_for_plugin(plugin_name)
  }

  mod = modules[plugin_name]

  plugin = Bolt::Plugin::Module.load(mod, opts)
  add_plugin(plugin)
end

#add_plugin(plugin) ⇒ Object



171
172
173
# File 'lib/bolt/plugin.rb', line 171

def add_plugin(plugin)
  @plugins[plugin.name] = plugin
end

#add_ruby_plugin(plugin_name) ⇒ Object



175
176
177
178
179
180
181
182
183
184
185
186
187
# File 'lib/bolt/plugin.rb', line 175

def add_ruby_plugin(plugin_name)
  cls_name = Bolt::Util.snake_name_to_class_name(plugin_name)
  filename = "bolt/plugin/#{plugin_name}"
  require filename
  cls = Kernel.const_get("Bolt::Plugin::#{cls_name}")
  opts = {
    context: @plugin_context,
    config: config_for_plugin(plugin_name)
  }

  plugin = cls.new(**opts)
  add_plugin(plugin)
end

#by_name(plugin_name) ⇒ Object

Calling by_name or get_hook will load any module based plugin automatically



234
235
236
237
238
239
240
241
242
243
244
245
246
# File 'lib/bolt/plugin.rb', line 234

def by_name(plugin_name)
  if known_plugin?(plugin_name)
    if @plugins.include?(plugin_name)
      @plugins[plugin_name]
    elsif !@load_plugins
      raise PluginError::LoadingDisabled, plugin_name
    elsif RUBY_PLUGINS.include?(plugin_name)
      add_ruby_plugin(plugin_name)
    else
      add_module_plugin(plugin_name)
    end
  end
end

#config_for_plugin(plugin_name) ⇒ Object



204
205
206
207
208
209
210
211
212
213
214
215
216
# File 'lib/bolt/plugin.rb', line 204

def config_for_plugin(plugin_name)
  return {} unless @unresolved_plugin_configs.include?(plugin_name)
  if @resolution_stack.include?(plugin_name)
    msg = "Configuration for plugin '#{plugin_name}' depends on the plugin itself"
    raise PluginError.new(msg, 'bolt/plugin-error')
  else
    @resolution_stack.push(plugin_name)
    config = resolve_references(@unresolved_plugin_configs[plugin_name])
    @unresolved_plugin_configs.delete(plugin_name)
    @resolution_stack.pop
    config
  end
end

#get_hook(plugin_name, hook) ⇒ Object



224
225
226
227
228
229
230
231
# File 'lib/bolt/plugin.rb', line 224

def get_hook(plugin_name, hook)
  plugin = by_name(plugin_name)
  raise PluginError::Unknown, plugin_name unless plugin
  raise PluginError::UnsupportedHook.new(plugin_name, hook) unless plugin.hooks.include?(hook)
  @analytics.report_bundled_content("Plugin #{hook}", plugin_name)

  plugin.method(hook)
end

#known_plugin?(plugin_name) ⇒ Boolean

Returns:

  • (Boolean)


218
219
220
221
222
# File 'lib/bolt/plugin.rb', line 218

def known_plugin?(plugin_name)
  @plugins.include?(plugin_name) ||
    RUBY_PLUGINS.include?(plugin_name) ||
    (modules.include?(plugin_name) && modules[plugin_name].plugin?)
end

#list_pluginsObject

Loads all plugins and returns a map of plugin names to hooks.



250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
# File 'lib/bolt/plugin.rb', line 250

def list_plugins
  load_all_plugins

  hooks = KNOWN_HOOKS.map { |hook| [hook, {}] }.to_h

  @plugins.sort.each do |name, plugin|
    # Don't show the Puppet Connect plugin for now.
    next if name == 'puppet_connect_data'

    case plugin
    when Bolt::Plugin::Module
      plugin.hook_map.each do |hook, spec|
        next unless hooks.include?(hook)
        hooks[hook][name] = spec['task'].description
      end
    else
      plugin.hook_descriptions.each do |hook, description|
        hooks[hook][name] = description
      end
    end
  end

  hooks
end

#modulesObject



167
168
169
# File 'lib/bolt/plugin.rb', line 167

def modules
  @modules ||= Bolt::Module.discover(@pal.full_modulepath, @config.project)
end

#puppetdb_clientObject



286
287
288
# File 'lib/bolt/plugin.rb', line 286

def puppetdb_client
  by_name('puppetdb').puppetdb_client
end

#reference?(input) ⇒ Boolean

Checks whether a given value is a _plugin reference

Returns:

  • (Boolean)


373
374
375
# File 'lib/bolt/plugin.rb', line 373

def reference?(input)
  input.is_a?(Hash) && input.key?('_plugin')
end

#resolve_references(data) ⇒ Object

Evaluate all _plugin references in a data structure. Leaves are evaluated and then their parents are evaluated with references replaced by their values. If the result of a reference contains more references, they are resolved again before continuing to ascend the tree. The final result will not contain any references.



295
296
297
298
299
300
301
302
# File 'lib/bolt/plugin.rb', line 295

def resolve_references(data)
  Bolt::Util.postwalk_vals(data) do |value|
    reference?(value) ? resolve_references(resolve_single_reference(value)) : value
  end
rescue SystemStackError
  raise Bolt::Error.new("Stack depth exceeded while recursively resolving references.",
                        "bolt/recursive-reference-loop")
end

#resolve_top_level_references(data) ⇒ Object

Iteratively resolves “top-level” references until the result no longer has top-level references. A top-level reference is one which is not contained within another hash. It may be either the actual top-level result or arbitrarily nested within arrays. If parameters of the reference are themselves references, they will be looked. Any remaining references nested inside the result will not be evaluated once the top-level result is not a reference. This is used to resolve the ‘targets` and `groups` keys which are allowed to be references or arrays of references, but which may return data with nested references that should be resolved lazily. The end result will either be a single hash or a flat array of hashes.



315
316
317
318
319
320
321
322
323
324
325
326
327
328
# File 'lib/bolt/plugin.rb', line 315

def resolve_top_level_references(data)
  if data.is_a?(Array)
    data.flat_map { |elem| resolve_top_level_references(elem) }
  elsif reference?(data)
    partially_resolved = data.transform_values do |v|
      resolve_references(v)
    end
    fully_resolved = resolve_single_reference(partially_resolved)
    # The top-level reference may have returned more references, so repeat the process
    resolve_top_level_references(fully_resolved)
  else
    data
  end
end