/usr/lib/ruby/vendor_ruby/octocatalog-diff/catalog-util/builddir.rb is in octocatalog-diff 1.5.3-1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 | # frozen_string_literal: true
require 'yaml'
require_relative '../facts'
require_relative 'enc'
require_relative '../util/util'
module OctocatalogDiff
module CatalogUtil
# 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
class BuildDir
# Allow the path to the temporary directory to be read
attr_reader :tempdir, :enc, :fact_file
# 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
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
# Create common structure
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
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 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
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.
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 the fact file in temporary directory
# @param options [Hash] Options
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 symbolic link to puppet environment
# @param dir [String] Directory to link to
# @param target [String] Where the symlink is created, relative to tempdir
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
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 hiera config file
# @param options [Hash] Options hash
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 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
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
# Jump-off for hiera v3 (or earlier)
# @param logger [Logger] Logger object
# @param options [Hash] Options hash
# @param obj [Hash] Parsed hiera.yaml file
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
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
# Hiera munge - shared method to apply :hiera_path_strip and :hiera_path
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
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
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
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
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
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
def environment
@options.fetch(:environment, 'production')
end
end
end
end
|