octocatalog-diff 0.6.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.version +1 -1
- data/README.md +5 -2
- data/bin/octocatalog-diff +9 -49
- data/doc/CHANGELOG.md +14 -0
- data/doc/advanced-filter.md +59 -1
- data/doc/advanced-override-enc.md +54 -0
- data/doc/advanced-pe-enc.md +1 -1
- data/doc/advanced.md +2 -1
- data/doc/dev/api.md +5 -0
- data/doc/dev/api/v1.md +41 -0
- data/doc/dev/api/v1/calls/catalog-diff.md +209 -0
- data/doc/dev/api/v1/calls/catalog.md +115 -0
- data/doc/dev/api/v1/calls/config.md +37 -0
- data/doc/dev/api/v1/objects/catalog.md +127 -0
- data/doc/dev/api/v1/objects/diff.md +261 -0
- data/doc/dev/api/v1/objects/override.md +30 -0
- data/doc/dev/how-to-add-options.md +12 -12
- data/doc/optionsref.md +91 -74
- data/doc/versions/v1.md +22 -0
- data/lib/octocatalog-diff.rb +1 -8
- data/lib/octocatalog-diff/api/v1.rb +27 -0
- data/lib/octocatalog-diff/api/v1/catalog-compile.rb +40 -0
- data/lib/octocatalog-diff/api/v1/catalog-diff.rb +68 -0
- data/lib/octocatalog-diff/api/v1/catalog.rb +84 -0
- data/lib/octocatalog-diff/api/v1/common.rb +24 -0
- data/lib/octocatalog-diff/api/v1/config.rb +125 -0
- data/lib/octocatalog-diff/api/v1/diff.rb +194 -0
- data/lib/octocatalog-diff/api/v1/override.rb +103 -0
- data/lib/octocatalog-diff/catalog-diff/differ.rb +66 -47
- data/lib/octocatalog-diff/catalog-diff/display.rb +8 -2
- data/lib/octocatalog-diff/catalog-diff/display/json.rb +3 -2
- data/lib/octocatalog-diff/catalog-diff/display/legacy_json.rb +28 -0
- data/lib/octocatalog-diff/catalog-diff/display/text.rb +64 -9
- data/lib/octocatalog-diff/catalog-diff/filter.rb +45 -6
- data/lib/octocatalog-diff/catalog-diff/filter/absent_file.rb +65 -0
- data/lib/octocatalog-diff/catalog-diff/filter/compilation_dir.rb +78 -0
- data/lib/octocatalog-diff/catalog-diff/filter/yaml.rb +10 -7
- data/lib/octocatalog-diff/catalog-util/bootstrap.rb +13 -14
- data/lib/octocatalog-diff/catalog-util/builddir.rb +1 -0
- data/lib/octocatalog-diff/catalog-util/cached_master_directory.rb +2 -2
- data/lib/octocatalog-diff/catalog-util/enc.rb +49 -14
- data/lib/octocatalog-diff/catalog-util/enc/pe.rb +3 -5
- data/lib/octocatalog-diff/catalog-util/enc/pe/v1.rb +3 -1
- data/lib/octocatalog-diff/catalog-util/git.rb +36 -24
- data/lib/octocatalog-diff/catalog.rb +5 -9
- data/lib/octocatalog-diff/catalog/computed.rb +9 -1
- data/lib/octocatalog-diff/catalog/puppetdb.rb +4 -3
- data/lib/octocatalog-diff/cli.rb +195 -0
- data/lib/octocatalog-diff/cli/diffs.rb +40 -0
- data/lib/octocatalog-diff/cli/options.rb +183 -0
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/basedir.rb +1 -1
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/bootstrap_current.rb +1 -1
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/bootstrap_environment.rb +1 -1
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/bootstrap_script.rb +1 -1
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/bootstrap_then_exit.rb +1 -1
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/bootstrapped_dirs.rb +1 -1
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/cached_master_dir.rb +1 -1
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/catalog_only.rb +1 -1
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/color.rb +1 -1
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/command_line.rb +2 -2
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/compare_file_text.rb +1 -1
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/create_symlinks.rb +2 -2
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/debug.rb +1 -1
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/debug_bootstrap.rb +1 -1
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/display_datatype_changes.rb +1 -1
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/display_detail_add.rb +1 -1
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/display_source_file_line.rb +1 -1
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/enc.rb +1 -1
- data/lib/octocatalog-diff/cli/options/enc_override.rb +21 -0
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/environment.rb +2 -2
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/existing_catalogs.rb +1 -1
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/fact_file.rb +1 -1
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/fact_override.rb +2 -2
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/facts_terminus.rb +1 -1
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/filters.rb +5 -2
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/from_puppetdb.rb +1 -1
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/header.rb +1 -1
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/hiera_config.rb +1 -1
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/hiera_path.rb +1 -1
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/hiera_path_strip.rb +1 -1
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/hostname.rb +1 -1
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/ignore.rb +1 -1
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/ignore_attr.rb +1 -1
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/ignore_tags.rb +1 -1
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/include_tags.rb +1 -1
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/master_cache_branch.rb +1 -1
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/output_file.rb +1 -1
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/output_format.rb +5 -3
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/parallel.rb +1 -1
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/parser.rb +1 -1
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/pass_env_vars.rb +1 -1
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/pe_enc_ssl_ca.rb +1 -1
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/pe_enc_ssl_client_cert.rb +1 -1
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/pe_enc_ssl_client_key.rb +1 -1
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/pe_enc_token.rb +1 -1
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/pe_enc_token_file.rb +1 -1
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/pe_enc_url.rb +1 -1
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/preserve_environments.rb +1 -1
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/puppet_binary.rb +2 -2
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/puppet_master.rb +2 -2
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/puppet_master_api_version.rb +2 -2
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/puppet_master_ssl_ca.rb +2 -2
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/puppet_master_ssl_client_cert.rb +2 -2
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/puppet_master_ssl_client_key.rb +2 -2
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/puppetdb_api_version.rb +1 -1
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/puppetdb_ssl_ca.rb +1 -1
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/puppetdb_ssl_client_cert.rb +1 -1
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/puppetdb_ssl_client_key.rb +1 -1
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/puppetdb_ssl_client_password.rb +1 -1
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/puppetdb_ssl_client_password_file.rb +1 -1
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/puppetdb_url.rb +1 -1
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/quiet.rb +1 -1
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/retry_failed_catalog.rb +1 -1
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/safe_to_delete_cached_master_dir.rb +1 -1
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/storeconfigs.rb +1 -1
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/suppress_absent_file_details.rb +2 -1
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/to_from_branch.rb +1 -1
- data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/validate_references.rb +1 -1
- data/lib/octocatalog-diff/cli/printer.rb +52 -0
- data/lib/octocatalog-diff/errors.rb +33 -0
- data/lib/octocatalog-diff/facts.rb +1 -4
- data/lib/octocatalog-diff/facts/puppetdb.rb +8 -7
- data/lib/octocatalog-diff/puppetdb.rb +5 -9
- data/lib/octocatalog-diff/util/catalogs.rb +242 -0
- metadata +97 -75
- data/lib/octocatalog-diff/catalog-diff/cli.rb +0 -211
- data/lib/octocatalog-diff/catalog-diff/cli/catalogs.rb +0 -246
- data/lib/octocatalog-diff/catalog-diff/cli/diffs.rb +0 -147
- data/lib/octocatalog-diff/catalog-diff/cli/helpers/fact_override.rb +0 -100
- data/lib/octocatalog-diff/catalog-diff/cli/options.rb +0 -185
- data/lib/octocatalog-diff/catalog-diff/cli/printer.rb +0 -54
@@ -3,7 +3,7 @@
|
|
3
3
|
# Set storeconfigs (integration with PuppetDB for collected resources)
|
4
4
|
# @param parser [OptionParser object] The OptionParser argument
|
5
5
|
# @param options [Hash] Options hash being constructed; this is modified in this method.
|
6
|
-
OctocatalogDiff::
|
6
|
+
OctocatalogDiff::Cli::Options::Option.newoption(:storeconfigs) do
|
7
7
|
has_weight 220
|
8
8
|
|
9
9
|
def parse(parser, options)
|
@@ -3,9 +3,10 @@
|
|
3
3
|
# If enabled, this option will suppress changes to certain attributes of a file, if the
|
4
4
|
# file is specified to be 'absent' in the target catalog. Suppressed changes in this case
|
5
5
|
# include user, group, mode, and content, because a removed file has none of those.
|
6
|
+
# <i>This option is DEPRECATED; please use <code>--filters AbsentFile</code> instead.</i>
|
6
7
|
# @param parser [OptionParser object] The OptionParser argument
|
7
8
|
# @param options [Hash] Options hash being constructed; this is modified in this method.
|
8
|
-
OctocatalogDiff::
|
9
|
+
OctocatalogDiff::Cli::Options::Option.newoption(:suppress_absent_file_details) do
|
9
10
|
has_weight 600
|
10
11
|
|
11
12
|
def parse(parser, options)
|
@@ -4,7 +4,7 @@
|
|
4
4
|
# the current contents of the base code directory without any git checkouts.
|
5
5
|
# @param parser [OptionParser object] The OptionParser argument
|
6
6
|
# @param options [Hash] Options hash being constructed; this is modified in this method.
|
7
|
-
OctocatalogDiff::
|
7
|
+
OctocatalogDiff::Cli::Options::Option.newoption(:to_from_branch) do
|
8
8
|
has_weight 20
|
9
9
|
|
10
10
|
def parse(parser, options)
|
@@ -5,7 +5,7 @@
|
|
5
5
|
# parameters are to be checked.
|
6
6
|
# @param parser [OptionParser object] The OptionParser argument
|
7
7
|
# @param options [Hash] Options hash being constructed; this is modified in this method.
|
8
|
-
OctocatalogDiff::
|
8
|
+
OctocatalogDiff::Cli::Options::Option.newoption(:validate_references) do
|
9
9
|
has_weight 205
|
10
10
|
|
11
11
|
def parse(parser, options)
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../catalog-diff/display'
|
4
|
+
require_relative '../errors'
|
5
|
+
|
6
|
+
module OctocatalogDiff
|
7
|
+
class Cli
|
8
|
+
# Wrapper around OctocatalogDiff::CatalogDiff::Display to set the options and
|
9
|
+
# output to a file or the screen depending on selection.
|
10
|
+
class Printer
|
11
|
+
# Constructor
|
12
|
+
# @param options [Hash] Options from cli/options
|
13
|
+
# @param logger [Logger] Logger object
|
14
|
+
def initialize(options, logger)
|
15
|
+
@options = options
|
16
|
+
@logger = logger
|
17
|
+
end
|
18
|
+
|
19
|
+
# The method to call externally, passing in diffs. This takes the appropriate action
|
20
|
+
# based on options, which is either to write the result into an output file, or print
|
21
|
+
# the result on STDOUT. Does not return anything.
|
22
|
+
# @param diffs [Array<Diffs>] Array of differences
|
23
|
+
# @param from_dir [String] Directory in which "from" catalog was compiled
|
24
|
+
# @param to_dir [String] Directory in which "to" catalog was compiled
|
25
|
+
def printer(diffs, from_dir = nil, to_dir = nil)
|
26
|
+
unless diffs.is_a?(Array)
|
27
|
+
raise ArgumentError, "printer() expects an array, not #{diffs.class}"
|
28
|
+
end
|
29
|
+
display_opts = @options.merge(compilation_from_dir: from_dir, compilation_to_dir: to_dir)
|
30
|
+
diff_text = OctocatalogDiff::CatalogDiff::Display.output(diffs, display_opts, @logger)
|
31
|
+
if @options[:output_file].nil?
|
32
|
+
puts diff_text unless diff_text.empty?
|
33
|
+
else
|
34
|
+
output_to_file(diff_text)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
# Output to a file, handling errors related to writing files.
|
41
|
+
# @param diff_in [String|Array] Text to write to file
|
42
|
+
def output_to_file(diff_in)
|
43
|
+
diff_text = diff_in.is_a?(Array) ? diff_in.join("\n") : diff_in
|
44
|
+
File.open(@options[:output_file], 'w') { |f| f.write(diff_text) }
|
45
|
+
@logger.info "Wrote diff to #{@options[:output_file]}"
|
46
|
+
rescue Errno::ENOENT, Errno::EACCES, Errno::EISDIR => exc
|
47
|
+
@logger.error "Cannot write to #{@options[:output_file]}: #{exc}"
|
48
|
+
raise OctocatalogDiff::Errors::PrinterError, "Cannot write to #{@options[:output_file]}: #{exc}"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OctocatalogDiff
|
4
|
+
# Contains error classes raised by this gem
|
5
|
+
class Errors
|
6
|
+
# Error classes for handled configuration file errors
|
7
|
+
class ConfigurationFileNotFoundError < RuntimeError; end
|
8
|
+
class ConfigurationFileContentError < RuntimeError; end
|
9
|
+
|
10
|
+
# Error classes for building catalogs
|
11
|
+
class BootstrapError < RuntimeError; end
|
12
|
+
class CatalogError < RuntimeError; end
|
13
|
+
class PuppetVersionError < RuntimeError; end
|
14
|
+
class ReferenceValidationError < RuntimeError; end
|
15
|
+
class GitCheckoutError < RuntimeError; end
|
16
|
+
|
17
|
+
# Error classes for retrieving facts
|
18
|
+
class FactSourceError < RuntimeError; end
|
19
|
+
class FactRetrievalError < RuntimeError; end
|
20
|
+
|
21
|
+
# Errors for PuppetDB
|
22
|
+
class PuppetDBNodeNotFoundError < RuntimeError; end
|
23
|
+
class PuppetDBGenericError < RuntimeError; end
|
24
|
+
class PuppetDBConnectionError < RuntimeError; end
|
25
|
+
|
26
|
+
# Errors for Puppet Enterprise
|
27
|
+
class PEClassificationError < RuntimeError; end
|
28
|
+
|
29
|
+
# Miscellanous catalog-diff errors
|
30
|
+
class DifferError < RuntimeError; end
|
31
|
+
class PrinterError < RuntimeError; end
|
32
|
+
end
|
33
|
+
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'errors'
|
3
4
|
require_relative 'facts/json'
|
4
5
|
require_relative 'facts/yaml'
|
5
6
|
require_relative 'facts/puppetdb'
|
@@ -119,9 +120,5 @@ module OctocatalogDiff
|
|
119
120
|
@facts['values'][key] = value
|
120
121
|
end
|
121
122
|
end
|
122
|
-
|
123
|
-
# Separate classes to handle errors we throw explicitly
|
124
|
-
class FactSourceError < RuntimeError; end
|
125
|
-
class FactRetrievalError < RuntimeError; end
|
126
123
|
end
|
127
124
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative '../errors'
|
3
4
|
require_relative '../facts'
|
4
5
|
require_relative '../puppetdb'
|
5
6
|
require 'yaml'
|
@@ -42,21 +43,21 @@ module OctocatalogDiff
|
|
42
43
|
result.map { |x| facts[x['name']] = x['value'] }
|
43
44
|
if facts.empty?
|
44
45
|
message = "Unable to retrieve facts for node #{node} from PuppetDB (empty or nil)!"
|
45
|
-
raise OctocatalogDiff::
|
46
|
+
raise OctocatalogDiff::Errors::FactRetrievalError, message
|
46
47
|
end
|
47
48
|
|
48
49
|
# Create a structure compatible with YAML fact files.
|
49
50
|
obj_to_return = { 'name' => node, 'values' => {} }
|
50
51
|
facts.each { |k, v| obj_to_return['values'][k.sub(/^::/, '')] = v }
|
51
52
|
break # Not return, to avoid LocalJumpError in Ruby 2.2
|
52
|
-
rescue OctocatalogDiff::
|
53
|
-
exception_class = OctocatalogDiff::
|
53
|
+
rescue OctocatalogDiff::Errors::PuppetDBConnectionError => exc
|
54
|
+
exception_class = OctocatalogDiff::Errors::FactSourceError
|
54
55
|
exception_message = "Fact retrieval failed (#{exc.class}) (#{exc.message})"
|
55
|
-
rescue OctocatalogDiff::
|
56
|
-
exception_class = OctocatalogDiff::
|
56
|
+
rescue OctocatalogDiff::Errors::PuppetDBNodeNotFoundError => exc
|
57
|
+
exception_class = OctocatalogDiff::Errors::FactRetrievalError
|
57
58
|
exception_message = "Node #{node} not found in PuppetDB (#{exc.message})"
|
58
|
-
rescue OctocatalogDiff::
|
59
|
-
exception_class = OctocatalogDiff::
|
59
|
+
rescue OctocatalogDiff::Errors::PuppetDBGenericError => exc
|
60
|
+
exception_class = OctocatalogDiff::Errors::FactRetrievalError
|
60
61
|
exception_message = "Fact retrieval failed for node #{node} from PuppetDB (#{exc.message})"
|
61
62
|
end
|
62
63
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'errors'
|
3
4
|
require_relative 'util/httparty'
|
4
5
|
|
5
6
|
require 'uri'
|
@@ -14,11 +15,6 @@ URI::HTTPS.const_set(:DEFAULT_PORT, 8081)
|
|
14
15
|
module OctocatalogDiff
|
15
16
|
# A standard way to connect to PuppetDB from the various scripts in this repository.
|
16
17
|
class PuppetDB
|
17
|
-
# This class raises errors for certain handled problems
|
18
|
-
class NotFoundError < RuntimeError; end
|
19
|
-
class PuppetDBError < RuntimeError; end
|
20
|
-
class ConnectionError < RuntimeError; end
|
21
|
-
|
22
18
|
# Allow connections to be read (used in tests for now)
|
23
19
|
attr_reader :connections
|
24
20
|
|
@@ -87,7 +83,7 @@ module OctocatalogDiff
|
|
87
83
|
def get(path)
|
88
84
|
_get(path)
|
89
85
|
rescue Net::OpenTimeout, Errno::ECONNREFUSED => exc
|
90
|
-
raise
|
86
|
+
raise OctocatalogDiff::Errors::PuppetDBConnectionError, "#{exc.class} connecting to PuppetDB (need VPN on?): #{exc.message}"
|
91
87
|
end
|
92
88
|
|
93
89
|
private
|
@@ -120,8 +116,8 @@ module OctocatalogDiff
|
|
120
116
|
|
121
117
|
# Handle all non-200's from PuppetDB
|
122
118
|
unless response[:code] == 200
|
123
|
-
raise
|
124
|
-
raise
|
119
|
+
raise OctocatalogDiff::Errors::PuppetDBNodeNotFoundError, "404 - #{response[:error]}" if response[:code] == 404
|
120
|
+
raise OctocatalogDiff::Errors::PuppetDBGenericError, "#{response[:code]} - #{response[:error]}"
|
125
121
|
end
|
126
122
|
|
127
123
|
# PuppetDB can return 'Not Found' as a string with a 200 response code
|
@@ -129,7 +125,7 @@ module OctocatalogDiff
|
|
129
125
|
|
130
126
|
# PuppetDB can also return an error message in a 200; we'll call this a 500
|
131
127
|
if response.key?(:error)
|
132
|
-
raise
|
128
|
+
raise OctocatalogDiff::Errors::PuppetDBGenericError, "500 - #{response[:error]}"
|
133
129
|
end
|
134
130
|
|
135
131
|
# If we get here without raising an error, it will fall out of the begin/rescue
|
@@ -0,0 +1,242 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'open3'
|
5
|
+
require 'yaml'
|
6
|
+
require_relative '../catalog'
|
7
|
+
require_relative '../errors'
|
8
|
+
require_relative 'parallel'
|
9
|
+
|
10
|
+
module OctocatalogDiff
|
11
|
+
module Util
|
12
|
+
# Helper class to construct catalogs, performing all necessary steps such as
|
13
|
+
# bootstrapping directories, installing facts, and running puppet.
|
14
|
+
class Catalogs
|
15
|
+
# Constructor
|
16
|
+
# @param options [Hash] Options
|
17
|
+
# @param logger [Logger] Logger object
|
18
|
+
def initialize(options, logger)
|
19
|
+
@options = options
|
20
|
+
@logger = logger
|
21
|
+
@catalogs = nil
|
22
|
+
raise '@logger must not be nil' if @logger.nil?
|
23
|
+
end
|
24
|
+
|
25
|
+
# Compile catalogs. This handles building both the old and new catalog (in parallel) and returns
|
26
|
+
# only when both catalogs have been built.
|
27
|
+
# @return [Hash] { :from => [OctocatalogDiff::Catalog], :to => [OctocatalogDiff::Catalog] }
|
28
|
+
def catalogs
|
29
|
+
@catalogs ||= build_catalog_parallelizer
|
30
|
+
end
|
31
|
+
|
32
|
+
# Handles the "bootstrap then exit" option, which bootstraps directories but
|
33
|
+
# exits without compiling catalogs.
|
34
|
+
def bootstrap_then_exit
|
35
|
+
@logger.debug('Begin bootstrap_then_exit')
|
36
|
+
OctocatalogDiff::CatalogUtil::Bootstrap.bootstrap_directory_parallelizer(@options, @logger)
|
37
|
+
@logger.debug('Success bootstrap_then_exit')
|
38
|
+
@logger.info('Successfully completed --bootstrap-then-exit action')
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
# Parallelizes bootstrapping of directories and building catalogs.
|
44
|
+
# @return [Hash] { :from => OctocatalogDiff::Catalog, :to => OctocatalogDiff::Catalog }
|
45
|
+
def build_catalog_parallelizer
|
46
|
+
# Construct parallel tasks. The array supplied to OctocatalogDiff::Util::Parallel is the task portion
|
47
|
+
# of each of the tuples in catalog_tasks.
|
48
|
+
catalog_tasks = build_catalog_tasks
|
49
|
+
|
50
|
+
# Update any tasks for catalogs that do not need to be compiled. This is the case when --catalog-only
|
51
|
+
# is specified and only one catalog is to be built. This will change matching catalog tasks to the 'noop' type.
|
52
|
+
catalog_tasks.map! do |x|
|
53
|
+
if @options["#{x[0]}_catalog".to_sym] == '-'
|
54
|
+
x[1].args[:backend] = :noop
|
55
|
+
elsif @options["#{x[0]}_catalog".to_sym].is_a?(String)
|
56
|
+
x[1].args[:json] = File.read(@options["#{x[0]}_catalog".to_sym])
|
57
|
+
x[1].args[:backend] = :json
|
58
|
+
end
|
59
|
+
x
|
60
|
+
end
|
61
|
+
|
62
|
+
# Initialize the objects for each parallel task. Initializing the object is very fast and does not actually
|
63
|
+
# build the catalog.
|
64
|
+
result = {}
|
65
|
+
catalog_tasks.each do |x|
|
66
|
+
result[x[0]] = OctocatalogDiff::Catalog.new(x[1].args)
|
67
|
+
@logger.debug "Initialized #{result[x[0]].builder} for #{x[0]}-catalog"
|
68
|
+
end
|
69
|
+
|
70
|
+
# Disable --compare-file-text if either (or both) of the chosen backends do not support it
|
71
|
+
if @options.fetch(:compare_file_text, false)
|
72
|
+
result.each do |_key, val|
|
73
|
+
next unless val.convert_file_resources == false
|
74
|
+
@logger.debug "Disabling --compare-file-text; not supported by #{val.builder}"
|
75
|
+
@options[:compare_file_text] = false
|
76
|
+
catalog_tasks.map! do |x|
|
77
|
+
x[1].args[:compare_file_text] = false
|
78
|
+
x
|
79
|
+
end
|
80
|
+
break
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Inject the starting object into the catalog tasks
|
85
|
+
catalog_tasks.map! do |x|
|
86
|
+
x[1].args[:object] = result[x[0]]
|
87
|
+
x
|
88
|
+
end
|
89
|
+
|
90
|
+
# Execute the parallelized catalog builds
|
91
|
+
passed_catalog_tasks = catalog_tasks.map { |x| x[1] }
|
92
|
+
parallel_catalogs = OctocatalogDiff::Util::Parallel.run_tasks(passed_catalog_tasks, @logger, @options[:parallel])
|
93
|
+
|
94
|
+
# If the catalogs array is empty at this point, there is an unexpected size mismatch. This should
|
95
|
+
# never happen, but test for it anyway.
|
96
|
+
unless parallel_catalogs.size == catalog_tasks.size
|
97
|
+
# :nocov:
|
98
|
+
raise "BUG: mismatch catalog_result (#{parallel_catalogs.size} vs #{catalog_tasks.size})"
|
99
|
+
# :nocov:
|
100
|
+
end
|
101
|
+
|
102
|
+
# Construct result hash. Will eventually be in the format
|
103
|
+
# { :from => OctocatalogDiff::Catalog, :to => OctocatalogDiff::Catalog }
|
104
|
+
|
105
|
+
# Analyze the results from parallel run.
|
106
|
+
catalog_tasks.each do |x|
|
107
|
+
# The `parallel_catalog_obj` is a OctocatalogDiff::Util::Parallel::Result. Get the first element from
|
108
|
+
# the parallel_catalogs output.
|
109
|
+
parallel_catalog_obj = parallel_catalogs.shift
|
110
|
+
|
111
|
+
# Add the result to the 'result' hash
|
112
|
+
add_parallel_result(result, parallel_catalog_obj, x)
|
113
|
+
end
|
114
|
+
|
115
|
+
# Things have succeeded if the :to and :from catalogs exist at this point. If not, things have
|
116
|
+
# failed, and an exception should be thrown.
|
117
|
+
return result if result.key?(:to) && result.key?(:from)
|
118
|
+
|
119
|
+
# This is believed to be a bug condition.
|
120
|
+
# :nocov:
|
121
|
+
raise OctocatalogDiff::Errors::CatalogError, 'One or more catalogs failed to compile.'
|
122
|
+
# :nocov:
|
123
|
+
end
|
124
|
+
|
125
|
+
# Get catalog compilation tasks.
|
126
|
+
# @return [Array<[key, task]>] Catalog tasks
|
127
|
+
def build_catalog_tasks
|
128
|
+
[:from, :to].map do |key|
|
129
|
+
# These are arguments to OctocatalogDiff::Util::Parallel::Task. In most cases the arguments
|
130
|
+
# of OctocatalogDiff::Util::Parallel::Task are taken directly from options, but there are
|
131
|
+
# some defaults or otherwise-named options that must be set here.
|
132
|
+
args = @options.merge(
|
133
|
+
tag: key.to_s,
|
134
|
+
branch: @options["#{key}_env".to_sym] || '-',
|
135
|
+
bootstrapped_dir: @options["bootstrapped_#{key}_dir".to_sym],
|
136
|
+
basedir: @options[:basedir],
|
137
|
+
compare_file_text: @options.fetch(:compare_file_text, true),
|
138
|
+
retry_failed_catalog: @options.fetch(:retry_failed_catalog, 0),
|
139
|
+
parser: @options["parser_#{key}".to_sym]
|
140
|
+
)
|
141
|
+
|
142
|
+
# If any options are in the form of 'to_SOMETHING' or 'from_SOMETHING', this sets the option to
|
143
|
+
# 'SOMETHING' for the catalog if it matches this key. For example, when compiling the 'to' catalog
|
144
|
+
# when an option of :to_some_arg => 'foo', this sets :some_arg => foo, and deletes :to_some_arg and
|
145
|
+
# :from_some_arg.
|
146
|
+
@options.keys.select { |x| x.to_s =~ /^(to|from)_/ }.each do |opt_key|
|
147
|
+
args[opt_key.to_s.sub(/^(to|from)_/, '').to_sym] = @options[opt_key] if opt_key.to_s.start_with?(key.to_s)
|
148
|
+
args.delete(opt_key)
|
149
|
+
end
|
150
|
+
|
151
|
+
# The task is a OctocatalogDiff::Util::Parallel::Task object that contains the method to execute,
|
152
|
+
# validator method, text description, and arguments to provide when calling the method.
|
153
|
+
task = OctocatalogDiff::Util::Parallel::Task.new(
|
154
|
+
method: method(:build_catalog),
|
155
|
+
validator: method(:catalog_validator),
|
156
|
+
validator_args: { task: key },
|
157
|
+
description: "build_catalog for #{@options["#{key}_env".to_sym]}",
|
158
|
+
args: args
|
159
|
+
)
|
160
|
+
|
161
|
+
# The format of `catalog_tasks` will be a tuple, where the first element is the key
|
162
|
+
# (e.g. :to or :from) and the second element is the OctocatalogDiff::Util::Parallel::Task object.
|
163
|
+
[key, task]
|
164
|
+
end.compact
|
165
|
+
end
|
166
|
+
|
167
|
+
# Given a result from the 'parallel' run and a corresponding (key,task) tuple, add valid
|
168
|
+
# catalogs to the 'result' hash and throw errors for invalid catalogs.
|
169
|
+
# @param result [Hash] Result hash for build_catalog_parallelizer (may be modified)
|
170
|
+
# @param parallel_catalog_obj [OctocatalogDiff::Util::Parallel::Result] Parallel catalog result
|
171
|
+
# @param key_task_tuple [Array<key, task>] Key, task tuple
|
172
|
+
def add_parallel_result(result, parallel_catalog_obj, key_task_tuple)
|
173
|
+
# Expand the tuple into variables
|
174
|
+
key, task = key_task_tuple
|
175
|
+
|
176
|
+
# For reporting purposes, get the branch name.
|
177
|
+
branch = task.args[:branch]
|
178
|
+
|
179
|
+
# Check the result of the parallel run on this object.
|
180
|
+
if parallel_catalog_obj.status.nil?
|
181
|
+
# The compile was killed because another task failed.
|
182
|
+
@logger.warn "Catalog compile for #{branch} was aborted due to another failure"
|
183
|
+
|
184
|
+
elsif parallel_catalog_obj.output.is_a?(OctocatalogDiff::Catalog)
|
185
|
+
# The result is a catalog, but we do not know if it was successfully compiled
|
186
|
+
# until we test the validity.
|
187
|
+
catalog = parallel_catalog_obj.output
|
188
|
+
if catalog.valid?
|
189
|
+
# The catalog was successfully compiled.
|
190
|
+
result[key] = parallel_catalog_obj.output
|
191
|
+
else
|
192
|
+
# The catalog failed, but a catalog object was returned so that better error reporting
|
193
|
+
# can take place. In this error reporting, we will replace 'Error:' with '[Puppet Error]'
|
194
|
+
# and remove the compilation directory (which is a tmpdir) to reveal only the relative
|
195
|
+
# path to the files involved.
|
196
|
+
dir = catalog.compilation_dir || ''
|
197
|
+
dir_regex = Regexp.new(Regexp.escape(dir) + '/environments/[^/]+/')
|
198
|
+
error_display = catalog.error_message.split("\n").map do |line|
|
199
|
+
line.sub(/^Error:/, '[Puppet Error]').gsub(dir_regex, '')
|
200
|
+
end.join("\n")
|
201
|
+
message = "Catalog for #{branch} failed to compile due to errors:\n#{error_display}"
|
202
|
+
raise OctocatalogDiff::Errors::CatalogError, message
|
203
|
+
end
|
204
|
+
else
|
205
|
+
# Something unhandled went wrong, and an exception was thrown. Reveal a generic message.
|
206
|
+
msg = parallel_catalog_obj.exception.message
|
207
|
+
message = "Catalog for '#{key}' (#{branch}) failed to compile with #{parallel_catalog_obj.exception.class}: #{msg}"
|
208
|
+
message += "\n" + parallel_catalog_obj.exception.backtrace.map { |x| " #{x}" }.join("\n") if @options[:debug]
|
209
|
+
raise OctocatalogDiff::Errors::CatalogError, message
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
# Performs the steps necessary to build a catalog.
|
214
|
+
# @param opts [Hash] Options hash
|
215
|
+
# @return [Hash] { :rc => exit code, :catalog => Catalog as JSON string }
|
216
|
+
def build_catalog(opts, logger = @logger)
|
217
|
+
logger.debug("Setting up Puppet catalog build for #{opts[:branch]}")
|
218
|
+
catalog = opts[:object]
|
219
|
+
logger.debug("Catalog for #{opts[:branch]} will be built with #{catalog.builder}")
|
220
|
+
time_start = Time.now
|
221
|
+
catalog.build(logger)
|
222
|
+
time_it_took = Time.now - time_start
|
223
|
+
retries_str = " retries = #{catalog.retries}" if catalog.retries.is_a?(Fixnum)
|
224
|
+
time_str = "in #{time_it_took} seconds#{retries_str}"
|
225
|
+
status_str = catalog.valid? ? 'successfully built' : 'failed'
|
226
|
+
logger.debug "Catalog for #{opts[:branch]} #{status_str} with #{catalog.builder} #{time_str}"
|
227
|
+
catalog
|
228
|
+
end
|
229
|
+
|
230
|
+
# Validate a catalog in the parallel execution
|
231
|
+
# @param catalog [OctocatalogDiff::Catalog] Catalog object
|
232
|
+
# @param logger [Logger] Logger object (presently unused)
|
233
|
+
# @param args [Hash] Additional arguments set specifically for validator
|
234
|
+
# @return [Boolean] true if catalog is valid, false otherwise
|
235
|
+
def catalog_validator(catalog = nil, _logger = @logger, args = {})
|
236
|
+
return false unless catalog.is_a?(OctocatalogDiff::Catalog)
|
237
|
+
catalog.validate_references if args[:task] == :to
|
238
|
+
catalog.valid?
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|