octocatalog-diff 1.5.2 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +5 -5
  2. data/.version +1 -1
  3. data/README.md +4 -4
  4. data/doc/CHANGELOG.md +50 -0
  5. data/doc/advanced-compare-file-text.md +79 -0
  6. data/doc/advanced-environments.md +5 -5
  7. data/doc/advanced-ignores.md +10 -0
  8. data/doc/advanced-puppet-master.md +25 -7
  9. data/doc/configuration-puppet.md +19 -19
  10. data/doc/configuration-puppetdb.md +8 -0
  11. data/doc/configuration.md +31 -31
  12. data/doc/dev/api/v1/calls/catalog-diff.md +6 -2
  13. data/doc/dev/api/v1/objects/diff.md +3 -3
  14. data/doc/dev/integration-tests.md +2 -2
  15. data/doc/dev/releasing.md +41 -41
  16. data/doc/dev/run-from-branch.md +23 -23
  17. data/doc/installation.md +14 -14
  18. data/doc/limitations.md +9 -9
  19. data/doc/optionsref.md +166 -11
  20. data/doc/requirements.md +6 -2
  21. data/lib/octocatalog-diff/catalog-diff/differ.rb +32 -5
  22. data/lib/octocatalog-diff/catalog-diff/filter/compilation_dir.rb +29 -25
  23. data/lib/octocatalog-diff/catalog-util/command.rb +25 -3
  24. data/lib/octocatalog-diff/catalog-util/fileresources.rb +39 -16
  25. data/lib/octocatalog-diff/catalog.rb +22 -4
  26. data/lib/octocatalog-diff/catalog/computed.rb +2 -1
  27. data/lib/octocatalog-diff/catalog/puppetmaster.rb +43 -5
  28. data/lib/octocatalog-diff/cli.rb +38 -6
  29. data/lib/octocatalog-diff/cli/options.rb +36 -1
  30. data/lib/octocatalog-diff/cli/options/compare_file_text.rb +18 -0
  31. data/lib/octocatalog-diff/cli/options/hostname.rb +13 -2
  32. data/lib/octocatalog-diff/cli/options/puppet_master_api_version.rb +2 -2
  33. data/lib/octocatalog-diff/cli/options/puppet_master_token.rb +20 -0
  34. data/lib/octocatalog-diff/cli/options/puppet_master_token_file.rb +35 -0
  35. data/lib/octocatalog-diff/cli/options/puppet_master_update_catalog.rb +20 -0
  36. data/lib/octocatalog-diff/cli/options/puppet_master_update_facts.rb +20 -0
  37. data/lib/octocatalog-diff/cli/options/puppetdb_package_inventory.rb +18 -0
  38. data/lib/octocatalog-diff/cli/options/use_lcs.rb +14 -0
  39. data/lib/octocatalog-diff/facts/json.rb +13 -2
  40. data/lib/octocatalog-diff/facts/puppetdb.rb +43 -2
  41. data/lib/octocatalog-diff/util/parallel.rb +20 -16
  42. data/lib/octocatalog-diff/util/util.rb +2 -0
  43. data/scripts/env/env.sh +1 -1
  44. data/scripts/git-extract/git-extract.sh +1 -1
  45. data/scripts/puppet/puppet.sh +1 -1
  46. metadata +36 -30
data/doc/requirements.md CHANGED
@@ -2,10 +2,14 @@
2
2
 
3
3
  To run `octocatalog-diff` you will need these basics:
4
4
 
