class OctocatalogDiff::CatalogUtil::BuildDir

Represents a directory that is created such that a catalog can be compiled in it. This has the following major functions:

Attributes

enc[R]

Allow the path to the temporary directory to be read

fact_file[R]

Allow the path to the temporary directory to be read

tempdir[R]

Allow the path to the temporary directory to be read

Public Class Methods

new(options = {}, logger = nil) click to toggle source

Constructor Options for constructor: :puppetdb_url [String] PuppetDB Server URLs :puppetdb_server_url_timeout [Integer] Timeout (seconds) for puppetdb.conf :facts [OctocatalogDiff::Facts] Facts object :fact_file [String] File from which to read facts :node [String] Node name :basedir [String] Directory containing puppet code :enc [String] ENC script file (can be relative or absolute path) :pe_enc_url [String] ENC URL (for Puppet Enterprise node classification service) :hiera_config [String] hiera configuration file (relative to base directory) :hiera_path [String] relative path to hiera data files (mutually exclusive with :hiera_path_strip) :hiera_path_strip [String] string to strip off the beginning of :datadir :puppetdb_ssl_ca [String] Path to SSL CA certificate :puppetdb_ssl_client_key [String] String representation of SSL client key :puppetdb_ssl_client_cert [String] String representation of SSL client certificate :puppetdb_ssl_client_password [String] Password to unlock SSL private key @param options [Hash] Options for class; see above description

# File lib/octocatalog-diff/catalog-util/builddir.rb, line 40
def initialize(options = {}, logger = nil)
  @options = options.dup
  @tempdir = OctocatalogDiff::Util::Util.temp_dir('ocd-builddir-')
  @factdir = nil
  @enc = nil
  @fact_file = nil
  @node = options[:node]
  @facts_terminus = options.fetch(:facts_terminus, 'yaml')

  create_structure
  create_symlinks(logger)

  # These configurations are optional. Don't call the methods if parameters are nil.
  unless options[:puppetdb_url].nil?
    install_puppetdb_conf(logger, options[:puppetdb_url], options[:puppetdb_server_url_timeout])
    install_routes_yaml(logger)
  end
  install_hiera_config(logger, options) unless options[:hiera_config].nil?

  @fact_file = install_fact_file(logger, options) if @facts_terminus == 'yaml'
  @enc = install_enc(logger) unless options[:enc].nil? && options[:pe_enc_url].nil?
  install_ssl(logger, options) if options[:puppetdb_ssl_ca] || options[:puppetdb_ssl_client_cert]
end

Public Instance Methods

create_structure() click to toggle source

Create common structure

# File lib/octocatalog-diff/catalog-util/builddir.rb, line 65
def create_structure
  %w(facts var var/ssl var/yaml var/yaml/facts).each do |dir|
    Dir.mkdir(File.join(@tempdir, dir))
    FileUtils.chmod 0o755, File.join(@tempdir, dir)
  end
end
install_enc(logger) click to toggle source

Install ENC @param enc [String] Path to ENC script, relative to checkout

# File lib/octocatalog-diff/catalog-util/builddir.rb, line 201
def install_enc(logger)
  raise ArgumentError, 'A node must be specified when using an ENC' unless @node.is_a?(String)
  enc_obj = OctocatalogDiff::CatalogUtil::ENC.new(@options.merge(tempdir: @tempdir))
  enc_obj.execute(logger)
  raise "Failed ENC: #{enc_obj.error_message}" if enc_obj.error_message

  enc_path = File.join(@tempdir, 'enc.sh')
  File.open(enc_path, 'w') do |f|
    f.write "#!/bin/sh\n"
    f.write "cat <<-EOF\n"
    f.write enc_obj.content
    f.write "\nEOF\n"
  end
  FileUtils.chmod 0o755, enc_path

  logger.debug("Installed ENC to echo content, #{enc_obj.content.length} bytes")
  enc_path
end
install_fact_file(logger, options) click to toggle source

Install the fact file in temporary directory @param options [Hash] Options

