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
@@ -0,0 +1,103 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module OctocatalogDiff
|
6
|
+
module API
|
7
|
+
module V1
|
8
|
+
# Sets up the override of a fact or ENC parameter during catalog compilation.
|
9
|
+
class Override
|
10
|
+
# Accessors
|
11
|
+
attr_reader :key, :value
|
12
|
+
|
13
|
+
# Constructor: Accepts a key and value.
|
14
|
+
# @param input [Hash] Must contain :key and :value
|
15
|
+
def initialize(input)
|
16
|
+
@key = input.fetch(:key)
|
17
|
+
@value = parsed_value(input.fetch(:value))
|
18
|
+
end
|
19
|
+
|
20
|
+
# Initialize from a parsed command line
|
21
|
+
# @param input [String] Command line parameter
|
22
|
+
# @return [OctocatalogDiff::API::V1::Override] Initialized object
|
23
|
+
def self.create_from_input(input, key = nil)
|
24
|
+
# Normally the input will be a string in the format key=(data type)value where the data
|
25
|
+
# type is optional and the parentheses are literal. Example:
|
26
|
+
# foo=1 (auto-determine data type - in this case it would be a fixnum)
|
27
|
+
# foo=(fixnum)1 (will be a fixnum)
|
28
|
+
# foo=(string)1 (will be '1' the string)
|
29
|
+
# If input is not a string, we can still construct the object if the key is given.
|
30
|
+
# That input would come directly from code and not from the command line, since inputs
|
31
|
+
# from the command line are always strings.
|
32
|
+
if key.nil? && input.is_a?(String)
|
33
|
+
unless input.include?('=')
|
34
|
+
raise ArgumentError, "Fact override '#{input}' is not in 'key=(data type)value' format"
|
35
|
+
end
|
36
|
+
k, v = input.strip.split('=', 2).map(&:strip)
|
37
|
+
new(key: k, value: v)
|
38
|
+
elsif key.nil?
|
39
|
+
message = "Define a key when the input is not a string (#{input.class} => #{input.inspect})"
|
40
|
+
raise ArgumentError, message
|
41
|
+
else
|
42
|
+
new(key: key, value: input)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
# Guess the datatype from a particular input
|
49
|
+
# @param input [String] Input in string format
|
50
|
+
# @return [?] Output in appropriate format
|
51
|
+
def parsed_value(input)
|
52
|
+
# If data type is explicitly given
|
53
|
+
if input =~ /^\((\w+)\)(.*)$/m
|
54
|
+
datatype = Regexp.last_match(1)
|
55
|
+
value = Regexp.last_match(2)
|
56
|
+
return convert_to_data_type(datatype.downcase, value)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Guess data type
|
60
|
+
return input.to_i if input =~ /^-?\d+$/
|
61
|
+
return input.to_f if input =~ /^-?\d*\.\d+$/
|
62
|
+
return true if input.casecmp('true').zero?
|
63
|
+
return false if input.casecmp('false').zero?
|
64
|
+
input
|
65
|
+
end
|
66
|
+
|
67
|
+
# Handle data type that's explicitly given
|
68
|
+
# @param datatype [String] Data type (as a string)
|
69
|
+
# @param value [String] Value given
|
70
|
+
# @return [?] Value converted to specified data type
|
71
|
+
def convert_to_data_type(datatype, value)
|
72
|
+
return value if datatype == 'string'
|
73
|
+
return parse_json(value) if datatype == 'json'
|
74
|
+
return nil if datatype == 'nil'
|
75
|
+
if datatype == 'fixnum'
|
76
|
+
return Regexp.last_match(1).to_i if value =~ /^(-?\d+)$/
|
77
|
+
raise ArgumentError, "Illegal fixnum '#{value}'"
|
78
|
+
end
|
79
|
+
if datatype == 'float'
|
80
|
+
return Regexp.last_match(1).to_f if value =~ /^(-?\d*\.\d+)$/
|
81
|
+
return Regexp.last_match(1).to_f if value =~ /^(-?\d+)$/
|
82
|
+
raise ArgumentError, "Illegal float '#{value}'"
|
83
|
+
end
|
84
|
+
if datatype == 'boolean'
|
85
|
+
return true if value.casecmp('true').zero?
|
86
|
+
return false if value.casecmp('false').zero?
|
87
|
+
raise ArgumentError, "Illegal boolean '#{value}'"
|
88
|
+
end
|
89
|
+
raise ArgumentError, "Unknown data type '#{datatype}'"
|
90
|
+
end
|
91
|
+
|
92
|
+
# Parse JSON value
|
93
|
+
# @param input [String] Input, hopefully in JSON format
|
94
|
+
# @return [?] Output data structure
|
95
|
+
def parse_json(input)
|
96
|
+
JSON.parse(input)
|
97
|
+
rescue JSON::ParserError => exc
|
98
|
+
raise JSON::ParserError, "Failed to parse JSON: input=#{input} error=#{exc}"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -7,6 +7,7 @@ require 'set'
|
|
7
7
|
require 'stringio'
|
8
8
|
|
9
9
|
require_relative '../catalog'
|
10
|
+
require_relative '../errors'
|
10
11
|
require_relative 'filter'
|
11
12
|
|
12
13
|
module OctocatalogDiff
|
@@ -55,14 +56,12 @@ module OctocatalogDiff
|
|
55
56
|
# The heavy lifting is still handled by 'hashdiff' but we're pre-simplifying the input and post-processing
|
56
57
|
# the output to make it easier to deal with later.
|
57
58
|
class Differ
|
58
|
-
# This class is to distinguish handled errors from unhandled ones, for spec testing.
|
59
|
-
class DifferError < RuntimeError
|
60
|
-
end
|
61
|
-
|
62
59
|
# Constructor
|
63
60
|
# @param catalog1_in [OctocatalogDiff::Catalog] First catalog to compare
|
64
61
|
# @param catalog2_in [OctocatalogDiff::Catalog] Second catalog to compare
|
65
62
|
def initialize(opts, catalog1_in, catalog2_in)
|
63
|
+
@catalog1_raw = catalog1_in
|
64
|
+
@catalog2_raw = catalog2_in
|
66
65
|
@catalog1 = catalog_resources(catalog1_in, 'First catalog')
|
67
66
|
@catalog2 = catalog_resources(catalog2_in, 'Second catalog')
|
68
67
|
@logger = opts.fetch(:logger, Logger.new(StringIO.new))
|
@@ -105,6 +104,32 @@ module OctocatalogDiff
|
|
105
104
|
self
|
106
105
|
end
|
107
106
|
|
107
|
+
# Handle --ignore-tags option, the ability to tag resources within modules/manifests and
|
108
|
+
# have catalog-diff ignore them.
|
109
|
+
def ignore_tags
|
110
|
+
return unless @opts[:ignore_tags].is_a?(Array) && @opts[:ignore_tags].any?
|
111
|
+
|
112
|
+
# Go through the "to" catalog and identify any resources that have been tagged with one or more
|
113
|
+
# specified "ignore tags." Add any such items to the ignore list. The 'to' catalog has the authoritative
|
114
|
+
# list of dynamic ignores.
|
115
|
+
@catalog2_raw.resources.each do |resource|
|
116
|
+
next unless tagged_for_ignore?(resource)
|
117
|
+
ignore(type: resource['type'], title: resource['title'])
|
118
|
+
@logger.debug "Ignoring type='#{resource['type']}', title='#{resource['title']}' based on tag in to-catalog"
|
119
|
+
end
|
120
|
+
|
121
|
+
# Go through the "from" catalog and identify any resources that have been tagged with one or more
|
122
|
+
# specified "ignore tags." Only mark the resources for ignoring if they do not appear in the 'to'
|
123
|
+
# catalog, thereby allowing the 'to' catalog to be the authoritative ignore list. This allows deleted
|
124
|
+
# items that were previously ignored to continue to be ignored.
|
125
|
+
@catalog1_raw.resources.each do |resource|
|
126
|
+
next if @catalog2_raw.resource(type: resource['type'], title: resource['title'])
|
127
|
+
next unless tagged_for_ignore?(resource)
|
128
|
+
ignore(type: resource['type'], title: resource['title'])
|
129
|
+
@logger.debug "Ignoring type='#{resource['type']}', title='#{resource['title']}' based on tag in from-catalog"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
108
133
|
# Return catalog1 with filter_and_cleanups applied.
|
109
134
|
# This is in the public section because it's called from spec tests as well
|
110
135
|
# as being called internally.
|
@@ -123,6 +148,20 @@ module OctocatalogDiff
|
|
123
148
|
|
124
149
|
private
|
125
150
|
|
151
|
+
# Determine if a resource is tagged with any ignore-tag.
|
152
|
+
# @param resource [Hash] The resource
|
153
|
+
# @return [Boolean] true if tagged for ignore, false if not
|
154
|
+
def tagged_for_ignore?(resource)
|
155
|
+
return false unless @opts[:ignore_tags].is_a?(Array)
|
156
|
+
return false unless resource.key?('tags') && resource['tags'].is_a?(Array)
|
157
|
+
@opts[:ignore_tags].each do |tag|
|
158
|
+
# tag_with_type will be like: 'ignored_catalog_diff__mymodule__mytype'
|
159
|
+
tag_with_type = [tag, resource['type'].downcase.gsub(/\W/, '_')].join('__')
|
160
|
+
return true if resource['tags'].include?(tag) || resource['tags'].include?(tag_with_type)
|
161
|
+
end
|
162
|
+
false
|
163
|
+
end
|
164
|
+
|
126
165
|
# Actually perform the catalog diff. This implements the 3-part algorithm described in the
|
127
166
|
# comment block at the top of this file.
|
128
167
|
def catdiff
|
@@ -150,18 +189,31 @@ module OctocatalogDiff
|
|
150
189
|
# Remove resources that have been explicitly ignored
|
151
190
|
filter_diffs_for_ignored_items(result)
|
152
191
|
|
153
|
-
#
|
154
|
-
|
155
|
-
|
192
|
+
# Legacy options which are now filters
|
193
|
+
@opts[:filters] ||= []
|
194
|
+
add_element_to_array(@opts[:filters], 'CompilationDir')
|
195
|
+
add_element_to_array(@opts[:filters], 'AbsentFile') if @opts[:suppress_absent_file_details]
|
156
196
|
|
157
197
|
# Apply any additional pluggable filters.
|
158
|
-
|
198
|
+
filter_opts = {
|
199
|
+
logger: @logger,
|
200
|
+
from_compilation_dir: @catalog1_raw.compilation_dir,
|
201
|
+
to_compilation_dir: @catalog2_raw.compilation_dir
|
202
|
+
}
|
203
|
+
OctocatalogDiff::CatalogDiff::Filter.apply_filters(result, @opts[:filters], filter_opts) if @opts[:filters].any?
|
159
204
|
|
160
205
|
# That's it!
|
161
206
|
@logger.debug "Exiting catdiff; change count: #{result.size}"
|
162
207
|
result
|
163
208
|
end
|
164
209
|
|
210
|
+
# Add an element to an array if it doesn't already exist in that array
|
211
|
+
# @param array_in [Array] Array to have element added (**mutated** by this method)
|
212
|
+
# @param element [?] Element to add
|
213
|
+
def add_element_to_array(array_in, element)
|
214
|
+
array_in << element unless array_in.include?(element)
|
215
|
+
end
|
216
|
+
|
165
217
|
# Filter the differences for any items that were ignored, by some combination of type, title, and
|
166
218
|
# attribute. This modifies the array itself by selecting only items that do not meet the ignored
|
167
219
|
# filter.
|
@@ -169,43 +221,6 @@ module OctocatalogDiff
|
|
169
221
|
result.reject! { |item| ignored?(item) }
|
170
222
|
end
|
171
223
|
|
172
|
-
# If a file has ensure => absent, there are certain parameters that don't matter anymore. Filter
|
173
|
-
# out any such parameters from the result array.
|
174
|
-
# @param result [Array] Diff result list (modified by this method)
|
175
|
-
def filter_diffs_for_absent_files(result)
|
176
|
-
@logger.debug "Entering filter_diffs_for_absent_files with #{result.size} diffs"
|
177
|
-
|
178
|
-
# Scan for files in the result that are file resources with ensure => absent.
|
179
|
-
absent_files = Set.new
|
180
|
-
result.each do |diff|
|
181
|
-
next unless diff[0] == '~' || diff[0] == '!'
|
182
|
-
next unless diff[1] =~ /^File\f([^\f]+)\fparameters\fensure$/
|
183
|
-
next unless ['absent', 'false', false].include?(diff[3])
|
184
|
-
absent_files.add Regexp.last_match(1)
|
185
|
-
end
|
186
|
-
|
187
|
-
# If there are any absent files, remove all diffs referencing that file, except for
|
188
|
-
# the change to 'ensure'.
|
189
|
-
if absent_files.any?
|
190
|
-
keep = %w(ensure backup force provider)
|
191
|
-
result.map! do |diff|
|
192
|
-
if (diff[0] == '!' || diff[0] == '~') && diff[1] =~ /^File\f(.+)\fparameters\f(.+)$/
|
193
|
-
if absent_files.include?(Regexp.last_match(1)) && !keep.include?(Regexp.last_match(2))
|
194
|
-
@logger.debug "Removing file=#{Regexp.last_match(1)} parameter=#{Regexp.last_match(2)} for absent file"
|
195
|
-
nil
|
196
|
-
else
|
197
|
-
diff
|
198
|
-
end
|
199
|
-
else
|
200
|
-
diff
|
201
|
-
end
|
202
|
-
end
|
203
|
-
result.compact!
|
204
|
-
end
|
205
|
-
|
206
|
-
@logger.debug "Exiting filter_diffs_for_absent_files with #{result.size} diffs"
|
207
|
-
end
|
208
|
-
|
209
224
|
# Pre-processing of a catalog.
|
210
225
|
# - Remove 'before' and 'require' from parameters
|
211
226
|
# - Sort 'tags' array, or remove the tags array if tags are being ignored
|
@@ -368,7 +383,11 @@ module OctocatalogDiff
|
|
368
383
|
rule = rule_in.dup
|
369
384
|
|
370
385
|
# Type matches?
|
371
|
-
|
386
|
+
if rule[:type].is_a?(Regexp)
|
387
|
+
return false unless hsh[:type].match(rule[:type])
|
388
|
+
elsif rule[:type].is_a?(String)
|
389
|
+
return false unless rule[:type] == '*' || rule[:type].casecmp(hsh[:type]).zero?
|
390
|
+
end
|
372
391
|
|
373
392
|
# Title matches? (Support regexp and string)
|
374
393
|
if rule[:title].is_a?(Regexp)
|
@@ -594,7 +613,7 @@ module OctocatalogDiff
|
|
594
613
|
# @return [Hash] Internal simplified hash object
|
595
614
|
def catalog_resources(catalog_in, name = 'Passed catalog')
|
596
615
|
return catalog_in.resources if catalog_in.is_a?(OctocatalogDiff::Catalog)
|
597
|
-
raise DifferError, "#{name} is not a valid catalog (input datatype: #{catalog_in.class})"
|
616
|
+
raise OctocatalogDiff::Errors::DifferError, "#{name} is not a valid catalog (input datatype: #{catalog_in.class})"
|
598
617
|
end
|
599
618
|
|
600
619
|
# Turn array of resources into a hash by serialized keys. For consistency with 'hashdiff'
|
@@ -1,7 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative '../api/v1/diff'
|
3
4
|
require_relative 'differ'
|
4
5
|
require_relative 'display/json'
|
6
|
+
require_relative 'display/legacy_json'
|
5
7
|
require_relative 'display/text'
|
6
8
|
|
7
9
|
module OctocatalogDiff
|
@@ -21,8 +23,9 @@ module OctocatalogDiff
|
|
21
23
|
# @param logger [Logger] Logger object
|
22
24
|
# @return [String] Text output for provided diff
|
23
25
|
def self.output(diff_in, options = {}, logger = nil)
|
24
|
-
|
25
|
-
raise ArgumentError, "text_output requires Array<Diff results>; passed in #{diff_in.class}" unless
|
26
|
+
diff_x = diff_in.is_a?(OctocatalogDiff::CatalogDiff::Differ) ? diff_in.diff : diff_in
|
27
|
+
raise ArgumentError, "text_output requires Array<Diff results>; passed in #{diff_in.class}" unless diff_x.is_a?(Array)
|
28
|
+
diff = diff_x.map { |x| OctocatalogDiff::API::V1::Diff.factory(x) }
|
26
29
|
|
27
30
|
# req_format means 'requested format' because 'format' has a built-in meaning to Ruby
|
28
31
|
req_format = options.fetch(:format, :color_text)
|
@@ -41,6 +44,9 @@ module OctocatalogDiff
|
|
41
44
|
when :json
|
42
45
|
logger.debug 'Generating JSON output' if logger
|
43
46
|
OctocatalogDiff::CatalogDiff::Display::Json.generate(diff, opts, logger)
|
47
|
+
when :legacy_json
|
48
|
+
logger.debug 'Generating Legacy JSON output' if logger
|
49
|
+
OctocatalogDiff::CatalogDiff::Display::LegacyJson.generate(diff, opts, logger)
|
44
50
|
when :text
|
45
51
|
logger.debug 'Generating non-colored text output' if logger
|
46
52
|
OctocatalogDiff::CatalogDiff::Display::Text.generate(diff, opts.merge(color: false), logger)
|
@@ -7,7 +7,8 @@ require 'json'
|
|
7
7
|
module OctocatalogDiff
|
8
8
|
module CatalogDiff
|
9
9
|
class Display
|
10
|
-
# Display the output from a diff in JSON format.
|
10
|
+
# Display the output from a diff in JSON format. This is the new format, used in octocatalog-diff
|
11
|
+
# 1.x, where each diff is represented by an hash with named keys.
|
11
12
|
class Json < OctocatalogDiff::CatalogDiff::Display
|
12
13
|
# Generate JSON representation of the 'diff' suitable for further analysis.
|
13
14
|
# @param diff [Array<Diff results>] The diff which *must* be in this format
|
@@ -16,7 +17,7 @@ module OctocatalogDiff
|
|
16
17
|
# @param _logger [Logger] Not used here
|
17
18
|
def self.generate(diff, options = {}, _logger = nil)
|
18
19
|
result = {
|
19
|
-
'diff' => diff
|
20
|
+
'diff' => diff.map(&:to_h_with_string_keys)
|
20
21
|
}
|
21
22
|
result['header'] = options[:header] unless options[:header].nil?
|
22
23
|
result.to_json
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../display'
|
4
|
+
|
5
|
+
require 'json'
|
6
|
+
|
7
|
+
module OctocatalogDiff
|
8
|
+
module CatalogDiff
|
9
|
+
class Display
|
10
|
+
# Display the output from a diff in JSON format. This is the legacy format, used in octocatalog-diff
|
11
|
+
# 0.x, where each diff is represented by an array.
|
12
|
+
class LegacyJson < OctocatalogDiff::CatalogDiff::Display
|
13
|
+
# Generate JSON representation of the 'diff' suitable for further analysis.
|
14
|
+
# @param diff [Array<Diff results>] The diff which *must* be in this format
|
15
|
+
# @param options [Hash] Options which are:
|
16
|
+
# - :header => [String] Header to print; no header is printed if not specified
|
17
|
+
# @param _logger [Logger] Not used here
|
18
|
+
def self.generate(diff, options = {}, _logger = nil)
|
19
|
+
result = {
|
20
|
+
'diff' => diff.map(&:raw)
|
21
|
+
}
|
22
|
+
result['header'] = options[:header] unless options[:header].nil?
|
23
|
+
result.to_json
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -257,13 +257,67 @@ module OctocatalogDiff
|
|
257
257
|
# @param depth [Fixnum] Depth, for correct indentation
|
258
258
|
# @return Array<String> Displayable result
|
259
259
|
def self.diff_two_strings_with_diffy(string1, string2, depth)
|
260
|
-
#
|
261
|
-
|
262
|
-
|
260
|
+
# Single line strings?
|
261
|
+
if single_lines?(string1, string2)
|
262
|
+
string1, string2 = add_trailing_newlines(string1, string2)
|
263
|
+
diff = Diffy::Diff.new(string1, string2, context: 2, include_diff_info: false).to_s.split("\n")
|
264
|
+
return diff.map { |x| left_pad(2 * depth + 2, make_trailing_whitespace_visible(adjust_position_of_plus_minus(x))) }
|
265
|
+
end
|
266
|
+
|
267
|
+
# Multiple line strings
|
268
|
+
string1, string2 = add_trailing_newlines(string1, string2)
|
263
269
|
diff = Diffy::Diff.new(string1, string2, context: 2, include_diff_info: true).to_s.split("\n")
|
264
270
|
diff.shift # Remove first line of diff info (filename that makes no sense)
|
265
271
|
diff.shift # Remove second line of diff info (filename that makes no sense)
|
266
|
-
diff.map { |x| left_pad(2 * depth + 2, x) }
|
272
|
+
diff.map { |x| left_pad(2 * depth + 2, make_trailing_whitespace_visible(x)) }
|
273
|
+
end
|
274
|
+
|
275
|
+
# Determine if two incoming strings are single lines. Returns true if both
|
276
|
+
# incoming strings are single lines, false otherwise.
|
277
|
+
# @param string_1 [String] First string
|
278
|
+
# @param string_2 [String] Second string
|
279
|
+
# @return [Boolean] Whether both incoming strings are single lines
|
280
|
+
def self.single_lines?(string_1, string_2)
|
281
|
+
string_1.strip !~ /\n/ && string_2.strip !~ /\n/
|
282
|
+
end
|
283
|
+
|
284
|
+
# Add "\n" to the end of both strings, only if both strings are lacking it.
|
285
|
+
# This prevents "\" for single string comparison.
|
286
|
+
# @param string_1 [String] First string
|
287
|
+
# @param string_2 [String] Second string
|
288
|
+
# @return [Array<String>] Adjusted string_1, string_2
|
289
|
+
def self.add_trailing_newlines(string_1, string_2)
|
290
|
+
return [string_1, string_2] unless string_1 !~ /\n\Z/ && string_2 !~ /\n\Z/
|
291
|
+
[string_1 + "\n", string_2 + "\n"]
|
292
|
+
end
|
293
|
+
|
294
|
+
# Adjust the space after of the `-` / `+` in the diff for single line diffs.
|
295
|
+
# Diffy prints diffs with no space between the `-` / `+` in the text, but for
|
296
|
+
# single lines it's easier to read with that space added.
|
297
|
+
# @param string_in [String] Input string, which is a line of a diff from diffy
|
298
|
+
# @return [String] Modified string
|
299
|
+
def self.adjust_position_of_plus_minus(string_in)
|
300
|
+
string_in.sub(/\A(\e\[\d+m)?([\-\+])/, '\1\2 ')
|
301
|
+
end
|
302
|
+
|
303
|
+
# Convert trailing whitespace to underscore for display purposes. Also convert special
|
304
|
+
# whitespace (\r, \n, \t, ...) to character representation.
|
305
|
+
# @param string_in [String] Input string, which might contain trailing whitespace
|
306
|
+
# @return [String] Modified string
|
307
|
+
def self.make_trailing_whitespace_visible(string_in)
|
308
|
+
return string_in unless string_in =~ /\A((?:.|\n)*?)(\s+)(\e\[0m)?\Z/
|
309
|
+
beginning = Regexp.last_match(1)
|
310
|
+
trailing_space = Regexp.last_match(2)
|
311
|
+
end_escape = Regexp.last_match(3)
|
312
|
+
|
313
|
+
# Trailing space adjustment for line endings
|
314
|
+
trailing_space.gsub! "\n", '\n'
|
315
|
+
trailing_space.gsub! "\r", '\r'
|
316
|
+
trailing_space.gsub! "\t", '\t'
|
317
|
+
trailing_space.gsub! "\f", '\f'
|
318
|
+
trailing_space.tr! ' ', '_'
|
319
|
+
|
320
|
+
[beginning, trailing_space, end_escape].join('')
|
267
321
|
end
|
268
322
|
|
269
323
|
# Get the diff of two hashes. Call the 'diffy' gem for this.
|
@@ -319,9 +373,9 @@ module OctocatalogDiff
|
|
319
373
|
if nested && obj[:old].is_a?(Hash) && obj[:new].is_a?(Hash)
|
320
374
|
# Nested hashes will be stringified and then use 'diffy'
|
321
375
|
result.concat diff_two_hashes_with_diffy(depth: depth, hash1: obj[:old], hash2: obj[:new])
|
322
|
-
elsif obj[:old].is_a?(String) && obj[:new].is_a?(String)
|
323
|
-
#
|
324
|
-
#
|
376
|
+
elsif obj[:old].is_a?(String) && obj[:new].is_a?(String)
|
377
|
+
# Strings will use 'diffy' to mimic the output seen when using
|
378
|
+
# "diff" on the command line.
|
325
379
|
result.concat diff_two_strings_with_diffy(obj[:old], obj[:new], depth)
|
326
380
|
else
|
327
381
|
# Stuff we don't recognize will be converted to a string and printed
|
@@ -395,8 +449,9 @@ module OctocatalogDiff
|
|
395
449
|
else
|
396
450
|
# Adjust the display and return modified object
|
397
451
|
msg = "Adjust display for #{diff_obj[1].gsub(/\f/, '::')}: " \
|
398
|
-
"
|
399
|
-
"#{diff_obj[
|
452
|
+
"old=#{x2.inspect} new=#{x3.inspect} "\
|
453
|
+
"(extra debugging: #{diff_obj[2].inspect} -> #{x2}; "\
|
454
|
+
"#{diff_obj[3].inspect} -> #{x3})"
|
400
455
|
logger.debug(msg) if logger
|
401
456
|
diff_obj[2] = x2
|
402
457
|
diff_obj[3] = x3
|