class Pupistry::Agent

Functions for running the Pupistry agent aka “apply mode” to actually download and run Puppet against the contents of the artifact.

Public Class Methods

apply(options) click to toggle source
# File lib/pupistry/agent.rb, line 56
def self.apply(options)
  ## Download and apply the latest artifact (if any)

  # Fetch artifact versions
  $logger.info 'Checking version of artifact available...'

  artifact = Pupistry::Artifact.new
  artifact.checksum = artifact.fetch_latest

  unless artifact.checksum
    $logger.error 'There is no current artifact available for download, no steps can be taken.'
    return false
  end

  artifact_installed = Pupistry::Artifact.new
  artifact_installed.checksum = artifact_installed.fetch_installed

  if artifact_installed.checksum
    $logger.debug "Currently on #{artifact_installed.checksum}"
  else
    $logger.debug 'No currently installed artifact - blank slate!'
  end

  # Download the new artifact if one has changed. If we already have this
  # version, then we should skip downloading and go straight to running
  # Puppet - unless the user runs with --force (eg to fix a corrupted
  # artifact).

  if artifact.checksum != artifact_installed.checksum || options[:force]
    $logger.warn 'Forcing download of latest artifact regardless of current one.' if options[:force]

    # Install the artifact
    $logger.info "Downloading latest artifact (#{artifact.checksum})..."

    artifact.fetch_artifact
    artifact.unpack
    artifact.hieracrypt_decrypt

    unless artifact.install
      $logger.fatal 'An unexpected error happened when installing the latest artifact, cancelling Puppet run'
      return false
    end

    # Remove temporary unpacked files
    artifact.clean_unpack
  else
    $logger.info 'Already have latest artifact applied.'

    # By default we run Puppet even if we have the latest artifact. There's
    # some grounds for debate about whether this is the right thing - in some
    # ways it is often a waste of CPU, since if the artifact hasn't changed,
    # then it's unlikley anything else has changed.
    #
    # But that's not always 100% true - Puppet will undo local changes or
    # upgrade package versions (ensure => latest) if appropiate, so we should
    # act like the standard command and attempt to apply whatever we can.
    #
    # To provide users with options, we provide the --lazy parameter to avoid
    # running Puppet except when the artifact changes. By default, Puppet
    # runs every thing to avoid surprise.

    if options[:minimal]
      $logger.info 'Running with minimal effort mode enabled, not running Puppet since artifact version already applied'
      return false
    end

  end

  # If the environment has been specified, use it.
  environment = $config['agent']['environment'] || 'master'
  # override if environment is supplied on CLI
  environment = options["environment"] || environment

  unless Dir.exist?("#{$config['agent']['puppetcode']}/#{environment}")
    $logger.fatal "The requested branch/environment of #{environment} does not exist, unable to run Puppet"
    return false
  end

  # Execute Puppet.
  puppet_cmd = 'puppet apply'

  puppet_cmd += ' --noop' if options[:noop]
  puppet_cmd += ' --show_diff' if options[:verbose]

  puppet_cmd += " --environment #{environment}"
  puppet_cmd += " --confdir #{$config['agent']['puppetcode']}"
  puppet_cmd += " --environmentpath #{$config['agent']['puppetcode']}"
  puppet_cmd += " --modulepath #{build_modulepath(environment)}"
  puppet_cmd += " --hiera_config #{$config['agent']['puppetcode']}/#{environment}/hiera.yaml"
  puppet_cmd += " #{$config['agent']['puppetcode']}/#{environment}/manifests/"

  $logger.info 'Executing Puppet...'
  $logger.debug "With: #{puppet_cmd}"

  $logger.error 'An unexpected issue occured when running puppet' unless system puppet_cmd
end
build_modulepath(environment) click to toggle source
# File lib/pupistry/agent.rb, line 153
def self.build_modulepath(environment)

  environment_path = "#{$config['agent']['puppetcode']}/#{environment}"
  environment_conf = "#{environment_path}/environment.conf"

  configured_paths = []

  if File.exist?(environment_conf)
    $logger.debug "Adding modulepath config from '#{environment_path}'"

    File.open(environment_conf, 'r').readlines.each do |line|
      if line !~ /^\s*#/ && /^(.*)=(.*)/ =~ line
        key, val = $1.strip, $2.strip
        configured_paths = val.split(':') if key == 'modulepath'
      end
    end
  end

  modulepaths = configured_paths.map { |path| File.expand_path(path, environment_path) }

  # Ensure '<environment_path>/modules' in modulepath.
  ensure_path = File.expand_path('modules', environment_path)
  modulepaths.insert(0, ensure_path) unless modulepaths.include? ensure_path

  modulepaths.join(File::PATH_SEPARATOR)
end
daemon(options) click to toggle source

Run as a daemon

# File lib/pupistry/agent.rb, line 12
def self.daemon(options)
  # Since options comes from Thor, it can't be modified, so we need to
  # copy the options and then we can edit it.

  options_new = options.inject({}) do |new, (name, value)|
    new[name] = value
    new
  end

  # If the minimal mode has been enabled in config, respect.
  options_new[:minimal] = true if $config['agent']['daemon_minimal']

  # If no frequency supplied, use 300 seconds safe default.
  $config['agent']['daemon_frequency'] = 300 unless $config['agent']['daemon_frequency']

  # Use rufus-scheduler to run our apply job as a regularly scheduled job
  # but with build in locking handling.

  $logger.info "Launching daemon... frequency of #{$config['agent']['daemon_frequency']} seconds."

  begin

    scheduler = Rufus::Scheduler.new

    scheduler.every "#{$config['agent']['daemon_frequency']}s", overlap: false, timeout: '1d', first_at: Time.now + 1 do
      $logger.info "Triggering another Pupistry run (#{$config['agent']['daemon_frequency']}s)"
      apply options_new
    end

    scheduler.join

  rescue Rufus::Scheduler::TimeoutError
    $logger.error 'A run of Pupistry timed out after 1 day as a safety measure. There may be a bug or a Puppet action causing it to get stuck'

  rescue SignalException
    # Clean shutdown signal (eg SIGTERM)
    $logger.info 'Clean shutdown of Pupistry daemon requests'
    exit 0

  rescue StandardError => e
    raise e
  end
end