class OctocatalogDiff::Catalog::Computed

Represents a Puppet catalog that is computed (via `puppet master –compile …`) By instantiating this class, the catalog is computed.

Public Class Methods

new(options) click to toggle source

Constructor @param :node [String] REQUIRED: Node name @param :basedir [String] Directory in which to compile the catalog @param :pass_env_vars [Array<String>] Environment variables to pass when compiling catalog @param :retry_failed_catalog [Integer] Number of retries if a catalog compilation fails @param :tag [String] For display purposes, the catalog being compiled @param :puppet_binary [String] Full path to Puppet @param :puppet_version [String] Puppet version (optional; if not supplied, it is calculated) @param :puppet_command [String] Full command to run Puppet (optional; if not supplied, it is calculated)

Calls superclass method OctocatalogDiff::Catalog::new
# File lib/octocatalog-diff/catalog/computed.rb, line 30
def initialize(options)
  super

  raise ArgumentError, 'Node name must be passed to OctocatalogDiff::Catalog::Computed' unless options[:node].is_a?(String)
  raise ArgumentError, 'Branch is undefined' unless options[:branch]

  # Additional class variables
  @pass_env_vars = options.fetch(:pass_env_vars, [])
  @retry_failed_catalog = options.fetch(:retry_failed_catalog, 0)
  @tag = options.fetch(:tag, 'catalog')
  @puppet_binary = options[:puppet_binary]
  @puppet_version = options[:puppet_version]
  @puppet_command = options[:puppet_command]
  @builddir = nil
  @facts_terminus = options.fetch(:facts_terminus, 'yaml')
end

Public Instance Methods

assert_that_puppet_environment_directory_exists() click to toggle source

Private method: Make sure that the Puppet environment directory exists.

# File lib/octocatalog-diff/catalog/computed.rb, line 194
def assert_that_puppet_environment_directory_exists
  target_dir = File.join(@builddir.tempdir, 'environments', environment)
  return if File.directory?(target_dir)
  raise Errno::ENOENT, "Environment directory #{target_dir} does not exist"
end
bootstrap(logger) click to toggle source

Private method: Bootstrap a directory

# File lib/octocatalog-diff/catalog/computed.rb, line 74
def bootstrap(logger)
  return if @builddir

  # Fill options for creating and populating the temporary directory
  tmphash = @options.dup

  # Bootstrap directory if needed
  if !@options[:bootstrapped_dir].nil?
    raise Errno::ENOENT, "Invalid dir #{@options[:bootstrapped_dir]}" unless File.directory?(@options[:bootstrapped_dir])
    tmphash[:basedir] = @options[:bootstrapped_dir]
  elsif @options[:branch] == '.'
    if @options[:bootstrap_current]
      tmphash[:basedir] = OctocatalogDiff::Util::Util.temp_dir('ocd-bootstrap-basedir-')
      FileUtils.cp_r File.join(@options[:basedir], '.'), tmphash[:basedir]

      o = @options.reject { |k, _v| k == :branch }.merge(path: tmphash[:basedir])
      OctocatalogDiff::CatalogUtil::Bootstrap.bootstrap_directory(o, logger)
    else
      tmphash[:basedir] = @options[:basedir]
    end
  else
    tmphash[:basedir] = OctocatalogDiff::Util::Util.temp_dir('ocd-bootstrap-checkout-')
    OctocatalogDiff::CatalogUtil::Bootstrap.bootstrap_directory(@options.merge(path: tmphash[:basedir]), logger)
  end

  # Create and populate the temporary directory
  @builddir ||= OctocatalogDiff::CatalogUtil::BuildDir.new(tmphash, logger)
end
build_catalog(logger) click to toggle source

Private method: Build catalog by running Puppet @param logger [Logger] Logger object

# File lib/octocatalog-diff/catalog/computed.rb, line 105
def build_catalog(logger)
  if @facts_terminus != 'facter'
    facts_obj = OctocatalogDiff::CatalogUtil::Facts.new(@options, logger)
    logger.debug "Start retrieving facts for #{@node} from #{self.class}"
    @options[:facts] = facts_obj.facts
    logger.debug "Success retrieving facts for #{@node} from #{self.class}"
  end

  bootstrap(logger)
  result = run_puppet(logger)
  @retries = result[:retries]
  if (result[:exitcode]).zero?
    begin
      @catalog = ::JSON.parse(result[:stdout])
      @catalog_json = result[:stdout]
      @error_message = nil
    rescue ::JSON::ParserError => exc
      @catalog = nil
      @catalog_json = nil
      @error_message = "Catalog has invalid JSON: #{exc.message}"
    end
  else
    @error_message = result[:stderr]
    @catalog = nil
    @catalog_json = nil
  end
end
compilation_dir() click to toggle source

Compilation directory @return [String] Compilation directory

# File lib/octocatalog-diff/catalog/computed.rb, line 56
def compilation_dir
  raise 'Catalog was not built' if @builddir.nil?
  @builddir.tempdir
