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
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative 'pe/v1'
4
4
  require_relative '../../util/httparty'
5
+ require_relative '../../errors'
5
6
  require_relative '../facts'
6
7
 
7
8
  module OctocatalogDiff
@@ -10,9 +11,6 @@ module OctocatalogDiff
10
11
  # Support the Puppet Enterprise classification API.
11
12
  # Documentation: https://docs.puppet.com/pe/latest/nc_index.html
12
13
  class PE
13
- # Error class that can be caught
14
- class ClassificationError < RuntimeError; end
15
-
16
14
  # Allow the main ENC object to retrieve these values
17
15
  attr_reader :content, :error_message
18
16
 
@@ -69,7 +67,7 @@ module OctocatalogDiff
69
67
  begin
70
68
  @content = @api.result(response[:parsed], logger)
71
69
  @error_message = nil
72
- rescue OctocatalogDiff::CatalogUtil::ENC::PE::ClassificationError => exc
70
+ rescue OctocatalogDiff::Errors::PEClassificationError => exc
73
71
  @error_message = exc.message
74
72
  logger.error "PE ENC failed: #{exc.message}"
75
73
  return
@@ -87,7 +85,7 @@ module OctocatalogDiff
87
85
  begin
88
86
  result = facts_obj.facts
89
87
  logger.debug "Success retrieving facts for #{@node} from #{self.class}"
90
- rescue OctocatalogDiff::Facts::FactRetrievalError, OctocatalogDiff::Facts::FactSourceError => exc
88
+ rescue OctocatalogDiff::Errors::FactRetrievalError, OctocatalogDiff::Errors::FactSourceError => exc
91
89
  @content = nil
92
90
  @error_message = "Fact retrieval failed: #{exc.class} - #{exc.message}"
93
91
  logger.error @error_message
@@ -4,6 +4,8 @@ require 'json'
4
4
  require 'uri'
5
5
  require 'yaml'
6
6
 
7
+ require_relative '../../../errors'
8
+
7
9
  module OctocatalogDiff
8
10
  module CatalogUtil
9
11
  class ENC
@@ -50,7 +52,7 @@ module OctocatalogDiff
50
52
  %w(classes parameters).each do |required_key|
51
53
  next if parsed[required_key]
52
54
  logger.debug parsed.keys.inspect
53
- raise OctocatalogDiff::CatalogUtil::ENC::PE::ClassificationError, "Response missing: #{required_key}"
55
+ raise OctocatalogDiff::Errors::PEClassificationError, "Response missing: #{required_key}"
54
56
  end
55
57
 
56
58
  obj = { 'classes' => parsed['classes'], 'parameters' => parsed['parameters'] }
@@ -6,15 +6,13 @@ require 'rugged'
6
6
  require 'shellwords'
7
7
  require 'tempfile'
8
8
 
9
+ require_relative '../errors'
10
+
9
11
  module OctocatalogDiff
10
12
  module CatalogUtil
11
13
  # Class to perform a git checkout (via 'git archive') of a branch from the base git
12
14
  # directory into another targeted directory.
13
15
  class Git
14
- # Trapped errors
15
- class GitCheckoutError < RuntimeError
16
- end
17
-
18
16
  # Check out a branch via 'git archive' from one directory into another.
19
17
  # @param options [Hash] Options hash:
20
18
  # - :branch => Branch name to check out
@@ -28,27 +26,39 @@ module OctocatalogDiff
28
26
  logger = options.fetch(:logger)
29
27
 
30
28
  # Validate parameters
