octocatalog-diff 0.5.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|