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
@@ -1,147 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative '../differ'
|
4
|
-
|
5
|
-
module OctocatalogDiff
|
6
|
-
module CatalogDiff
|
7
|
-
class Cli
|
8
|
-
# Wrapper around OctocatalogDiff::CatalogDiff::Differ to provide the logger object, set up
|
9
|
-
# ignores, and add additional ignores for items dependent upon the compilation directory.
|
10
|
-
class Diffs
|
11
|
-
# Constructor
|
12
|
-
# @param options [Hash] Options from cli/options
|
13
|
-
# @param logger [Logger] Logger object
|
14
|
-
def initialize(options, logger)
|
15
|
-
@options = options
|
16
|
-
@logger = logger
|
17
|
-
end
|
18
|
-
|
19
|
-
# The method to call externally, passing in the catalogs as a hash (see parameter). This
|
20
|
-
# sets up options and ignores and then actually performs the diffs. The result is the array
|
21
|
-
# of diffs.
|
22
|
-
# @param catalogs [Hash] { :to => OctocatalogDiff::Catalog, :from => OctocatalogDiff::Catalog }
|
23
|
-
# @return [Array<diffs>] Array of diffs
|
24
|
-
def diffs(catalogs)
|
25
|
-
@logger.debug 'Begin compute diffs between catalogs'
|
26
|
-
diff_opts = @options.merge(logger: @logger)
|
27
|
-
|
28
|
-
# Construct the actual differ object that the present one wraps
|
29
|
-
differ = OctocatalogDiff::CatalogDiff::Differ.new(diff_opts, catalogs[:from], catalogs[:to])
|
30
|
-
differ.ignore(attr: 'tags') unless @options.fetch(:include_tags, false)
|
31
|
-
differ.ignore(@options.fetch(:ignore, []))
|
32
|
-
|
33
|
-
# Handle --ignore-tags option, the ability to tag resources within modules/manifests and
|
34
|
-
# have catalog-diff ignore them.
|
35
|
-
if @options[:ignore_tags].is_a?(Array) && @options[:ignore_tags].any?
|
36
|
-
# Go through the "to" catalog and identify any resources that have been tagged with one or more
|
37
|
-
# specified "ignore tags." Add any such items to the ignore list. The 'to' catalog has the authoritative
|
38
|
-
# list of dynamic ignores.
|
39
|
-
catalogs[:to].resources.each do |resource|
|
40
|
-
next unless tagged_for_ignore?(resource)
|
41
|
-
differ.ignore(type: resource['type'], title: resource['title'])
|
42
|
-
@logger.debug "Ignoring type='#{resource['type']}', title='#{resource['title']}' based on tag in to-catalog"
|
43
|
-
end
|
44
|
-
|
45
|
-
# Go through the "from" catalog and identify any resources that have been tagged with one or more
|
46
|
-
# specified "ignore tags." Only mark the resources for ignoring if they do not appear in the 'to'
|
47
|
-
# catalog, thereby allowing the 'to' catalog to be the authoritative ignore list. This allows deleted
|
48
|
-
# items that were previously ignored to continue to be ignored.
|
49
|
-
catalogs[:from].resources.each do |resource|
|
50
|
-
next if catalogs[:to].resource(type: resource['type'], title: resource['title'])
|
51
|
-
next unless tagged_for_ignore?(resource)
|
52
|
-
differ.ignore(type: resource['type'], title: resource['title'])
|
53
|
-
@logger.debug "Ignoring type='#{resource['type']}', title='#{resource['title']}' based on tag in from-catalog"
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
# Actually perform the diff
|
58
|
-
diff_result = differ.diff
|
59
|
-
diff_result.delete_if { |element| change_due_to_compilation_dir?(element, catalogs) }
|
60
|
-
@logger.debug 'Success compute diffs between catalogs'
|
61
|
-
diff_result
|
62
|
-
end
|
63
|
-
|
64
|
-
# Catch anything that explictly changed as a result of different compilation directories and
|
65
|
-
# warn about it. These are probably things that should be refactored. For now we're going to pull
|
66
|
-
# these out after the fact so we can warn about them if they do show up.
|
67
|
-
# @param change [Array(diff format)] Change in diff format
|
68
|
-
# @param catalogs [Hash] { :to => OctocatalogDiff::Catalog, :from => OctocatalogDiff::Catalog }
|
69
|
-
# @return [Boolean] True if change includes compilation directory, false otherwise
|
70
|
-
def change_due_to_compilation_dir?(change, catalogs)
|
71
|
-
dir1 = catalogs.fetch(:to).compilation_dir
|
72
|
-
dir2 = catalogs.fetch(:from).compilation_dir
|
73
|
-
return false if dir1.nil? || dir2.nil?
|
74
|
-
|
75
|
-
dir1_rexp = Regexp.escape(dir1)
|
76
|
-
dir2_rexp = Regexp.escape(dir2)
|
77
|
-
dir = "(?:#{dir1_rexp}|#{dir2_rexp})"
|
78
|
-
|
79
|
-
# Check for added/removed resources where the title of the resource includes the compilation directory
|
80
|
-
if change[0] == '+' || change[0] == '-'
|
81
|
-
if change[1] =~ /^([^\f]+)\f([^\f]*#{dir}[^\f]*)/
|
82
|
-
message = "Resource #{Regexp.last_match(1)}[#{Regexp.last_match(2)}]"
|
83
|
-
message += ' appears to depend on catalog compilation directory. Suppressed from results.'
|
84
|
-
@logger.warn message
|
85
|
-
return true
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
# Check for a change where the difference in a parameter exactly corresponds to the difference in the
|
90
|
-
# compilation directory.
|
91
|
-
if change[0] == '~' || change[0] == '!'
|
92
|
-
from_before = nil
|
93
|
-
from_after = nil
|
94
|
-
from_match = false
|
95
|
-
to_before = nil
|
96
|
-
to_after = nil
|
97
|
-
to_match = false
|
98
|
-
|
99
|
-
if change[2] =~ /^(.*)#{dir2}(.*)$/m
|
100
|
-
from_before = Regexp.last_match(1) || ''
|
101
|
-
from_after = Regexp.last_match(2) || ''
|
102
|
-
from_match = true
|
103
|
-
end
|
104
|
-
|
105
|
-
if change[3] =~ /^(.*)#{dir1}(.*)$/m
|
106
|
-
to_before = Regexp.last_match(1) || ''
|
107
|
-
to_after = Regexp.last_match(2) || ''
|
108
|
-
to_match = true
|
109
|
-
end
|
110
|
-
|
111
|
-
if from_match && to_match && to_before == from_before && to_after == from_after
|
112
|
-
message = "Resource key #{change[1].gsub(/\f/, ' => ')}"
|
113
|
-
message += ' appears to depend on catalog compilation directory. Suppressed from results.'
|
114
|
-
@logger.warn message
|
115
|
-
return true
|
116
|
-
end
|
117
|
-
|
118
|
-
if from_match || to_match
|
119
|
-
message = "Resource key #{change[1].gsub(/\f/, ' => ')}"
|
120
|
-
message += ' may depend on catalog compilation directory, but there may be differences.'
|
121
|
-
message += ' This is included in results for now, but please verify.'
|
122
|
-
@logger.warn message
|
123
|
-
end
|
124
|
-
end
|
125
|
-
|
126
|
-
false
|
127
|
-
end
|
128
|
-
|
129
|
-
private
|
130
|
-
|
131
|
-
# Determine if a resource is tagged with any ignore-tag.
|
132
|
-
# @param resource [Hash] The resource
|
133
|
-
# @return [Boolean] true if tagged for ignore, false if not
|
134
|
-
def tagged_for_ignore?(resource)
|
135
|
-
return false unless @options[:ignore_tags].is_a?(Array)
|
136
|
-
return false unless resource.key?('tags') && resource['tags'].is_a?(Array)
|
137
|
-
@options[:ignore_tags].each do |tag|
|
138
|
-
# tag_with_type will be like: 'ignored_catalog_diff__mymodule__mytype'
|
139
|
-
tag_with_type = [tag, resource['type'].downcase.gsub(/\W/, '_')].join('__')
|
140
|
-
return true if resource['tags'].include?(tag) || resource['tags'].include?(tag_with_type)
|
141
|
-
end
|
142
|
-
false
|
143
|
-
end
|
144
|
-
end
|
145
|
-
end
|
146
|
-
end
|
147
|
-
end
|
@@ -1,100 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'json'
|
4
|
-
|
5
|
-
module OctocatalogDiff
|
6
|
-
module CatalogDiff
|
7
|
-
class Cli
|
8
|
-
class Helpers
|
9
|
-
# Since input from the command line is in the format of a string, this class helps to guess
|
10
|
-
# at the data type.
|
11
|
-
class FactOverride
|
12
|
-
# Accessors
|
13
|
-
attr_reader :key, :value
|
14
|
-
|
15
|
-
# Constructor: Input will be a string (since it comes from command line).
|
16
|
-
# This code will make a best guess at the data type (or use a supplied data type if any).
|
17
|
-
# @param input [String] Input in the format: key=(data type)value
|
18
|
-
def initialize(input, key = nil)
|
19
|
-
# Normally the input will be a string in the format key=(data type)value where the data
|
20
|
-
# type is optional and the parentheses are literal. Example:
|
21
|
-
# foo=1 (auto-determine data type - in this case it would be a fixnum)
|
22
|
-
# foo=(fixnum)1 (will be a fixnum)
|
23
|
-
# foo=(string)1 (will be '1' the string)
|
24
|
-
# If input is not a string, we can still construct the object if the key is given.
|
25
|
-
# That input would come directly from code and not from the command line, since inputs
|
26
|
-
# from the command line are always strings.
|
27
|
-
if input.is_a?(String)
|
28
|
-
unless input.include?('=')
|
29
|
-
raise ArgumentError, "Fact override '#{input}' is not in 'key=(data type)value' format"
|
30
|
-
end
|
31
|
-
@key, raw_value = input.strip.split('=', 2)
|
32
|
-
@value = parsed_value(raw_value)
|
33
|
-
elsif key.nil?
|
34
|
-
message = "Define a key when the input is not a string (#{input.class} => #{input.inspect})"
|
35
|
-
raise ArgumentError, message
|
36
|
-
else
|
37
|
-
@key = key
|
38
|
-
@value = input
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
private
|
43
|
-
|
44
|
-
# Guess the datatype from a particular input
|
45
|
-
# @param input [String] Input in string format
|
46
|
-
# @return [?] Output in appropriate format
|
47
|
-
def parsed_value(input)
|
48
|
-
# If data type is explicitly given
|
49
|
-
if input =~ /^\((\w+)\)(.*)$/m
|
50
|
-
datatype = Regexp.last_match(1)
|
51
|
-
value = Regexp.last_match(2)
|
52
|
-
return convert_to_data_type(datatype.downcase, value)
|
53
|
-
end
|
54
|
-
|
55
|
-
# Guess data type
|
56
|
-
return input.to_i if input =~ /^-?\d+$/
|
57
|
-
return input.to_f if input =~ /^-?\d*\.\d+$/
|
58
|
-
return true if input.casecmp('true').zero?
|
59
|
-
return false if input.casecmp('false').zero?
|
60
|
-
input
|
61
|
-
end
|
62
|
-
|
63
|
-
# Handle data type that's explicitly given
|
64
|
-
# @param datatype [String] Data type (as a string)
|
65
|
-
# @param value [String] Value given
|
66
|
-
# @return [?] Value converted to specified data type
|
67
|
-
def convert_to_data_type(datatype, value)
|
68
|
-
return value if datatype == 'string'
|
69
|
-
return parse_json(value) if datatype == 'json'
|
70
|
-
return nil if datatype == 'nil'
|
71
|
-
if datatype == 'fixnum'
|
72
|
-
return Regexp.last_match(1).to_i if value =~ /^(-?\d+)$/
|
73
|
-
raise ArgumentError, "Illegal fixnum '#{value}'"
|
74
|
-
end
|
75
|
-
if datatype == 'float'
|
76
|
-
return Regexp.last_match(1).to_f if value =~ /^(-?\d*\.\d+)$/
|
77
|
-
return Regexp.last_match(1).to_f if value =~ /^(-?\d+)$/
|
78
|
-
raise ArgumentError, "Illegal float '#{value}'"
|
79
|
-
end
|
80
|
-
if datatype == 'boolean'
|
81
|
-
return true if value.casecmp('true').zero?
|
82
|
-
return false if value.casecmp('false').zero?
|
83
|
-
raise ArgumentError, "Illegal boolean '#{value}'"
|
84
|
-
end
|
85
|
-
raise ArgumentError, "Unknown data type '#{datatype}'"
|
86
|
-
end
|
87
|
-
|
88
|
-
# Parse JSON value
|
89
|
-
# @param input [String] Input, hopefully in JSON format
|
90
|
-
# @return [?] Output data structure
|
91
|
-
def parse_json(input)
|
92
|
-
JSON.parse(input)
|
93
|
-
rescue JSON::ParserError => exc
|
94
|
-
raise JSON::ParserError, "Failed to parse JSON: input=#{input} error=#{exc}"
|
95
|
-
end
|
96
|
-
end
|
97
|
-
end
|
98
|
-
end
|
99
|
-
end
|
100
|
-
end
|
@@ -1,185 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative '../cli'
|
4
|
-
require_relative '../../facts'
|
5
|
-
require_relative '../../version'
|
6
|
-
|
7
|
-
require 'optparse'
|
8
|
-
|
9
|
-
module OctocatalogDiff
|
10
|
-
module CatalogDiff
|
11
|
-
class Cli
|
12
|
-
# This class contains the option parser. 'parse_options' is the external entry point.
|
13
|
-
class Options
|
14
|
-
# The usage banner.
|
15
|
-
BANNER = 'Usage: catalog-diff -n <hostname> [-f <from environment>] [-t <to environment>]'.freeze
|
16
|
-
|
17
|
-
# List of classes
|
18
|
-
def self.classes
|
19
|
-
@classes ||= []
|
20
|
-
end
|
21
|
-
|
22
|
-
# Define the Option class and newoption() method for use by catalog-diff/cli/options/*.rb files
|
23
|
-
class Option
|
24
|
-
DEFAULT_WEIGHT = 999
|
25
|
-
def self.has_weight(w) # rubocop:disable Style/PredicateName
|
26
|
-
@weight = w
|
27
|
-
end
|
28
|
-
|
29
|
-
def self.weight
|
30
|
-
@weight || DEFAULT_WEIGHT
|
31
|
-
end
|
32
|
-
|
33
|
-
def self.name
|
34
|
-
self::NAME
|
35
|
-
end
|
36
|
-
|
37
|
-
def self.newoption(name, &block)
|
38
|
-
klass = Class.new(OctocatalogDiff::CatalogDiff::Cli::Options::Option)
|
39
|
-
klass.const_set('NAME', name)
|
40
|
-
klass.class_exec(&block)
|
41
|
-
Options.classes.push(klass)
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
# Method to call all of the other methods in this class. Except in very specific circumstances,
|
46
|
-
# this should be the method called from outside of this class.
|
47
|
-
# @param argv [Array] Array of command line arguments
|
48
|
-
# @param defaults [Hash] Default values
|
49
|
-
# @return [Hash] Parsed options
|
50
|
-
def self.parse_options(argv, defaults = {})
|
51
|
-
options = defaults.dup
|
52
|
-
Options.classes.clear
|
53
|
-
::OptionParser.new do |parser|
|
54
|
-
parser.banner = "#{BANNER}\n\n"
|
55
|
-
option_classes.each do |klass|
|
56
|
-
obj = klass.new
|
57
|
-
obj.parse(parser, options)
|
58
|
-
end
|
59
|
-
parser.on_tail('-v', '--version', 'Show version information about this program and quit.') do
|
60
|
-
puts "octocatalog-diff #{OctocatalogDiff::Version::VERSION}"
|
61
|
-
exit
|
62
|
-
end
|
63
|
-
end.parse! argv
|
64
|
-
options
|
65
|
-
end
|
66
|
-
|
67
|
-
# Read in *.rb files in the 'options' directory and create classes from them.
|
68
|
-
# Sort the classes according to weight and name and return the list of sorted classes.
|
69
|
-
# @return [Array<Class>] Sorted classes
|
70
|
-
def self.option_classes
|
71
|
-
files = Dir.glob(File.join(File.dirname(__FILE__), 'options', '*.rb'))
|
72
|
-
files.each { |file| load file } # Populates self.classes
|
73
|
-
classes.sort do |a, b|
|
74
|
-
[
|
75
|
-
a.weight <=> b.weight,
|
76
|
-
a.name.downcase <=> b.name.downcase,
|
77
|
-
a.object_id <=> b.object_id
|
78
|
-
].find(&:nonzero?)
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
# Sets up options that can be defined globally or for just one branch. For example, with a
|
83
|
-
# CLI name of 'puppet-binary' this will acknowledge 3 options: --puppet-binary (global),
|
84
|
-
# --from-puppet-binary (for the from branch only), and --to-puppet-binary (for the to branch
|
85
|
-
# only). The only options that will be created are the 'to' and 'from' variants, but the global
|
86
|
-
# option will populate any of the 'to' and 'from' variants that are missing.
|
87
|
-
# @param :datatype [?] Expected data type
|
88
|
-
def self.option_globally_or_per_branch(opts = {})
|
89
|
-
datatype = opts.fetch(:datatype, '')
|
90
|
-
return option_globally_or_per_branch_string(opts) if datatype.is_a?(String)
|
91
|
-
return option_globally_or_per_branch_array(opts) if datatype.is_a?(Array)
|
92
|
-
raise ArgumentError, "option_globally_or_per_branch not equipped to handle #{datatype.class}"
|
93
|
-
end
|
94
|
-
|
95
|
-
# See description of `option_globally_or_per_branch`. This implements the logic for a string value.
|
96
|
-
# @param :parser [OptionParser object] The OptionParser argument
|
97
|
-
# @param :options [Hash] Options hash being constructed; this is modified in this method.
|
98
|
-
# @param :cli_name [String] Name of option on command line (e.g. puppet-binary)
|
99
|
-
# @param :option_name [Symbol] Name of option in the options hash (e.g. :puppet_binary)
|
100
|
-
# @param :desc [String] Description of option on the command line; will have "for the XX branch" appended
|
101
|
-
def self.option_globally_or_per_branch_string(opts)
|
102
|
-
parser = opts.fetch(:parser)
|
103
|
-
options = opts.fetch(:options)
|
104
|
-
cli_name = opts.fetch(:cli_name)
|
105
|
-
option_name = opts.fetch(:option_name)
|
106
|
-
desc = opts.fetch(:desc)
|
107
|
-
|
108
|
-
flag = "#{cli_name} STRING"
|
109
|
-
from_option = "from_#{option_name}".to_sym
|
110
|
-
to_option = "to_#{option_name}".to_sym
|
111
|
-
parser.on("--#{flag}", "#{desc} globally") do |x|
|
112
|
-
validate_option(opts[:validator], x) if opts[:validator]
|
113
|
-
translated = translate_option(opts[:translator], x)
|
114
|
-
options[to_option] ||= translated
|
115
|
-
options[from_option] ||= translated
|
116
|
-
end
|
117
|
-
parser.on("--to-#{flag}", "#{desc} for the to branch") do |x|
|
118
|
-
validate_option(opts[:validator], x) if opts[:validator]
|
119
|
-
options[to_option] = translate_option(opts[:translator], x)
|
120
|
-
end
|
121
|
-
parser.on("--from-#{flag}", "#{desc} for the from branch") do |x|
|
122
|
-
validate_option(opts[:validator], x) if opts[:validator]
|
123
|
-
options[from_option] = translate_option(opts[:translator], x)
|
124
|
-
end
|
125
|
-
end
|
126
|
-
|
127
|
-
# See description of `option_globally_or_per_branch`. This implements the logic for an array.
|
128
|
-
# @param :parser [OptionParser object] The OptionParser argument
|
129
|
-
# @param :options [Hash] Options hash being constructed; this is modified in this method.
|
130
|
-
# @param :cli_name [String] Name of option on command line (e.g. puppet-binary)
|
131
|
-
# @param :option_name [Symbol] Name of option in the options hash (e.g. :puppet_binary)
|
132
|
-
# @param :desc [String] Description of option on the command line; will have "for the XX branch" appended
|
133
|
-
def self.option_globally_or_per_branch_array(opts = {})
|
134
|
-
parser = opts.fetch(:parser)
|
135
|
-
options = opts.fetch(:options)
|
136
|
-
cli_name = opts.fetch(:cli_name)
|
137
|
-
option_name = opts.fetch(:option_name)
|
138
|
-
desc = opts.fetch(:desc)
|
139
|
-
|
140
|
-
flag = "#{cli_name} STRING1[,STRING2[,...]]"
|
141
|
-
from_option = "from_#{option_name}".to_sym
|
142
|
-
to_option = "to_#{option_name}".to_sym
|
143
|
-
parser.on("--#{flag}", Array, "#{desc} globally") do |x|
|
144
|
-
validate_option(opts[:validator], x) if opts[:validator]
|
145
|
-
translated = translate_option(opts[:translator], x)
|
146
|
-
options[to_option] ||= []
|
147
|
-
options[to_option].concat translated
|
148
|
-
options[from_option] ||= []
|
149
|
-
options[from_option].concat translated
|
150
|
-
end
|
151
|
-
parser.on("--to-#{flag}", Array, "#{desc} for the to branch") do |x|
|
152
|
-
validate_option(opts[:validator], x) if opts[:validator]
|
153
|
-
options[to_option] ||= []
|
154
|
-
options[to_option].concat translate_option(opts[:translator], x)
|
155
|
-
end
|
156
|
-
parser.on("--from-#{flag}", Array, "#{desc} for the from branch") do |x|
|
157
|
-
validate_option(opts[:validator], x) if opts[:validator]
|
158
|
-
options[from_option] ||= []
|
159
|
-
options[from_option].concat translate_option(opts[:translator], x)
|
160
|
-
end
|
161
|
-
end
|
162
|
-
|
163
|
-
# If a validator was provided, run the validator on the supplied value. The validator is expected to
|
164
|
-
# throw an error if there is a problem. Note that the validator runs *before* the translator if both
|
165
|
-
# a validator and translator are supplied.
|
166
|
-
# @param validator [Code] Validation function
|
167
|
-
# @param value [?] Value to validate (typically a String but can really be anything)
|
168
|
-
def self.validate_option(validator, value)
|
169
|
-
validator.call(value)
|
170
|
-
end
|
171
|
-
|
172
|
-
# If a translator was provided, run the translator on the supplied value. The translator is expected
|
173
|
-
# to return the data type needed for the option (typically a String but can really be anything). Note
|
174
|
-
# that the translator runs *after* the validator if both a validator and translator are supplied.
|
175
|
-
# @param translator [Code] Translator function
|
176
|
-
# @param value [?] Original input value
|
177
|
-
# @return [?] Translated value
|
178
|
-
def self.translate_option(translator, value)
|
179
|
-
return value if translator.nil?
|
180
|
-
translator.call(value)
|
181
|
-
end
|
182
|
-
end
|
183
|
-
end
|
184
|
-
end
|
185
|
-
end
|
@@ -1,54 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative '../display'
|
4
|
-
|
5
|
-
module OctocatalogDiff
|
6
|
-
module CatalogDiff
|
7
|
-
class Cli
|
8
|
-
# Wrapper around OctocatalogDiff::CatalogDiff::Display to set the options and
|
9
|
-
# output to a file or the screen depending on selection.
|
10
|
-
class Printer
|
11
|
-
# Class for thrown exceptions
|
12
|
-
class PrinterError < RuntimeError
|
13
|
-
end
|
14
|
-
|
15
|
-
# Constructor
|
16
|
-
# @param options [Hash] Options from cli/options
|
17
|
-
# @param logger [Logger] Logger object
|
18
|
-
def initialize(options, logger)
|
19
|
-
@options = options
|
20
|
-
@logger = logger
|
21
|
-
end
|
22
|
-
|
23
|
-
# The method to call externally, passing in diffs. This takes the appropriate action
|
24
|
-
# based on options, which is either to write the result into an output file, or print
|
25
|
-
# the result on STDOUT. Does not return anything.
|
26
|
-
# @param diffs [OctocatalogDiff::CatalogDiff::Differ] Difference array
|
27
|
-
# @param from_dir [String] Directory in which "from" catalog was compiled
|
28
|
-
# @param to_dir [String] Directory in which "to" catalog was compiled
|
29
|
-
def printer(diffs, from_dir = nil, to_dir = nil)
|
30
|
-
display_opts = @options.merge(compilation_from_dir: from_dir, compilation_to_dir: to_dir)
|
31
|
-
diff_text = OctocatalogDiff::CatalogDiff::Display.output(diffs, display_opts, @logger)
|
32
|
-
if @options[:output_file].nil?
|
33
|
-
puts diff_text unless diff_text.empty?
|
34
|
-
else
|
35
|
-
output_to_file(diff_text)
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
private
|
40
|
-
|
41
|
-
# Output to a file, handling errors related to writing files.
|
42
|
-
# @param diff_in [String|Array] Text to write to file
|
43
|
-
def output_to_file(diff_in)
|
44
|
-
diff_text = diff_in.is_a?(Array) ? diff_in.join("\n") : diff_in
|
45
|
-
File.open(@options[:output_file], 'w') { |f| f.write(diff_text) }
|
46
|
-
@logger.info "Wrote diff to #{@options[:output_file]}"
|
47
|
-
rescue Errno::ENOENT, Errno::EACCES, Errno::EISDIR => exc
|
48
|
-
@logger.error "Cannot write to #{@options[:output_file]}: #{exc}"
|
49
|
-
raise PrinterError, "Cannot write to #{@options[:output_file]}: #{exc}"
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
54
|
-
end
|