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
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require_relative 'pe/v1'
|
4
4
|
require_relative '../../util/httparty'
|
5
|
+
require_relative '../../errors'
|
5
6
|
require_relative '../facts'
|
6
7
|
|
7
8
|
module OctocatalogDiff
|
@@ -10,9 +11,6 @@ module OctocatalogDiff
|
|
10
11
|
# Support the Puppet Enterprise classification API.
|
11
12
|
# Documentation: https://docs.puppet.com/pe/latest/nc_index.html
|
12
13
|
class PE
|
13
|
-
# Error class that can be caught
|
14
|
-
class ClassificationError < RuntimeError; end
|
15
|
-
|
16
14
|
# Allow the main ENC object to retrieve these values
|
17
15
|
attr_reader :content, :error_message
|
18
16
|
|
@@ -69,7 +67,7 @@ module OctocatalogDiff
|
|
69
67
|
begin
|
70
68
|
@content = @api.result(response[:parsed], logger)
|
71
69
|
@error_message = nil
|
72
|
-
rescue OctocatalogDiff::
|
70
|
+
rescue OctocatalogDiff::Errors::PEClassificationError => exc
|
73
71
|
@error_message = exc.message
|
74
72
|
logger.error "PE ENC failed: #{exc.message}"
|
75
73
|
return
|
@@ -87,7 +85,7 @@ module OctocatalogDiff
|
|
87
85
|
begin
|
88
86
|
result = facts_obj.facts
|
89
87
|
logger.debug "Success retrieving facts for #{@node} from #{self.class}"
|
90
|
-
rescue OctocatalogDiff::
|
88
|
+
rescue OctocatalogDiff::Errors::FactRetrievalError, OctocatalogDiff::Errors::FactSourceError => exc
|
91
89
|
@content = nil
|
92
90
|
@error_message = "Fact retrieval failed: #{exc.class} - #{exc.message}"
|
93
91
|
logger.error @error_message
|
@@ -4,6 +4,8 @@ require 'json'
|
|
4
4
|
require 'uri'
|
5
5
|
require 'yaml'
|
6
6
|
|
7
|
+
require_relative '../../../errors'
|
8
|
+
|
7
9
|
module OctocatalogDiff
|
8
10
|
module CatalogUtil
|
9
11
|
class ENC
|
@@ -50,7 +52,7 @@ module OctocatalogDiff
|
|
50
52
|
%w(classes parameters).each do |required_key|
|
51
53
|
next if parsed[required_key]
|
52
54
|
logger.debug parsed.keys.inspect
|
53
|
-
raise OctocatalogDiff::
|
55
|
+
raise OctocatalogDiff::Errors::PEClassificationError, "Response missing: #{required_key}"
|
54
56
|
end
|
55
57
|
|
56
58
|
obj = { 'classes' => parsed['classes'], 'parameters' => parsed['parameters'] }
|
@@ -6,15 +6,13 @@ require 'rugged'
|
|
6
6
|
require 'shellwords'
|
7
7
|
require 'tempfile'
|
8
8
|
|
9
|
+
require_relative '../errors'
|
10
|
+
|
9
11
|
module OctocatalogDiff
|
10
12
|
module CatalogUtil
|
11
13
|
# Class to perform a git checkout (via 'git archive') of a branch from the base git
|
12
14
|
# directory into another targeted directory.
|
13
15
|
class Git
|
14
|
-
# Trapped errors
|
15
|
-
class GitCheckoutError < RuntimeError
|
16
|
-
end
|
17
|
-
|
18
16
|
# Check out a branch via 'git archive' from one directory into another.
|
19
17
|
# @param options [Hash] Options hash:
|
20
18
|
# - :branch => Branch name to check out
|
@@ -28,27 +26,39 @@ module OctocatalogDiff
|
|
28
26
|
logger = options.fetch(:logger)
|
29
27
|
|
30
28
|
# Validate parameters
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
tmp_script = Tempfile.new(['git-checkout', '.sh'])
|
38
|
-
tmp_script.write "#!/bin/bash\n"
|
39
|
-
tmp_script.write "set -euf -o pipefail\n"
|
40
|
-
tmp_script.write "git archive --format=tar #{Shellwords.escape(branch)} | \\\n"
|
41
|
-
tmp_script.write " ( cd #{Shellwords.escape(path)} && tar -xf - )\n"
|
42
|
-
tmp_script.close
|
43
|
-
FileUtils.chmod 0o755, tmp_script.path
|
29
|
+
if dir.nil? || !File.directory?(dir)
|
30
|
+
raise OctocatalogDiff::Errors::GitCheckoutError, "Source directory #{dir.inspect} does not exist"
|
31
|
+
end
|
32
|
+
if path.nil? || !File.directory?(path)
|
33
|
+
raise OctocatalogDiff::Errors::GitCheckoutError, "Target directory #{path.inspect} does not exist"
|
34
|
+
end
|
44
35
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
36
|
+
# Create and execute checkout script
|
37
|
+
script = create_git_checkout_script(branch, path)
|
38
|
+
logger.debug("Begin git archive #{dir}:#{branch} -> #{path}")
|
39
|
+
output, status = Open3.capture2e(script, chdir: dir)
|
40
|
+
unless status.exitstatus.zero?
|
41
|
+
raise OctocatalogDiff::Errors::GitCheckoutError, "Git archive #{branch}->#{path} failed: #{output}"
|
51
42
|
end
|
43
|
+
logger.debug("Success git archive #{dir}:#{branch}")
|
44
|
+
end
|
45
|
+
|
46
|
+
# Create the temporary file used to interact with the git command line.
|
47
|
+
# To get the options working correctly (-o pipefail in particular) this needs to run under
|
48
|
+
# bash. It's just creating a script, rather than figuring out all the shell escapes...
|
49
|
+
# @param branch [String] Branch name
|
50
|
+
# @param path [String] Target directory
|
51
|
+
# @return [String] Name of script
|
52
|
+
def self.create_git_checkout_script(branch, path)
|
53
|
+
tmp_script = Tempfile.new(['git-checkout', '.sh'])
|
54
|
+
tmp_script.write "#!/bin/bash\n"
|
55
|
+
tmp_script.write "set -euf -o pipefail\n"
|
56
|
+
tmp_script.write "git archive --format=tar #{Shellwords.escape(branch)} | \\\n"
|
57
|
+
tmp_script.write " ( cd #{Shellwords.escape(path)} && tar -xf - )\n"
|
58
|
+
tmp_script.close
|
59
|
+
FileUtils.chmod 0o755, tmp_script.path
|
60
|
+
at_exit { FileUtils.rm_f tmp_script.path if File.exist?(tmp_script.path) }
|
61
|
+
tmp_script.path
|
52
62
|
end
|
53
63
|
|
54
64
|
# Determine the SHA of origin/master (or any other branch really) in the git repo
|
@@ -58,7 +68,9 @@ module OctocatalogDiff
|
|
58
68
|
def self.branch_sha(options = {})
|
59
69
|
branch = options.fetch(:branch)
|
60
70
|
dir = options.fetch(:basedir)
|
61
|
-
|
71
|
+
if dir.nil? || !File.directory?(dir)
|
72
|
+
raise Errno::ENOENT, "Git directory #{dir.inspect} does not exist"
|
73
|
+
end
|
62
74
|
repo = Rugged::Repository.new(dir)
|
63
75
|
repo.branches[branch].target_id
|
64
76
|
end
|
@@ -9,6 +9,7 @@ require_relative 'catalog/noop'
|
|
9
9
|
require_relative 'catalog/puppetdb'
|
10
10
|
require_relative 'catalog/puppetmaster'
|
11
11
|
require_relative 'catalog-util/fileresources'
|
12
|
+
require_relative 'errors'
|
12
13
|
|
13
14
|
module OctocatalogDiff
|
14
15
|
# This class represents a catalog. Generation of the catalog is handled via one of the
|
@@ -18,11 +19,6 @@ module OctocatalogDiff
|
|
18
19
|
# Readable
|
19
20
|
attr_reader :built, :catalog, :catalog_json
|
20
21
|
|
21
|
-
# Error classes that we can throw
|
22
|
-
class PuppetVersionError < RuntimeError; end
|
23
|
-
class CatalogError < RuntimeError; end
|
24
|
-
class ReferenceValidationError < RuntimeError; end
|
25
|
-
|
26
22
|
# Constructor
|
27
23
|
# @param :backend [Symbol] If set, this will force a backend
|
28
24
|
# @param :json [String] JSON catalog content (will avoid running Puppet to compile catalog)
|
@@ -159,8 +155,8 @@ module OctocatalogDiff
|
|
159
155
|
# This is a compatibility layer for the resources, which are in a different place in Puppet 3.x and Puppet 4.x
|
160
156
|
# @return [Array] Resource array
|
161
157
|
def resources
|
162
|
-
raise CatalogError, 'Catalog does not appear to have been built' if !valid? && error_message.nil?
|
163
|
-
raise CatalogError, error_message unless valid?
|
158
|
+
raise OctocatalogDiff::Errors::CatalogError, 'Catalog does not appear to have been built' if !valid? && error_message.nil?
|
159
|
+
raise OctocatalogDiff::Errors::CatalogError, error_message unless valid?
|
164
160
|
return @catalog['data']['resources'] if @catalog['data'].is_a?(Hash) && @catalog['data']['resources'].is_a?(Array)
|
165
161
|
return @catalog['resources'] if @catalog['resources'].is_a?(Array)
|
166
162
|
# This is a bug condition
|
@@ -183,7 +179,7 @@ module OctocatalogDiff
|
|
183
179
|
end
|
184
180
|
|
185
181
|
# Determine if all of the (before, notify, require, subscribe) targets are actually in the catalog.
|
186
|
-
# Raise a ReferenceValidationError for any found to be missing.
|
182
|
+
# Raise a OctocatalogDiff::Errors::ReferenceValidationError for any found to be missing.
|
187
183
|
# Uses @options[:validate_references] to influence which references are checked.
|
188
184
|
def validate_references
|
189
185
|
# Skip out early if no reference validation has been requested.
|
@@ -223,7 +219,7 @@ module OctocatalogDiff
|
|
223
219
|
formatted_references.flatten!
|
224
220
|
plural = formatted_references.size == 1 ? '' : 's'
|
225
221
|
errors = formatted_references.join('; ')
|
226
|
-
raise ReferenceValidationError, "Catalog has broken reference#{plural}: #{errors}"
|
222
|
+
raise OctocatalogDiff::Errors::ReferenceValidationError, "Catalog has broken reference#{plural}: #{errors}"
|
227
223
|
end
|
228
224
|
|
229
225
|
private
|
@@ -89,7 +89,15 @@ module OctocatalogDiff
|
|
89
89
|
def cleanup_checkout_dir(checkout_dir, logger)
|
90
90
|
return unless File.directory?(checkout_dir)
|
91
91
|
logger.debug("Cleaning up temporary directory #{checkout_dir}")
|
92
|
-
|
92
|
+
# Sometimes this seems to break when handling the recursive removal when running under
|
93
|
+
# a parallel environment. Trap and ignore the errors here if we don't care about them.
|
94
|
+
begin
|
95
|
+
FileUtils.remove_entry_secure checkout_dir
|
96
|
+
# :nocov:
|
97
|
+
rescue Errno::ENOTEMPTY, Errno::ENOENT => exc
|
98
|
+
logger.debug "cleanup_checkout_dir(#{checkout_dir}) logged #{exc.class} - this can be ignored"
|
99
|
+
# :nocov:
|
100
|
+
end
|
93
101
|
end
|
94
102
|
|
95
103
|
# Private method: Bootstrap a directory
|
@@ -3,6 +3,7 @@
|
|
3
3
|
require 'json'
|
4
4
|
require 'stringio'
|
5
5
|
|
6
|
+
require_relative '../errors'
|
6
7
|
require_relative '../puppetdb'
|
7
8
|
|
8
9
|
module OctocatalogDiff
|
@@ -67,11 +68,11 @@ module OctocatalogDiff
|
|
67
68
|
# Set the other variables
|
68
69
|
@catalog_json = ::JSON.generate(@catalog)
|
69
70
|
@error_message = nil
|
70
|
-
rescue OctocatalogDiff::
|
71
|
+
rescue OctocatalogDiff::Errors::PuppetDBConnectionError => exc
|
71
72
|
@error_message = "Catalog retrieval failed (#{exc.class}) (#{exc.message})"
|
72
|
-
rescue OctocatalogDiff::
|
73
|
+
rescue OctocatalogDiff::Errors::PuppetDBNodeNotFoundError => exc
|
73
74
|
@error_message = "Node #{node} not found in PuppetDB (#{exc.message})"
|
74
|
-
rescue OctocatalogDiff::
|
75
|
+
rescue OctocatalogDiff::Errors::PuppetDBGenericError => exc
|
75
76
|
@error_message = "Catalog retrieval failed for node #{node} from PuppetDB (#{exc.message})"
|
76
77
|
rescue ::JSON::GeneratorError => exc
|
77
78
|
@error_message = "Failed to generate result from PuppetDB as JSON (#{exc.message})"
|
@@ -0,0 +1,195 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'api/v1'
|
4
|
+
require_relative 'catalog-util/cached_master_directory'
|
5
|
+
require_relative 'cli/diffs'
|
6
|
+
require_relative 'cli/options'
|
7
|
+
require_relative 'cli/printer'
|
8
|
+
require_relative 'errors'
|
9
|
+
require_relative 'util/catalogs'
|
10
|
+
require_relative 'version'
|
11
|
+
|
12
|
+
require 'logger'
|
13
|
+
require 'socket'
|
14
|
+
|
15
|
+
module OctocatalogDiff
|
16
|
+
# This is the CLI for catalog-diff. It's responsible for parsing the command line
|
17
|
+
# arguments and then handing off to appropriate methods to perform the catalog-diff.
|
18
|
+
class Cli
|
19
|
+
# Version number
|
20
|
+
VERSION = OctocatalogDiff::Version::VERSION
|
21
|
+
|
22
|
+
# Exit codes
|
23
|
+
EXITCODE_SUCCESS_NO_DIFFS = 0
|
24
|
+
EXITCODE_FAILURE = 1
|
25
|
+
EXITCODE_SUCCESS_WITH_DIFFS = 2
|
26
|
+
|
27
|
+
# The default type+title+attribute to ignore in catalog-diff.
|
28
|
+
DEFAULT_IGNORES = [
|
29
|
+
{ type: 'Class' } # Don't care about classes themselves, only what they actually do!
|
30
|
+
].freeze
|
31
|
+
|
32
|
+
# The default options.
|
33
|
+
DEFAULT_OPTIONS = {
|
34
|
+
from_env: 'origin/master',
|
35
|
+
to_env: '.',
|
36
|
+
colors: true,
|
37
|
+
debug: false,
|
38
|
+
quiet: false,
|
39
|
+
format: :color_text,
|
40
|
+
display_source_file_line: false,
|
41
|
+
compare_file_text: true,
|
42
|
+
display_datatype_changes: true,
|
43
|
+
parallel: true,
|
44
|
+
suppress_absent_file_details: true,
|
45
|
+
hiera_path: 'hieradata'
|
46
|
+
}.freeze
|
47
|
+
|
48
|
+
# This method is the one to call externally. It is possible to specify alternate
|
49
|
+
# command line arguments, for testing.
|
50
|
+
# @param argv [Array] Use specified arguments (defaults to ARGV)
|
51
|
+
# @param logger [Logger] Logger object
|
52
|
+
# @param opts [Hash] Additional options
|
53
|
+
# @return [Fixnum] Exit code: 0=no diffs, 1=something went wrong, 2=worked but there are diffs
|
54
|
+
def self.cli(argv = ARGV, logger = Logger.new(STDERR), opts = {})
|
55
|
+
# Save a copy of argv to print out later in debugging
|
56
|
+
argv_save = argv.dup
|
57
|
+
|
58
|
+
# Are there additional ARGV to munge, e.g. that have been supplied in the options from a
|
59
|
+
# configuration file?
|
60
|
+
if opts.key?(:additional_argv)
|
61
|
+
raise ArgumentError, ':additional_argv must be array!' unless opts[:additional_argv].is_a?(Array)
|
62
|
+
argv.concat opts[:additional_argv]
|
63
|
+
end
|
64
|
+
|
65
|
+
# Parse command line
|
66
|
+
options = parse_opts(argv)
|
67
|
+
|
68
|
+
# Additional options from hard-coded specified options. These are only processed if
|
69
|
+
# there are not already values defined from command line options.
|
70
|
+
# Note: do NOT use 'options[k] ||= v' here because if the value of options[k] is boolean(false)
|
71
|
+
# it will then be overridden. Whereas the intent is to define values only for those keys that don't exist.
|
72
|
+
opts.each { |k, v| options[k] = v unless options.key?(k) }
|
73
|
+
veto_options = %w(enc header hiera_config include_tags)
|
74
|
+
veto_options.each { |x| options.delete(x.to_sym) if options["no_#{x}".to_sym] }
|
75
|
+
options[:ignore].concat opts.fetch(:additional_ignores, [])
|
76
|
+
|
77
|
+
# Incorporate default options where needed.
|
78
|
+
# Note: do NOT use 'options[k] ||= v' here because if the value of options[k] is boolean(false)
|
79
|
+
# it will then be overridden. Whereas the intent is to define values only for those keys that don't exist.
|
80
|
+
DEFAULT_OPTIONS.each { |k, v| options[k] = v unless options.key?(k) }
|
81
|
+
veto_with_none_options = %w(hiera_path hiera_path_strip)
|
82
|
+
veto_with_none_options.each { |x| options.delete(x.to_sym) if options[x.to_sym] == :none }
|
83
|
+
|
84
|
+
# Fact and ENC overrides come in here - 'options' is modified
|
85
|
+
setup_fact_overrides(options)
|
86
|
+
setup_enc_overrides(options)
|
87
|
+
|
88
|
+
# Configure the logger and logger.debug initial information
|
89
|
+
# 'logger' is modified and used
|
90
|
+
setup_logger(logger, options, argv_save)
|
91
|
+
|
92
|
+
# --catalog-only is a special case that compiles the catalog for the "to" branch
|
93
|
+
# and then exits, without doing any 'diff' whatsoever. Support that option.
|
94
|
+
return catalog_only(logger, options) if options[:catalog_only]
|
95
|
+
|
96
|
+
# Set up the cached master directory - maintain it, adjust options if needed. However, if we
|
97
|
+
# are getting the 'from' catalog from PuppetDB, then don't do this.
|
98
|
+
unless options[:cached_master_dir].nil? || options[:from_puppetdb]
|
99
|
+
OctocatalogDiff::CatalogUtil::CachedMasterDirectory.run(options, logger)
|
100
|
+
end
|
101
|
+
|
102
|
+
# bootstrap_then_exit is a special case that only prepares directories and does not
|
103
|
+
# depend on facts. This happens within the 'catalogs' object, since bootstrapping and
|
104
|
+
# preparing catalogs are tightly coupled operations. However this does not actually
|
105
|
+
# build catalogs.
|
106
|
+
if options[:bootstrap_then_exit]
|
107
|
+
catalogs_obj = OctocatalogDiff::Util::Catalogs.new(options, logger)
|
108
|
+
return bootstrap_then_exit(logger, catalogs_obj)
|
109
|
+
end
|
110
|
+
|
111
|
+
# Compile catalogs and do catalog-diff
|
112
|
+
catalog_diff = OctocatalogDiff::API::V1.catalog_diff(options.merge(logger: logger))
|
113
|
+
diffs = catalog_diff.diffs
|
114
|
+
|
115
|
+
# Display diffs
|
116
|
+
printer_obj = OctocatalogDiff::Cli::Printer.new(options, logger)
|
117
|
+
printer_obj.printer(diffs, catalog_diff.from.compilation_dir, catalog_diff.to.compilation_dir)
|
118
|
+
|
119
|
+
# Return the resulting diff object if requested (generally for testing) or otherwise return exit code
|
120
|
+
return catalog_diff if opts[:INTEGRATION]
|
121
|
+
diffs.any? ? EXITCODE_SUCCESS_WITH_DIFFS : EXITCODE_SUCCESS_NO_DIFFS
|
122
|
+
end
|
123
|
+
|
124
|
+
# Parse command line options with 'optparse'. Returns a hash with the parsed arguments.
|
125
|
+
# @param argv [Array] Command line arguments (MUST be specified)
|
126
|
+
# @return [Hash] Options
|
127
|
+
def self.parse_opts(argv)
|
128
|
+
options = { ignore: DEFAULT_IGNORES.dup }
|
129
|
+
Options.parse_options(argv, options)
|
130
|
+
end
|
131
|
+
|
132
|
+
# Generic overrides
|
133
|
+
def self.setup_overrides(key, options)
|
134
|
+
o = options["#{key}_in".to_sym]
|
135
|
+
return unless o.is_a?(Array)
|
136
|
+
return unless o.any?
|
137
|
+
options[key] ||= []
|
138
|
+
options[key].concat o.map { |x| OctocatalogDiff::API::V1::Override.create_from_input(x) }
|
139
|
+
end
|
140
|
+
|
141
|
+
# Fact overrides come in here
|
142
|
+
def self.setup_fact_overrides(options)
|
143
|
+
setup_overrides(:from_fact_override, options)
|
144
|
+
setup_overrides(:to_fact_override, options)
|
145
|
+
end
|
146
|
+
|
147
|
+
# ENC parameter overrides come in here
|
148
|
+
def self.setup_enc_overrides(options)
|
149
|
+
setup_overrides(:from_enc_override, options)
|
150
|
+
setup_overrides(:to_enc_override, options)
|
151
|
+
end
|
152
|
+
|
153
|
+
# Helper method: Configure and setup logger
|
154
|
+
def self.setup_logger(logger, options, argv_save)
|
155
|
+
# Configure the logger
|
156
|
+
logger.level = Logger::INFO
|
157
|
+
logger.level = Logger::DEBUG if options[:debug]
|
158
|
+
logger.level = Logger::ERROR if options[:quiet]
|
159
|
+
|
160
|
+
# Some debugging information up front
|
161
|
+
version_display = ENV['OCTOCATALOG_DIFF_CUSTOM_VERSION'] || VERSION
|
162
|
+
logger.debug "Running octocatalog-diff #{version_display} with ruby #{RUBY_VERSION}"
|
163
|
+
logger.debug "Command line arguments: #{argv_save.inspect}"
|
164
|
+
logger.debug "Running on host #{Socket.gethostname} (#{RUBY_PLATFORM})"
|
165
|
+
end
|
166
|
+
|
167
|
+
# Compile the catalog only
|
168
|
+
def self.catalog_only(logger, options)
|
169
|
+
opts = options.merge(logger: logger)
|
170
|
+
to_catalog = OctocatalogDiff::API::V1.catalog(opts)
|
171
|
+
|
172
|
+
# If the catalog compilation failed, an exception would have been thrown. So if
|
173
|
+
# we get here, the catalog succeeded. Dump the catalog to the appropriate place
|
174
|
+
# and exit successfully.
|
175
|
+
if options[:output_file]
|
176
|
+
File.open(options[:output_file], 'w') { |f| f.write(to_catalog.to_json) }
|
177
|
+
logger.info "Wrote catalog to #{options[:output_file]}"
|
178
|
+
else
|
179
|
+
puts to_catalog.to_json
|
180
|
+
end
|
181
|
+
|
182
|
+
return { exitcode: EXITCODE_SUCCESS_NO_DIFFS, to: to_catalog } if options[:INTEGRATION] # For integration testing
|
183
|
+
EXITCODE_SUCCESS_NO_DIFFS
|
184
|
+
end
|
185
|
+
|
186
|
+
# --bootstrap-then-exit command
|
187
|
+
def self.bootstrap_then_exit(logger, catalogs_obj)
|
188
|
+
catalogs_obj.bootstrap_then_exit
|
189
|
+
return EXITCODE_SUCCESS_NO_DIFFS
|
190
|
+
rescue OctocatalogDiff::Errors::BootstrapError => exc
|
191
|
+
logger.fatal("--bootstrap-then-exit error: bootstrap failed (#{exc})")
|
192
|
+
return EXITCODE_FAILURE
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../catalog-diff/differ'
|
4
|
+
|
5
|
+
module OctocatalogDiff
|
6
|
+
class Cli
|
7
|
+
# Wrapper around OctocatalogDiff::CatalogDiff::Differ to provide the logger object, set up
|
8
|
+
# ignores, and add additional ignores for items dependent upon the compilation directory.
|
9
|
+
class Diffs
|
10
|
+
# Constructor
|
11
|
+
# @param options [Hash] Options from cli/options
|
12
|
+
# @param logger [Logger] Logger object
|
13
|
+
def initialize(options, logger)
|
14
|
+
@options = options
|
15
|
+
@logger = logger
|
16
|
+
end
|
17
|
+
|
18
|
+
# The method to call externally, passing in the catalogs as a hash (see parameter). This
|
19
|
+
# sets up options and ignores and then actually performs the diffs. The result is the array
|
20
|
+
# of diffs.
|
21
|
+
# @param catalogs [Hash] { :to => OctocatalogDiff::Catalog, :from => OctocatalogDiff::Catalog }
|
22
|
+
# @return [Array<diffs>] Array of diffs
|
23
|
+
def diffs(catalogs)
|
24
|
+
@logger.debug 'Begin compute diffs between catalogs'
|
25
|
+
diff_opts = @options.merge(logger: @logger)
|
26
|
+
|
27
|
+
# Construct the actual differ object that the present one wraps
|
28
|
+
differ = OctocatalogDiff::CatalogDiff::Differ.new(diff_opts, catalogs[:from], catalogs[:to])
|
29
|
+
differ.ignore(attr: 'tags') unless @options.fetch(:include_tags, false)
|
30
|
+
differ.ignore(@options.fetch(:ignore, []))
|
31
|
+
differ.ignore_tags
|
32
|
+
|
33
|
+
# Actually perform the diff
|
34
|
+
diff_result = differ.diff
|
35
|
+
@logger.debug 'Success compute diffs between catalogs'
|
36
|
+
diff_result
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|