5
- - Ruby 2.0 through 2.4 (we test octocatalog-diff with Ruby 2.0, 2.1, 2.2, 2.3, and 2.4)
5
+ - An appropriate Puppet version and [corresponding ruby version](https://puppet.com/docs/puppet/5.4/system_requirements.html)
6
+ - Puppet 5.x officially supports Ruby 2.4
7
+ - Puppet 4.x officially supports Ruby 2.1, but seems to work fine with later versions as well
8
+ - Puppet 3.8.7 -- we attempt to maintain compatibility in `octocatalog-diff` to facilitate upgrades even though this version is no longer supported by Puppet
9
+ - We don't officially support Puppet 3.8.6 or before
6
10
  - Mac OS, Linux, or other Unix-line operating system (Windows is not supported)
7
11
  - Ability to install gems, e.g. with [rbenv](https://github.com/rbenv/rbenv) or [rvm](https://rvm.io/), or root privileges to install into the system Ruby
8
- - Puppet agent for [Linux](https://docs.puppet.com/puppet/latest/reference/install_linux.html) or [Mac OS X](https://docs.puppet.com/puppet/latest/reference/install_osx.html), or installed as a gem (we support Puppet 3.8.7 and all versions of Puppet 4.x)
12
+ - Puppet agent for [Linux](https://docs.puppet.com/puppet/latest/reference/install_linux.html) or [Mac OS X](https://docs.puppet.com/puppet/latest/reference/install_osx.html), or installed as a gem
9
13
 
10
14
  We recommend that you also have the following to get the most out of `octocatalog-diff`, but these are not absolute requirements:
11
15
 
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'diffy'
4
+ require 'digest'
4
5
  require 'hashdiff'
5
6
  require 'json'
6
7
  require 'set'
@@ -11,6 +12,8 @@ require_relative '../errors'
11
12
  require_relative '../util/util'
12
13
  require_relative 'filter'
13
14
 
15
+ HashDiff = Hashdiff unless defined? HashDiff
16
+
14
17
  module OctocatalogDiff
15
18
  module CatalogDiff
16
19
  # Calculate the difference between two Puppet catalogs.
@@ -263,7 +266,7 @@ module OctocatalogDiff
263
266
 
264
267
  # Handle parameters
265
268
  if k == 'parameters'
266
- cleansed_param = cleanse_parameters_hash(v)
269
+ cleansed_param = cleanse_parameters_hash(v, resource.fetch('sensitive_parameters', []))
267
270
  hsh[k] = cleansed_param unless cleansed_param.nil? || cleansed_param.empty?
268
271
  elsif k == 'tags'
269
272
  # The order of tags is unimportant. Sort this array to avoid false diffs if order changes.
@@ -294,10 +297,13 @@ module OctocatalogDiff
294
297
  # Use diffy to get only the lines that have changed in a text object.
295
298
  # As we iterate through the diff, jump out if we have our answer: either
296
299
  # true if '=~>' finds ANY match, or false if '=&>' fails to find a match.
297
- Diffy::Diff.new(old_val, new_val, context: 0).each do |line|
300
+ diffy_result = Diffy::Diff.new(old_val, new_val, context: 0)
301
+ newline_alerts = diffy_result.count { |line| line.strip == '\' }
302
+ diffy_result.each do |line|
298
303
  if regex.match(line.strip)
299
304
  return true if operator == '=~>'
300
305
  elsif operator == '=&>'
306
+ next if line.strip == '\' && newline_alerts == 2
301
307
  return false
302
308
  end
303
309
  end
@@ -334,7 +340,8 @@ module OctocatalogDiff
334
340
  # =-> Attribute must have been removed and equal this
335
341
  # =~> Change must match regexp (one line of change matching is sufficient)
336
342
  # =&> Change must match regexp (all lines of change MUST match regexp)
337
- if rule_attr =~ /\A(.+?)(=[\-\+~&]?>)(.+)/m
343
+ # =s> Change must be array and contain identical elements, ignoring order
344
+ if rule_attr =~ /\A(.+?)(=[\-\+~&s]?>)(.+)/m
338
345
  rule_attr = Regexp.last_match(1)
339
346
  operator = Regexp.last_match(2)
340
347
  value = Regexp.last_match(3)
@@ -355,6 +362,9 @@ module OctocatalogDiff
355
362
  raise RegexpError, "Invalid ignore regexp for #{key}: #{exc.message}"
356
363
  end
357
364
  matcher = ->(x, y) { regexp_operator_match?(operator, my_regex, x, y) }
365
+ elsif operator == '=s>'
366
+ raise ArgumentError, "Invalid ignore option for =s>, must be '='" unless value == '='
367
+ matcher = ->(x, y) { x.is_a?(Array) && y.is_a?(Array) && Set.new(x) == Set.new(y) }
358
368
  end
359
369
  end
360
370
 
@@ -394,6 +404,13 @@ module OctocatalogDiff
394
404
  return false unless rule[:title].casecmp(hsh[:title]).zero?
395
405
  end
396
406
 
407
+ # If rule[:attr] is a regular expression, handle that case here.
408
+ if rule[:attr].is_a?(Regexp)
409
+ return false unless hsh[:attr].is_a?(String)
410
+ return false unless rule[:attr].match(hsh[:attr])
411
+ return ignore_match_true(hsh, rule)
412
+ end
413
+
397
414
  # Special 'attributes': Ignore specific diff types (+ add, - remove, ~ and ! change)
398
415
  if rule[:attr] =~ /\A[\-\+~!]+\Z/
399
416
  return ignore_match_true(hsh, rule) if rule[:attr].include?(diff_type)
@@ -446,10 +463,18 @@ module OctocatalogDiff
446
463
 
447
464
  # Cleanse parameters of filtered attributes.
448
465
  # @param parameters_hash [Hash] Hash of parameters
466
+ # @param sensitive_parameters [Array] Array of sensitive parameters
449
467
  # @return [Hash] Cleaned parameters hash (original input hash is not altered)
450
- def cleanse_parameters_hash(parameters_hash)
468
+ def cleanse_parameters_hash(parameters_hash, sensitive_parameters)
451
469
  result = parameters_hash.dup
452
470
 
471
+ # hides sensitive params. We still need to know if there's a going to
472
+ # be a diff, so we hash the value.
473
+ sensitive_parameters.each do |p|
474
+ md5 = Digest::MD5.hexdigest Marshal.dump(result[p])
475
+ result[p] = 'Sensitive [md5sum ' + md5 + ']'
476
+ end
477
+
453
478
  # 'before' and 'require' handle internal Puppet ordering but do not affect what
454
479
  # happens on the target machine. Don't consider these for the purpose of catalog diff.
455
480
  result.delete('before')
@@ -510,9 +535,11 @@ module OctocatalogDiff
510
535
  catalog2_resources = catalog2_in[:catalog]
511
536
 
512
537
  @logger.debug "Entering hashdiff_initial; catalog sizes: #{catalog1_resources.size}, #{catalog2_resources.size}"
538
+ use_lcs = @opts.fetch(:use_lcs, true)
539
+ @logger.debug "HashDiff configuration: (use_lcs: #{use_lcs})"
513
540
  result = []
514
541
  hashdiff_add_remove = Set.new
515
- hashdiff_result = HashDiff.diff(catalog1_resources, catalog2_resources, delimiter: "\f")
542
+ hashdiff_result = HashDiff.diff(catalog1_resources, catalog2_resources, delimiter: "\f", use_lcs: use_lcs)
516
543
  hashdiff_result.each do |obj|
517
544
  # Regular change
518
545
  if obj[0] == '~'
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative '../filter'
4
+ require_relative '../../util/util'
4
5
 
5
6
  module OctocatalogDiff
6
7
  module CatalogDiff
@@ -35,43 +36,46 @@ module OctocatalogDiff
35
36
 
36
37
  # Check for a change where the difference in a parameter exactly corresponds to the difference in the
37
38
  # compilation directory.
38
- if diff.change? && (diff.old_value.is_a?(String) || diff.new_value.is_a?(String))
39
- from_before = nil
40
- from_after = nil
41
- from_match = false
42
- to_before = nil
43
- to_after = nil
44
- to_match = false
39
+ if diff.change?
40
+ o = remove_compilation_dir(diff.old_value, dir2)
41
+ n = remove_compilation_dir(diff.new_value, dir1)
45
42
 
46
- if diff.old_value =~ /^(.*)#{dir2}(.*)$/m
47
- from_before = Regexp.last_match(1) || ''
48
- from_after = Regexp.last_match(2) || ''
49
- from_match = true
50
- end
51
-
52
- if diff.new_value =~ /^(.*)#{dir1}(.*)$/m
53
- to_before = Regexp.last_match(1) || ''
54
- to_after = Regexp.last_match(2) || ''
55
- to_match = true
56
- end
57
-
58
- if from_match && to_match && to_before == from_before && to_after == from_after
43
+ if o != diff.old_value || n != diff.new_value
59
44
  message = "Resource key #{diff.type}[#{diff.title}] #{diff.structure.join(' => ')}"
60
- message += ' appears to depend on catalog compilation directory. Suppressed from results.'
45
+ message += ' may depend on catalog compilation directory, but there may be differences.'
46
+ message += ' This is included in results for now, but please verify.'
61
47
  @logger.warn message
62
- return true
63
48
  end
64
49
 
65
- if from_match || to_match
50
+ if o == n
66
51
  message = "Resource key #{diff.type}[#{diff.title}] #{diff.structure.join(' => ')}"
67
- message += ' may depend on catalog compilation directory, but there may be differences.'
68
- message += ' This is included in results for now, but please verify.'
52
+ message += ' appears to depend on catalog compilation directory. Suppressed from results.'
69
53
  @logger.warn message
54
+ return true
70
55
  end
71
56
  end
72
57
 
73
58
  false
74
59
  end
60
+
61
+ def remove_compilation_dir(v, dir)
62
+ value = OctocatalogDiff::Util::Util.deep_dup(v)
63
+ traverse(value) do |e|
64
+ e.gsub!(dir, '') if e.respond_to?(:gsub!)
65
+ end
66
+ value
67
+ end
68
+
69
+ def traverse(a)
70
+ case a
71
+ when Array
72
+ a.map { |v| traverse(v, &Proc.new) }
73
+ when Hash
74
+ traverse(a.values, &Proc.new)
75
+ else
76
+ yield a
77
+ end
78
+ end
75
79
  end
76
80
  end
77
81
  end
@@ -54,9 +54,21 @@ module OctocatalogDiff
54
54
  raise ArgumentError, 'Puppet binary was not supplied' if @puppet_binary.nil?
55
55
  raise Errno::ENOENT, "Puppet binary #{@puppet_binary} doesn't exist" unless File.file?(@puppet_binary)
56
56
 
57
+ puppet_version = Gem::Version.new(@options[:puppet_version])
58
+
57
59
  # Node to compile
58
60
  cmdline = []
59
- cmdline.concat ['master', '--compile', Shellwords.escape(@node)]
61
+ # The 'puppet master --compile' command was removed in Puppet 6.x and replaced in
62
+ # Puppet 6.5 with an identically functioning 'puppet catalog compile' command.
63
+ # From versions 6.0.0 until 6.5.0 there is no compatible invocation method.
64
+ if puppet_version < Gem::Version.new('6.0.0')
65
+ cmdline.concat ['master', '--compile', Shellwords.escape(@node)]
66
+ elsif puppet_version < Gem::Version.new('6.5.0')
67
+ raise OctocatalogDiff::Errors::PuppetVersionError,
68
+ 'Octocatalog-diff does not support Puppet versions >= 6.0.0 and < 6.5.0'
69
+ else
70
+ cmdline.concat ['catalog', 'compile', Shellwords.escape(@node)]
71
+ end
60
72
 
61
73
  # storeconfigs?
62
74
  if @options[:storeconfigs]
@@ -93,11 +105,21 @@ module OctocatalogDiff
93
105
  # Some typical options for puppet
94
106
  cmdline.concat %w(
95
107
  --no-daemonize
96
- --no-ca
97
108
  --color=false
98
- --config_version="/bin/echo catalogscript"
99
109
  )
100
110
 
111
+ if puppet_version < Gem::Version.new('6.0.0')
112
+ # This config_version parameter causes an error when run with Puppet 6.x. Per
113
+ # the Puppet configuration settings docs, the below config_version argument
114
+ # may not actually be valid, but for backward compatibility's sake we'll keep it
115
+ # for the versions it has always worked with:
116
+ cmdline.concat ['--config_version="/bin/echo catalogscript"']
117
+
118
+ # The 'ca' configuration option was removed in Puppet 6, but we'll keep it
119
+ # for older versions:
120
+ cmdline.concat ['--no-ca']
121
+ end
122
+
101
123
  # Add environment - only make this variable if preserve_environments is used.
102
124
  # If preserve_environments is not used, the hard-coded 'production' here matches
103
125
  # up with the symlink created under the temporary directory structure.
@@ -13,9 +13,16 @@ module OctocatalogDiff
13
13
  # Public method: Convert file resources to text. See the description of the class
14
14
  # just above for details.
15
15
  # @param obj [OctocatalogDiff::Catalog] Catalog object (will be modified)
16
+ # @param environment [String] Environment (defaults to production)
16
17
  def self.convert_file_resources(obj, environment = 'production')
17
18
  return unless obj.valid? && obj.compilation_dir.is_a?(String) && !obj.compilation_dir.empty?
18
- _convert_file_resources(obj.resources, obj.compilation_dir, environment)
19
+ _convert_file_resources(
20
+ obj.resources,
21
+ obj.compilation_dir,
22
+ environment,
23
+ obj.options[:compare_file_text_ignore_tags],
24
+ obj.options[:tag]
25
+ )
19
26
  begin
20
27
  obj.catalog_json = ::JSON.generate(obj.catalog)
21
28
  rescue ::JSON::GeneratorError => exc
@@ -59,7 +66,7 @@ module OctocatalogDiff
59
66
  result = []
60
67
  Regexp.last_match(1).split(/:/).map(&:strip).each do |path|
61
68
  next if path.start_with?('$')
62
- result << File.expand_path(path, dir)
69
+ result.concat(Dir.glob(File.expand_path(path, dir)))
63
70
  end
64
71
  result
65
72
  else
@@ -70,8 +77,11 @@ module OctocatalogDiff
70
77
  # Internal method: Static method to convert file resources. The compilation directory is
71
78
  # required, or else this is a no-op. The passed-in array of resources is modified by this method.
72
79
  # @param resources [Array<Hash>] Array of catalog resources
73
- # @param compilation_dir [String] Compilation directory (so files can be looked up)
74
- def self._convert_file_resources(resources, compilation_dir, environment = 'production')
80
+ # @param compilation_dir [String] Compilation directory
81
+ # @param environment [String] Environment
82
+ # @param ignore_tags [Array<String>] Tags that exempt a resource from conversion
83
+ # @param to_from_tag [String] Either "to" or "from" for catalog type
84
+ def self._convert_file_resources(resources, compilation_dir, environment, ignore_tags, to_from_tag)
75
85
  # Calculate compilation directory. There is not explicit error checking here because
76
86
  # there is on-demand, explicit error checking for each file within the modification loop.
77
87
  return unless compilation_dir.is_a?(String) && compilation_dir != ''
@@ -88,14 +98,10 @@ module OctocatalogDiff
88
98
  resources.map! do |resource|
89
99
  if resource_convertible?(resource)
90
100
  path = file_path(resource['parameters']['source'], modulepaths)
91
- if path.nil?
92
- # Pass this through as a wrapped exception, because it's more likely to be something wrong
93
- # in the catalog itself than it is to be a broken setup of octocatalog-diff.
94
- message = "Errno::ENOENT: Unable to resolve '#{resource['parameters']['source']}'!"
95
- raise OctocatalogDiff::Errors::CatalogError, message
96
- end
97
101
 
98
- if File.file?(path)
102
+ if resource['tags'] && ignore_tags && (resource['tags'] & ignore_tags).any?
103
+ # Resource tagged not to be converted -- do nothing.
104
+ elsif path && File.file?(path)
99
105
  # If the file is found, read its content. If the content is all ASCII, substitute it into
100
106
  # the 'content' parameter for easier comparison. If not, instead populate the md5sum.
101
107
  # Delete the 'source' attribute as well.
@@ -103,23 +109,40 @@ module OctocatalogDiff
103
109
  is_ascii = content.force_encoding('UTF-8').ascii_only?
104
110
  resource['parameters']['content'] = is_ascii ? content : '{md5}' + Digest::MD5.hexdigest(content)
105
111
  resource['parameters'].delete('source')
106
- elsif File.exist?(path)
112
+ elsif path && File.exist?(path)
107
113
  # We are not handling recursive file installs from a directory or anything else.
108
114
  # However, the fact that we found *something* at this location indicates that the catalog
109
115
  # is probably correct. Hence, the very general .exist? check.
116
+ elsif to_from_tag == 'from'
117
+ # Don't raise an exception for an invalid source in the "from"
118
+ # catalog, because the developer may be fixing this in the "to"
119
+ # catalog. If it's broken in the "to" catalog as well, the
120
+ # exception will be raised when this code runs on that catalog.
110
121
  else
111
- # This is probably a bug
112
- # :nocov:
113
- raise "Unable to find '#{resource['parameters']['source']}' at #{path}!"
114
- # :nocov:
122
+ # Pass this through as a wrapped exception, because it's more likely to be something wrong
123
+ # in the catalog itself than it is to be a broken setup of octocatalog-diff.
124
+ #
125
+ # Example error: <OctocatalogDiff::Errors::CatalogError: Unable to resolve
126
+ # source=>'puppet:///modules/test/tmp/bar' in File[/tmp/bar]
127
+ # (/x/modules/test/manifests/init.pp:46)>
128
+ source = resource['parameters']['source']
129
+ type = resource['type']
130
+ title = resource['title']
131
+ file = resource['file'].sub(Regexp.new('^' + Regexp.escape(env_dir) + '/'), '')
132
+ line = resource['line']
133
+ message = "Unable to resolve source=>'#{source}' in #{type}[#{title}] (#{file}:#{line})"
134
+ raise OctocatalogDiff::Errors::CatalogError, message
115
135
  end
116
136
  end
137
+
117
138
  resource
118
139
  end
119
140
  end
120
141
 
121
142
  # Internal method: Determine if a resource is convertible. It is convertible if it
122
143
  # is a file resource with no declared 'content' and with a declared and parseable 'source'.
144
+ # It is not convertible if the resource is tagged with one of the tags declared by
145
+ # the option `--compare-file-text-ignore-tags`.
123
146
  # @param resource [Hash] Resource to check
124
147
  # @return [Boolean] True of resource is convertible, false if not
125
148
  def self.resource_convertible?(resource)
@@ -191,6 +191,8 @@ module OctocatalogDiff
191
191
  build
192
192
  raise OctocatalogDiff::Errors::CatalogError, 'Catalog does not appear to have been built' if !valid? && error_message.nil?
193
193
  raise OctocatalogDiff::Errors::CatalogError, error_message unless valid?
194
+ # Handle the structure returned by the /puppet/v4/catalog Puppetserver endpoint:
195
+ return @catalog['catalog']['resources'] if @catalog['catalog'].is_a?(Hash) && @catalog['catalog']['resources'].is_a?(Array)
194
196
  return @catalog['data']['resources'] if @catalog['data'].is_a?(Hash) && @catalog['data']['resources'].is_a?(Array)
195
197
  return @catalog['resources'] if @catalog['resources'].is_a?(Array)
196
198
  # This is a bug condition
@@ -304,20 +306,36 @@ module OctocatalogDiff
304
306
  unless res =~ /\A([\w:]+)\[(.+)\]\z/
305
307
  raise ArgumentError, "Resource #{res} is not in the expected format"
306
308
  end
307
- resource(type: Regexp.last_match(1), title: Regexp.last_match(2)).nil?
309
+
310
+ type = Regexp.last_match(1)
311
+ title = normalized_title(Regexp.last_match(2), type)
312
+ resource(type: type, title: title).nil?
308
313
  end
309
314
  end
310
315
 
316
+ # Private method: Given a title string, normalize it according to the rules
317
+ # used by puppet 4.10.x for file resource title normalization:
318
+ # https://github.com/puppetlabs/puppet/blob/4.10.x/lib/puppet/type/file.rb#L42
319
+ def normalized_title(title_string, type)
320
+ return title_string if type != 'File'
321
+
322
+ matches = title_string.match(%r{^(?<normalized_path>/|.+:/|.*[^/])/*\Z}m)
323
+ matches[:normalized_path] || title_string
324
+ end
325
+
311
326
  # Private method: Build the resource hash to be used used for O(1) lookups by type and title.
312
327
  # This method is called the first time the resource hash is accessed.
313
328
  def build_resource_hash
314
329
  @resource_hash = {}
315
330
  resources.each do |resource|
316
331
  @resource_hash[resource['type']] ||= {}
317
- @resource_hash[resource['type']][resource['title']] = resource
318
332
 
319
- if resource.key?('parameters') && resource['parameters'].key?('alias')
320
- @resource_hash[resource['type']][resource['parameters']['alias']] = resource
333
+ title = normalized_title(resource['title'], resource['type'])
334
+ @resource_hash[resource['type']][title] = resource
335
+
336
+ if resource.key?('parameters')
337
+ @resource_hash[resource['type']][resource['parameters']['alias']] = resource if resource['parameters'].key?('alias')
338
+ @resource_hash[resource['type']][resource['parameters']['name']] = resource if resource['parameters'].key?('name')
321
339
  end
322
340
  end
323
341
  end
@@ -147,7 +147,8 @@ module OctocatalogDiff
147
147
  puppet_binary: @puppet_binary,
148
148
  fact_file: @builddir.fact_file,
149
149
  dir: @builddir.tempdir,
150
- enc: @builddir.enc
150
+ enc: @builddir.enc,
151
+ puppet_version: puppet_version
151
152
  )
152
153
  OctocatalogDiff::CatalogUtil::Command.new(command_opts)
153
154
  end
@@ -62,16 +62,19 @@ module OctocatalogDiff
62
62
  fetch_catalog(logger)
63
63
  end
64
64
 
65
- # Returns a hash of parameters for each supported version of the Puppet Server Catalog API.
65
+ # Returns a hash of parameters for the requested version of the Puppet Server Catalog API.
66
66
  # @return [Hash] Hash of parameters
67
67
  #
68
68
  # Note: The double escaping of the facts here is implemented to correspond to a long standing
69
69
  # bug in the Puppet code. See https://github.com/puppetlabs/puppet/pull/1818 and
70
70
  # https://docs.puppet.com/puppet/latest/http_api/http_catalog.html#parameters for explanation.
71
- def puppet_catalog_api
72
- {
71
+ def puppet_catalog_api(version)
72
+ api_style = {
73
73
  2 => {
74
74
  url: "https://#{@options[:puppet_master]}/#{@options[:branch]}/catalog/#{@node}",
75
+ headers: {
76
+ 'Accept' => 'text/pson'
77
+ },
75
78
  parameters: {
76
79
  'facts_format' => 'pson',
77
80
  'facts' => CGI.escape(@facts.fudge_timestamp.without('trusted').to_pson),
@@ -80,24 +83,59 @@ module OctocatalogDiff
80
83
  },
81
84
  3 => {
82
85
  url: "https://#{@options[:puppet_master]}/puppet/v3/catalog/#{@node}",
86
+ headers: {
87
+ 'Accept' => 'text/pson'
88
+ },
83
89
  parameters: {
84
90
  'environment' => @options[:branch],
85
91
  'facts_format' => 'pson',
86
92
  'facts' => CGI.escape(@facts.fudge_timestamp.without('trusted').to_pson),
87
93
  'transaction_uuid' => SecureRandom.uuid
88
94
  }
95
+ },
96
+ 4 => {
97
+ url: "https://#{@options[:puppet_master]}/puppet/v4/catalog",
98
+ headers: {
99
+ 'Content-Type' => 'application/json'
100
+ },
101
+ parameters: {
102
+ 'certname' => @node,
103
+ 'persistence' => {
104
+ 'facts' => @options[:puppet_master_update_facts] || false,
105
+ 'catalog' => @options[:puppet_master_update_catalog] || false
106
+ },
107
+ 'environment' => @options[:branch],
108
+ 'facts' => { 'values' => @facts.facts['values'] },
109
+ 'options' => {
110
+ 'prefer_requested_environment' => true,
111
+ 'capture_logs' => false,
112
+ 'log_level' => 'warning'
113
+ },
114
+ 'transaction_uuid' => SecureRandom.uuid
115
+ }
89
116
  }
90
117
  }
118
+
119
+ params = api_style[version]
120
+ return nil if params.nil?
121
+
122
+ unless @options[:puppet_master_token].nil?
123
+ params[:headers]['X-Authentication'] = @options[:puppet_master_token]
124
+ end
125
+
126
+ params[:parameters] = params[:parameters].to_json if version >= 4
127
+
128
+ params
91
129
  end
92
130
 
93
131
  # Fetch catalog by contacting the Puppet master, sending the facts, and asking for the catalog. When the
94
132
  # catalog is returned in PSON format, parse it to JSON and then set appropriate variables.
95
133
  def fetch_catalog(logger)
96
134
  api_version = @options[:puppet_master_api_version] || DEFAULT_PUPPET_SERVER_API
97
- api = puppet_catalog_api[api_version]
135
+ api = puppet_catalog_api(api_version)
98
136
  raise ArgumentError, "Unsupported or invalid API version #{api_version}" unless api.is_a?(Hash)
99
137
 
100
- more_options = { headers: { 'Accept' => 'text/pson' }, timeout: @timeout }
138
+ more_options = { headers: api[:headers], timeout: @timeout }
101
139
  post_hash = api[:parameters]
102
140
 
103
141
  response = nil