octocatalog-diff 0.5.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.version +1 -0
- data/LICENSE +20 -0
- data/README.md +82 -0
- data/bin/octocatalog-diff +75 -0
- data/doc/advanced-bootstrap.md +33 -0
- data/doc/advanced-cache-dir.md +24 -0
- data/doc/advanced-catalog-only.md +37 -0
- data/doc/advanced-ci.md +13 -0
- data/doc/advanced-dynamic-ignores.md +123 -0
- data/doc/advanced-future-parser.md +11 -0
- data/doc/advanced-ignores.md +224 -0
- data/doc/advanced-output-formats.md +96 -0
- data/doc/advanced-output-hacks.md +45 -0
- data/doc/advanced-override-facts.md +67 -0
- data/doc/advanced-pe-enc.md +52 -0
- data/doc/advanced-puppet-master.md +50 -0
- data/doc/advanced-puppet-versions.md +9 -0
- data/doc/advanced-storeconfigs.md +72 -0
- data/doc/advanced-using-without-git.md +15 -0
- data/doc/advanced.md +43 -0
- data/doc/basic.md +70 -0
- data/doc/configuration-enc.md +69 -0
- data/doc/configuration-hiera.md +103 -0
- data/doc/configuration-puppetdb.md +49 -0
- data/doc/configuration.md +51 -0
- data/doc/dev/README.md +1 -0
- data/doc/dev/coverage.md +34 -0
- data/doc/dev/how-to-add-options.md +83 -0
- data/doc/dev/integration-tests.md +63 -0
- data/doc/dev/releasing.md +19 -0
- data/doc/installation.md +49 -0
- data/doc/limitations.md +34 -0
- data/doc/optionsref.md +947 -0
- data/doc/requirements.md +16 -0
- data/doc/roadmap.md +26 -0
- data/doc/similar.md +17 -0
- data/doc/troubleshooting.md +54 -0
- data/lib/octocatalog-diff.rb +12 -0
- data/lib/octocatalog-diff/bootstrap.rb +53 -0
- data/lib/octocatalog-diff/catalog-diff/cli.rb +205 -0
- data/lib/octocatalog-diff/catalog-diff/cli/catalogs.rb +240 -0
- data/lib/octocatalog-diff/catalog-diff/cli/diffs.rb +145 -0
- data/lib/octocatalog-diff/catalog-diff/cli/helpers/fact_override.rb +99 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options.rb +173 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/basedir.rb +14 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/bootstrap_environment.rb +18 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/bootstrap_script.rb +14 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/bootstrap_then_exit.rb +12 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/bootstrapped_dirs.rb +18 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/cached_master_dir.rb +21 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/catalog_only.rb +14 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/color.rb +13 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/compare_file_text.rb +15 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/debug.rb +12 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/display_datatype_changes.rb +16 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/display_detail_add.rb +12 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/display_source_file_line.rb +12 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/enc.rb +31 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/existing_catalogs.rb +25 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/fact_file.rb +23 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/fact_override.rb +19 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/facts_terminus.rb +16 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/from_puppetdb.rb +13 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/header.rb +24 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/hiera_config.rb +18 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/hiera_path_strip.rb +12 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/hostname.rb +13 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/ignore.rb +24 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/ignore_attr.rb +16 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/ignore_tags.rb +23 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/include_tags.rb +12 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/master_cache_branch.rb +12 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/output_file.rb +15 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/output_format.rb +15 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/parallel.rb +12 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/parser.rb +48 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/pass_env_vars.rb +19 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/pe_enc_ssl_ca.rb +15 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/pe_enc_ssl_client_cert.rb +14 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/pe_enc_ssl_client_key.rb +14 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/pe_enc_token.rb +15 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/pe_enc_token_file.rb +17 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/pe_enc_url.rb +19 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/puppet_binary.rb +16 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/puppet_master.rb +16 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/puppet_master_api_version.rb +20 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/puppet_master_ssl_ca.rb +19 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/puppet_master_ssl_client_cert.rb +19 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/puppet_master_ssl_client_key.rb +19 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/puppetdb_ssl_ca.rb +15 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/puppetdb_ssl_client_cert.rb +14 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/puppetdb_ssl_client_key.rb +14 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/puppetdb_ssl_client_password.rb +14 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/puppetdb_ssl_client_password_file.rb +13 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/puppetdb_url.rb +18 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/quiet.rb +12 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/retry_failed_catalog.rb +13 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/safe_to_delete_cached_master_dir.rb +15 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/storeconfigs.rb +12 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/suppress_absent_file_details.rb +14 -0
- data/lib/octocatalog-diff/catalog-diff/cli/options/to_from_branch.rb +16 -0
- data/lib/octocatalog-diff/catalog-diff/cli/printer.rb +52 -0
- data/lib/octocatalog-diff/catalog-diff/differ.rb +615 -0
- data/lib/octocatalog-diff/catalog-diff/display.rb +125 -0
- data/lib/octocatalog-diff/catalog-diff/display/json.rb +25 -0
- data/lib/octocatalog-diff/catalog-diff/display/text.rb +452 -0
- data/lib/octocatalog-diff/catalog-util/bootstrap.rb +145 -0
- data/lib/octocatalog-diff/catalog-util/builddir.rb +289 -0
- data/lib/octocatalog-diff/catalog-util/cached_master_directory.rb +169 -0
- data/lib/octocatalog-diff/catalog-util/command.rb +96 -0
- data/lib/octocatalog-diff/catalog-util/enc.rb +77 -0
- data/lib/octocatalog-diff/catalog-util/enc/noop.rb +22 -0
- data/lib/octocatalog-diff/catalog-util/enc/pe.rb +99 -0
- data/lib/octocatalog-diff/catalog-util/enc/pe/v1.rb +61 -0
- data/lib/octocatalog-diff/catalog-util/enc/script.rb +88 -0
- data/lib/octocatalog-diff/catalog-util/facts.rb +89 -0
- data/lib/octocatalog-diff/catalog-util/fileresources.rb +83 -0
- data/lib/octocatalog-diff/catalog-util/git.rb +65 -0
- data/lib/octocatalog-diff/catalog.rb +209 -0
- data/lib/octocatalog-diff/catalog/computed.rb +205 -0
- data/lib/octocatalog-diff/catalog/json.rb +30 -0
- data/lib/octocatalog-diff/catalog/noop.rb +19 -0
- data/lib/octocatalog-diff/catalog/puppetdb.rb +82 -0
- data/lib/octocatalog-diff/catalog/puppetmaster.rb +121 -0
- data/lib/octocatalog-diff/external/pson/LICENSE +17 -0
- data/lib/octocatalog-diff/external/pson/README.md +20 -0
- data/lib/octocatalog-diff/external/pson/common.rb +370 -0
- data/lib/octocatalog-diff/external/pson/pure.rb +15 -0
- data/lib/octocatalog-diff/external/pson/pure/generator.rb +395 -0
- data/lib/octocatalog-diff/external/pson/pure/parser.rb +307 -0
- data/lib/octocatalog-diff/external/pson/version.rb +8 -0
- data/lib/octocatalog-diff/facts.rb +125 -0
- data/lib/octocatalog-diff/facts/json.rb +20 -0
- data/lib/octocatalog-diff/facts/puppetdb.rb +59 -0
- data/lib/octocatalog-diff/facts/yaml.rb +29 -0
- data/lib/octocatalog-diff/puppetdb.rb +163 -0
- data/lib/octocatalog-diff/util/colored.rb +20 -0
- data/lib/octocatalog-diff/util/httparty.rb +158 -0
- data/lib/octocatalog-diff/util/parallel.rb +170 -0
- data/lib/octocatalog-diff/util/puppetversion.rb +24 -0
- data/lib/octocatalog-diff/version.rb +7 -0
- metadata +386 -0
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'open3'
|
3
|
+
require 'shellwords'
|
4
|
+
|
5
|
+
module OctocatalogDiff
|
6
|
+
module CatalogUtil
|
7
|
+
# Used to construct the command to run 'puppet' to construct the catalog.
|
8
|
+
class Command
|
9
|
+
# Constructor
|
10
|
+
def initialize(options = {}, logger = nil)
|
11
|
+
@options = options
|
12
|
+
@logger = logger
|
13
|
+
|
14
|
+
# Required parameters
|
15
|
+
@compilation_dir = options[:compilation_dir]
|
16
|
+
raise ArgumentError, 'Compile dir (:compilation_dir) must be a string' unless @compilation_dir.is_a?(String)
|
17
|
+
raise Errno::ENOENT, "Compile dir #{@compilation_dir} doesn't exist" unless File.exist?(@compilation_dir)
|
18
|
+
raise ArgumentError, "Compile dir #{@compilation_dir} not a directory" unless File.directory?(@compilation_dir)
|
19
|
+
|
20
|
+
@node = options[:node]
|
21
|
+
raise ArgumentError, 'Node must be specified to compile catalog' if @node.nil? || !@node.is_a?(String)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Build up the command line to run Puppet
|
25
|
+
def puppet_command
|
26
|
+
cmdline = []
|
27
|
+
|
28
|
+
# Where is the puppet binary?
|
29
|
+
puppet = @options[:puppet_binary]
|
30
|
+
raise ArgumentError, 'Puppet binary was not supplied' if puppet.nil?
|
31
|
+
raise Errno::ENOENT, "Puppet binary #{puppet} doesn't exist" unless File.file?(puppet)
|
32
|
+
cmdline << puppet
|
33
|
+
|
34
|
+
# Node to compile
|
35
|
+
cmdline.concat ['master', '--compile', Shellwords.escape(@node)]
|
36
|
+
|
37
|
+
# storeconfigs?
|
38
|
+
if @options[:storeconfigs]
|
39
|
+
cmdline.concat %w(--storeconfigs --storeconfigs_backend=puppetdb)
|
40
|
+
else
|
41
|
+
cmdline << '--no-storeconfigs'
|
42
|
+
end
|
43
|
+
|
44
|
+
# enc?
|
45
|
+
if @options[:enc]
|
46
|
+
raise Errno::ENOENT, "Did not find ENC as expected at #{@options[:enc]}" unless File.file?(@options[:enc])
|
47
|
+
cmdline << "--node_terminus=exec --external_nodes=#{Shellwords.escape(@options[:enc])}"
|
48
|
+
end
|
49
|
+
|
50
|
+
# Future parser?
|
51
|
+
cmdline << '--parser=future' if @options[:parser] == :future
|
52
|
+
|
53
|
+
# Path to facts, or a specific fact file?
|
54
|
+
facts_terminus = @options.fetch(:facts_terminus, 'yaml')
|
55
|
+
if facts_terminus == 'yaml'
|
56
|
+
cmdline << "--factpath=#{Shellwords.escape(File.join(@compilation_dir, 'var', 'yaml', 'facts'))}"
|
57
|
+
if @options[:fact_file].is_a?(String) && @options[:fact_file] =~ /.*\.(\w+)$/
|
58
|
+
fact_file = File.join(@compilation_dir, 'var', 'yaml', 'facts', "#{@node}.#{Regexp.last_match(1)}")
|
59
|
+
FileUtils.cp @options[:fact_file], fact_file unless File.file?(fact_file) || @options[:fact_file] == fact_file
|
60
|
+
end
|
61
|
+
cmdline << '--facts_terminus=yaml'
|
62
|
+
elsif facts_terminus == 'facter'
|
63
|
+
cmdline << '--facts_terminus=facter'
|
64
|
+
else
|
65
|
+
raise ArgumentError, "Unrecognized facts_terminus setting: '#{facts_terminus}'"
|
66
|
+
end
|
67
|
+
|
68
|
+
# Some typical options for puppet
|
69
|
+
cmdline.concat %w(
|
70
|
+
--no-daemonize
|
71
|
+
--no-ca
|
72
|
+
--color=false
|
73
|
+
--config_version="/bin/echo catalogscript"
|
74
|
+
--environment=production
|
75
|
+
)
|
76
|
+
|
77
|
+
# For people who aren't running hiera, a hiera-config will not be generated when @options[:hiera_config]
|
78
|
+
# is nil. For everyone else, the hiera config was generated/copied/munged in the 'builddir' class
|
79
|
+
# and was installed into the compile directory and named hiera.yaml.
|
80
|
+
unless @options[:hiera_config].nil?
|
81
|
+
cmdline << "--hiera_config=#{Shellwords.escape(File.join(@compilation_dir, 'hiera.yaml'))}"
|
82
|
+
end
|
83
|
+
|
84
|
+
# Options with parameters
|
85
|
+
cmdline << "--environmentpath=#{Shellwords.escape(File.join(@compilation_dir, 'environments'))}"
|
86
|
+
cmdline << "--vardir=#{Shellwords.escape(File.join(@compilation_dir, 'var'))}"
|
87
|
+
cmdline << "--logdir=#{Shellwords.escape(File.join(@compilation_dir, 'var'))}"
|
88
|
+
cmdline << "--ssldir=#{Shellwords.escape(File.join(@compilation_dir, 'var', 'ssl'))}"
|
89
|
+
cmdline << "--confdir=#{Shellwords.escape(@compilation_dir)}"
|
90
|
+
|
91
|
+
# Return full command
|
92
|
+
cmdline.join(' ')
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require_relative 'enc/noop'
|
2
|
+
require_relative 'enc/pe'
|
3
|
+
require_relative 'enc/script'
|
4
|
+
|
5
|
+
require 'stringio'
|
6
|
+
|
7
|
+
module OctocatalogDiff
|
8
|
+
module CatalogUtil
|
9
|
+
# Support a generic ENC. It must use one of the supported backends found in the
|
10
|
+
# 'enc' subdirectory.
|
11
|
+
class ENC
|
12
|
+
attr_reader :builder
|
13
|
+
|
14
|
+
# Constructor
|
15
|
+
# @param :backend [Symbol] If set, this will force a backend
|
16
|
+
# @param :enc [String] Path to ENC script (node_terminus = exec)
|
17
|
+
# @param # FIXME: Add support for PE's ENC endpoint API
|
18
|
+
def initialize(options = {})
|
19
|
+
@options = options
|
20
|
+
|
21
|
+
# Determine appropriate backend based on options supplied
|
22
|
+
@enc_obj = backend
|
23
|
+
|
24
|
+
# Initialize instance variables for content and error message.
|
25
|
+
@builder = @enc_obj.class.to_s
|
26
|
+
|
27
|
+
# Set the executed flag to false, so that it can be executed when something is retrieved.
|
28
|
+
@executed = false
|
29
|
+
end
|
30
|
+
|
31
|
+
# Retrieve content
|
32
|
+
# @return [String] ENC content, or nil if there was an error
|
33
|
+
def content
|
34
|
+
execute
|
35
|
+
@content ||= @enc_obj.content
|
36
|
+
end
|
37
|
+
|
38
|
+
# Retrieve error message
|
39
|
+
# @return [String] Error message, or nil if there was no error
|
40
|
+
def error_message
|
41
|
+
execute
|
42
|
+
@error_message ||= @enc_obj.error_message
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
# Backend - given options, choose an appropriate backend and construct the corresponding object.
|
48
|
+
# @return [?] Backend object
|
49
|
+
def backend
|
50
|
+
# Hard-coded backend
|
51
|
+
if @options[:backend]
|
52
|
+
return OctocatalogDiff::CatalogUtil::ENC::Noop.new(@options) if @options[:backend] == :noop
|
53
|
+
return OctocatalogDiff::CatalogUtil::ENC::PE.new(@options) if @options[:backend] == :pe
|
54
|
+
return OctocatalogDiff::CatalogUtil::ENC::Script.new(@options) if @options[:backend] == :script
|
55
|
+
raise ArgumentError, "Unknown backend :#{@options[:backend]}"
|
56
|
+
end
|
57
|
+
|
58
|
+
# Determine backend based on arguments
|
59
|
+
return OctocatalogDiff::CatalogUtil::ENC::PE.new(@options) if @options[:pe_enc_url]
|
60
|
+
return OctocatalogDiff::CatalogUtil::ENC::Script.new(@options) if @options[:enc]
|
61
|
+
|
62
|
+
# At this point we do not know what backend to use for the ENC
|
63
|
+
raise ArgumentError, 'Unable to determine ENC backend to use'
|
64
|
+
end
|
65
|
+
|
66
|
+
# Execute the 'execute' method of the object, but only once
|
67
|
+
# @param [Logger] Logger (optional) - if not supplied any logger messages will be discarded
|
68
|
+
def execute(logger = nil)
|
69
|
+
return if @executed
|
70
|
+
logger ||= @options[:logger]
|
71
|
+
logger ||= Logger.new(StringIO.new)
|
72
|
+
@enc_obj.execute(logger) if @enc_obj.respond_to?(:execute)
|
73
|
+
@executed = true
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module OctocatalogDiff
|
2
|
+
module CatalogUtil
|
3
|
+
class ENC
|
4
|
+
# No-op ENC.
|
5
|
+
class Noop
|
6
|
+
# Constructor
|
7
|
+
def initialize(_options)
|
8
|
+
end
|
9
|
+
|
10
|
+
# Retrieve content
|
11
|
+
def content
|
12
|
+
''
|
13
|
+
end
|
14
|
+
|
15
|
+
# Error message
|
16
|
+
def error_message
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require_relative 'pe/v1'
|
2
|
+
require_relative '../../util/httparty'
|
3
|
+
require_relative '../facts'
|
4
|
+
|
5
|
+
module OctocatalogDiff
|
6
|
+
module CatalogUtil
|
7
|
+
class ENC
|
8
|
+
# Support the Puppet Enterprise classification API.
|
9
|
+
# Documentation: https://docs.puppet.com/pe/latest/nc_index.html
|
10
|
+
class PE
|
11
|
+
# Error class that can be caught
|
12
|
+
class ClassificationError < RuntimeError; end
|
13
|
+
|
14
|
+
# Allow the main ENC object to retrieve these values
|
15
|
+
attr_reader :content, :error_message
|
16
|
+
|
17
|
+
# Constructor
|
18
|
+
# @param options [Hash] Options - must contain the Puppet Enterprise URL and the node
|
19
|
+
def initialize(options)
|
20
|
+
# Make sure the node is in the options
|
21
|
+
raise ArgumentError, 'OctocatalogDiff::CatalogUtil::ENC::PE#new requires :node' unless options.key?(:node)
|
22
|
+
@node = options[:node]
|
23
|
+
|
24
|
+
# Retrieve the base URL for the Puppet Enterprise ENC service
|
25
|
+
raise ArgumentError, 'OctocatalogDiff::CatalogUtil::ENC::PE#new requires :pe_enc_url' unless options.key?(:pe_enc_url)
|
26
|
+
|
27
|
+
# Save options
|
28
|
+
@options = options
|
29
|
+
|
30
|
+
# Get the object corresponding to the version of the API in use.
|
31
|
+
# (Right now this is hard-coded at V1 because that is the only version there is. In the future
|
32
|
+
# if there are different versions, this will need to be parameterized.)
|
33
|
+
@api = OctocatalogDiff::CatalogUtil::ENC::PE::V1.new(@options)
|
34
|
+
|
35
|
+
# Initialize the content and error message
|
36
|
+
@content = nil
|
37
|
+
@error_message = 'The execute method was never run'
|
38
|
+
end
|
39
|
+
|
40
|
+
# Executor
|
41
|
+
# @param logger [Logger] Logger object
|
42
|
+
def execute(logger)
|
43
|
+
logger.debug "Beginning OctocatalogDiff::CatalogUtil::ENC::PE#execute for #{@node}"
|
44
|
+
|
45
|
+
@options[:facts] ||= facts(logger)
|
46
|
+
return unless @options[:facts]
|
47
|
+
|
48
|
+
more_options = { headers: @api.headers, timeout: @options[:timeout] || 10 }
|
49
|
+
post_hash = @api.body
|
50
|
+
url = @api.url
|
51
|
+
response = OctocatalogDiff::Util::HTTParty.post(url, @options.merge(more_options), post_hash, 'pe_enc')
|
52
|
+
|
53
|
+
unless response[:code] == 200
|
54
|
+
logger.debug "PE ENC failed: #{response.inspect}"
|
55
|
+
logger.error "PE ENC failed: Response from #{url} was #{response[:code]}"
|
56
|
+
@error_message = "Response from #{url} was #{response[:code]}"
|
57
|
+
return
|
58
|
+
end
|
59
|
+
|
60
|
+
logger.debug "Response from #{url} was #{response[:code]}"
|
61
|
+
unless response[:parsed].is_a?(Hash)
|
62
|
+
logger.error "PE ENC failed: Response from #{url} was not a hash! #{response[:parsed].inspect}"
|
63
|
+
@error_message = "PE ENC failed: Response from #{url} was not a hash! #{response[:parsed].class}"
|
64
|
+
return
|
65
|
+
end
|
66
|
+
|
67
|
+
begin
|
68
|
+
@content = @api.result(response[:parsed], logger)
|
69
|
+
@error_message = nil
|
70
|
+
rescue OctocatalogDiff::CatalogUtil::ENC::PE::ClassificationError => exc
|
71
|
+
@error_message = exc.message
|
72
|
+
logger.error "PE ENC failed: #{exc.message}"
|
73
|
+
return
|
74
|
+
end
|
75
|
+
|
76
|
+
logger.debug "Completed OctocatalogDiff::CatalogUtil::ENC::PE#execute for #{@node}"
|
77
|
+
end
|
78
|
+
|
79
|
+
# Facts
|
80
|
+
# @param logger [Logger] Logger object
|
81
|
+
# @return [OctocatalogDiff::Facts] Facts object
|
82
|
+
def facts(logger)
|
83
|
+
facts_obj = OctocatalogDiff::CatalogUtil::Facts.new(@options, logger)
|
84
|
+
logger.debug "Start retrieving facts for #{@node} from #{self.class}"
|
85
|
+
begin
|
86
|
+
result = facts_obj.facts
|
87
|
+
logger.debug "Success retrieving facts for #{@node} from #{self.class}"
|
88
|
+
rescue OctocatalogDiff::Facts::FactRetrievalError, OctocatalogDiff::Facts::FactSourceError => exc
|
89
|
+
@content = nil
|
90
|
+
@error_message = "Fact retrieval failed: #{exc.class} - #{exc.message}"
|
91
|
+
logger.error @error_message
|
92
|
+
result = nil
|
93
|
+
end
|
94
|
+
result
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'uri'
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
module OctocatalogDiff
|
6
|
+
module CatalogUtil
|
7
|
+
class ENC
|
8
|
+
class PE
|
9
|
+
# Support the Puppet Enterprise classification API.
|
10
|
+
# Documentation: https://docs.puppet.com/pe/latest/nc_index.html
|
11
|
+
# This is version 1 of the API
|
12
|
+
class V1
|
13
|
+
# Constructor
|
14
|
+
# @param options [Hash] All input options
|
15
|
+
def initialize(options)
|
16
|
+
@options = options
|
17
|
+
end
|
18
|
+
|
19
|
+
# Return the URL to the API
|
20
|
+
# @return [String] API URL
|
21
|
+
def url
|
22
|
+
"#{@options[:pe_enc_url]}/v1/classified/nodes/#{@options[:node]}"
|
23
|
+
end
|
24
|
+
|
25
|
+
# Headers
|
26
|
+
# @return [Hash] Headers for request
|
27
|
+
def headers
|
28
|
+
result = {
|
29
|
+
'Accept' => 'application/json',
|
30
|
+
'Content-Type' => 'application/json'
|
31
|
+
}
|
32
|
+
result['X-Authentication'] = @options[:pe_enc_token] if @options[:pe_enc_token]
|
33
|
+
result
|
34
|
+
end
|
35
|
+
|
36
|
+
# POST body
|
37
|
+
# @return [String] POST body data
|
38
|
+
def body
|
39
|
+
raise ":facts required (got #{@options[:facts].class})" unless @options[:facts].is_a?(OctocatalogDiff::Facts)
|
40
|
+
{ 'fact' => @options[:facts].facts }.to_json
|
41
|
+
end
|
42
|
+
|
43
|
+
# Parse response from ENC and return the final ENC data
|
44
|
+
# @param parsed [Parsed response] Parsed response from ENC
|
45
|
+
# @param logger [Logger] Logger.object
|
46
|
+
# @return [String] ENC data as text
|
47
|
+
def result(parsed, logger)
|
48
|
+
%w(classes parameters).each do |required_key|
|
49
|
+
next if parsed[required_key]
|
50
|
+
logger.debug parsed.keys.inspect
|
51
|
+
raise OctocatalogDiff::CatalogUtil::ENC::PE::ClassificationError, "Response missing: #{required_key}"
|
52
|
+
end
|
53
|
+
|
54
|
+
obj = { 'classes' => parsed['classes'], 'parameters' => parsed['parameters'] }
|
55
|
+
obj.to_yaml
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'open3'
|
3
|
+
require 'shellwords'
|
4
|
+
require 'tempfile'
|
5
|
+
|
6
|
+
module OctocatalogDiff
|
7
|
+
module CatalogUtil
|
8
|
+
class ENC
|
9
|
+
# Support an ENC that executes a script on this system which returns the ENC data on STDOUT.
|
10
|
+
class Script
|
11
|
+
attr_reader :content, :error_message, :script
|
12
|
+
|
13
|
+
# Constructor
|
14
|
+
# @param options [Hash] Options - must contain script name and node name, plus tempdir if it's a relative path
|
15
|
+
def initialize(options)
|
16
|
+
# Make sure the node is in the options
|
17
|
+
raise ArgumentError, 'OctocatalogDiff::CatalogUtil::ENC::Script#new requires :node' unless options.key?(:node)
|
18
|
+
@node = options[:node]
|
19
|
+
|
20
|
+
# Determine path to ENC and make sure it exists
|
21
|
+
raise ArgumentError, 'OctocatalogDiff::CatalogUtil::ENC::Script#new requires :enc' unless options.key?(:enc)
|
22
|
+
@script = script_path(options[:enc], options[:tempdir])
|
23
|
+
|
24
|
+
# Other options we may recognize
|
25
|
+
@pass_env_vars = options.fetch(:pass_env_vars, [])
|
26
|
+
|
27
|
+
# Initialize the content and error message
|
28
|
+
@content = nil
|
29
|
+
@error_message = 'The execute method was never run'
|
30
|
+
end
|
31
|
+
|
32
|
+
# Executor
|
33
|
+
# @param logger [Logger] Logger object
|
34
|
+
def execute(logger)
|
35
|
+
logger.debug "Beginning OctocatalogDiff::CatalogUtil::ENC::Script#execute for #{@node} with #{@script}"
|
36
|
+
logger.debug "Passing these extra environment variables: #{@pass_env_vars}" if @pass_env_vars.any?
|
37
|
+
|
38
|
+
# Copy the script and make it executable
|
39
|
+
# Then run the command in the restricted environment
|
40
|
+
raise Errno::ENOENT, "ENC #{@script} wasn't found" unless File.file?(@script)
|
41
|
+
file = Tempfile.open('enc.sh')
|
42
|
+
file.close
|
43
|
+
begin
|
44
|
+
FileUtils.cp @script, file.path
|
45
|
+
FileUtils.chmod 0o755, file.path
|
46
|
+
env = {
|
47
|
+
'HOME' => ENV['HOME'],
|
48
|
+
'PATH' => ENV['PATH'],
|
49
|
+
'PWD' => File.dirname(@script)
|
50
|
+
}
|
51
|
+
@pass_env_vars.each { |var| env[var] ||= ENV[var] }
|
52
|
+
command = [file.path, @node].map { |x| Shellwords.escape(x) }.join(' ')
|
53
|
+
out, err, status = Open3.capture3(env, command, unsetenv_others: true, chdir: File.dirname(@script))
|
54
|
+
logger.debug "ENC exited #{status.exitstatus}: #{out.length} bytes to STDOUT, #{err.length} bytes to STDERR"
|
55
|
+
ensure
|
56
|
+
file.unlink
|
57
|
+
end
|
58
|
+
|
59
|
+
# Analyze the output
|
60
|
+
if status.exitstatus.zero?
|
61
|
+
@content = out
|
62
|
+
@error_message = nil
|
63
|
+
logger.warn "ENC STDERR: #{err}" unless err.empty?
|
64
|
+
else
|
65
|
+
@content = nil
|
66
|
+
@error_message = "ENC failed with status #{status.exitstatus}: #{out} #{err}"
|
67
|
+
logger.error "ENC failed - Status #{status.exitstatus}"
|
68
|
+
logger.error "Failed ENC printed this to STDOUT: #{out}" unless out.empty?
|
69
|
+
logger.error "Failed ENC printed this to STDERR: #{err}" unless err.empty?
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
# Determine the script path for the incoming file -- absolute or relative
|
76
|
+
# @param enc [String] Path to ENC supplied by user/config
|
77
|
+
# @param tempdir [String]
|
78
|
+
# @return [String] Full path to file on system
|
79
|
+
def script_path(enc, tempdir)
|
80
|
+
return enc if enc.start_with? '/'
|
81
|
+
raise ArgumentError, 'OctocatalogDiff::CatalogUtil::ENC::Script#new requires :tempdir' unless tempdir.is_a?(String)
|
82
|
+
return File.join(tempdir, enc) if enc =~ %r{^environments/production/}
|
83
|
+
File.join(tempdir, 'environments', 'production', enc)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|