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