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.
Files changed (132) hide show
  1. checksums.yaml +4 -4
  2. data/.version +1 -1
  3. data/README.md +5 -2
  4. data/bin/octocatalog-diff +9 -49
  5. data/doc/CHANGELOG.md +14 -0
  6. data/doc/advanced-filter.md +59 -1
  7. data/doc/advanced-override-enc.md +54 -0
  8. data/doc/advanced-pe-enc.md +1 -1
  9. data/doc/advanced.md +2 -1
  10. data/doc/dev/api.md +5 -0
  11. data/doc/dev/api/v1.md +41 -0
  12. data/doc/dev/api/v1/calls/catalog-diff.md +209 -0
  13. data/doc/dev/api/v1/calls/catalog.md +115 -0
  14. data/doc/dev/api/v1/calls/config.md +37 -0
  15. data/doc/dev/api/v1/objects/catalog.md +127 -0
  16. data/doc/dev/api/v1/objects/diff.md +261 -0
  17. data/doc/dev/api/v1/objects/override.md +30 -0
  18. data/doc/dev/how-to-add-options.md +12 -12
  19. data/doc/optionsref.md +91 -74
  20. data/doc/versions/v1.md +22 -0
  21. data/lib/octocatalog-diff.rb +1 -8
  22. data/lib/octocatalog-diff/api/v1.rb +27 -0
  23. data/lib/octocatalog-diff/api/v1/catalog-compile.rb +40 -0
  24. data/lib/octocatalog-diff/api/v1/catalog-diff.rb +68 -0
  25. data/lib/octocatalog-diff/api/v1/catalog.rb +84 -0
  26. data/lib/octocatalog-diff/api/v1/common.rb +24 -0
  27. data/lib/octocatalog-diff/api/v1/config.rb +125 -0
  28. data/lib/octocatalog-diff/api/v1/diff.rb +194 -0
  29. data/lib/octocatalog-diff/api/v1/override.rb +103 -0
  30. data/lib/octocatalog-diff/catalog-diff/differ.rb +66 -47
  31. data/lib/octocatalog-diff/catalog-diff/display.rb +8 -2
  32. data/lib/octocatalog-diff/catalog-diff/display/json.rb +3 -2
  33. data/lib/octocatalog-diff/catalog-diff/display/legacy_json.rb +28 -0
  34. data/lib/octocatalog-diff/catalog-diff/display/text.rb +64 -9
  35. data/lib/octocatalog-diff/catalog-diff/filter.rb +45 -6
  36. data/lib/octocatalog-diff/catalog-diff/filter/absent_file.rb +65 -0
  37. data/lib/octocatalog-diff/catalog-diff/filter/compilation_dir.rb +78 -0
  38. data/lib/octocatalog-diff/catalog-diff/filter/yaml.rb +10 -7
  39. data/lib/octocatalog-diff/catalog-util/bootstrap.rb +13 -14
  40. data/lib/octocatalog-diff/catalog-util/builddir.rb +1 -0
  41. data/lib/octocatalog-diff/catalog-util/cached_master_directory.rb +2 -2
  42. data/lib/octocatalog-diff/catalog-util/enc.rb +49 -14
  43. data/lib/octocatalog-diff/catalog-util/enc/pe.rb +3 -5
  44. data/lib/octocatalog-diff/catalog-util/enc/pe/v1.rb +3 -1
  45. data/lib/octocatalog-diff/catalog-util/git.rb +36 -24
  46. data/lib/octocatalog-diff/catalog.rb +5 -9
  47. data/lib/octocatalog-diff/catalog/computed.rb +9 -1
  48. data/lib/octocatalog-diff/catalog/puppetdb.rb +4 -3
  49. data/lib/octocatalog-diff/cli.rb +195 -0
  50. data/lib/octocatalog-diff/cli/diffs.rb +40 -0
  51. data/lib/octocatalog-diff/cli/options.rb +183 -0
  52. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/basedir.rb +1 -1
  53. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/bootstrap_current.rb +1 -1
  54. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/bootstrap_environment.rb +1 -1
  55. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/bootstrap_script.rb +1 -1
  56. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/bootstrap_then_exit.rb +1 -1
  57. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/bootstrapped_dirs.rb +1 -1
  58. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/cached_master_dir.rb +1 -1
  59. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/catalog_only.rb +1 -1
  60. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/color.rb +1 -1
  61. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/command_line.rb +2 -2
  62. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/compare_file_text.rb +1 -1
  63. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/create_symlinks.rb +2 -2
  64. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/debug.rb +1 -1
  65. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/debug_bootstrap.rb +1 -1
  66. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/display_datatype_changes.rb +1 -1
  67. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/display_detail_add.rb +1 -1
  68. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/display_source_file_line.rb +1 -1
  69. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/enc.rb +1 -1
  70. data/lib/octocatalog-diff/cli/options/enc_override.rb +21 -0
  71. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/environment.rb +2 -2
  72. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/existing_catalogs.rb +1 -1
  73. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/fact_file.rb +1 -1
  74. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/fact_override.rb +2 -2
  75. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/facts_terminus.rb +1 -1
  76. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/filters.rb +5 -2
  77. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/from_puppetdb.rb +1 -1
  78. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/header.rb +1 -1
  79. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/hiera_config.rb +1 -1
  80. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/hiera_path.rb +1 -1
  81. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/hiera_path_strip.rb +1 -1
  82. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/hostname.rb +1 -1
  83. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/ignore.rb +1 -1
  84. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/ignore_attr.rb +1 -1
  85. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/ignore_tags.rb +1 -1
  86. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/include_tags.rb +1 -1
  87. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/master_cache_branch.rb +1 -1
  88. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/output_file.rb +1 -1
  89. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/output_format.rb +5 -3
  90. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/parallel.rb +1 -1
  91. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/parser.rb +1 -1
  92. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/pass_env_vars.rb +1 -1
  93. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/pe_enc_ssl_ca.rb +1 -1
  94. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/pe_enc_ssl_client_cert.rb +1 -1
  95. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/pe_enc_ssl_client_key.rb +1 -1
  96. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/pe_enc_token.rb +1 -1
  97. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/pe_enc_token_file.rb +1 -1
  98. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/pe_enc_url.rb +1 -1
  99. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/preserve_environments.rb +1 -1
  100. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/puppet_binary.rb +2 -2
  101. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/puppet_master.rb +2 -2
  102. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/puppet_master_api_version.rb +2 -2
  103. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/puppet_master_ssl_ca.rb +2 -2
  104. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/puppet_master_ssl_client_cert.rb +2 -2
  105. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/puppet_master_ssl_client_key.rb +2 -2
  106. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/puppetdb_api_version.rb +1 -1
  107. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/puppetdb_ssl_ca.rb +1 -1
  108. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/puppetdb_ssl_client_cert.rb +1 -1
  109. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/puppetdb_ssl_client_key.rb +1 -1
  110. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/puppetdb_ssl_client_password.rb +1 -1
  111. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/puppetdb_ssl_client_password_file.rb +1 -1
  112. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/puppetdb_url.rb +1 -1
  113. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/quiet.rb +1 -1
  114. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/retry_failed_catalog.rb +1 -1
  115. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/safe_to_delete_cached_master_dir.rb +1 -1
  116. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/storeconfigs.rb +1 -1
  117. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/suppress_absent_file_details.rb +2 -1
  118. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/to_from_branch.rb +1 -1
  119. data/lib/octocatalog-diff/{catalog-diff/cli → cli}/options/validate_references.rb +1 -1
  120. data/lib/octocatalog-diff/cli/printer.rb +52 -0
  121. data/lib/octocatalog-diff/errors.rb +33 -0
  122. data/lib/octocatalog-diff/facts.rb +1 -4
  123. data/lib/octocatalog-diff/facts/puppetdb.rb +8 -7
  124. data/lib/octocatalog-diff/puppetdb.rb +5 -9
  125. data/lib/octocatalog-diff/util/catalogs.rb +242 -0
  126. metadata +97 -75
  127. data/lib/octocatalog-diff/catalog-diff/cli.rb +0 -211
  128. data/lib/octocatalog-diff/catalog-diff/cli/catalogs.rb +0 -246
  129. data/lib/octocatalog-diff/catalog-diff/cli/diffs.rb +0 -147
  130. data/lib/octocatalog-diff/catalog-diff/cli/helpers/fact_override.rb +0 -100
  131. data/lib/octocatalog-diff/catalog-diff/cli/options.rb +0 -185
  132. 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