octocatalog-diff 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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,125 @@
|
|
|
1
|
+
require_relative 'differ'
|
|
2
|
+
require_relative 'display/json'
|
|
3
|
+
require_relative 'display/text'
|
|
4
|
+
|
|
5
|
+
module OctocatalogDiff
|
|
6
|
+
module CatalogDiff
|
|
7
|
+
# Prepare a display of the results from a catalog-diff. Intended that this will contain utility
|
|
8
|
+
# methods but call out to a OctocatalogDiff::CatalogDiff::Display::<something> class to display in
|
|
9
|
+
# the desired format.
|
|
10
|
+
class Display
|
|
11
|
+
# Display the diff in some specified format.
|
|
12
|
+
# @param diff_in [OctocatalogDiff::CatalogDiff::Differ | Array<Diff results>] Diff to display
|
|
13
|
+
# @param options [Hash] Consisting of:
|
|
14
|
+
# - :header [String] => Header (can be :default to construct header)
|
|
15
|
+
# - :display_source_file_line [Boolean] => Display manifest filename and line number where declared
|
|
16
|
+
# - :compilation_from_dir [String] => Directory where 'from' catalog was compiled
|
|
17
|
+
# - :compilation_to_dir [String] => Directory where 'to' catalog was compiled
|
|
18
|
+
# - :display_detail_add [Boolean] => Set true to display parameters of newly added resources
|
|
19
|
+
# @param logger [Logger] Logger object
|
|
20
|
+
# @return [String] Text output for provided diff
|
|
21
|
+
def self.output(diff_in, options = {}, logger = nil)
|
|
22
|
+
diff = diff_in.is_a?(OctocatalogDiff::CatalogDiff::Differ) ? diff_in.diff : diff_in
|
|
23
|
+
raise ArgumentError, "text_output requires Array<Diff results>; passed in #{diff_in.class}" unless diff.is_a?(Array)
|
|
24
|
+
|
|
25
|
+
# req_format means 'requested format' because 'format' has a built-in meaning to Ruby
|
|
26
|
+
req_format = options.fetch(:format, :color_text)
|
|
27
|
+
|
|
28
|
+
# Options hash to pass to display method
|
|
29
|
+
opts = {}
|
|
30
|
+
opts[:header] = header(options)
|
|
31
|
+
opts[:display_source_file_line] = options.fetch(:display_source_file_line, false)
|
|
32
|
+
opts[:compilation_from_dir] = options[:compilation_from_dir] || nil
|
|
33
|
+
opts[:compilation_to_dir] = options[:compilation_to_dir] || nil
|
|
34
|
+
opts[:display_detail_add] = options.fetch(:display_detail_add, false)
|
|
35
|
+
opts[:display_datatype_changes] = options.fetch(:display_datatype_changes, false)
|
|
36
|
+
|
|
37
|
+
# Call appropriate display method
|
|
38
|
+
case req_format
|
|
39
|
+
when :json
|
|
40
|
+
logger.debug 'Generating JSON output' if logger
|
|
41
|
+
OctocatalogDiff::CatalogDiff::Display::Json.generate(diff, opts, logger)
|
|
42
|
+
when :text
|
|
43
|
+
logger.debug 'Generating non-colored text output' if logger
|
|
44
|
+
OctocatalogDiff::CatalogDiff::Display::Text.generate(diff, opts.merge(color: false), logger)
|
|
45
|
+
when :color_text
|
|
46
|
+
logger.debug 'Generating colored text output' if logger
|
|
47
|
+
OctocatalogDiff::CatalogDiff::Display::Text.generate(diff, opts.merge(color: true), logger)
|
|
48
|
+
else
|
|
49
|
+
raise ArgumentError, "Unrecognized text format '#{req_format}'"
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Utility method!
|
|
54
|
+
# Construct the header for diffs
|
|
55
|
+
# Default is diff <old_branch_name>/<node_name> <new_branch_name>/<node_name>
|
|
56
|
+
# @param opts [Hash] Options hash from CLI
|
|
57
|
+
# @return [String] Header in indicated format
|
|
58
|
+
def self.header(opts)
|
|
59
|
+
return nil if opts[:no_header]
|
|
60
|
+
return opts[:header] unless opts[:header] == :default
|
|
61
|
+
node = opts.fetch(:node, 'node')
|
|
62
|
+
from_br = opts.fetch(:from_env, 'a')
|
|
63
|
+
to_br = opts.fetch(:to_env, 'b')
|
|
64
|
+
from_br = 'current' if from_br == '.'
|
|
65
|
+
to_br = 'current' if to_br == '.'
|
|
66
|
+
"diff #{from_br}/#{node} #{to_br}/#{node}"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Utility method!
|
|
70
|
+
# Go through the 'diff' array, filtering out ignored items and classifying each change
|
|
71
|
+
# as an addition (+), subtraction (-), change (~), or nested change (!). This creates
|
|
72
|
+
# hashes for each type of change that are consumed later for ordering purposes.
|
|
73
|
+
# @param diff [Array<Diff results>] The diff which *must* be in this format
|
|
74
|
+
# @return [Array<Hash of adds, Hash of removes, Hash of changes, Hash of nested] Processed results
|
|
75
|
+
def self.parse_diff_array_into_categorized_hashes(diff)
|
|
76
|
+
only_in_old = {}
|
|
77
|
+
only_in_new = {}
|
|
78
|
+
changed = {}
|
|
79
|
+
diff.each do |diff_obj|
|
|
80
|
+
(type, title, the_rest) = diff_obj[1].split(/\f/, 3)
|
|
81
|
+
key = "#{type}[#{title}]"
|
|
82
|
+
if ['-', '+'].include?(diff_obj[0])
|
|
83
|
+
only_in_old[key] = { diff: diff_obj[2], loc: diff_obj[3] } if diff_obj[0] == '-'
|
|
84
|
+
only_in_new[key] = { diff: diff_obj[2], loc: diff_obj[3] } if diff_obj[0] == '+'
|
|
85
|
+
elsif ['~', '!'].include?(diff_obj[0])
|
|
86
|
+
# HashDiff reports these as diffs for some reason
|
|
87
|
+
next if diff_obj[2].nil? && diff_obj[3].nil?
|
|
88
|
+
|
|
89
|
+
# This turns "foo\fbar\fbaz" into hash['foo']['bar']['baz']
|
|
90
|
+
result = the_rest.split(/\f/).reverse.inject(old: diff_obj[2], new: diff_obj[3]) { |a, e| { e => a } }
|
|
91
|
+
|
|
92
|
+
# Assign to appropriate variable
|
|
93
|
+
diff = changed.key?(key) ? changed[key][:diff] : {}
|
|
94
|
+
simple_deep_merge!(diff, result)
|
|
95
|
+
changed[key] = { diff: diff, old_loc: diff_obj[4], new_loc: diff_obj[5] }
|
|
96
|
+
else
|
|
97
|
+
raise "Unrecognized diff symbol '#{diff_obj[0]}' in #{diff_obj.inspect}"
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
[only_in_new, only_in_old, changed]
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Utility Method!
|
|
104
|
+
# Deep merge two hashes. (The 'deep_merge' gem seems to de-duplicate arrays so this is a reinvention
|
|
105
|
+
# of the wheel, but a simpler wheel that does just exactly what we need.)
|
|
106
|
+
# @param hash1 [Hash] First object
|
|
107
|
+
# @param hash2 [Hash] Second object
|
|
108
|
+
def self.simple_deep_merge!(hash1, hash2)
|
|
109
|
+
raise ArgumentError, 'First argument to simple_deep_merge must be a hash' unless hash1.is_a?(Hash)
|
|
110
|
+
raise ArgumentError, 'Second argument to simple_deep_merge must be a hash' unless hash2.is_a?(Hash)
|
|
111
|
+
hash2.each do |k, v|
|
|
112
|
+
if v.is_a?(Hash) && hash1[k].is_a?(Hash)
|
|
113
|
+
# We can only merge a hash with a hash. If hash1[k] is something other than a hash, say for example
|
|
114
|
+
# a string, then the merging is NOT invoked and hash1[k] gets directly overwritten in the `else` clause.
|
|
115
|
+
# Also if hash1[k] is nil, it falls through to the `else` clause where it gets set directly to the result
|
|
116
|
+
# hash without needless iterations.
|
|
117
|
+
simple_deep_merge!(hash1[k], v)
|
|
118
|
+
else
|
|
119
|
+
hash1[k] = v
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
require_relative '../display'
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
module OctocatalogDiff
|
|
6
|
+
module CatalogDiff
|
|
7
|
+
class Display
|
|
8
|
+
# Display the output from a diff in JSON format.
|
|
9
|
+
class Json < OctocatalogDiff::CatalogDiff::Display
|
|
10
|
+
# Generate JSON representation of the 'diff' suitable for further analysis.
|
|
11
|
+
# @param diff [Array<Diff results>] The diff which *must* be in this format
|
|
12
|
+
# @param options [Hash] Options which are:
|
|
13
|
+
# - :header => [String] Header to print; no header is printed if not specified
|
|
14
|
+
# @param _logger [Logger] Not used here
|
|
15
|
+
def self.generate(diff, options = {}, _logger = nil)
|
|
16
|
+
result = {
|
|
17
|
+
'diff' => diff
|
|
18
|
+
}
|
|
19
|
+
result['header'] = options[:header] unless options[:header].nil?
|
|
20
|
+
result.to_json
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
require_relative '../display'
|
|
2
|
+
require_relative '../../util/colored.rb'
|
|
3
|
+
|
|
4
|
+
require 'diffy'
|
|
5
|
+
require 'json'
|
|
6
|
+
|
|
7
|
+
module OctocatalogDiff
|
|
8
|
+
module CatalogDiff
|
|
9
|
+
class Display
|
|
10
|
+
# Display the output from a diff in text format. Uses the 'diffy' gem to provide diffs in
|
|
11
|
+
# blocks of text. Formats results in a logical Puppet output.
|
|
12
|
+
class Text < OctocatalogDiff::CatalogDiff::Display
|
|
13
|
+
SEPARATOR = '*******************************************'.freeze
|
|
14
|
+
|
|
15
|
+
# Generate the text representation of the 'diff' suitable for rendering in a console or log.
|
|
16
|
+
# @param diff [Array<Diff results>] The diff which *must* be in this format
|
|
17
|
+
# @param options_in [Hash] Options which are:
|
|
18
|
+
# - :color => [Boolean] True or false, whether to use color codes
|
|
19
|
+
# - :header => [String] Header to print; no header is printed if not specified
|
|
20
|
+
# - :display_source_file_line [Boolean] True or false, print filename and line (if known)
|
|
21
|
+
# - :display_detail_add [Boolean] If true, print details of any added resources
|
|
22
|
+
# @param logger [Logger] Logger object
|
|
23
|
+
# @return [Array<String>] Results
|
|
24
|
+
def self.generate(diff, options_in = {}, logger = nil)
|
|
25
|
+
# Empty?
|
|
26
|
+
return [] if diff.empty?
|
|
27
|
+
|
|
28
|
+
# We may modify this for temporary local use, but don't want to pass these changes
|
|
29
|
+
# back to the rest of the program.
|
|
30
|
+
options = options_in.dup
|
|
31
|
+
|
|
32
|
+
# Enable color support if requested...
|
|
33
|
+
String.colors_enabled = options.fetch(:color, true)
|
|
34
|
+
|
|
35
|
+
previous_diffy_default_format = Diffy::Diff.default_format
|
|
36
|
+
Diffy::Diff.default_format = options.fetch(:color, true) ? :color : :text
|
|
37
|
+
|
|
38
|
+
# Strip out differences or update display where string matches but data type differs.
|
|
39
|
+
# For example, 28 (the integer) and "28" (the string) have identical string
|
|
40
|
+
# representations, but are different data types. Same for nil vs. "".
|
|
41
|
+
adjust_for_display_datatype_changes(diff, options[:display_datatype_changes], logger)
|
|
42
|
+
|
|
43
|
+
# Call the utility method to sort changes into their respective types
|
|
44
|
+
only_in_new, only_in_old, changed = parse_diff_array_into_categorized_hashes(diff)
|
|
45
|
+
sorted_list = only_in_old.keys | only_in_new.keys | changed.keys
|
|
46
|
+
sorted_list.sort!
|
|
47
|
+
unless logger.nil?
|
|
48
|
+
logger.debug "Added resources: #{only_in_new.keys.count}"
|
|
49
|
+
logger.debug "Removed resources: #{only_in_old.keys.count}"
|
|
50
|
+
logger.debug "Changed resources: #{changed.keys.count}"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Run through the list to build the result
|
|
54
|
+
result = []
|
|
55
|
+
sorted_list.each do |item|
|
|
56
|
+
# Print the header if needed
|
|
57
|
+
unless options[:header].nil?
|
|
58
|
+
result << options[:header] unless options[:header].empty?
|
|
59
|
+
result << SEPARATOR
|
|
60
|
+
options[:header] = nil
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# A removed item appears only in the old hash.
|
|
64
|
+
if only_in_old.key?(item)
|
|
65
|
+
result.concat display_removed_item(
|
|
66
|
+
item: item,
|
|
67
|
+
old_loc: only_in_old[item][:loc],
|
|
68
|
+
options: options,
|
|
69
|
+
logger: logger
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# An added item appears only in the new hash.
|
|
73
|
+
elsif only_in_new.key?(item)
|
|
74
|
+
result.concat display_added_item(
|
|
75
|
+
item: item,
|
|
76
|
+
new_loc: only_in_new[item][:loc],
|
|
77
|
+
diff: only_in_new[item][:diff],
|
|
78
|
+
options: options,
|
|
79
|
+
logger: logger
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
# A change can appear either in the change hash, the nested hash, or both.
|
|
83
|
+
# Therefore, changes and nested changes are combined for display.
|
|
84
|
+
elsif changed.key?(item)
|
|
85
|
+
result.concat display_changed_or_nested_item(
|
|
86
|
+
item: item,
|
|
87
|
+
old_loc: changed[item][:old_loc],
|
|
88
|
+
new_loc: changed[item][:new_loc],
|
|
89
|
+
diff: changed[item][:diff],
|
|
90
|
+
options: options,
|
|
91
|
+
logger: logger
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
# An unrecognized change throws an error. This indicates a bug.
|
|
95
|
+
else
|
|
96
|
+
# :nocov:
|
|
97
|
+
raise "BUG (please report): Unable to determine diff type of item: #{item.inspect}"
|
|
98
|
+
# :nocov:
|
|
99
|
+
end
|
|
100
|
+
result << SEPARATOR
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Reset the global color-related flags
|
|
104
|
+
String.colors_enabled = false
|
|
105
|
+
Diffy::Diff.default_format = previous_diffy_default_format
|
|
106
|
+
|
|
107
|
+
# The end
|
|
108
|
+
result
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Display a changed or nested item
|
|
112
|
+
# @param item [String] Item (type+title) that has changed
|
|
113
|
+
# @param old_loc [Hash] File and line number of location in "from" catalog
|
|
114
|
+
# @param new_loc [Hash] File and line number of location in "to" catalog
|
|
115
|
+
# @param diff [Hash] Difference hash
|
|
116
|
+
# @param options [Hash] Display options
|
|
117
|
+
# @param logger [Logger] Logger object
|
|
118
|
+
# @return [Array] Lines of text
|
|
119
|
+
def self.display_changed_or_nested_item(opts = {})
|
|
120
|
+
item = opts.fetch(:item)
|
|
121
|
+
old_loc = opts.fetch(:old_loc)
|
|
122
|
+
new_loc = opts.fetch(:new_loc)
|
|
123
|
+
diff = opts.fetch(:diff)
|
|
124
|
+
options = opts.fetch(:options)
|
|
125
|
+
logger = opts[:logger]
|
|
126
|
+
|
|
127
|
+
result = []
|
|
128
|
+
info_hash = { item: item, result: result, old_loc: old_loc, new_loc: new_loc, options: options, logger: logger }
|
|
129
|
+
add_source_file_line_info(info_hash)
|
|
130
|
+
result << " #{item} =>"
|
|
131
|
+
diff.keys.sort.each { |key| result.concat hash_diff(diff[key], 1, key, true) }
|
|
132
|
+
result
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Display a removed item
|
|
136
|
+
# @param item [String] Item (type+title) that has changed
|
|
137
|
+
# @param old_loc [Hash] File and line number of location in "from" catalog
|
|
138
|
+
# @param options [Hash] Display options
|
|
139
|
+
# @param logger [Logger] Logger object
|
|
140
|
+
# @return [Array] Lines of text
|
|
141
|
+
def self.display_removed_item(opts = {})
|
|
142
|
+
item = opts.fetch(:item)
|
|
143
|
+
old_loc = opts.fetch(:old_loc)
|
|
144
|
+
options = opts.fetch(:options)
|
|
145
|
+
logger = opts[:logger]
|
|
146
|
+
|
|
147
|
+
result = []
|
|
148
|
+
add_source_file_line_info(item: item, result: result, old_loc: old_loc, options: options, logger: logger)
|
|
149
|
+
result << "- #{item}".red
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Display an added item
|
|
153
|
+
# @param item [String] Item (type+title) that has changed
|
|
154
|
+
# @param new_loc [Hash] File and line number of location in "to" catalog
|
|
155
|
+
# @param diff [Hash] Difference hash
|
|
156
|
+
# @param options [Hash] Display options
|
|
157
|
+
# @param logger [Logger] Logger object
|
|
158
|
+
# @return [Array] Lines of text
|
|
159
|
+
def self.display_added_item(opts = {})
|
|
160
|
+
item = opts.fetch(:item)
|
|
161
|
+
new_loc = opts.fetch(:new_loc)
|
|
162
|
+
diff = opts.fetch(:diff)
|
|
163
|
+
options = opts.fetch(:options)
|
|
164
|
+
logger = opts[:logger]
|
|
165
|
+
|
|
166
|
+
result = []
|
|
167
|
+
add_source_file_line_info(item: item, result: result, new_loc: new_loc, options: options, logger: logger)
|
|
168
|
+
if options[:display_detail_add] && diff.key?('parameters')
|
|
169
|
+
result << "+ #{item} =>".green
|
|
170
|
+
result << ' parameters =>'.green
|
|
171
|
+
result.concat(
|
|
172
|
+
diff_two_hashes_with_diffy(
|
|
173
|
+
depth: 1,
|
|
174
|
+
hash2: Hash[diff['parameters'].sort], # Should work with somewhat older rubies too
|
|
175
|
+
limit: 80,
|
|
176
|
+
strip_diff: true
|
|
177
|
+
).map(&:green)
|
|
178
|
+
)
|
|
179
|
+
else
|
|
180
|
+
result << "+ #{item}".green
|
|
181
|
+
if diff.key?('parameters') && logger && !options[:display_detail_add_notice_printed]
|
|
182
|
+
logger.info 'Note: you can use --display-detail-add to view details of added resources'
|
|
183
|
+
options[:display_detail_add_notice_printed] = true
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
result
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Generate info about the source of the change. Pass in parameters as a hash with indicated names.
|
|
191
|
+
# @param item [Hash] Item that is added/removed/changed
|
|
192
|
+
# @param result [Array] Result array (modified by this method)
|
|
193
|
+
# @param old_loc [Hash] Old location hash { file => ..., line => ... }
|
|
194
|
+
# @param new_loc [Hash] New location hash { file => ..., line => ... }
|
|
195
|
+
# @param options [Hash] Options hash
|
|
196
|
+
# @param logger [Logger] Logger object
|
|
197
|
+
def self.add_source_file_line_info(opts = {})
|
|
198
|
+
item = opts.fetch(:item)
|
|
199
|
+
result = opts.fetch(:result)
|
|
200
|
+
old_loc = opts[:old_loc]
|
|
201
|
+
new_loc = opts[:new_loc]
|
|
202
|
+
options = opts.fetch(:options, {})
|
|
203
|
+
logger = opts[:logger]
|
|
204
|
+
|
|
205
|
+
# Initialize any currently undefined settings
|
|
206
|
+
empty_hash = { 'file' => nil, 'line' => nil }
|
|
207
|
+
old_loc ||= empty_hash
|
|
208
|
+
new_loc ||= empty_hash
|
|
209
|
+
return if old_loc == empty_hash && new_loc == empty_hash
|
|
210
|
+
|
|
211
|
+
# Convert old_loc and new_loc to strings
|
|
212
|
+
old_loc_string = loc_string(old_loc, options[:compilation_from_dir], logger)
|
|
213
|
+
new_loc_string = loc_string(new_loc, options[:compilation_to_dir], logger)
|
|
214
|
+
|
|
215
|
+
# Debug log information and build up local_result with printable changes
|
|
216
|
+
local_result = []
|
|
217
|
+
if old_loc == new_loc || new_loc == empty_hash || old_loc_string == new_loc_string
|
|
218
|
+
logger.debug "#{item} @ #{old_loc_string || 'nil'}" if logger
|
|
219
|
+
local_result << " #{old_loc_string}".cyan unless old_loc_string.nil?
|
|
220
|
+
elsif old_loc == empty_hash
|
|
221
|
+
logger.debug "#{item} @ #{new_loc_string || 'nil'}" if logger
|
|
222
|
+
local_result << " #{new_loc_string}".cyan unless new_loc_string.nil?
|
|
223
|
+
else
|
|
224
|
+
logger.debug "#{item} -@ #{old_loc_string} +@ #{new_loc_string}" if logger
|
|
225
|
+
local_result << "- #{old_loc_string}".cyan
|
|
226
|
+
local_result << "+ #{new_loc_string}".cyan
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# Only modify result if option to display source file and line is enabled
|
|
230
|
+
result.concat local_result if options[:display_source_file_line]
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# Convert { file => ..., line => ... } to displayable string
|
|
234
|
+
# @param loc [Hash] file => ..., line => ... hash
|
|
235
|
+
# @param compilation_dir [String] Compilation directory
|
|
236
|
+
# @param logger [Logger] Logger object
|
|
237
|
+
# @return [String] Location string
|
|
238
|
+
def self.loc_string(loc, compilation_dir, logger)
|
|
239
|
+
return nil if loc.nil? || !loc.is_a?(Hash) || loc['file'].nil? || loc['line'].nil?
|
|
240
|
+
result = "#{loc['file']}:#{loc['line']}"
|
|
241
|
+
if compilation_dir
|
|
242
|
+
rex = Regexp.new('^' + Regexp.escape(compilation_dir + '/'))
|
|
243
|
+
result_new = result.sub(rex, '')
|
|
244
|
+
if result_new != result
|
|
245
|
+
logger.debug "Removed compilation directory in #{result} -> #{result_new}" if logger
|
|
246
|
+
result = result_new
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
result
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
# Get the diff of two long strings. Call the 'diffy' gem for this.
|
|
253
|
+
# @param string1 [String] First string (-)
|
|
254
|
+
# @param string2 [String] Second string (+)
|
|
255
|
+
# @param depth [Fixnum] Depth, for correct indentation
|
|
256
|
+
# @return Array<String> Displayable result
|
|
257
|
+
def self.diff_two_strings_with_diffy(string1, string2, depth)
|
|
258
|
+
# prevent 'No newline at end of file' for single line strings
|
|
259
|
+
string1 += "\n" unless string1 =~ /\n/
|
|
260
|
+
string2 += "\n" unless string2 =~ /\n/
|
|
261
|
+
diff = Diffy::Diff.new(string1, string2, context: 2, include_diff_info: true).to_s.split("\n")
|
|
262
|
+
diff.shift # Remove first line of diff info (filename that makes no sense)
|
|
263
|
+
diff.shift # Remove second line of diff info (filename that makes no sense)
|
|
264
|
+
diff.map { |x| left_pad(2 * depth + 2, x) }
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
# Get the diff of two hashes. Call the 'diffy' gem for this.
|
|
268
|
+
# @param hash1 [Hash] First hash (-)
|
|
269
|
+
# @param hash1 [Hash] Second hash (+)
|
|
270
|
+
# @param depth [Fixnum] Depth, for correct indentation
|
|
271
|
+
# @param limit [Fixnum] Maximum string length
|
|
272
|
+
# @param strip_diff [Boolean] Strip leading +/-/" "
|
|
273
|
+
# @return Array<String> Displayable result
|
|
274
|
+
def self.diff_two_hashes_with_diffy(opts = {})
|
|
275
|
+
depth = opts.fetch(:depth, 0)
|
|
276
|
+
hash1 = opts.fetch(:hash1, {})
|
|
277
|
+
hash2 = opts.fetch(:hash2, {})
|
|
278
|
+
limit = opts[:limit]
|
|
279
|
+
strip_diff = opts.fetch(:strip_diff, false)
|
|
280
|
+
|
|
281
|
+
json_old = stringify_for_diffy(hash1)
|
|
282
|
+
json_new = stringify_for_diffy(hash2)
|
|
283
|
+
|
|
284
|
+
# If stripping the diff, we need to make sure diffy does not colorize the output, so that
|
|
285
|
+
# there are not color codes in the output to deal with.
|
|
286
|
+
diff = if strip_diff
|
|
287
|
+
Diffy::Diff.new(json_old, json_new, context: 0).to_s(:text).split("\n")
|
|
288
|
+
else
|
|
289
|
+
Diffy::Diff.new(json_old, json_new, context: 0).to_s.split("\n")
|
|
290
|
+
end
|
|
291
|
+
raise "Diffy diff empty for string: #{json_old}" if diff.empty?
|
|
292
|
+
|
|
293
|
+
# This is the array that is returned
|
|
294
|
+
diff.map do |x|
|
|
295
|
+
x = x[2..-1] if strip_diff # Drop first 2 characters: '+ ', '- ', or ' '
|
|
296
|
+
truncate_string(left_pad(2 * depth + 2, x), limit)
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
# Limit length of a string
|
|
301
|
+
# @param str [String] String
|
|
302
|
+
# @param limit [Fixnum] Limit (0=unlimited)
|
|
303
|
+
# @return [String] Truncated string
|
|
304
|
+
def self.truncate_string(str, limit)
|
|
305
|
+
return str if limit.nil? || str.length <= limit
|
|
306
|
+
"#{str[0..limit]}..."
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
# Get the diff between two hashes. This is recursive-aware.
|
|
310
|
+
# @param obj [diff object] diff object
|
|
311
|
+
# @param depth [Fixnum] Depth of nesting, used for indentation
|
|
312
|
+
# @return Array<String> Printable diff outputs
|
|
313
|
+
def self.hash_diff(obj, depth, key_in, nested = false)
|
|
314
|
+
result = []
|
|
315
|
+
result << left_pad(2 * depth, " #{key_in} =>")
|
|
316
|
+
if obj.key?(:old) && obj.key?(:new)
|
|
317
|
+
if nested && obj[:old].is_a?(Hash) && obj[:new].is_a?(Hash)
|
|
318
|
+
# Nested hashes will be stringified and then use 'diffy'
|
|
319
|
+
result.concat diff_two_hashes_with_diffy(depth: depth, hash1: obj[:old], hash2: obj[:new])
|
|
320
|
+
elsif obj[:old].is_a?(String) && obj[:new].is_a?(String) && (obj[:old] =~ /\n/ || obj[:new] =~ /\n/)
|
|
321
|
+
# Multi-line strings will be split and then use 'diffy' to mimic the
|
|
322
|
+
# output seen when using "diff" on the command line
|
|
323
|
+
result.concat diff_two_strings_with_diffy(obj[:old], obj[:new], depth)
|
|
324
|
+
else
|
|
325
|
+
# Stuff we don't recognize will be converted to a string and printed
|
|
326
|
+
# with '+' and '-' unless the object resolves to an empty string.
|
|
327
|
+
result.concat diff_at_depth(depth, obj[:old], obj[:new])
|
|
328
|
+
end
|
|
329
|
+
else
|
|
330
|
+
obj.keys.sort.each { |key| result.concat hash_diff(obj[key], 1 + depth, key, nested) }
|
|
331
|
+
end
|
|
332
|
+
result
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
# Get the diff between two arbitrary objects
|
|
336
|
+
# @param depth [Fixnum] Depth of nesting, used for indentation
|
|
337
|
+
# @param old_obj [?] Old object
|
|
338
|
+
# @param new_obj [?] New object
|
|
339
|
+
# @return Array<String> Diff output
|
|
340
|
+
def self.diff_at_depth(depth, old_obj, new_obj)
|
|
341
|
+
old_s = old_obj.to_s
|
|
342
|
+
new_s = new_obj.to_s
|
|
343
|
+
result = []
|
|
344
|
+
result << left_pad(2 * depth + 2, "- #{old_s}").red unless old_s == ''
|
|
345
|
+
result << left_pad(2 * depth + 2, "+ #{new_s}").green unless new_s == ''
|
|
346
|
+
result
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
# Utility Method!
|
|
350
|
+
# Indent a given text string with a certain number of spaces
|
|
351
|
+
# @param spaces [Fixnum] Number of spaces
|
|
352
|
+
# @param text [String] Text
|
|
353
|
+
def self.left_pad(spaces, text = '')
|
|
354
|
+
[' ' * spaces, text].join('')
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
# Utility Method!
|
|
358
|
+
# Given an arbitrary object, convert it into a string for use by 'diffy'.
|
|
359
|
+
# This basically exists so we can do something prettier than just calling .inspect or .to_s
|
|
360
|
+
# on object types we anticipate seeing, while not failing entirely on other object types.
|
|
361
|
+
# @param obj [?] Object to be stringified
|
|
362
|
+
# @return [String] String representation of object for diffy
|
|
363
|
+
def self.stringify_for_diffy(obj)
|
|
364
|
+
return JSON.pretty_generate(obj) if [Hash, Array].include?(obj.class)
|
|
365
|
+
return '""' if obj.is_a?(String) && obj == ''
|
|
366
|
+
return obj if [String, Fixnum, Float].include?(obj.class)
|
|
367
|
+
"#{obj.class}: #{obj.inspect}"
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
# Utility Method!
|
|
371
|
+
# Implement the --display-datatype-changes option by:
|
|
372
|
+
# - Removing string-equivalent differences when option == false
|
|
373
|
+
# - Updating display of string-equivalent differences when option == true
|
|
374
|
+
# @param diff [Array<Diff Objects>] Difference array
|
|
375
|
+
# @param option [Boolean] Selected behavior; see description
|
|
376
|
+
# @param logger [Logger] Logger object
|
|
377
|
+
def self.adjust_for_display_datatype_changes(diff, option, logger = nil)
|
|
378
|
+
diff.map! do |diff_obj|
|
|
379
|
+
if diff_obj[0] == '+' || diff_obj[0] == '-'
|
|
380
|
+
diff_obj[2] = 'undef' if diff_obj[2].nil?
|
|
381
|
+
diff_obj
|
|
382
|
+
else
|
|
383
|
+
x2, x3 = _adjust_for_display_datatype(diff_obj[2], diff_obj[3], option, logger)
|
|
384
|
+
if x2.nil? && x3.nil?
|
|
385
|
+
# Delete this! Return nil and compact! will get rid of them.
|
|
386
|
+
msg = "Adjust display for #{diff_obj[1].gsub(/\f/, '::')}: " \
|
|
387
|
+
"#{diff_obj[2].inspect} != #{diff_obj[3].inspect} DELETED"
|
|
388
|
+
logger.debug(msg) if logger
|
|
389
|
+
nil
|
|
390
|
+
elsif x2 == diff_obj[2] && x3 == diff_obj[3]
|
|
391
|
+
# Neither object changed
|
|
392
|
+
diff_obj
|
|
393
|
+
else
|
|
394
|
+
# Adjust the display and return modified object
|
|
395
|
+
msg = "Adjust display for #{diff_obj[1].gsub(/\f/, '::')}: " \
|
|
396
|
+
"#{diff_obj[2].inspect} -> #{x2}; "\
|
|
397
|
+
"#{diff_obj[3].inspect} -> #{x3}"
|
|
398
|
+
logger.debug(msg) if logger
|
|
399
|
+
diff_obj[2] = x2
|
|
400
|
+
diff_obj[3] = x3
|
|
401
|
+
diff_obj
|
|
402
|
+
end
|
|
403
|
+
end
|
|
404
|
+
end
|
|
405
|
+
diff.compact!
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
# Utility Method!
|
|
409
|
+
# Called by adjust_for_display_datatype_changes to compare an old value
|
|
410
|
+
# to a new value and adjust as appropriate.
|
|
411
|
+
# @param obj1 [?] First object
|
|
412
|
+
# @param obj2 [?] Second object
|
|
413
|
+
# @param option [Boolean] Selected behavior; see adjust_for_display_datatype_changes
|
|
414
|
+
# @return [<String, String> or <?, ?>] Updated values of objects
|
|
415
|
+
def self._adjust_for_display_datatype(obj1, obj2, option, logger)
|
|
416
|
+
# If not string-equal, return to leave untouched
|
|
417
|
+
return [obj1, obj2] unless obj1.to_s == obj2.to_s
|
|
418
|
+
|
|
419
|
+
# Delete if option to display these is false
|
|
420
|
+
return [nil, nil] unless option
|
|
421
|
+
|
|
422
|
+
# Delete if both objects are nil
|
|
423
|
+
return [nil, nil] if obj1.nil? && obj2.nil?
|
|
424
|
+
|
|
425
|
+
# If one is nil and the other is the empty string...
|
|
426
|
+
return ['undef', '""'] if obj1.nil?
|
|
427
|
+
return ['""', 'undef'] if obj2.nil?
|
|
428
|
+
|
|
429
|
+
# If one is an integer and the other is a string
|
|
430
|
+
return [obj1, "\"#{obj2}\""] if obj1.is_a?(Fixnum) && obj2.is_a?(String)
|
|
431
|
+
return ["\"#{obj1}\"", obj2] if obj1.is_a?(String) && obj2.is_a?(Fixnum)
|
|
432
|
+
|
|
433
|
+
# True and false
|
|
434
|
+
return [obj1, "\"#{obj2}\""] if obj1.is_a?(TrueClass) && obj2.is_a?(String)
|
|
435
|
+
return [obj1, "\"#{obj2}\""] if obj1.is_a?(FalseClass) && obj2.is_a?(String)
|
|
436
|
+
return ["\"#{obj1}\"", obj2] if obj1.is_a?(String) && obj2.is_a?(TrueClass)
|
|
437
|
+
return ["\"#{obj1}\"", obj2] if obj1.is_a?(String) && obj2.is_a?(FalseClass)
|
|
438
|
+
|
|
439
|
+
# Unhandled case - warn about it and then return inputs untouched
|
|
440
|
+
# Note: If you encounter this, please report it so we can add a handler.
|
|
441
|
+
# :nocov:
|
|
442
|
+
msg = "In _adjust_for_display_datatype, objects '#{obj1.inspect}' (#{obj1.class}) and"\
|
|
443
|
+
" '#{obj2.inspect}' (#{obj2.class}) have identical string representations but"\
|
|
444
|
+
' formatting is not implemented to update display.'
|
|
445
|
+
logger.warn(msg) if logger
|
|
446
|
+
[obj1, obj2]
|
|
447
|
+
# :nocov:
|
|
448
|
+
end
|
|
449
|
+
end
|
|
450
|
+
end
|
|
451
|
+
end
|
|
452
|
+
end
|