octocatalog-diff 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (143) hide show
  1. checksums.yaml +7 -0
  2. data/.version +1 -0
  3. data/LICENSE +20 -0
  4. data/README.md +82 -0
  5. data/bin/octocatalog-diff +75 -0
  6. data/doc/advanced-bootstrap.md +33 -0
  7. data/doc/advanced-cache-dir.md +24 -0
  8. data/doc/advanced-catalog-only.md +37 -0
  9. data/doc/advanced-ci.md +13 -0
  10. data/doc/advanced-dynamic-ignores.md +123 -0
  11. data/doc/advanced-future-parser.md +11 -0
  12. data/doc/advanced-ignores.md +224 -0
  13. data/doc/advanced-output-formats.md +96 -0
  14. data/doc/advanced-output-hacks.md +45 -0
  15. data/doc/advanced-override-facts.md +67 -0
  16. data/doc/advanced-pe-enc.md +52 -0
  17. data/doc/advanced-puppet-master.md +50 -0
  18. data/doc/advanced-puppet-versions.md +9 -0
  19. data/doc/advanced-storeconfigs.md +72 -0
  20. data/doc/advanced-using-without-git.md +15 -0
  21. data/doc/advanced.md +43 -0
  22. data/doc/basic.md +70 -0
  23. data/doc/configuration-enc.md +69 -0
  24. data/doc/configuration-hiera.md +103 -0
  25. data/doc/configuration-puppetdb.md +49 -0
  26. data/doc/configuration.md +51 -0
  27. data/doc/dev/README.md +1 -0
  28. data/doc/dev/coverage.md +34 -0
  29. data/doc/dev/how-to-add-options.md +83 -0
  30. data/doc/dev/integration-tests.md +63 -0
  31. data/doc/dev/releasing.md +19 -0
  32. data/doc/installation.md +49 -0
  33. data/doc/limitations.md +34 -0
  34. data/doc/optionsref.md +947 -0
  35. data/doc/requirements.md +16 -0
  36. data/doc/roadmap.md +26 -0
  37. data/doc/similar.md +17 -0
  38. data/doc/troubleshooting.md +54 -0
  39. data/lib/octocatalog-diff.rb +12 -0
  40. data/lib/octocatalog-diff/bootstrap.rb +53 -0
  41. data/lib/octocatalog-diff/catalog-diff/cli.rb +205 -0
  42. data/lib/octocatalog-diff/catalog-diff/cli/catalogs.rb +240 -0
  43. data/lib/octocatalog-diff/catalog-diff/cli/diffs.rb +145 -0
  44. data/lib/octocatalog-diff/catalog-diff/cli/helpers/fact_override.rb +99 -0
  45. data/lib/octocatalog-diff/catalog-diff/cli/options.rb +173 -0
  46. data/lib/octocatalog-diff/catalog-diff/cli/options/basedir.rb +14 -0
  47. data/lib/octocatalog-diff/catalog-diff/cli/options/bootstrap_environment.rb +18 -0
  48. data/lib/octocatalog-diff/catalog-diff/cli/options/bootstrap_script.rb +14 -0
  49. data/lib/octocatalog-diff/catalog-diff/cli/options/bootstrap_then_exit.rb +12 -0
  50. data/lib/octocatalog-diff/catalog-diff/cli/options/bootstrapped_dirs.rb +18 -0
  51. data/lib/octocatalog-diff/catalog-diff/cli/options/cached_master_dir.rb +21 -0
  52. data/lib/octocatalog-diff/catalog-diff/cli/options/catalog_only.rb +14 -0
  53. data/lib/octocatalog-diff/catalog-diff/cli/options/color.rb +13 -0
  54. data/lib/octocatalog-diff/catalog-diff/cli/options/compare_file_text.rb +15 -0
  55. data/lib/octocatalog-diff/catalog-diff/cli/options/debug.rb +12 -0
  56. data/lib/octocatalog-diff/catalog-diff/cli/options/display_datatype_changes.rb +16 -0
  57. data/lib/octocatalog-diff/catalog-diff/cli/options/display_detail_add.rb +12 -0
  58. data/lib/octocatalog-diff/catalog-diff/cli/options/display_source_file_line.rb +12 -0
  59. data/lib/octocatalog-diff/catalog-diff/cli/options/enc.rb +31 -0
  60. data/lib/octocatalog-diff/catalog-diff/cli/options/existing_catalogs.rb +25 -0
  61. data/lib/octocatalog-diff/catalog-diff/cli/options/fact_file.rb +23 -0
  62. data/lib/octocatalog-diff/catalog-diff/cli/options/fact_override.rb +19 -0
  63. data/lib/octocatalog-diff/catalog-diff/cli/options/facts_terminus.rb +16 -0
  64. data/lib/octocatalog-diff/catalog-diff/cli/options/from_puppetdb.rb +13 -0
  65. data/lib/octocatalog-diff/catalog-diff/cli/options/header.rb +24 -0
  66. data/lib/octocatalog-diff/catalog-diff/cli/options/hiera_config.rb +18 -0
  67. data/lib/octocatalog-diff/catalog-diff/cli/options/hiera_path_strip.rb +12 -0
  68. data/lib/octocatalog-diff/catalog-diff/cli/options/hostname.rb +13 -0
  69. data/lib/octocatalog-diff/catalog-diff/cli/options/ignore.rb +24 -0
  70. data/lib/octocatalog-diff/catalog-diff/cli/options/ignore_attr.rb +16 -0
  71. data/lib/octocatalog-diff/catalog-diff/cli/options/ignore_tags.rb +23 -0
  72. data/lib/octocatalog-diff/catalog-diff/cli/options/include_tags.rb +12 -0
  73. data/lib/octocatalog-diff/catalog-diff/cli/options/master_cache_branch.rb +12 -0
  74. data/lib/octocatalog-diff/catalog-diff/cli/options/output_file.rb +15 -0
  75. data/lib/octocatalog-diff/catalog-diff/cli/options/output_format.rb +15 -0
  76. data/lib/octocatalog-diff/catalog-diff/cli/options/parallel.rb +12 -0
  77. data/lib/octocatalog-diff/catalog-diff/cli/options/parser.rb +48 -0
  78. data/lib/octocatalog-diff/catalog-diff/cli/options/pass_env_vars.rb +19 -0
  79. data/lib/octocatalog-diff/catalog-diff/cli/options/pe_enc_ssl_ca.rb +15 -0
  80. data/lib/octocatalog-diff/catalog-diff/cli/options/pe_enc_ssl_client_cert.rb +14 -0
  81. data/lib/octocatalog-diff/catalog-diff/cli/options/pe_enc_ssl_client_key.rb +14 -0
  82. data/lib/octocatalog-diff/catalog-diff/cli/options/pe_enc_token.rb +15 -0
  83. data/lib/octocatalog-diff/catalog-diff/cli/options/pe_enc_token_file.rb +17 -0
  84. data/lib/octocatalog-diff/catalog-diff/cli/options/pe_enc_url.rb +19 -0
  85. data/lib/octocatalog-diff/catalog-diff/cli/options/puppet_binary.rb +16 -0
  86. data/lib/octocatalog-diff/catalog-diff/cli/options/puppet_master.rb +16 -0
  87. data/lib/octocatalog-diff/catalog-diff/cli/options/puppet_master_api_version.rb +20 -0
  88. data/lib/octocatalog-diff/catalog-diff/cli/options/puppet_master_ssl_ca.rb +19 -0
  89. data/lib/octocatalog-diff/catalog-diff/cli/options/puppet_master_ssl_client_cert.rb +19 -0
  90. data/lib/octocatalog-diff/catalog-diff/cli/options/puppet_master_ssl_client_key.rb +19 -0
  91. data/lib/octocatalog-diff/catalog-diff/cli/options/puppetdb_ssl_ca.rb +15 -0
  92. data/lib/octocatalog-diff/catalog-diff/cli/options/puppetdb_ssl_client_cert.rb +14 -0
  93. data/lib/octocatalog-diff/catalog-diff/cli/options/puppetdb_ssl_client_key.rb +14 -0
  94. data/lib/octocatalog-diff/catalog-diff/cli/options/puppetdb_ssl_client_password.rb +14 -0
  95. data/lib/octocatalog-diff/catalog-diff/cli/options/puppetdb_ssl_client_password_file.rb +13 -0
  96. data/lib/octocatalog-diff/catalog-diff/cli/options/puppetdb_url.rb +18 -0
  97. data/lib/octocatalog-diff/catalog-diff/cli/options/quiet.rb +12 -0
  98. data/lib/octocatalog-diff/catalog-diff/cli/options/retry_failed_catalog.rb +13 -0
  99. data/lib/octocatalog-diff/catalog-diff/cli/options/safe_to_delete_cached_master_dir.rb +15 -0
  100. data/lib/octocatalog-diff/catalog-diff/cli/options/storeconfigs.rb +12 -0
  101. data/lib/octocatalog-diff/catalog-diff/cli/options/suppress_absent_file_details.rb +14 -0
  102. data/lib/octocatalog-diff/catalog-diff/cli/options/to_from_branch.rb +16 -0
  103. data/lib/octocatalog-diff/catalog-diff/cli/printer.rb +52 -0
  104. data/lib/octocatalog-diff/catalog-diff/differ.rb +615 -0
  105. data/lib/octocatalog-diff/catalog-diff/display.rb +125 -0
  106. data/lib/octocatalog-diff/catalog-diff/display/json.rb +25 -0
  107. data/lib/octocatalog-diff/catalog-diff/display/text.rb +452 -0
  108. data/lib/octocatalog-diff/catalog-util/bootstrap.rb +145 -0
  109. data/lib/octocatalog-diff/catalog-util/builddir.rb +289 -0
  110. data/lib/octocatalog-diff/catalog-util/cached_master_directory.rb +169 -0
  111. data/lib/octocatalog-diff/catalog-util/command.rb +96 -0
  112. data/lib/octocatalog-diff/catalog-util/enc.rb +77 -0
  113. data/lib/octocatalog-diff/catalog-util/enc/noop.rb +22 -0
  114. data/lib/octocatalog-diff/catalog-util/enc/pe.rb +99 -0
  115. data/lib/octocatalog-diff/catalog-util/enc/pe/v1.rb +61 -0
  116. data/lib/octocatalog-diff/catalog-util/enc/script.rb +88 -0
  117. data/lib/octocatalog-diff/catalog-util/facts.rb +89 -0
  118. data/lib/octocatalog-diff/catalog-util/fileresources.rb +83 -0
  119. data/lib/octocatalog-diff/catalog-util/git.rb +65 -0
  120. data/lib/octocatalog-diff/catalog.rb +209 -0
  121. data/lib/octocatalog-diff/catalog/computed.rb +205 -0
  122. data/lib/octocatalog-diff/catalog/json.rb +30 -0
  123. data/lib/octocatalog-diff/catalog/noop.rb +19 -0
  124. data/lib/octocatalog-diff/catalog/puppetdb.rb +82 -0
  125. data/lib/octocatalog-diff/catalog/puppetmaster.rb +121 -0
  126. data/lib/octocatalog-diff/external/pson/LICENSE +17 -0
  127. data/lib/octocatalog-diff/external/pson/README.md +20 -0
  128. data/lib/octocatalog-diff/external/pson/common.rb +370 -0
  129. data/lib/octocatalog-diff/external/pson/pure.rb +15 -0
  130. data/lib/octocatalog-diff/external/pson/pure/generator.rb +395 -0
  131. data/lib/octocatalog-diff/external/pson/pure/parser.rb +307 -0
  132. data/lib/octocatalog-diff/external/pson/version.rb +8 -0
  133. data/lib/octocatalog-diff/facts.rb +125 -0
  134. data/lib/octocatalog-diff/facts/json.rb +20 -0
  135. data/lib/octocatalog-diff/facts/puppetdb.rb +59 -0
  136. data/lib/octocatalog-diff/facts/yaml.rb +29 -0
  137. data/lib/octocatalog-diff/puppetdb.rb +163 -0
  138. data/lib/octocatalog-diff/util/colored.rb +20 -0
  139. data/lib/octocatalog-diff/util/httparty.rb +158 -0
  140. data/lib/octocatalog-diff/util/parallel.rb +170 -0
  141. data/lib/octocatalog-diff/util/puppetversion.rb +24 -0
  142. data/lib/octocatalog-diff/version.rb +7 -0
  143. 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