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