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:
-
Create the temporary directory that will serve as the puppet configuration directory
-
Register a handler to remove the temporary directory upon exit
-
Install needed configuration files within the directory (e.g. puppetdb.conf)
-
Install the facts into the directory
-
Install 'environments/(environment)' which is a symlink to the checkout of the puppet code
Attributes
Allow the path to the temporary directory to be read
Allow the path to the temporary directory to be read
Allow the path to the temporary directory to be read
Public Class Methods
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 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
Create symlinks.
If the `–preserve-environments` option is used, the `environments` directory, plus `modules` and `manifests` symlinks are created. Otherwise, `environments/production` is pointed at the base directory.
@param logger [Logger] Logger object
# File lib/octocatalog-diff/catalog-util/builddir.rb, line 79 def create_symlinks(logger = nil) if @options[:preserve_environments] install_directory_symlink(logger, File.join(@options[:basedir], 'environments'), 'environments') @options.fetch(:create_symlinks, %w(modules manifests)).each do |x| install_directory_symlink(logger, File.join(@options[:basedir], x), x) end else if @options[:create_symlinks] && @options[:environment] unless logger.nil? logger.warn '--create-symlinks with --environment ignored unless --preserve-environments is used' end elsif @options[:create_symlinks] logger.warn '--create-symlinks is ignored unless --preserve-environments is used' unless logger.nil? elsif @options[:environment] return install_directory_symlink(logger, @options[:basedir], "environments/#{@options[:environment]}") end install_directory_symlink(logger, @options[:basedir]) end end
Install symbolic link to puppet environment @param dir [String] Directory to link to @param target [String] Where the symlink is created, relative to tempdir
# File lib/octocatalog-diff/catalog-util/builddir.rb, line 183 def install_directory_symlink(logger, dir, target = 'environments/production') raise ArgumentError, "Called install_directory_symlink with #{dir.class} argument" unless dir.is_a?(String) raise Errno::ENOENT, "Specified directory #{dir} doesn't exist" unless File.directory?(dir) symlink_target = File.join(@tempdir, target) if target =~ %r{/} parent_dir = File.dirname(symlink_target) FileUtils.mkdir_p parent_dir end FileUtils.rm_f symlink_target if File.exist?(symlink_target) FileUtils.symlink dir, symlink_target logger.debug("Symlinked #{symlink_target} -> #{dir}") end
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 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 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 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 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 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
# File lib/octocatalog-diff/catalog-util/builddir.rb, line 395 def environment @options.fetch(:environment, 'production') end
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 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 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
# 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
# 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
# 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
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
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