# File lib/octocatalog-diff/catalog-util/builddir.rb, line 144
def install_fact_file(logger, options)
  unless @facts_terminus == 'yaml'
    raise ArgumentError, "Called install_fact_file but :facts_terminus = #{@facts_terminus}"
  end
  unless options[:node].is_a?(String) && !options[:node].empty?
    raise ArgumentError, 'Called install_fact_file without node, or with an empty node'
  end

  facts = if options[:facts].is_a?(OctocatalogDiff::Facts)
    options[:facts].dup
  elsif options[:fact_file]
    raise Errno::ENOENT, "Fact file #{options[:fact_file]} does not exist" unless File.file?(options[:fact_file])
    fact_file_opts = { fact_file_string: File.read(options[:fact_file]) }
    fact_file_opts[:backend] = Regexp.last_match(1).to_sym if options[:fact_file] =~ /.*\.(\w+)$/
    OctocatalogDiff::Facts.new(fact_file_opts)
  else
    raise ArgumentError, 'No facts passed to "install_fact_file" method'
  end

  if options[:fact_override].is_a?(Array)
    options[:fact_override].each do |override|
      keys = override.key.is_a?(Regexp) ? facts.matching(override.key) : [override.key]
      keys.each do |key|
        old_value = facts.fact(key)
        facts.override(key, override.value)
        logger.debug("Override #{key} from #{old_value.inspect} to #{override.value.inspect}")
      end
    end
  end

  fact_file_out = File.join(@tempdir, 'var', 'yaml', 'facts', "#{options[:node]}.yaml")
  File.open(fact_file_out, 'w') { |f| f.write(facts.facts_to_yaml(options[:node])) }
  logger.debug("Installed fact file at #{fact_file_out}")
  fact_file_out
end
install_hiera_config(logger, options) click to toggle source

Install hiera config file @param options [Hash] Options hash