end
convert_file_resources(dry_run = false) click to toggle source

Convert file resources source => “puppet:///…” to content => “actual content of file”.

# File lib/octocatalog-diff/catalog/computed.rb, line 67
def convert_file_resources(dry_run = false)
  return @options.key?(:basedir) if dry_run
  return false unless @options[:basedir]
  OctocatalogDiff::CatalogUtil::FileResources.convert_file_resources(self, environment)
end
environment() click to toggle source

Environment used to compile catalog

# File lib/octocatalog-diff/catalog/computed.rb, line 62
def environment
  @options.fetch(:environment, 'production')
end
exec_puppet(logger) click to toggle source

Private method: Actually execute puppet @return [Hash] { stdout, stderr, exitcode }

# File lib/octocatalog-diff/catalog/computed.rb, line 159
def exec_puppet(logger)
  # This is the environment provided to the puppet command.
  env = {}
  @pass_env_vars.each { |var| env[var] ||= ENV[var] }

  # This is the Puppet command itself
  env['OCD_PUPPET_BINARY'] = @puppet_command_obj.puppet_binary

  # Additional passed-in options
  sr_run_opts = env.merge(
    logger: logger,
    working_dir: @builddir.tempdir,
    argv: @puppet_command_obj.puppet_argv
  )

  # Set up the ScriptRunner
  scriptrunner = OctocatalogDiff::Util::ScriptRunner.new(
    default_script: 'puppet/puppet.sh',
    override_script_path: @options[:override_script_path]
  )

  begin
    scriptrunner.run(sr_run_opts)
  rescue OctocatalogDiff::Util::ScriptRunner::ScriptException => exc
    logger.warn "Puppet command failed: #{exc.message}" if logger
  end

  {
    stdout: scriptrunner.stdout,
    stderr: scriptrunner.stderr,
    exitcode: scriptrunner.exitcode
  }
end
puppet_command() click to toggle source

Get the command to compile the catalog @return [String] Puppet command line

# File lib/octocatalog-diff/catalog/computed.rb, line 135
def puppet_command
  puppet_command_obj.puppet_command
end
puppet_command_obj() click to toggle source
# File lib/octocatalog-diff/catalog/computed.rb, line 139
def puppet_command_obj
  @puppet_command_obj ||= begin
    raise ArgumentError, '"puppet_binary" was not passed to OctocatalogDiff::Catalog::Computed' unless @puppet_binary

    command_opts = @options.merge(
      node: @node,
      compilation_dir: @builddir.tempdir,
      parser: @options.fetch(:parser, :default),
      puppet_binary: @puppet_binary,
      fact_file: @builddir.fact_file,
      dir: @builddir.tempdir,
      enc: @builddir.enc,
      puppet_version: puppet_version
    )
    OctocatalogDiff::CatalogUtil::Command.new(command_opts)
  end
end
puppet_version() click to toggle source

Get the Puppet version @return [String] Puppet version

# File lib/octocatalog-diff/catalog/computed.rb, line 49
def puppet_version
  raise ArgumentError, '"puppet_binary" was not passed to OctocatalogDiff::Catalog::Computed' unless @puppet_binary
  @puppet_version ||= OctocatalogDiff::Util::PuppetVersion.puppet_version(@puppet_binary, @options)
end
run_puppet(logger) click to toggle source

Private method: Runs puppet on the command line to compile the catalog Exit code is 0 if catalog generation was successful, non-zero otherwise. @param logger [Logger] Logger object @return [Hash] { stdout: <catalog as JSON>, stderr: <error messages>, exitcode: <hopefully 0> }

# File lib/octocatalog-diff/catalog/computed.rb, line 204
def run_puppet(logger)
  assert_that_puppet_environment_directory_exists

  # Run 'cmd' with environment 'env' from directory 'dir'
  # First line of a successful result needs to be stripped off. It will look like:
  # Notice: Compiled catalog for xxx in environment production in 27.88 seconds
  retval = {}
  0.upto(@retry_failed_catalog) do |retry_num|
    @retries = retry_num
    time_begin = Time.now
    logger.debug("(#{@tag}) Try #{1 + retry_num} executing Puppet #{puppet_version}: #{puppet_command}")
    result = exec_puppet(logger)

    # Success
    if (result[:exitcode]).zero?
      logger.debug("(#{@tag}) Catalog succeeded on try #{1 + retry_num} in #{Time.now - time_begin} seconds")
      first_brace = result[:stdout].index('{') || 0
      retval = {
        stdout: result[:stdout][first_brace..-1],
        stderr: nil,
        exitcode: 0,
        retries: retry_num
      }
      break
    end

    # Failure
    logger.debug("(#{@tag}) Catalog failed on try #{1 + retry_num} in #{Time.now - time_begin} seconds")
    retval = result.merge(retries: retry_num)
  end
  retval
end