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,30 @@
1
+ require 'json'
2
+
3
+ module OctocatalogDiff
4
+ class Catalog
5
+ # Represents a Puppet catalog that is read in directly from a JSON file.
6
+ class JSON
7
+ attr_accessor :node
8
+ attr_reader :error_message, :catalog, :catalog_json
9
+
10
+ # Constructor
11
+ # @param :json [String] REQUIRED: Content of catalog, will be parsed as JSON
12
+ # @param :node [String] Node name (if not supplied, will be determined from catalog)
13
+ def initialize(options)
14
+ raise ArgumentError, 'Usage: OctocatalogDiff::Catalog::JSON.initialize(options_hash)' unless options.is_a?(Hash)
15
+ raise ArgumentError, "Must supply :json as string in options: #{options[:json].class}" unless options[:json].is_a?(String)
16
+ @catalog_json = options[:json]
17
+ begin
18
+ @catalog = ::JSON.parse(@catalog_json)
19
+ @error_message = nil
20
+ @node ||= @catalog['name'] if @catalog.key?('name') # Puppet 4.x
21
+ @node ||= @catalog['data']['name'] if @catalog.key?('data') && @catalog['data'].is_a?(Hash) # Puppet 3.x
22
+ rescue ::JSON::ParserError => exc
23
+ @error_message = "Catalog JSON input failed to parse: #{exc.message}"
24
+ @catalog = nil
25
+ @catalog_json = nil
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,19 @@
1
+ require 'json'
2
+
3
+ module OctocatalogDiff
4
+ class Catalog
5
+ # Represents a null Puppet catalog.
6
+ class Noop
7
+ attr_accessor :node
8
+ attr_reader :error_message, :catalog, :catalog_json
9
+
10
+ # Constructor
11
+ def initialize(options)
12
+ @catalog_json = '{"resources":[]}'
13
+ @catalog = { 'resources' => [] }
14
+ @error_message = nil
15
+ @node = options.fetch(:node, 'noop')
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,82 @@
1
+ require 'json'
2
+ require 'stringio'
3
+
4
+ require_relative '../puppetdb'
5
+
6
+ module OctocatalogDiff
7
+ class Catalog
8
+ # Represents a Puppet catalog that is read from PuppetDB.
9
+ class PuppetDB
10
+ attr_accessor :node
11
+ attr_reader :error_message, :catalog, :catalog_json, :retries, :convert_file_resources
12
+
13
+ # Constructor - See OctocatalogDiff::PuppetDB for additional parameters
14
+ # @param :node [String] Node name
15
+ # @param :retry [Fixnum] Number of retries, if fetch fails
16
+ def initialize(options)
17
+ raise ArgumentError, 'Hash of options must be passed to OctocatalogDiff::Catalog::PuppetDB' unless options.is_a?(Hash)
18
+ raise ArgumentError, 'node must be a non-empty string' unless options[:node].is_a?(String) && options[:node] != ''
19
+ @node = options[:node]
20
+ @catalog = nil
21
+ @error_message = nil
22
+ @retries = nil
23
+
24
+ # Cannot convert file resources from this type of catalog
25
+ @convert_file_resources = false
26
+
27
+ # Save options
28
+ @options = options
29
+ end
30
+
31
+ def build(logger = Logger.new(StringIO.new))
32
+ fetch_catalog(logger)
33
+ end
34
+
35
+ private
36
+
37
+ # Private method: Get catalog from PuppetDB. Sets @catalog / @catalog_json or @error_message
38
+ # @param logger [Logger object] Logger object
39
+ def fetch_catalog(logger)
40
+ # Use OctocatalogDiff::PuppetDB to interact with puppetdb
41
+ puppetdb_obj = OctocatalogDiff::PuppetDB.new(@options)
42
+
43
+ # Loop to retrieve catalog from PuppetDB
44
+ uri = "/pdb/query/v4/catalogs/#{@node}"
45
+ retries = @options.fetch(:retry, 1)
46
+ (retries + 1).times do
47
+ @retries = -1 if @retries.nil?
48
+ @retries += 1
49
+ begin
50
+ # Fetch catalog from PuppetDB
51
+ logger.debug "Retrieving #{@node} from #{uri}"
52
+ time_start = Time.now
53
+ result = puppetdb_obj.get(uri)
54
+ time_it_took = Time.now - time_start
55
+
56
+ # Validate received catalog
57
+ raise "PuppetDB catalog for #{@node} failed: no 'resources' hash in object" unless result.key?('resources')
58
+ raise "PuppetDB catalog for #{@node} failed: 'resources' was not a hash" unless result['resources'].is_a?(Hash)
59
+ logger.debug "Catalog for #{@node} retrieved from PuppetDB in #{time_it_took} seconds"
60
+
61
+ # Make this look like a generated catalog in Puppet 4.x
62
+ @catalog = result.merge('resources' => result['resources']['data'])
63
+ @catalog['resources'] = @catalog['resources'].map { |x| x.reject { |k, _v| k == 'resource' } }
64
+
65
+ # Set the other variables
66
+ @catalog_json = ::JSON.generate(@catalog)
67
+ @error_message = nil
68
+ rescue OctocatalogDiff::PuppetDB::ConnectionError => exc
69
+ @error_message = "Catalog retrieval failed (#{exc.class}) (#{exc.message})"
70
+ rescue OctocatalogDiff::PuppetDB::NotFoundError => exc
71
+ @error_message = "Node #{node} not found in PuppetDB (#{exc.message})"
72
+ rescue OctocatalogDiff::PuppetDB::PuppetDBError => exc
73
+ @error_message = "Catalog retrieval failed for node #{node} from PuppetDB (#{exc.message})"
74
+ rescue ::JSON::GeneratorError => exc
75
+ @error_message = "Failed to generate result from PuppetDB as JSON (#{exc.message})"
76
+ end
77
+ break if @catalog
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,121 @@
1
+ require_relative '../catalog-util/facts'
2
+ require_relative '../external/pson/pure'
3
+ require_relative '../util/httparty'
4
+
5
+ require 'json'
6
+ require 'securerandom'
7
+ require 'stringio'
8
+
9
+ module OctocatalogDiff
10
+ class Catalog
11
+ # Represents a Puppet catalog that is obtained by contacting the Puppet Master.
12
+ class PuppetMaster
13
+ attr_accessor :node
14
+ attr_reader :error_message, :catalog, :catalog_json, :convert_file_resources, :options
15
+
16
+ # Defaults
17
+ DEFAULT_PUPPET_PORT_NUMBER = 8140
18
+ DEFAULT_PUPPET_SERVER_API = 3
19
+ PUPPET_MASTER_TIMEOUT = 60
20
+
21
+ # Constructor
22
+ # @param :node [String] Node name
23
+ # @param :retry [Fixnum] Number of retries, if fetch fails
24
+ # @param :branch [String] Environment to fetch from Puppet Master
25
+ # @param :puppet_master [String] Puppet server and port number (assumed to be DEFAULT_PUPPET_PORT_NUMBER if not given)
26
+ # @param :puppet_master_api_version [Fixnum] Puppet server API (default DEFAULT_PUPPET_SERVER_API)
27
+ # @param :puppet_master_ssl_ca [String] Path to file used to sign puppet master's certificate
28
+ # @param :puppet_master_ssl_verify [Boolean] Override the CA verification setting guessed from parameters
29
+ # @param :puppet_master_ssl_client_pem [String] PEM-encoded client key and certificate
30
+ # @param :puppet_master_ssl_client_p12 [String] pkcs12-encoded client key and certificate
31
+ # @param :puppet_master_ssl_client_password [String] Path to file containing password for SSL client key (any format)
32
+ # @param :puppet_master_ssl_client_auth [Boolean] Override the client-auth that is guessed from parameters
33
+ # @param :timeout [Fixnum] Connection timeout for Puppet master (default=PUPPET_MASTER_TIMEOUT seconds)
34
+ def initialize(options)
35
+ raise ArgumentError, 'Hash of options must be passed to OctocatalogDiff::Catalog::PuppetMaster' unless options.is_a?(Hash)
36
+ raise ArgumentError, 'node must be a non-empty string' unless options[:node].is_a?(String) && options[:node] != ''
37
+ unless options[:branch].is_a?(String) && options[:branch] != ''
38
+ raise ArgumentError, 'Environment must be a non-empty string'
39
+ end
40
+ unless options[:puppet_master].is_a?(String) && options[:puppet_master] != ''
41
+ raise ArgumentError, 'Puppet Master must be a non-empty string'
42
+ end
43
+
44
+ @node = options[:node]
45
+ @catalog = nil
46
+ @error_message = nil
47
+ @retries = nil
48
+ @timeout = options.fetch(:timeout, PUPPET_MASTER_TIMEOUT)
49
+
50
+ # Cannot convert file resources from this type of catalog right now.
51
+ # FIXME: This is possible with additional API calls but is current unimplemented.
52
+ @convert_file_resources = false
53
+
54
+ options[:puppet_master] += ":#{DEFAULT_PUPPET_PORT_NUMBER}" unless options[:puppet_master] =~ /\:\d+$/
55
+ @options = options
56
+ end
57
+
58
+ # Build method
59
+ def build(logger = Logger.new(StringIO.new))
60
+ facts_obj = OctocatalogDiff::CatalogUtil::Facts.new(@options, logger)
61
+ logger.debug "Start retrieving facts for #{@node} from #{self.class}"
62
+ @facts = facts_obj.facts
63
+ logger.debug "Success retrieving facts for #{@node} from #{self.class}"
64
+ fetch_catalog(logger)
65
+ end
66
+
67
+ private
68
+
69
+ # Returns a hash of parameters for each supported version of the Puppet Server Catalog API.
70
+ # @return [Hash] Hash of parameters
71
+ def puppet_catalog_api
72
+ {
73
+ 2 => {
74
+ url: "https://#{@options[:puppet_master]}/#{@options[:branch]}/catalog/#{@node}",
75
+ parameters: {
76
+ 'facts_format' => 'pson',
77
+ 'facts' => @facts.fudge_timestamp.without('trusted').to_pson,
78
+ 'transaction_uuid' => SecureRandom.uuid
79
+ }
80
+ },
81
+ 3 => {
82
+ url: "https://#{@options[:puppet_master]}/puppet/v3/catalog/#{@node}",
83
+ parameters: {
84
+ 'environment' => @options[:branch],
85
+ 'facts_format' => 'pson',
86
+ 'facts' => @facts.fudge_timestamp.without('trusted').to_pson,
87
+ 'transaction_uuid' => SecureRandom.uuid
88
+ }
89
+ }
90
+ }
91
+ end
92
+
93
+ # Fetch catalog by contacting the Puppet master, sending the facts, and asking for the catalog. When the
94
+ # catalog is returned in PSON format, parse it to JSON and then set appropriate variables.
95
+ def fetch_catalog(logger)
96
+ api_version = @options[:puppet_master_api_version] || DEFAULT_PUPPET_SERVER_API
97
+ api = puppet_catalog_api[api_version]
98
+ raise ArgumentError, "Unsupported or invalid API version #{api_version}" unless api.is_a?(Hash)
99
+
100
+ logger.debug "Retrieve catalog from #{api[:url]} environment #{@options[:branch]}"
101
+
102
+ more_options = { headers: { 'Accept' => 'text/pson' }, timeout: @timeout }
103
+ post_hash = api[:parameters]
104
+ response = OctocatalogDiff::Util::HTTParty.post(api[:url], @options.merge(more_options), post_hash, 'puppet_master')
105
+
106
+ logger.debug "Response from #{api[:url]} environment #{@options[:branch]} was #{response[:code]}"
107
+
108
+ unless response[:code] == 200
109
+ @error_message = "Failed to retrieve catalog from #{api[:url]}: #{response[:code]} #{response[:body]}"
110
+ @catalog = nil
111
+ @catalog_json = nil
112
+ return
113
+ end
114
+
115
+ @catalog = response[:parsed]
116
+ @catalog_json = ::JSON.generate(@catalog)
117
+ @error_message = nil
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,17 @@
1
+ Puppet - Automating Configuration Management.
2
+
3
+ Copyright (C) 2005-2016 Puppet, Inc.
4
+
5
+ Puppet, Inc. can be contacted at: info@puppet.com
6
+
7
+ Licensed under the Apache License, Version 2.0 (the "License");
8
+ you may not use this file except in compliance with the License.
9
+ You may obtain a copy of the License at
10
+
11
+ https://www.apache.org/licenses/LICENSE-2.0
12
+
13
+ Unless required by applicable law or agreed to in writing, software
14
+ distributed under the License is distributed on an "AS IS" BASIS,
15
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ See the License for the specific language governing permissions and
17
+ limitations under the License.
@@ -0,0 +1,20 @@
1
+ # PSON
2
+
3
+ Puppet uses a JSON variant called PSON to serialize data (e.g. facts) transmitting to/from a Puppet master.
4
+
5
+ Documentation for PSON can be found here:
6
+
7
+ https://docs.puppet.com/puppet/4.6/reference/http_api/pson.html
8
+
9
+ The code in this directory was taken directly from Puppet and can be found at:
10
+
11
+ https://github.com/puppetlabs/puppet/tree/master/lib/puppet/external/pson
12
+
13
+ If you have found this code to deal with Puppet serialization, you should probably take the original and most up-to-date code from Puppet at the location above.
14
+
15
+ This code contains the following modifications:
16
+
17
+ - Change the `require` statements to `require_relative` statements so they work in this gem's directory structure
18
+ - Change `$MATCH` to `$&` because `$MATCH` is undefined without `require 'english`` or equivalent
19
+
20
+ This code is licensed by Puppet under the Apache 2.0 license. A copy of the Puppet license can be found in this directory.
@@ -0,0 +1,370 @@
1
+ require_relative 'version'
2
+
3
+ module PSON
4
+ class << self
5
+ # If _object_ is string-like parse the string and return the parsed result
6
+ # as a Ruby data structure. Otherwise generate a PSON text from the Ruby
7
+ # data structure object and return it.
8
+ #
9
+ # The _opts_ argument is passed through to generate/parse respectively, see
10
+ # generate and parse for their documentation.
11
+ def [](object, opts = {})
12
+ if object.respond_to? :to_str
13
+ PSON.parse(object.to_str, opts => {})
14
+ else
15
+ PSON.generate(object, opts => {})
16
+ end
17
+ end
18
+
19
+ # Returns the PSON parser class, that is used by PSON. This might be either
20
+ # PSON::Ext::Parser or PSON::Pure::Parser.
21
+ attr_reader :parser
22
+
23
+ # Set the PSON parser class _parser_ to be used by PSON.
24
+ def parser=(parser) # :nodoc:
25
+ @parser = parser
26
+ remove_const :Parser if const_defined? :Parser
27
+ const_set :Parser, parser
28
+ end
29
+
30
+ # Return the constant located at _path_.
31
+ # Anything may be registered as a path by calling register_path, above.
32
+ # Otherwise, the format of _path_ has to be either ::A::B::C or A::B::C.
33
+ # In either of these cases A has to be defined in Object (e.g. the path
34
+ # must be an absolute namespace path. If the constant doesn't exist at
35
+ # the given path, an ArgumentError is raised.
36
+ def deep_const_get(path) # :nodoc:
37
+ path = path.to_s
38
+ path.split(/::/).inject(Object) do |p, c|
39
+ case
40
+ when c.empty? then p
41
+ when p.const_defined?(c) then p.const_get(c)
42
+ else raise ArgumentError, "can't find const for unregistered document type #{path}"
43
+ end
44
+ end
45
+ end
46
+
47
+ # Set the module _generator_ to be used by PSON.
48
+ def generator=(generator) # :nodoc:
49
+ @generator = generator
50
+ generator_methods = generator::GeneratorMethods
51
+ for const in generator_methods.constants
52
+ klass = deep_const_get(const)
53
+ modul = generator_methods.const_get(const)
54
+ klass.class_eval do
55
+ instance_methods(false).each do |m|
56
+ m.to_s == 'to_pson' and remove_method m
57
+ end
58
+ include modul
59
+ end
60
+ end
61
+ self.state = generator::State
62
+ const_set :State, self.state
63
+ end
64
+
65
+ # Returns the PSON generator modul, that is used by PSON. This might be
66
+ # either PSON::Ext::Generator or PSON::Pure::Generator.
67
+ attr_reader :generator
68
+
69
+ # Returns the PSON generator state class, that is used by PSON. This might
70
+ # be either PSON::Ext::Generator::State or PSON::Pure::Generator::State.
71
+ attr_accessor :state
72
+
73
+ # This is create identifier, that is used to decide, if the _pson_create_
74
+ # hook of a class should be called. It defaults to 'document_type'.
75
+ attr_accessor :create_id
76
+ end
77
+ self.create_id = 'document_type'
78
+
79
+ NaN = (-1.0) ** 0.5
80
+
81
+ Infinity = 1.0/0
82
+
83
+ MinusInfinity = -Infinity
84
+
85
+ # The base exception for PSON errors.
86
+ class PSONError < StandardError; end
87
+
88
+ # This exception is raised, if a parser error occurs.
89
+ class ParserError < PSONError; end
90
+
91
+ # This exception is raised, if the nesting of parsed datastructures is too
92
+ # deep.
93
+ class NestingError < ParserError; end
94
+
95
+ # This exception is raised, if a generator or unparser error occurs.
96
+ class GeneratorError < PSONError; end
97
+ # For backwards compatibility
98
+ UnparserError = GeneratorError
99
+
100
+ # If a circular data structure is encountered while unparsing
101
+ # this exception is raised.
102
+ class CircularDatastructure < GeneratorError; end
103
+
104
+ # This exception is raised, if the required unicode support is missing on the
105
+ # system. Usually this means, that the iconv library is not installed.
106
+ class MissingUnicodeSupport < PSONError; end
107
+
108
+ module_function
109
+
110
+ # Parse the PSON string _source_ into a Ruby data structure and return it.
111
+ #
112
+ # _opts_ can have the following
113
+ # keys:
114
+ # * *max_nesting*: The maximum depth of nesting allowed in the parsed data
115
+ # structures. Disable depth checking with :max_nesting => false, it defaults
116
+ # to 19.
117
+ # * *allow_nan*: If set to true, allow NaN, Infinity and -Infinity in
118
+ # defiance of RFC 4627 to be parsed by the Parser. This option defaults
119
+ # to false.
120
+ def parse(source, opts = {})
121
+ PSON.parser.new(source, opts).parse
122
+ end
123
+
124
+ # Parse the PSON string _source_ into a Ruby data structure and return it.
125
+ # The bang version of the parse method, defaults to the more dangerous values
126
+ # for the _opts_ hash, so be sure only to parse trusted _source_ strings.
127
+ #
128
+ # _opts_ can have the following keys:
129
+ # * *max_nesting*: The maximum depth of nesting allowed in the parsed data
130
+ # structures. Enable depth checking with :max_nesting => anInteger. The parse!
131
+ # methods defaults to not doing max depth checking: This can be dangerous,
132
+ # if someone wants to fill up your stack.
133
+ # * *allow_nan*: If set to true, allow NaN, Infinity, and -Infinity in
134
+ # defiance of RFC 4627 to be parsed by the Parser. This option defaults
135
+ # to true.
136
+ def parse!(source, opts = {})
137
+ opts = {
138
+ :max_nesting => false,
139
+ :allow_nan => true
140
+ }.update(opts)
141
+ PSON.parser.new(source, opts).parse
142
+ end
143
+
144
+ # Unparse the Ruby data structure _obj_ into a single line PSON string and
145
+ # return it. _state_ is
146
+ # * a PSON::State object,
147
+ # * or a Hash like object (responding to to_hash),
148
+ # * an object convertible into a hash by a to_h method,
149
+ # that is used as or to configure a State object.
150
+ #
151
+ # It defaults to a state object, that creates the shortest possible PSON text
152
+ # in one line, checks for circular data structures and doesn't allow NaN,
153
+ # Infinity, and -Infinity.
154
+ #
155
+ # A _state_ hash can have the following keys:
156
+ # * *indent*: a string used to indent levels (default: ''),
157
+ # * *space*: a string that is put after, a : or , delimiter (default: ''),
158
+ # * *space_before*: a string that is put before a : pair delimiter (default: ''),
159
+ # * *object_nl*: a string that is put at the end of a PSON object (default: ''),
160
+ # * *array_nl*: a string that is put at the end of a PSON array (default: ''),
161
+ # * *check_circular*: true if checking for circular data structures
162
+ # should be done (the default), false otherwise.
163
+ # * *allow_nan*: true if NaN, Infinity, and -Infinity should be
164
+ # generated, otherwise an exception is thrown, if these values are
165
+ # encountered. This options defaults to false.
166
+ # * *max_nesting*: The maximum depth of nesting allowed in the data
167
+ # structures from which PSON is to be generated. Disable depth checking
168
+ # with :max_nesting => false, it defaults to 19.
169
+ #
170
+ # See also the fast_generate for the fastest creation method with the least
171
+ # amount of sanity checks, and the pretty_generate method for some
172
+ # defaults for a pretty output.
173
+ def generate(obj, state = nil)
174
+ if state
175
+ state = State.from_state(state)
176
+ else
177
+ state = State.new
178
+ end
179
+ obj.to_pson(state)
180
+ end
181
+
182
+ # :stopdoc:
183
+ # I want to deprecate these later, so I'll first be silent about them, and
184
+ # later delete them.
185
+ alias unparse generate
186
+ module_function :unparse
187
+ # :startdoc:
188
+
189
+ # Unparse the Ruby data structure _obj_ into a single line PSON string and
190
+ # return it. This method disables the checks for circles in Ruby objects, and
191
+ # also generates NaN, Infinity, and, -Infinity float values.
192
+ #
193
+ # *WARNING*: Be careful not to pass any Ruby data structures with circles as
194
+ # _obj_ argument, because this will cause PSON to go into an infinite loop.
195
+ def fast_generate(obj)
196
+ obj.to_pson(nil)
197
+ end
198
+
199
+ # :stopdoc:
200
+ # I want to deprecate these later, so I'll first be silent about them, and later delete them.
201
+ alias fast_unparse fast_generate
202
+ module_function :fast_unparse
203
+ # :startdoc:
204
+
205
+ # Unparse the Ruby data structure _obj_ into a PSON string and return it. The
206
+ # returned string is a prettier form of the string returned by #unparse.
207
+ #
208
+ # The _opts_ argument can be used to configure the generator, see the
209
+ # generate method for a more detailed explanation.
210
+ def pretty_generate(obj, opts = nil)
211
+
212
+ state = PSON.state.new(
213
+
214
+ :indent => ' ',
215
+ :space => ' ',
216
+ :object_nl => "\n",
217
+ :array_nl => "\n",
218
+
219
+ :check_circular => true
220
+ )
221
+ if opts
222
+ if opts.respond_to? :to_hash
223
+ opts = opts.to_hash
224
+ elsif opts.respond_to? :to_h
225
+ opts = opts.to_h
226
+ else
227
+ raise TypeError, "can't convert #{opts.class} into Hash"
228
+ end
229
+ state.configure(opts)
230
+ end
231
+ obj.to_pson(state)
232
+ end
233
+
234
+ # :stopdoc:
235
+ # I want to deprecate these later, so I'll first be silent about them, and later delete them.
236
+ alias pretty_unparse pretty_generate
237
+ module_function :pretty_unparse
238
+ # :startdoc:
239
+
240
+ # Load a ruby data structure from a PSON _source_ and return it. A source can
241
+ # either be a string-like object, an IO like object, or an object responding
242
+ # to the read method. If _proc_ was given, it will be called with any nested
243
+ # Ruby object as an argument recursively in depth first order.
244
+ #
245
+ # This method is part of the implementation of the load/dump interface of
246
+ # Marshal and YAML.
247
+ def load(source, proc = nil)
248
+ if source.respond_to? :to_str
249
+ source = source.to_str
250
+ elsif source.respond_to? :to_io
251
+ source = source.to_io.read
252
+ else
253
+ source = source.read
254
+ end
255
+ result = parse(source, :max_nesting => false, :allow_nan => true)
256
+ recurse_proc(result, &proc) if proc
257
+ result
258
+ end
259
+
260
+ def recurse_proc(result, &proc)
261
+ case result
262
+ when Array
263
+ result.each { |x| recurse_proc x, &proc }
264
+ proc.call result
265
+ when Hash
266
+ result.each { |x, y| recurse_proc x, &proc; recurse_proc y, &proc }
267
+ proc.call result
268
+ else
269
+ proc.call result
270
+ end
271
+ end
272
+ private :recurse_proc
273
+ module_function :recurse_proc
274
+
275
+ alias restore load
276
+ module_function :restore
277
+
278
+ # Dumps _obj_ as a PSON string, i.e. calls generate on the object and returns
279
+ # the result.
280
+ #
281
+ # If anIO (an IO like object or an object that responds to the write method)
282
+ # was given, the resulting PSON is written to it.
283
+ #
284
+ # If the number of nested arrays or objects exceeds _limit_ an ArgumentError
285
+ # exception is raised. This argument is similar (but not exactly the
286
+ # same!) to the _limit_ argument in Marshal.dump.
287
+ #
288
+ # This method is part of the implementation of the load/dump interface of
289
+ # Marshal and YAML.
290
+ def dump(obj, anIO = nil, limit = nil)
291
+ if anIO and limit.nil?
292
+ anIO = anIO.to_io if anIO.respond_to?(:to_io)
293
+ unless anIO.respond_to?(:write)
294
+ limit = anIO
295
+ anIO = nil
296
+ end
297
+ end
298
+ limit ||= 0
299
+ result = generate(obj, :allow_nan => true, :max_nesting => limit)
300
+ if anIO
301
+ anIO.write result
302
+ anIO
303
+ else
304
+ result
305
+ end
306
+ rescue PSON::NestingError
307
+ raise ArgumentError, "exceed depth limit", $!.backtrace
308
+ end
309
+
310
+
311
+ # Provide a smarter wrapper for changing string encoding that works with
312
+ # both Ruby 1.8 (iconv) and 1.9 (String#encode). Thankfully they seem to
313
+ # have compatible input syntax, at least for the encodings we touch.
314
+ if String.method_defined?("encode")
315
+ def encode(to, from, string)
316
+ string.encode(to, from)
317
+ end
318
+ else
319
+ require 'iconv'
320
+ def encode(to, from, string)
321
+ Iconv.conv(to, from, string)
322
+ end
323
+ end
324
+ end
325
+
326
+ module ::Kernel
327
+ private
328
+
329
+ # Outputs _objs_ to STDOUT as PSON strings in the shortest form, that is in
330
+ # one line.
331
+ def j(*objs)
332
+ objs.each do |obj|
333
+ puts PSON::generate(obj, :allow_nan => true, :max_nesting => false)
334
+ end
335
+ nil
336
+ end
337
+
338
+ # Outputs _objs_ to STDOUT as PSON strings in a pretty format, with
339
+ # indentation and over many lines.
340
+ def jj(*objs)
341
+ objs.each do |obj|
342
+ puts PSON::pretty_generate(obj, :allow_nan => true, :max_nesting => false)
343
+ end
344
+ nil
345
+ end
346
+
347
+ # If _object_ is string-like parse the string and return the parsed result as
348
+ # a Ruby data structure. Otherwise generate a PSON text from the Ruby data
349
+ # structure object and return it.
350
+ #
351
+ # The _opts_ argument is passed through to generate/parse respectively, see
352
+ # generate and parse for their documentation.
353
+ def PSON(object, opts = {})
354
+ if object.respond_to? :to_str
355
+ PSON.parse(object.to_str, opts)
356
+ else
357
+ PSON.generate(object, opts)
358
+ end
359
+ end
360
+ end
361
+
362
+ class ::Class
363
+ # Returns true, if this class can be used to create an instance
364
+ # from a serialised PSON string. The class has to implement a class
365
+ # method _pson_create_ that expects a hash as first parameter, which includes
366
+ # the required data.
367
+ def pson_creatable?
368
+ respond_to?(:pson_create)
369
+ end
370
+ end