31
- raise GitCheckoutError, "Source directory #{dir.inspect} does not exist" if dir.nil? || !File.directory?(dir)
32
- raise GitCheckoutError, "Target directory #{path.inspect} does not exist" if dir.nil? || !File.directory?(path)
33
-
34
- # To get the options working correctly (-o pipefail in particular) this needs to run under
35
- # bash. It's just creating a script, rather than figuring out all the shell escapes...
36
- begin
37
- tmp_script = Tempfile.new(['git-checkout', '.sh'])
38
- tmp_script.write "#!/bin/bash\n"
39
- tmp_script.write "set -euf -o pipefail\n"
40
- tmp_script.write "git archive --format=tar #{Shellwords.escape(branch)} | \\\n"
41
- tmp_script.write " ( cd #{Shellwords.escape(path)} && tar -xf - )\n"
42
- tmp_script.close
43
- FileUtils.chmod 0o755, tmp_script.path
29
+ if dir.nil? || !File.directory?(dir)
30
+ raise OctocatalogDiff::Errors::GitCheckoutError, "Source directory #{dir.inspect} does not exist"
31
+ end
32
+ if path.nil? || !File.directory?(path)
33
+ raise OctocatalogDiff::Errors::GitCheckoutError, "Target directory #{path.inspect} does not exist"
34
+ end
44
35
 
45
- logger.debug("Begin git archive #{dir}:#{branch} -> #{path}")
46
- output, status = Open3.capture2e(tmp_script.path, chdir: dir)
47
- raise GitCheckoutError, "Git archive #{branch}->#{path} failed: #{output}" unless status.exitstatus.zero?
48
- logger.debug("Success git archive #{dir}:#{branch}")
49
- ensure
50
- FileUtils.rm_f tmp_script.path if File.exist?(tmp_script.path)
36
+ # Create and execute checkout script
37
+ script = create_git_checkout_script(branch, path)
38
+ logger.debug("Begin git archive #{dir}:#{branch} -> #{path}")
39
+ output, status = Open3.capture2e(script, chdir: dir)
40
+ unless status.exitstatus.zero?
41
+ raise OctocatalogDiff::Errors::GitCheckoutError, "Git archive #{branch}->#{path} failed: #{output}"
51
42
  end
43
+ logger.debug("Success git archive #{dir}:#{branch}")
44
+ end
45
+
46
+ # Create the temporary file used to interact with the git command line.
47
+ # To get the options working correctly (-o pipefail in particular) this needs to run under
48
+ # bash. It's just creating a script, rather than figuring out all the shell escapes...
49
+ # @param branch [String] Branch name
50
+ # @param path [String] Target directory
51
+ # @return [String] Name of script
52
+ def self.create_git_checkout_script(branch, path)
53
+ tmp_script = Tempfile.new(['git-checkout', '.sh'])
54
+ tmp_script.write "#!/bin/bash\n"
55
+ tmp_script.write "set -euf -o pipefail\n"
56
+ tmp_script.write "git archive --format=tar #{Shellwords.escape(branch)} | \\\n"
57
+ tmp_script.write " ( cd #{Shellwords.escape(path)} && tar -xf - )\n"
58
+ tmp_script.close
59
+ FileUtils.chmod 0o755, tmp_script.path
60
+ at_exit { FileUtils.rm_f tmp_script.path if File.exist?(tmp_script.path) }
61
+ tmp_script.path
52
62
  end
53
63
 
54
64
  # Determine the SHA of origin/master (or any other branch really) in the git repo
@@ -58,7 +68,9 @@ module OctocatalogDiff
58
68
  def self.branch_sha(options = {})
59
69
  branch = options.fetch(:branch)
60
70
  dir = options.fetch(:basedir)
61
- raise GitCheckoutError, "Git directory #{dir.inspect} does not exist" if dir.nil? || !File.directory?(dir)
71
+ if dir.nil? || !File.directory?(dir)
72
+ raise Errno::ENOENT, "Git directory #{dir.inspect} does not exist"
73
+ end
62
74
  repo = Rugged::Repository.new(dir)
63
75
  repo.branches[branch].target_id
64
76
  end
@@ -9,6 +9,7 @@ require_relative 'catalog/noop'
9
9
  require_relative 'catalog/puppetdb'
10
10
  require_relative 'catalog/puppetmaster'
11
11
  require_relative 'catalog-util/fileresources'
12
+ require_relative 'errors'
12
13
 
13
14
  module OctocatalogDiff
14
15
  # This class represents a catalog. Generation of the catalog is handled via one of the
@@ -18,11 +19,6 @@ module OctocatalogDiff
18
19
  # Readable
19
20
  attr_reader :built, :catalog, :catalog_json
20
21
 
21
- # Error classes that we can throw
22
- class PuppetVersionError < RuntimeError; end
23
- class CatalogError < RuntimeError; end
24
- class ReferenceValidationError < RuntimeError; end
25
-
26
22
  # Constructor
27
23
  # @param :backend [Symbol] If set, this will force a backend
28
24
  # @param :json [String] JSON catalog content (will avoid running Puppet to compile catalog)
@@ -159,8 +155,8 @@ module OctocatalogDiff
159
155
  # This is a compatibility layer for the resources, which are in a different place in Puppet 3.x and Puppet 4.x
160
156
  # @return [Array] Resource array
161
157
  def resources
162
- raise CatalogError, 'Catalog does not appear to have been built' if !valid? && error_message.nil?
163
- raise CatalogError, error_message unless valid?
158
+ raise OctocatalogDiff::Errors::CatalogError, 'Catalog does not appear to have been built' if !valid? && error_message.nil?
159
+ raise OctocatalogDiff::Errors::CatalogError, error_message unless valid?
164
160
  return @catalog['data']['resources'] if @catalog['data'].is_a?(Hash) && @catalog['data']['resources'].is_a?(Array)
165
161
  return @catalog['resources'] if @catalog['resources'].is_a?(Array)
166
162
  # This is a bug condition
@@ -183,7 +179,7 @@ module OctocatalogDiff
183
179
  end
184
180
 
185
181
  # Determine if all of the (before, notify, require, subscribe) targets are actually in the catalog.
186
- # Raise a ReferenceValidationError for any found to be missing.
182
+ # Raise a OctocatalogDiff::Errors::ReferenceValidationError for any found to be missing.
187
183
  # Uses @options[:validate_references] to influence which references are checked.
188
184
  def validate_references
189
185
  # Skip out early if no reference validation has been requested.
@@ -223,7 +219,7 @@ module OctocatalogDiff
223
219
  formatted_references.flatten!
224
220
  plural = formatted_references.size == 1 ? '' : 's'
225
221
  errors = formatted_references.join('; ')
226
- raise ReferenceValidationError, "Catalog has broken reference#{plural}: #{errors}"
222
+ raise OctocatalogDiff::Errors::ReferenceValidationError, "Catalog has broken reference#{plural}: #{errors}"
227
223
  end
228
224
 
229
225
  private
@@ -89,7 +89,15 @@ module OctocatalogDiff
89
89
  def cleanup_checkout_dir(checkout_dir, logger)
90
90
  return unless File.directory?(checkout_dir)
91
91
  logger.debug("Cleaning up temporary directory #{checkout_dir}")
92
- FileUtils.remove_entry_secure checkout_dir
92
+ # Sometimes this seems to break when handling the recursive removal when running under
93
+ # a parallel environment. Trap and ignore the errors here if we don't care about them.
94
+ begin
95
+ FileUtils.remove_entry_secure checkout_dir
96
+ # :nocov:
97
+ rescue Errno::ENOTEMPTY, Errno::ENOENT => exc
98
+ logger.debug "cleanup_checkout_dir(#{checkout_dir}) logged #{exc.class} - this can be ignored"
99
+ # :nocov:
100
+ end
93
101
  end
94
102
 
95
103
  # Private method: Bootstrap a directory
@@ -3,6 +3,7 @@
3
3
  require 'json'
4
4
  require 'stringio'
5
5
 
6
+ require_relative '../errors'
6
7
  require_relative '../puppetdb'
7
8
 
8
9
  module OctocatalogDiff
@@ -67,11 +68,11 @@ module OctocatalogDiff
67
68
  # Set the other variables
68
69
  @catalog_json = ::JSON.generate(@catalog)
69
70
  @error_message = nil
70
- rescue OctocatalogDiff::PuppetDB::ConnectionError => exc
71
+ rescue OctocatalogDiff::Errors::PuppetDBConnectionError => exc
71
72
  @error_message = "Catalog retrieval failed (#{exc.class}) (#{exc.message})"
72
- rescue OctocatalogDiff::PuppetDB::NotFoundError => exc
73
+ rescue OctocatalogDiff::Errors::PuppetDBNodeNotFoundError => exc
73
74
  @error_message = "Node #{node} not found in PuppetDB (#{exc.message})"
74
- rescue OctocatalogDiff::PuppetDB::PuppetDBError => exc
75
+ rescue OctocatalogDiff::Errors::PuppetDBGenericError => exc
75
76
  @error_message = "Catalog retrieval failed for node #{node} from PuppetDB (#{exc.message})"
76
77
  rescue ::JSON::GeneratorError => exc
77
78
  @error_message = "Failed to generate result from PuppetDB as JSON (#{exc.message})"
@@ -0,0 +1,195 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'api/v1'
4
+ require_relative 'catalog-util/cached_master_directory'
5
+ require_relative 'cli/diffs'
6
+ require_relative 'cli/options'
7
+ require_relative 'cli/printer'
8
+ require_relative 'errors'
9
+ require_relative 'util/catalogs'
10
+ require_relative 'version'
11
+
12
+ require 'logger'
13
+ require 'socket'
14
+
15
+ module OctocatalogDiff
16
+ # This is the CLI for catalog-diff. It's responsible for parsing the command line
17
+ # arguments and then handing off to appropriate methods to perform the catalog-diff.
18
+ class Cli
19
+ # Version number
20
+ VERSION = OctocatalogDiff::Version::VERSION
21
+
22
+ # Exit codes
23
+ EXITCODE_SUCCESS_NO_DIFFS = 0
24
+ EXITCODE_FAILURE = 1
25
+ EXITCODE_SUCCESS_WITH_DIFFS = 2
26
+
27
+ # The default type+title+attribute to ignore in catalog-diff.
28
+ DEFAULT_IGNORES = [
29
+ { type: 'Class' } # Don't care about classes themselves, only what they actually do!
30
+ ].freeze
31
+
32
+ # The default options.
33
+ DEFAULT_OPTIONS = {
34
+ from_env: 'origin/master',
35
+ to_env: '.',
36
+ colors: true,
37
+ debug: false,
38
+ quiet: false,
39
+ format: :color_text,
40
+ display_source_file_line: false,
41
+ compare_file_text: true,
42
+ display_datatype_changes: true,
43
+ parallel: true,
44
+ suppress_absent_file_details: true,
45
+ hiera_path: 'hieradata'
46
+ }.freeze
47
+
48
+ # This method is the one to call externally. It is possible to specify alternate
49
+ # command line arguments, for testing.
50
+ # @param argv [Array] Use specified arguments (defaults to ARGV)
51
+ # @param logger [Logger] Logger object
52
+ # @param opts [Hash] Additional options
53
+ # @return [Fixnum] Exit code: 0=no diffs, 1=something went wrong, 2=worked but there are diffs
54
+ def self.cli(argv = ARGV, logger = Logger.new(STDERR), opts = {})
55
+ # Save a copy of argv to print out later in debugging
56
+ argv_save = argv.dup
57
+
58
+ # Are there additional ARGV to munge, e.g. that have been supplied in the options from a
59
+ # configuration file?
60
+ if opts.key?(:additional_argv)
61
+ raise ArgumentError, ':additional_argv must be array!' unless opts[:additional_argv].is_a?(Array)
62
+ argv.concat opts[:additional_argv]
63
+ end
64
+
65
+ # Parse command line
66
+ options = parse_opts(argv)
67
+
68
+ # Additional options from hard-coded specified options. These are only processed if
69
+ # there are not already values defined from command line options.
70
+ # Note: do NOT use 'options[k] ||= v' here because if the value of options[k] is boolean(false)
71
+ # it will then be overridden. Whereas the intent is to define values only for those keys that don't exist.
72
+ opts.each { |k, v| options[k] = v unless options.key?(k) }
73
+ veto_options = %w(enc header hiera_config include_tags)
74
+ veto_options.each { |x| options.delete(x.to_sym) if options["no_#{x}".to_sym] }
75
+ options[:ignore].concat opts.fetch(:additional_ignores, [])
76
+
77
+ # Incorporate default options where needed.
78
+ # Note: do NOT use 'options[k] ||= v' here because if the value of options[k] is boolean(false)
79
+ # it will then be overridden. Whereas the intent is to define values only for those keys that don't exist.
80
+ DEFAULT_OPTIONS.each { |k, v| options[k] = v unless options.key?(k) }
81
+ veto_with_none_options = %w(hiera_path hiera_path_strip)
82
+ veto_with_none_options.each { |x| options.delete(x.to_sym) if options[x.to_sym] == :none }
83
+
84
+ # Fact and ENC overrides come in here - 'options' is modified
85
+ setup_fact_overrides(options)
86
+ setup_enc_overrides(options)
87
+
88
+ # Configure the logger and logger.debug initial information
89
+ # 'logger' is modified and used
90
+ setup_logger(logger, options, argv_save)
91
+
92
+ # --catalog-only is a special case that compiles the catalog for the "to" branch
93
+ # and then exits, without doing any 'diff' whatsoever. Support that option.
94
+ return catalog_only(logger, options) if options[:catalog_only]
95
+
96
+ # Set up the cached master directory - maintain it, adjust options if needed. However, if we
97
+ # are getting the 'from' catalog from PuppetDB, then don't do this.
98
+ unless options[:cached_master_dir].nil? || options[:from_puppetdb]
99
+ OctocatalogDiff::CatalogUtil::CachedMasterDirectory.run(options, logger)
100
+ end
101
+
102
+ # bootstrap_then_exit is a special case that only prepares directories and does not
103
+ # depend on facts. This happens within the 'catalogs' object, since bootstrapping and
104
+ # preparing catalogs are tightly coupled operations. However this does not actually
105
+ # build catalogs.
106
+ if options[:bootstrap_then_exit]
107
+ catalogs_obj = OctocatalogDiff::Util::Catalogs.new(options, logger)
108
+ return bootstrap_then_exit(logger, catalogs_obj)
109
+ end
110
+
111
+ # Compile catalogs and do catalog-diff
112
+ catalog_diff = OctocatalogDiff::API::V1.catalog_diff(options.merge(logger: logger))
113
+ diffs = catalog_diff.diffs
114
+
115
+ # Display diffs
116
+ printer_obj = OctocatalogDiff::Cli::Printer.new(options, logger)
117
+ printer_obj.printer(diffs, catalog_diff.from.compilation_dir, catalog_diff.to.compilation_dir)
118
+
119
+ # Return the resulting diff object if requested (generally for testing) or otherwise return exit code
120
+ return catalog_diff if opts[:INTEGRATION]
121
+ diffs.any? ? EXITCODE_SUCCESS_WITH_DIFFS : EXITCODE_SUCCESS_NO_DIFFS
122
+ end
123
+
124
+ # Parse command line options with 'optparse'. Returns a hash with the parsed arguments.
125
+ # @param argv [Array] Command line arguments (MUST be specified)
126
+ # @return [Hash] Options
127
+ def self.parse_opts(argv)
128
+ options = { ignore: DEFAULT_IGNORES.dup }
129
+ Options.parse_options(argv, options)
130
+ end
131
+
132
+ # Generic overrides
133
+ def self.setup_overrides(key, options)
134
+ o = options["#{key}_in".to_sym]
135
+ return unless o.is_a?(Array)
136
+ return unless o.any?
137
+ options[key] ||= []
138
+ options[key].concat o.map { |x| OctocatalogDiff::API::V1::Override.create_from_input(x) }
139
+ end
140
+
141
+ # Fact overrides come in here
142
+ def self.setup_fact_overrides(options)
143
+ setup_overrides(:from_fact_override, options)
144
+ setup_overrides(:to_fact_override, options)
145
+ end
146
+
147
+ # ENC parameter overrides come in here
148
+ def self.setup_enc_overrides(options)
149
+ setup_overrides(:from_enc_override, options)
150
+ setup_overrides(:to_enc_override, options)
151
+ end
152
+
153
+ # Helper method: Configure and setup logger
154
+ def self.setup_logger(logger, options, argv_save)
155
+ # Configure the logger
156
+ logger.level = Logger::INFO
157
+ logger.level = Logger::DEBUG if options[:debug]
158
+ logger.level = Logger::ERROR if options[:quiet]
159
+
160
+ # Some debugging information up front
161
+ version_display = ENV['OCTOCATALOG_DIFF_CUSTOM_VERSION'] || VERSION
162
+ logger.debug "Running octocatalog-diff #{version_display} with ruby #{RUBY_VERSION}"
163
+ logger.debug "Command line arguments: #{argv_save.inspect}"
164
+ logger.debug "Running on host #{Socket.gethostname} (#{RUBY_PLATFORM})"
165
+ end
166
+
167
+ # Compile the catalog only
168
+ def self.catalog_only(logger, options)
169
+ opts = options.merge(logger: logger)
170
+ to_catalog = OctocatalogDiff::API::V1.catalog(opts)
171
+
172
+ # If the catalog compilation failed, an exception would have been thrown. So if
173
+ # we get here, the catalog succeeded. Dump the catalog to the appropriate place
174
+ # and exit successfully.
175
+ if options[:output_file]
176
+ File.open(options[:output_file], 'w') { |f| f.write(to_catalog.to_json) }
177
+ logger.info "Wrote catalog to #{options[:output_file]}"
178
+ else
179
+ puts to_catalog.to_json
180
+ end
181
+
182
+ return { exitcode: EXITCODE_SUCCESS_NO_DIFFS, to: to_catalog } if options[:INTEGRATION] # For integration testing
183
+ EXITCODE_SUCCESS_NO_DIFFS
184
+ end
185
+
186
+ # --bootstrap-then-exit command
187
+ def self.bootstrap_then_exit(logger, catalogs_obj)
188
+ catalogs_obj.bootstrap_then_exit
189
+ return EXITCODE_SUCCESS_NO_DIFFS
190
+ rescue OctocatalogDiff::Errors::BootstrapError => exc
191
+ logger.fatal("--bootstrap-then-exit error: bootstrap failed (#{exc})")
192
+ return EXITCODE_FAILURE
193
+ end
194
+ end
195
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../catalog-diff/differ'
4
+
5
+ module OctocatalogDiff
6
+ class Cli
7
+ # Wrapper around OctocatalogDiff::CatalogDiff::Differ to provide the logger object, set up
8
+ # ignores, and add additional ignores for items dependent upon the compilation directory.
9
+ class Diffs
10
+ # Constructor
11
+ # @param options [Hash] Options from cli/options
12
+ # @param logger [Logger] Logger object
13
+ def initialize(options, logger)
14
+ @options = options
15
+ @logger = logger
16
+ end
17
+
18
+ # The method to call externally, passing in the catalogs as a hash (see parameter). This
19
+ # sets up options and ignores and then actually performs the diffs. The result is the array
20
+ # of diffs.
21
+ # @param catalogs [Hash] { :to => OctocatalogDiff::Catalog, :from => OctocatalogDiff::Catalog }
22
+ # @return [Array<diffs>] Array of diffs
23
+ def diffs(catalogs)
24
+ @logger.debug 'Begin compute diffs between catalogs'
25
+ diff_opts = @options.merge(logger: @logger)
26
+
27
+ # Construct the actual differ object that the present one wraps
28
+ differ = OctocatalogDiff::CatalogDiff::Differ.new(diff_opts, catalogs[:from], catalogs[:to])
29
+ differ.ignore(attr: 'tags') unless @options.fetch(:include_tags, false)
30
+ differ.ignore(@options.fetch(:ignore, []))
31
+ differ.ignore_tags
32
+
33
+ # Actually perform the diff
34
+ diff_result = differ.diff
35
+ @logger.debug 'Success compute diffs between catalogs'
36
+ diff_result
37
+ end
38
+ end
39
+ end
40
+ end