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,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