Skip to content

Instantly share code, notes, and snippets.

@gucki
Last active January 17, 2022 08:03
Show Gist options
  • Save gucki/6b81030910fb5fca034559991b840a9f to your computer and use it in GitHub Desktop.
Save gucki/6b81030910fb5fca034559991b840a9f to your computer and use it in GitHub Desktop.
# frozen_string_literal: true
require 'bolt/error'
# Quick and dirty implementation of a file sync (instead of simple file upload) using rsync under the hood for puppet bolt.
# Use it the same was as file_upload, ex. sync_file("profiles/lb01/", "/etc/keepalived/", ["lb01-1", "lb01-2"]).
# Most of the code is just copied from the original upload_file method and adjusted as needed.
# This function does nothing if the list of targets is empty.
#
# > **Note:** Not available in apply block
Puppet::Functions.create_function(:sync_file, Puppet::Functions::InternalFunction) do
# Upload a file or directory.
# @param source A source path, either an absolute path or a modulename/filename selector for a
# file or directory in $MODULEROOT/files.
# @param destination An absolute path on the target(s).
# @param targets A pattern identifying zero or more targets. See {get_targets} for accepted patterns.
# @param options A hash of additional options.
# @option options [Boolean] _catch_errors Whether to catch raised errors.
# @option options [String] _run_as User to run as using privilege escalation.
# @return A list of results, one entry per target.
# @example Upload a local file to Linux targets and change owner to 'root'
# sync_file('/var/tmp/payload.tgz', '/tmp/payload.tgz', $targets, '_run_as' => 'root')
# @example Upload a module file to a Windows target
# sync_file('postgres/default.conf', 'C:/ProgramData/postgres/default.conf', $target)
dispatch :sync_file do
scope_param
param 'String[1]', :source
param 'String[1]', :destination
param 'Boltlib::TargetSpec', :targets
optional_param 'Hash[String[1], Any]', :options
end
# Upload a file or directory, logging the provided description.
# @param source A source path, either an absolute path or a modulename/filename selector for a
# file or directory in $MODULEROOT/files.
# @param destination An absolute path on the target(s).
# @param targets A pattern identifying zero or more targets. See {get_targets} for accepted patterns.
# @param description A description to be output when calling this function.
# @param options A hash of additional options.
# @option options [Boolean] _catch_errors Whether to catch raised errors.
# @option options [String] _run_as User to run as using privilege escalation.
# @return A list of results, one entry per target.
# @example Upload a file
# sync_file('/var/tmp/payload.tgz', '/tmp/payload.tgz', $targets, 'Uploading payload to unpack')
dispatch :sync_file_with_description do
scope_param
param 'String[1]', :source
param 'String[1]', :destination
param 'Boltlib::TargetSpec', :targets
param 'String', :description
optional_param 'Hash[String[1], Any]', :options
end
def sync_file(scope, source, destination, targets, options = {})
sync_file_with_description(scope, source, destination, targets, nil, options)
end
def sync_file_with_description(scope, source, destination, targets, description = nil, options = {})
unless Puppet[:tasks]
raise Puppet::ParseErrorWithIssue
.from_issue_and_stack(Bolt::PAL::Issues::PLAN_OPERATION_NOT_SUPPORTED_WHEN_COMPILING, action: 'sync_file')
end
options = options.select { |opt| opt.start_with?('_') }.transform_keys { |k| k.sub(/^_/, '').to_sym }
options[:description] = description if description
executor = Puppet.lookup(:bolt_executor)
inventory = Puppet.lookup(:bolt_inventory)
# Send Analytics Report
executor.report_function_call(self.class.name)
# Find the file path if it exists, otherwise return nil
found = Bolt::Util.find_file_from_scope(source, scope)
unless found && Puppet::FileSystem.exist?(found)
raise Puppet::ParseErrorWithIssue.from_issue_and_stack(
Puppet::Pops::Issues::NO_SUCH_FILE_OR_DIRECTORY, file: source
)
end
executor.report_file_source(self.class.name, source)
# Ensure that that given targets are all Target instances
targets = inventory.get_targets(targets)
if targets.empty?
call_function('debug', "Simulating file upload of '#{found}' - no targets given - no action taken")
Bolt::ResultSet.new([])
else
file_line = Puppet::Pops::PuppetStack.top_of_stack
success = sync_file_real(targets, found, destination, options, file_line)
if !success && !options[:catch_errors]
raise Bolt::RunFailure.new(r, 'sync_file', source)
end
end
end
def sync_file_real(targets, source, destination, options = {}, position = [])
executor = Puppet.lookup(:bolt_executor)
description = options.fetch(:description, "sync from #{source} to #{destination}")
executor.log_action(description, targets) do
options[:run_as] = executor.run_as if executor.run_as && !options.key?(:run_as)
targets.each do |target|
return false unless system("rsync -av --delete-during #{source} #{target.host}:#{destination}")
end
end
true
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment