octocatalog-diff 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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,96 @@
1
+ require 'fileutils'
2
+ require 'open3'
3
+ require 'shellwords'
4
+
5
+ module OctocatalogDiff
6
+ module CatalogUtil
7
+ # Used to construct the command to run 'puppet' to construct the catalog.
8
+ class Command
9
+ # Constructor
10
+ def initialize(options = {}, logger = nil)
11
+ @options = options
12
+ @logger = logger
13
+
14
+ # Required parameters
15
+ @compilation_dir = options[:compilation_dir]
16
+ raise ArgumentError, 'Compile dir (:compilation_dir) must be a string' unless @compilation_dir.is_a?(String)
17
+ raise Errno::ENOENT, "Compile dir #{@compilation_dir} doesn't exist" unless File.exist?(@compilation_dir)
18
+ raise ArgumentError, "Compile dir #{@compilation_dir} not a directory" unless File.directory?(@compilation_dir)
19
+
20
+ @node = options[:node]
21
+ raise ArgumentError, 'Node must be specified to compile catalog' if @node.nil? || !@node.is_a?(String)
22
+ end
23
+
24
+ # Build up the command line to run Puppet
25
+ def puppet_command
26
+ cmdline = []
27
+
28
+ # Where is the puppet binary?
29
+ puppet = @options[:puppet_binary]
30
+ raise ArgumentError, 'Puppet binary was not supplied' if puppet.nil?
31
+ raise Errno::ENOENT, "Puppet binary #{puppet} doesn't exist" unless File.file?(puppet)
32
+ cmdline << puppet
33
+
34
+ # Node to compile
35
+ cmdline.concat ['master', '--compile', Shellwords.escape(@node)]
36
+
37
+ # storeconfigs?
38
+ if @options[:storeconfigs]
39
+ cmdline.concat %w(--storeconfigs --storeconfigs_backend=puppetdb)
40
+ else
41
+ cmdline << '--no-storeconfigs'
42
+ end
43
+
44
+ # enc?
45
+ if @options[:enc]
46
+ raise Errno::ENOENT, "Did not find ENC as expected at #{@options[:enc]}" unless File.file?(@options[:enc])
47
+ cmdline << "--node_terminus=exec --external_nodes=#{Shellwords.escape(@options[:enc])}"
48
+ end
49
+
50
+ # Future parser?
51
+ cmdline << '--parser=future' if @options[:parser] == :future
52
+
53
+ # Path to facts, or a specific fact file?
54
+ facts_terminus = @options.fetch(:facts_terminus, 'yaml')
55
+ if facts_terminus == 'yaml'
56
+ cmdline << "--factpath=#{Shellwords.escape(File.join(@compilation_dir, 'var', 'yaml', 'facts'))}"
57
+ if @options[:fact_file].is_a?(String) && @options[:fact_file] =~ /.*\.(\w+)$/
58
+ fact_file = File.join(@compilation_dir, 'var', 'yaml', 'facts', "#{@node}.#{Regexp.last_match(1)}")
59
+ FileUtils.cp @options[:fact_file], fact_file unless File.file?(fact_file) || @options[:fact_file] == fact_file
60
+ end
61
+ cmdline << '--facts_terminus=yaml'
62
+ elsif facts_terminus == 'facter'
63
+ cmdline << '--facts_terminus=facter'
64
+ else
65
+ raise ArgumentError, "Unrecognized facts_terminus setting: '#{facts_terminus}'"
66
+ end
67
+
68
+ # Some typical options for puppet
69
+ cmdline.concat %w(
70
+ --no-daemonize
71
+ --no-ca
72
+ --color=false
73
+ --config_version="/bin/echo catalogscript"
74
+ --environment=production
75
+ )
76
+
77
+ # For people who aren't running hiera, a hiera-config will not be generated when @options[:hiera_config]
78
+ # is nil. For everyone else, the hiera config was generated/copied/munged in the 'builddir' class
79
+ # and was installed into the compile directory and named hiera.yaml.
80
+ unless @options[:hiera_config].nil?
81
+ cmdline << "--hiera_config=#{Shellwords.escape(File.join(@compilation_dir, 'hiera.yaml'))}"
82
+ end
83
+
84
+ # Options with parameters
85
+ cmdline << "--environmentpath=#{Shellwords.escape(File.join(@compilation_dir, 'environments'))}"
86
+ cmdline << "--vardir=#{Shellwords.escape(File.join(@compilation_dir, 'var'))}"
87
+ cmdline << "--logdir=#{Shellwords.escape(File.join(@compilation_dir, 'var'))}"
88
+ cmdline << "--ssldir=#{Shellwords.escape(File.join(@compilation_dir, 'var', 'ssl'))}"
89
+ cmdline << "--confdir=#{Shellwords.escape(@compilation_dir)}"
90
+
91
+ # Return full command
92
+ cmdline.join(' ')
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,77 @@
1
+ require_relative 'enc/noop'
2
+ require_relative 'enc/pe'
3
+ require_relative 'enc/script'
4
+
5
+ require 'stringio'
6
+
7
+ module OctocatalogDiff
8
+ module CatalogUtil
9
+ # Support a generic ENC. It must use one of the supported backends found in the
10
+ # 'enc' subdirectory.
11
+ class ENC
12
+ attr_reader :builder
13
+
14
+ # Constructor
15
+ # @param :backend [Symbol] If set, this will force a backend
16
+ # @param :enc [String] Path to ENC script (node_terminus = exec)
17
+ # @param # FIXME: Add support for PE's ENC endpoint API
18
+ def initialize(options = {})
19
+ @options = options
20
+
21
+ # Determine appropriate backend based on options supplied
22
+ @enc_obj = backend
23
+
24
+ # Initialize instance variables for content and error message.
25
+ @builder = @enc_obj.class.to_s
26
+
27
+ # Set the executed flag to false, so that it can be executed when something is retrieved.
28
+ @executed = false
29
+ end
30
+
31
+ # Retrieve content
32
+ # @return [String] ENC content, or nil if there was an error
33
+ def content
34
+ execute
35
+ @content ||= @enc_obj.content
36
+ end
37
+
38
+ # Retrieve error message
39
+ # @return [String] Error message, or nil if there was no error
40
+ def error_message
41
+ execute
42
+ @error_message ||= @enc_obj.error_message
43
+ end
44
+
45
+ private
46
+
47
+ # Backend - given options, choose an appropriate backend and construct the corresponding object.
48
+ # @return [?] Backend object
49
+ def backend
50
+ # Hard-coded backend
51
+ if @options[:backend]
52
+ return OctocatalogDiff::CatalogUtil::ENC::Noop.new(@options) if @options[:backend] == :noop
53
+ return OctocatalogDiff::CatalogUtil::ENC::PE.new(@options) if @options[:backend] == :pe
54
+ return OctocatalogDiff::CatalogUtil::ENC::Script.new(@options) if @options[:backend] == :script
55
+ raise ArgumentError, "Unknown backend :#{@options[:backend]}"
56
+ end
57
+
58
+ # Determine backend based on arguments
59
+ return OctocatalogDiff::CatalogUtil::ENC::PE.new(@options) if @options[:pe_enc_url]
60
+ return OctocatalogDiff::CatalogUtil::ENC::Script.new(@options) if @options[:enc]
61
+
62
+ # At this point we do not know what backend to use for the ENC
63
+ raise ArgumentError, 'Unable to determine ENC backend to use'
64
+ end
65
+
66
+ # Execute the 'execute' method of the object, but only once
67
+ # @param [Logger] Logger (optional) - if not supplied any logger messages will be discarded
68
+ def execute(logger = nil)
69
+ return if @executed
70
+ logger ||= @options[:logger]
71
+ logger ||= Logger.new(StringIO.new)
72
+ @enc_obj.execute(logger) if @enc_obj.respond_to?(:execute)
73
+ @executed = true
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,22 @@
1
+ module OctocatalogDiff
2
+ module CatalogUtil
3
+ class ENC
4
+ # No-op ENC.
5
+ class Noop
6
+ # Constructor
7
+ def initialize(_options)
8
+ end
9
+
10
+ # Retrieve content
11
+ def content
12
+ ''
13
+ end
14
+
15
+ # Error message
16
+ def error_message
17
+ nil
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,99 @@
1
+ require_relative 'pe/v1'
2
+ require_relative '../../util/httparty'
3
+ require_relative '../facts'
4
+
5
+ module OctocatalogDiff
6
+ module CatalogUtil
7
+ class ENC
8
+ # Support the Puppet Enterprise classification API.
9
+ # Documentation: https://docs.puppet.com/pe/latest/nc_index.html
10
+ class PE
11
+ # Error class that can be caught
12
+ class ClassificationError < RuntimeError; end
13
+
14
+ # Allow the main ENC object to retrieve these values
15
+ attr_reader :content, :error_message
16
+
17
+ # Constructor
18
+ # @param options [Hash] Options - must contain the Puppet Enterprise URL and the node
19
+ def initialize(options)
20
+ # Make sure the node is in the options
21
+ raise ArgumentError, 'OctocatalogDiff::CatalogUtil::ENC::PE#new requires :node' unless options.key?(:node)
22
+ @node = options[:node]
23
+
24
+ # Retrieve the base URL for the Puppet Enterprise ENC service
25
+ raise ArgumentError, 'OctocatalogDiff::CatalogUtil::ENC::PE#new requires :pe_enc_url' unless options.key?(:pe_enc_url)
26
+
27
+ # Save options
28
+ @options = options
29
+
30
+ # Get the object corresponding to the version of the API in use.
31
+ # (Right now this is hard-coded at V1 because that is the only version there is. In the future
32
+ # if there are different versions, this will need to be parameterized.)
33
+ @api = OctocatalogDiff::CatalogUtil::ENC::PE::V1.new(@options)
34
+
35
+ # Initialize the content and error message
36
+ @content = nil
37
+ @error_message = 'The execute method was never run'
38
+ end
39
+
40
+ # Executor
41
+ # @param logger [Logger] Logger object
42
+ def execute(logger)
43
+ logger.debug "Beginning OctocatalogDiff::CatalogUtil::ENC::PE#execute for #{@node}"
44
+
45
+ @options[:facts] ||= facts(logger)
46
+ return unless @options[:facts]
47
+
48
+ more_options = { headers: @api.headers, timeout: @options[:timeout] || 10 }
49
+ post_hash = @api.body
50
+ url = @api.url
51
+ response = OctocatalogDiff::Util::HTTParty.post(url, @options.merge(more_options), post_hash, 'pe_enc')
52
+
53
+ unless response[:code] == 200
54
+ logger.debug "PE ENC failed: #{response.inspect}"
55
+ logger.error "PE ENC failed: Response from #{url} was #{response[:code]}"
56
+ @error_message = "Response from #{url} was #{response[:code]}"
57
+ return
58
+ end
59
+
60
+ logger.debug "Response from #{url} was #{response[:code]}"
61
+ unless response[:parsed].is_a?(Hash)
62
+ logger.error "PE ENC failed: Response from #{url} was not a hash! #{response[:parsed].inspect}"
63
+ @error_message = "PE ENC failed: Response from #{url} was not a hash! #{response[:parsed].class}"
64
+ return
65
+ end
66
+
67
+ begin
68
+ @content = @api.result(response[:parsed], logger)
69
+ @error_message = nil
70
+ rescue OctocatalogDiff::CatalogUtil::ENC::PE::ClassificationError => exc
71
+ @error_message = exc.message
72
+ logger.error "PE ENC failed: #{exc.message}"
73
+ return
74
+ end
75
+
76
+ logger.debug "Completed OctocatalogDiff::CatalogUtil::ENC::PE#execute for #{@node}"
77
+ end
78
+
79
+ # Facts
80
+ # @param logger [Logger] Logger object
81
+ # @return [OctocatalogDiff::Facts] Facts object
82
+ def facts(logger)
83
+ facts_obj = OctocatalogDiff::CatalogUtil::Facts.new(@options, logger)
84
+ logger.debug "Start retrieving facts for #{@node} from #{self.class}"
85
+ begin
86
+ result = facts_obj.facts
87
+ logger.debug "Success retrieving facts for #{@node} from #{self.class}"
88
+ rescue OctocatalogDiff::Facts::FactRetrievalError, OctocatalogDiff::Facts::FactSourceError => exc
89
+ @content = nil
90
+ @error_message = "Fact retrieval failed: #{exc.class} - #{exc.message}"
91
+ logger.error @error_message
92
+ result = nil
93
+ end
94
+ result
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,61 @@
1
+ require 'json'
2
+ require 'uri'
3
+ require 'yaml'
4
+
5
+ module OctocatalogDiff
6
+ module CatalogUtil
7
+ class ENC
8
+ class PE
9
+ # Support the Puppet Enterprise classification API.
10
+ # Documentation: https://docs.puppet.com/pe/latest/nc_index.html
11
+ # This is version 1 of the API
12
+ class V1
13
+ # Constructor
14
+ # @param options [Hash] All input options
15
+ def initialize(options)
16
+ @options = options
17
+ end
18
+
19
+ # Return the URL to the API
20
+ # @return [String] API URL
21
+ def url
22
+ "#{@options[:pe_enc_url]}/v1/classified/nodes/#{@options[:node]}"
23
+ end
24
+
25
+ # Headers
26
+ # @return [Hash] Headers for request
27
+ def headers
28
+ result = {
29
+ 'Accept' => 'application/json',
30
+ 'Content-Type' => 'application/json'
31
+ }
32
+ result['X-Authentication'] = @options[:pe_enc_token] if @options[:pe_enc_token]
33
+ result
34
+ end
35
+
36
+ # POST body
37
+ # @return [String] POST body data
38
+ def body
39
+ raise ":facts required (got #{@options[:facts].class})" unless @options[:facts].is_a?(OctocatalogDiff::Facts)
40
+ { 'fact' => @options[:facts].facts }.to_json
41
+ end
42
+
43
+ # Parse response from ENC and return the final ENC data
44
+ # @param parsed [Parsed response] Parsed response from ENC
45
+ # @param logger [Logger] Logger.object
46
+ # @return [String] ENC data as text
47
+ def result(parsed, logger)
48
+ %w(classes parameters).each do |required_key|
49
+ next if parsed[required_key]
50
+ logger.debug parsed.keys.inspect
51
+ raise OctocatalogDiff::CatalogUtil::ENC::PE::ClassificationError, "Response missing: #{required_key}"
52
+ end
53
+
54
+ obj = { 'classes' => parsed['classes'], 'parameters' => parsed['parameters'] }
55
+ obj.to_yaml
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,88 @@
1
+ require 'fileutils'
2
+ require 'open3'
3
+ require 'shellwords'
4
+ require 'tempfile'
5
+
6
+ module OctocatalogDiff
7
+ module CatalogUtil
8
+ class ENC
9
+ # Support an ENC that executes a script on this system which returns the ENC data on STDOUT.
10
+ class Script
11
+ attr_reader :content, :error_message, :script
12
+
13
+ # Constructor
14
+ # @param options [Hash] Options - must contain script name and node name, plus tempdir if it's a relative path
15
+ def initialize(options)
16
+ # Make sure the node is in the options
17
+ raise ArgumentError, 'OctocatalogDiff::CatalogUtil::ENC::Script#new requires :node' unless options.key?(:node)
18
+ @node = options[:node]
19
+
20
+ # Determine path to ENC and make sure it exists
21
+ raise ArgumentError, 'OctocatalogDiff::CatalogUtil::ENC::Script#new requires :enc' unless options.key?(:enc)
22
+ @script = script_path(options[:enc], options[:tempdir])
23
+
24
+ # Other options we may recognize
25
+ @pass_env_vars = options.fetch(:pass_env_vars, [])
26
+
27
+ # Initialize the content and error message
28
+ @content = nil
29
+ @error_message = 'The execute method was never run'
30
+ end
31
+
32
+ # Executor
33
+ # @param logger [Logger] Logger object
34
+ def execute(logger)
35
+ logger.debug "Beginning OctocatalogDiff::CatalogUtil::ENC::Script#execute for #{@node} with #{@script}"
36
+ logger.debug "Passing these extra environment variables: #{@pass_env_vars}" if @pass_env_vars.any?
37
+
38
+ # Copy the script and make it executable
39
+ # Then run the command in the restricted environment
40
+ raise Errno::ENOENT, "ENC #{@script} wasn't found" unless File.file?(@script)
41
+ file = Tempfile.open('enc.sh')
42
+ file.close
43
+ begin
44
+ FileUtils.cp @script, file.path
45
+ FileUtils.chmod 0o755, file.path
46
+ env = {
47
+ 'HOME' => ENV['HOME'],
48
+ 'PATH' => ENV['PATH'],
49
+ 'PWD' => File.dirname(@script)
50
+ }
51
+ @pass_env_vars.each { |var| env[var] ||= ENV[var] }
52
+ command = [file.path, @node].map { |x| Shellwords.escape(x) }.join(' ')
53
+ out, err, status = Open3.capture3(env, command, unsetenv_others: true, chdir: File.dirname(@script))
54
+ logger.debug "ENC exited #{status.exitstatus}: #{out.length} bytes to STDOUT, #{err.length} bytes to STDERR"
55
+ ensure
56
+ file.unlink
57
+ end
58
+
59
+ # Analyze the output
60
+ if status.exitstatus.zero?
61
+ @content = out
62
+ @error_message = nil
63
+ logger.warn "ENC STDERR: #{err}" unless err.empty?
64
+ else
65
+ @content = nil
66
+ @error_message = "ENC failed with status #{status.exitstatus}: #{out} #{err}"
67
+ logger.error "ENC failed - Status #{status.exitstatus}"
68
+ logger.error "Failed ENC printed this to STDOUT: #{out}" unless out.empty?
69
+ logger.error "Failed ENC printed this to STDERR: #{err}" unless err.empty?
70
+ end
71
+ end
72
+
73
+ private
74
+
75
+ # Determine the script path for the incoming file -- absolute or relative
76
+ # @param enc [String] Path to ENC supplied by user/config
77
+ # @param tempdir [String]
78
+ # @return [String] Full path to file on system
79
+ def script_path(enc, tempdir)
80
+ return enc if enc.start_with? '/'
81
+ raise ArgumentError, 'OctocatalogDiff::CatalogUtil::ENC::Script#new requires :tempdir' unless tempdir.is_a?(String)
82
+ return File.join(tempdir, enc) if enc =~ %r{^environments/production/}
83
+ File.join(tempdir, 'environments', 'production', enc)
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end