Skip to content

Commit

Permalink
(puppetlabsGH-1934) Add support for selective sync of plugins to targ…
Browse files Browse the repository at this point in the history
…et nodes

* **Added support for selective sync of plugins to target** ([puppetlabs#1934](puppetlabs#1934))

This feature allows a plan author ti specificy what plugins need to be synced to the target node. You can do this by add an array of module names or an array of regular expressions to the config of a target. Here is an example:

    $target.set_config('selective_sync', ['stdlib'])
    apply($target) {
      notice 'Hallo'
    }

The apply block here ony sync’s the stdlib module to the target.
  • Loading branch information
hajee committed Jun 24, 2020
1 parent 3826576 commit 05b0a1f
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 16 deletions.
40 changes: 25 additions & 15 deletions lib/bolt/applicator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ def initialize(inventory, executor, modulepath, plugin_dirs, project,
pdb_client, hiera_config, max_compiles, apply_settings)
# lazy-load expensive gem code
require 'concurrent'

@inventory = inventory
@executor = executor
@modulepath = modulepath || []
Expand All @@ -30,23 +29,16 @@ def initialize(inventory, executor, modulepath, plugin_dirs, project,

@pool = Concurrent::ThreadPoolExecutor.new(max_threads: max_compiles)
@logger = Logging.logger[self]
@plugin_tarball = Concurrent::Delay.new do
build_plugin_tarball do |mod|
search_dirs = []
search_dirs << mod.plugins if mod.plugins?
search_dirs << mod.pluginfacts if mod.pluginfacts?
search_dirs << mod.files if mod.files?
type_files = "#{mod.path}/types"
search_dirs << type_files if File.exist?(type_files)
search_dirs
end
end
end

private def libexec
@libexec ||= File.join(Gem::Specification.find_by_name('bolt').gem_dir, 'libexec')
end

def selected_module?(mod)
@selected_modules.any? { |m| mod =~ /#{m}/ }
end

def custom_facts_task
@custom_facts_task ||= begin
path = File.join(libexec, 'custom_facts.rb')
Expand Down Expand Up @@ -235,9 +227,27 @@ def apply_ast(raw_ast, targets, options, plan_vars = {})
result
end
else
@selected_modules = Array(@inventory.target_config(target)['plugin_synced_modules'])
if @selected_modules.any?
@logger.debug("Using selective module sync with this selection: #{@selected_modules.join(',')}.")
end

plugin_tarball = Concurrent::Delay.new do
build_plugin_tarball do |mod|
next if @selected_modules.any? && !selected_module?(mod.name)
search_dirs = []
search_dirs << mod.plugins if mod.plugins?
search_dirs << mod.pluginfacts if mod.pluginfacts?
search_dirs << mod.files if mod.files?
type_files = "#{mod.path}/types"
search_dirs << type_files if File.exist?(type_files)
search_dirs
end
end

arguments = {
'catalog' => Puppet::Pops::Types::PSensitiveType::Sensitive.new(catalog),
'plugins' => Puppet::Pops::Types::PSensitiveType::Sensitive.new(plugins),
'plugins' => Puppet::Pops::Types::PSensitiveType::Sensitive.new(plugins(plugin_tarball)),
'apply_settings' => @apply_settings,
'_task' => catalog_apply_task.name,
'_noop' => options[:noop]
Expand Down Expand Up @@ -272,8 +282,8 @@ def apply_ast(raw_ast, targets, options, plan_vars = {})
r
end

def plugins
@plugin_tarball.value ||
def plugins(for_target_node)
for_target_node.value ||
raise(Bolt::Error.new("Failed to pack module plugins: #{@plugin_tarball.reason}", 'bolt/plugin-error'))
end

Expand Down
59 changes: 58 additions & 1 deletion spec/bolt/applicator_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

describe Bolt::Applicator do
let(:uri) { 'foobar' }
let(:plugindirs) { [] }
let(:target) { inventory.get_target(uri) }
let(:inventory) { Bolt::Inventory.empty }
let(:executor) { Bolt::Executor.new }
Expand All @@ -20,7 +21,7 @@
end
let(:pdb_client) { Bolt::PuppetDB::Client.new(config) }
let(:modulepath) { [Bolt::PAL::BOLTLIB_PATH, Bolt::PAL::MODULES_PATH] }
let(:applicator) { Bolt::Applicator.new(inventory, executor, modulepath, [], nil, pdb_client, nil, 2, {}) }
let(:applicator) { Bolt::Applicator.new(inventory, executor, modulepath, plugindirs, nil, pdb_client, nil, 2, {}) }
let(:ast) { { 'resources' => [] } }

let(:report) {
Expand Down Expand Up @@ -115,6 +116,62 @@

let(:scope) { double('scope') }

context 'selective module sync disabled (default)' do
before do
allow(Logging).to receive(:logger).and_return(mock_logger)
allow(mock_logger).to receive(:[]).and_return(mock_logger)
allow(mock_logger).to receive(:'level=').with(any_args)
allow(mock_logger).to receive(:debug).with(any_args)
end

let(:mock_logger) { instance_double("Logging.logger") }
let(:plugindirs) { modulepath }

it 'syncs all modules' do
#
# Use a variable here instead of the Rspec let, so we can mock the logger
#
applicator = Bolt::Applicator.new(inventory, executor, modulepath, plugindirs, nil, pdb_client, nil, 2, {})
expect(applicator).to receive(:compile).and_return(ast)
result = Bolt::Result.new(target, value: report)
allow_any_instance_of(Bolt::Transport::SSH).to receive(:batch_task).and_return(result)
allow(Bolt::ApplyResult).to receive(:puppet_missing_error).with(result).and_return(nil)

expect(mock_logger).to receive(:debug).with(/Packing plugin/).at_least(:once)
expect(mock_logger).to_not receive(:debug).with(/Using selective module sync/)
applicator.apply([target], :body, scope)
end
end

context 'selective sync enabled' do
before do
allow(Logging).to receive(:logger).and_return(mock_logger)
allow(mock_logger).to receive(:[]).and_return(mock_logger)
allow(mock_logger).to receive(:'level=').with(any_args)
allow(mock_logger).to receive(:debug).with(any_args)
end

let(:mock_logger) { instance_double("Logging.logger") }
let(:plugindirs) { modulepath }

it 'syncs only selected modules' do
#
# Use a variable here instead of the Rspec let, so we can mock the logger
#
applicator = Bolt::Applicator.new(inventory, executor, modulepath, plugindirs, nil, pdb_client, nil, 2, {})
expect(applicator).to receive(:compile).and_return(ast)
result = Bolt::Result.new(target, value: report)
allow_any_instance_of(Bolt::Transport::SSH).to receive(:batch_task).and_return(result)
allow(Bolt::ApplyResult).to receive(:puppet_missing_error).with(result).and_return(nil)

expect(mock_logger).to_not receive(:debug).with(/Packing plugin/)
expect(mock_logger).to receive(:debug).with(/Using selective module sync/)

inventory.set_config(target, 'plugin_synced_modules', ['just_an_invalid_name'])
applicator.apply([target], :body, scope)
end
end

it 'replaces failures to find Puppet' do
expect(applicator).to receive(:compile).and_return(ast)
result = Bolt::Result.new(target, value: report)
Expand Down

0 comments on commit 05b0a1f

Please sign in to comment.