octocatalog-diff 1.5.2 → 2.1.0
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.
- checksums.yaml +5 -5
- data/.version +1 -1
- data/README.md +4 -4
- data/doc/CHANGELOG.md +50 -0
- data/doc/advanced-compare-file-text.md +79 -0
- data/doc/advanced-environments.md +5 -5
- data/doc/advanced-ignores.md +10 -0
- data/doc/advanced-puppet-master.md +25 -7
- data/doc/configuration-puppet.md +19 -19
- data/doc/configuration-puppetdb.md +8 -0
- data/doc/configuration.md +31 -31
- data/doc/dev/api/v1/calls/catalog-diff.md +6 -2
- data/doc/dev/api/v1/objects/diff.md +3 -3
- data/doc/dev/integration-tests.md +2 -2
- data/doc/dev/releasing.md +41 -41
- data/doc/dev/run-from-branch.md +23 -23
- data/doc/installation.md +14 -14
- data/doc/limitations.md +9 -9
- data/doc/optionsref.md +166 -11
- data/doc/requirements.md +6 -2
- data/lib/octocatalog-diff/catalog-diff/differ.rb +32 -5
- data/lib/octocatalog-diff/catalog-diff/filter/compilation_dir.rb +29 -25
- data/lib/octocatalog-diff/catalog-util/command.rb +25 -3
- data/lib/octocatalog-diff/catalog-util/fileresources.rb +39 -16
- data/lib/octocatalog-diff/catalog.rb +22 -4
- data/lib/octocatalog-diff/catalog/computed.rb +2 -1
- data/lib/octocatalog-diff/catalog/puppetmaster.rb +43 -5
- data/lib/octocatalog-diff/cli.rb +38 -6
- data/lib/octocatalog-diff/cli/options.rb +36 -1
- data/lib/octocatalog-diff/cli/options/compare_file_text.rb +18 -0
- data/lib/octocatalog-diff/cli/options/hostname.rb +13 -2
- data/lib/octocatalog-diff/cli/options/puppet_master_api_version.rb +2 -2
- data/lib/octocatalog-diff/cli/options/puppet_master_token.rb +20 -0
- data/lib/octocatalog-diff/cli/options/puppet_master_token_file.rb +35 -0
- data/lib/octocatalog-diff/cli/options/puppet_master_update_catalog.rb +20 -0
- data/lib/octocatalog-diff/cli/options/puppet_master_update_facts.rb +20 -0
- data/lib/octocatalog-diff/cli/options/puppetdb_package_inventory.rb +18 -0
- data/lib/octocatalog-diff/cli/options/use_lcs.rb +14 -0
- data/lib/octocatalog-diff/facts/json.rb +13 -2
- data/lib/octocatalog-diff/facts/puppetdb.rb +43 -2
- data/lib/octocatalog-diff/util/parallel.rb +20 -16
- data/lib/octocatalog-diff/util/util.rb +2 -0
- data/scripts/env/env.sh +1 -1
- data/scripts/git-extract/git-extract.sh +1 -1
- data/scripts/puppet/puppet.sh +1 -1
- 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
|
-
-
|
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
|
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)
|
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
|
-
|
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?
|
39
|
-
|
40
|
-
|
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
|
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 += '
|
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
|
50
|
+
if o == n
|
66
51
|
message = "Resource key #{diff.type}[#{diff.title}] #{diff.structure.join(' => ')}"
|
67
|
-
message += '
|
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
|
-
|
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(
|
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
|
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
|
74
|
-
|
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
|
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
|
-
#
|
112
|
-
#
|
113
|
-
|
114
|
-
# :
|
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
|
-
|
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
|
-
|
320
|
-
|
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
|
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
|
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:
|
138
|
+
more_options = { headers: api[:headers], timeout: @timeout }
|
101
139
|
post_hash = api[:parameters]
|
102
140
|
|
103
141
|
response = nil
|