# File lib/octocatalog-diff/catalog-util/builddir.rb, line 222
def install_hiera_config(logger, options)
  # Validate hiera config file
  hiera_config = options[:hiera_config]
  unless hiera_config.is_a?(String)
    raise ArgumentError, "Called install_hiera_config with a #{hiera_config.class} argument"
  end
  file_src = if hiera_config.start_with? '/'
    hiera_config
  elsif hiera_config =~ %r{^environments/#{Regexp.escape(environment)}/}
    File.join(@tempdir, hiera_config)
  else
    File.join(@tempdir, 'environments', environment, hiera_config)
  end
  raise Errno::ENOENT, "hiera.yaml (#{file_src}) wasn't found" unless File.file?(file_src)

  # Munge datadir in hiera config file
  obj = YAML.load_file(file_src)
  version = obj['version'] || obj[:version] || 3
  if version.to_i == 5
    update_hiera_config_v5(logger, options, obj)
  else
    update_hiera_config_v3(logger, options, obj)
  end

  # Write properly formatted hiera config file into temporary directory
  File.open(File.join(@tempdir, 'hiera.yaml'), 'w') { |f| f.write(obj.to_yaml.gsub('!ruby/sym ', ':')) }
  logger.debug("Installed hiera.yaml from #{file_src} to #{File.join(@tempdir, 'hiera.yaml')}")
end
install_puppetdb_conf(logger, server_urls, server_url_timeout = 30) click to toggle source

Install puppetdb.conf file in temporary directory @param server_urls [String] String for server_urls in puppetdb.conf @param server_url_timeout [Integer] Value for server_url_timeout in puppetdb.conf

# File lib/octocatalog-diff/catalog-util/builddir.rb, line 102
def install_puppetdb_conf(logger, server_urls, server_url_timeout = 30)
  unless server_urls.is_a?(String)
    raise ArgumentError, "server_urls must be a string, got a: #{server_urls.class}"
  end

  server_url_timeout ||= 30 # If called with nil argument, supply default
  unless server_url_timeout.is_a?(Integer)
    raise ArgumentError, "server_url_timeout must be a fixnum, got a: #{server_url_timeout.class}"
  end

  puppetdb_conf = File.join(@tempdir, 'puppetdb.conf')
  File.open(puppetdb_conf, 'w') do |f|
    f.write "[main]\n"
    f.write "server_urls = #{server_urls}\n"
    f.write "server_url_timeout = #{server_url_timeout}\n"
  end
  logger.debug("Installed puppetdb.conf file at #{puppetdb_conf}")
end
install_routes_yaml(logger) click to toggle source

Install routes.yaml file in temporary directory No parameters or return - thus just writes a file (and notes it to debugging log) Note: catalog cache => json avoids sending the compiled catalog to PuppetDB even if storeconfigs is enabled.

# File lib/octocatalog-diff/catalog-util/builddir.rb, line 125
def install_routes_yaml(logger)
  routes_yaml = File.join(@tempdir, 'routes.yaml')
  routes_hash = {
    'master' => {
      'facts' => {
        'terminus' => @facts_terminus,
        'cache' => 'yaml'
      },
      'catalog' => {
        'cache' => 'json'
      }
    }
  }
  File.open(routes_yaml, 'w') { |f| f.write(routes_hash.to_yaml) }
  logger.debug("Installed routes.yaml file at #{routes_yaml}")
end
install_ssl(logger, options) click to toggle source

Install SSL certificate authority certificate, client key, and client certificate into the expected locations within Puppet's SSL directory. Note that if the client key has a password, this will write the key (without password) onto disk, because Puppet doesn't support unlocking the private key. @param logger [Logger] Logger object @param options [Hash] Options hash

# File lib/octocatalog-diff/catalog-util/builddir.rb, line 257
def install_ssl(logger, options)
  return unless options[:puppetdb_ssl_client_cert] || options[:puppetdb_ssl_client_key] || options[:puppetdb_ssl_ca]

  # Create directory structure expected by Puppet
  %w(var/ssl/certs var/ssl/private var/ssl/private_keys).each do |dir|
    Dir.mkdir(File.join(@tempdir, dir))
    FileUtils.chmod 0o700, File.join(@tempdir, dir)
  end

  # SSL client auth requested?
  if options[:puppetdb_ssl_client_cert] || options[:puppetdb_ssl_client_key]
    raise ArgumentError, '--puppetdb-ssl-ca must be provided for client auth' unless options[:puppetdb_ssl_ca]
    raise ArgumentError, '--puppetdb-ssl-client-cert must be provided' unless options[:puppetdb_ssl_client_cert]
    raise ArgumentError, '--puppetdb-ssl-client-key must be provided' unless options[:puppetdb_ssl_client_key]
    install_ssl_client(logger, options)
  end

  # SSL CA provided?
  install_ssl_ca(logger, options) if options[:puppetdb_ssl_ca]
end

Private Instance Methods

environment() click to toggle source
# File lib/octocatalog-diff/catalog-util/builddir.rb, line 395
def environment
  @options.fetch(:environment, 'production')
end
hiera_munge(options, current_value) click to toggle source

Hiera munge - shared method to apply :hiera_path_strip and :hiera_path

# File lib/octocatalog-diff/catalog-util/builddir.rb, line 336
def hiera_munge(options, current_value)
  return if current_value.nil?

  if options[:hiera_path_strip].is_a?(String)
    rexp1 = Regexp.new('^' + options[:hiera_path_strip])
    current_value.sub!(rexp1, @tempdir)
  elsif options[:hiera_path].is_a?(String)
    current_value = File.join(@tempdir, 'environments', environment, options[:hiera_path])
  end
  rexp2 = Regexp.new('%{(::)?environment}')
  current_value.sub!(rexp2, environment)

  current_value
end
install_ssl_ca(logger, options) click to toggle source

Install SSL certificate authority certificate @param logger [Logger] Logger object @param options [Hash] Options hash

# File lib/octocatalog-diff/catalog-util/builddir.rb, line 354
def install_ssl_ca(logger, options)
  ca_file = options[:puppetdb_ssl_ca]
  raise Errno::ENOENT, 'SSL CA file does not exist' unless File.file?(ca_file)
  ca_content = File.read(ca_file)
  ca_outfile = File.join(@tempdir, 'var', 'ssl', 'certs', 'ca.pem')
  File.open(ca_outfile, 'w') { |f| f.write(ca_content) }
  logger.debug "Installed CA certificate in #{ca_outfile}"
end
install_ssl_client(logger, options) click to toggle source

Install SSL keypair for client certificate authentication @param logger [Logger] Logger object @param options [Hash] Options hash

# File lib/octocatalog-diff/catalog-util/builddir.rb, line 366
def install_ssl_client(logger, options)
  # Since Puppet always looks for the key and cert in a file named after the hostname, determine the
  # hostname here for the purposes of naming the files.
  require 'socket'
  host = Socket.gethostname
  install_ssl_client_cert(logger, host, options[:puppetdb_ssl_client_cert])
  install_ssl_client_key(logger, host, options[:puppetdb_ssl_client_key])
  install_ssl_client_password(logger, options[:puppetdb_ssl_client_password])
end
install_ssl_client_cert(logger, host, content) click to toggle source
# File lib/octocatalog-diff/catalog-util/builddir.rb, line 376
def install_ssl_client_cert(logger, host, content)
  cert_outfile = File.join(@tempdir, 'var', 'ssl', 'certs', "#{host}.pem")
  File.open(cert_outfile, 'w') { |f| f.write(content) }
  logger.debug "Installed SSL client certificate in #{cert_outfile}"
end
install_ssl_client_key(logger, host, content) click to toggle source
# File lib/octocatalog-diff/catalog-util/builddir.rb, line 382
def install_ssl_client_key(logger, host, content)
  key_outfile = File.join(@tempdir, 'var', 'ssl', 'private_keys', "#{host}.pem")
  File.open(key_outfile, 'w') { |f| f.write(content) }
  logger.debug "Installed SSL client key in #{key_outfile}"
end
install_ssl_client_password(logger, password) click to toggle source
# File lib/octocatalog-diff/catalog-util/builddir.rb, line 388
def install_ssl_client_password(logger, password)
  return unless password
  password_outfile = File.join(@tempdir, 'var', 'ssl', 'private', 'password')
  File.open(password_outfile, 'w') { |f| f.write(password) }
  logger.debug "Installed SSL client key password in #{password_outfile}"
end
update_hiera_config_v3(logger, options, obj) click to toggle source

Jump-off for hiera v3 (or earlier) @param logger [Logger] Logger object @param options [Hash] Options hash @param obj [Hash] Parsed hiera.yaml file

# File lib/octocatalog-diff/catalog-util/builddir.rb, line 284
def update_hiera_config_v3(logger, options, obj)
  ([obj[:backends]].flatten || %w(yaml json)).each do |key|
    next unless obj.key?(key.to_sym) && !obj[key.to_sym][:datadir].nil?
    obj[key.to_sym][:datadir] = hiera_munge(options, obj[key.to_sym][:datadir])

    # Make sure the directory exists. If not, log a warning. This is *probably* a setup error, but we don't
    # want it to be fatal in case (for example) someone is doing an octocatalog-diff to verify moving this
    # directory around or even setting up Hiera for the very first time.
    unless File.directory?(obj[key.to_sym][:datadir])
      message = "WARNING: Hiera datadir for #{key} doesn't seem to exist at #{obj[key.to_sym][:datadir]}"
      logger.warn message
    end
  end
end
update_hiera_config_v5(_logger, options, obj) click to toggle source

Jump-off for hiera v5 @param logger [Logger] Logger object @param options [Hash] Options hash @param obj [Hash] Parsed hiera.yaml file

# File lib/octocatalog-diff/catalog-util/builddir.rb, line 303
def update_hiera_config_v5(_logger, options, obj)
  defaults_key = obj.key?(:defaults) ? :defaults : 'defaults'
  hierarchy_key = obj.key?(:hierarchy) ? :hierarchy : 'hierarchy'

  # Fix defaults:datadir
  if obj[defaults_key].is_a?(Hash)
    [:datadir, 'datadir'].each do |key|
      next unless obj[defaults_key].key?(key)
      obj[defaults_key][key] = hiera_munge(options, obj[defaults_key][key])
    end
  end

  # Fix hierarchy:datadir
  if obj[hierarchy_key].is_a?(Array)
    obj[hierarchy_key].each do |level|
      [:datadir, 'datadir'].each do |key|
        next unless level.key?(key)
        if options[:hiera_path_strip].is_a?(String)
          level[key] = hiera_munge(options, level[key])
        elsif options[:hiera_path].is_a?(String)
          message = [
            "Hierarchy item #{level.inspect} has a datadir.",
            '--hiera-path is not supported in this situation.',
            'Please use --hiera-path-strip.'
          ].join(' ')
          raise ArgumentError, message
        end
      end
    end
  end
end