Class: Bolt::Plugin

Inherits:
Object
  • Object
show all
Defined in:
lib/bolt/plugin.rb,
lib/bolt/plugin/task.rb,
lib/bolt/plugin/pkcs7.rb,
lib/bolt/plugin/module.rb,
lib/bolt/plugin/prompt.rb,
lib/bolt/plugin/puppetdb.rb,
lib/bolt/plugin/install_agent.rb

Defined Under Namespace

Classes: InstallAgent, Module, Pkcs7, PluginContext, PluginError, Prompt, 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[install_agent task pkcs7 prompt].freeze
BUILTIN_PLUGINS =
%w[task terraform pkcs7 prompt vault aws_inventory puppetdb azure_inventory yaml].freeze
DEFAULT_PLUGIN_HOOKS =
{ 'puppet_library' => { 'plugin' => 'puppet_agent', 'stop_service' => true } }.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config, pal, analytics) ⇒ Plugin

Returns a new instance of Plugin.



154
155
156
157
158
159
160
161
162
163
164
# File 'lib/bolt/plugin.rb', line 154

def initialize(config, pal, analytics)
  @config = config
  @analytics = analytics
  @plugin_context = PluginContext.new(config, pal, self)
  @plugins = {}
  @pal = pal
  @unknown = Set.new
  @resolution_stack = []
  @unresolved_plugin_configs = config.plugins.dup
  @plugin_hooks = DEFAULT_PLUGIN_HOOKS.dup
end

Instance Attribute Details

#palObject (readonly)

Returns the value of attribute pal.



149
150
151
# File 'lib/bolt/plugin.rb', line 149

def pal
  @pal
end

#plugin_contextObject (readonly)

Returns the value of attribute plugin_context.



149
150
151
# File 'lib/bolt/plugin.rb', line 149

def plugin_context
  @plugin_context
end

#plugin_hooksObject

Returns the value of attribute plugin_hooks.



150
151
152
# File 'lib/bolt/plugin.rb', line 150

def plugin_hooks
  @plugin_hooks
end

Class Method Details

.setup(config, pal, pdb_client, analytics) ⇒ Object



122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/bolt/plugin.rb', line 122

def self.setup(config, pal, pdb_client, analytics)
  plugins = new(config, pal, analytics)

  # PDB is special because it needs the PDB client. Since it has no config,
  # we can just add it first.
  plugins.add_plugin(Bolt::Plugin::Puppetdb.new(pdb_client))

  # Initialize any plugins referenced in config. This will also indirectly
  # initialize any plugins they depend on.
  if plugins.reference?(config.plugins)
    msg = "The 'plugins' setting cannot be set by a plugin reference"
    raise PluginError.new(msg, 'bolt/plugin-error')
  end

  config.plugins.keys.each do |plugin|
    plugins.by_name(plugin)
  end

  plugins.plugin_hooks.merge!(plugins.resolve_references(config.plugin_hooks))

  plugins
end

Instance Method Details

#add_module_plugin(plugin_name) ⇒ Object



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

def add_module_plugin(plugin_name)
  opts = {
    context: @plugin_context,
    config: config_for_plugin(plugin_name)
  }

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

#add_plugin(plugin) ⇒ Object

Generally this is private. Puppetdb is special though



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



223
224
225
226
227
228
229
230
231
232
233
234
235
# File 'lib/bolt/plugin.rb', line 223

def by_name(plugin_name)
  return @plugins[plugin_name] if @plugins.include?(plugin_name)
  begin
    if RUBY_PLUGINS.include?(plugin_name)
      add_ruby_plugin(plugin_name)
    elsif !@unknown.include?(plugin_name)
      add_module_plugin(plugin_name)
    end
  rescue PluginError::Unknown
    @unknown << plugin_name
    nil
  end
end

#config_for_plugin(plugin_name) ⇒ Object



199
200
201
202
203
204
205
206
207
208
209
210
211
# File 'lib/bolt/plugin.rb', line 199

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



213
214
215
216
217
218
219
220
# File 'lib/bolt/plugin.rb', line 213

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

#modulesObject



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

def modules
  @modules ||= Bolt::Module.discover(@pal.modulepath)
end

#reference?(input) ⇒ Boolean

Checks whether a given value is a _plugin reference

Returns:

  • (Boolean)


302
303
304
# File 'lib/bolt/plugin.rb', line 302

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.



242
243
244
245
246
247
248
249
# File 'lib/bolt/plugin.rb', line 242

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.



262
263
264
265
266
267
268
269
270
271
272
273
274
275
# File 'lib/bolt/plugin.rb', line 262

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.map do |k, v|
      [k, resolve_references(v)]
    end.to_h
    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