octocatalog-diff 1.1.0 → 1.2.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 +4 -4
- data/.version +1 -1
- data/doc/CHANGELOG.md +10 -0
- data/doc/advanced-override-facts.md +13 -0
- data/doc/dev/integration-tests.md +2 -2
- data/doc/requirements.md +2 -2
- data/lib/octocatalog-diff/api/v1/override.rb +5 -3
- data/lib/octocatalog-diff/bootstrap.rb +1 -1
- data/lib/octocatalog-diff/catalog-diff/differ.rb +1 -1
- data/lib/octocatalog-diff/catalog-diff/display/text.rb +32 -13
- data/lib/octocatalog-diff/catalog-util/bootstrap.rb +3 -0
- data/lib/octocatalog-diff/catalog-util/builddir.rb +9 -6
- data/lib/octocatalog-diff/catalog-util/command.rb +3 -2
- data/lib/octocatalog-diff/catalog-util/enc.rb +5 -2
- data/lib/octocatalog-diff/catalog-util/fileresources.rb +6 -1
- data/lib/octocatalog-diff/catalog/computed.rb +1 -1
- data/lib/octocatalog-diff/catalog/puppetdb.rb +1 -1
- data/lib/octocatalog-diff/catalog/puppetmaster.rb +3 -3
- data/lib/octocatalog-diff/cli.rb +1 -1
- data/lib/octocatalog-diff/cli/options/puppet_master_timeout.rb +1 -1
- data/lib/octocatalog-diff/facts.rb +7 -0
- data/lib/octocatalog-diff/facts/puppetdb.rb +1 -1
- data/lib/octocatalog-diff/puppetdb.rb +3 -3
- data/lib/octocatalog-diff/util/catalogs.rb +20 -6
- data/lib/octocatalog-diff/util/httparty.rb +14 -6
- data/lib/octocatalog-diff/util/parallel.rb +138 -83
- metadata +2 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 75c0856d744b0c909315a69aceb6e2d5e416991b
|
4
|
+
data.tar.gz: 127ac1617cc437a73d716ff68a0eea0623e62c8b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a4a873ed14e553d8362267e88fdac5eeaf46e5929275601871048959805f93515af9f8d8152115e49f1c15ddf17fc38fa70158b1e001be71d20a0646f05c422e
|
7
|
+
data.tar.gz: 7c3f9a881d1e2b9d8669c96d6ffbb9953a25b8ff2c63f2a9c3151201c09be9ee95c6182b040370b08a54720c830e98f701cbe3f903c7b876ae80ec140b08a3b0
|
data/.version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.
|
1
|
+
1.2.0
|
data/doc/CHANGELOG.md
CHANGED
@@ -8,6 +8,16 @@
|
|
8
8
|
</tr>
|
9
9
|
</thead><tbody>
|
10
10
|
<tr valign=top>
|
11
|
+
<td>1.2.0</td>
|
12
|
+
<td>2017-05-18</td>
|
13
|
+
<td>
|
14
|
+
<li><a href="https://github.com/github/octocatalog-diff/pull/112">#112</a>: Split arguments added for ENC</li>
|
15
|
+
<li><a href="https://github.com/github/octocatalog-diff/pull/113">#113</a>: (Enhancement) Override facts and ENC parameters using regular expressions</li>
|
16
|
+
<li><a href="https://github.com/github/octocatalog-diff/pull/103">#111</a>: Simplify parallel processing to solve some intermittent failures</li>
|
17
|
+
<li><a href="https://github.com/github/octocatalog-diff/pull/110">#110</a>: Ruby 2.4 compatibility</li>
|
18
|
+
</td>
|
19
|
+
</tr>
|
20
|
+
<tr valign=top>
|
11
21
|
<td>1.1.0</td>
|
12
22
|
<td>2017-05-08</td>
|
13
23
|
<td>
|
@@ -65,3 +65,16 @@ The following data types in parentheses are supported:
|
|
65
65
|
| `(json)` | Treat the input as a JSON string (calls `JSON.parse` in ruby) |
|
66
66
|
| `(boolean)` | Treat the input as a boolean -- it must be `true` or `false`, case-insensitive |
|
67
67
|
| `(nil)` | Ignore any characters after `(nil)` and deletes the fact if the fact exists |
|
68
|
+
|
69
|
+
## Regular expressions
|
70
|
+
|
71
|
+
If you wish to match multiple facts by pattern, specify the regular expression in place of the key name. For example:
|
72
|
+
|
73
|
+
```
|
74
|
+
octocatalog-diff -n some-node.example.com -f master -t master \
|
75
|
+
--to-fact-override /^ipaddress/=10.11.12.13
|
76
|
+
```
|
77
|
+
|
78
|
+
In this example, `$::ipaddress`, `$::ipaddress_eth0`, `$::ipaddress_bond0`, and any other facts starting with "ipaddress" would be overridden. However, a fact named `$::additional_ipaddress` would not be overridden, because it does not match the regular expression.
|
79
|
+
|
80
|
+
Please note that you cannot *add* a fact with a regular expression -- when using regular expressions you can only modify or delete facts.
|
@@ -22,7 +22,7 @@ describe 'whatever behavior' do
|
|
22
22
|
# @result[:logs] is a String containing everything printed to STDERR (Logger)
|
23
23
|
# @result[:output] is a String containing everything printed to STDOUT
|
24
24
|
# @result[:diffs] is an Array of differences
|
25
|
-
# @result[:exitcode] is
|
25
|
+
# @result[:exitcode] is an Integer representing the exit code: 0 = no changes, 1 = failure, 2 = success, with changes
|
26
26
|
# @result[:exception] contains any exception that was thrown
|
27
27
|
end
|
28
28
|
|
@@ -46,7 +46,7 @@ describe 'whatever behavior' do
|
|
46
46
|
# @result[:logs] is a String containing everything printed to STDERR (Logger)
|
47
47
|
# @result[:output] is a String containing everything printed to STDOUT
|
48
48
|
# @result[:diffs] is an Array of differences
|
49
|
-
# @result[:exitcode] is
|
49
|
+
# @result[:exitcode] is an Integer representing the exit code: 0 = no changes, 1 = failure, 2 = success, with changes
|
50
50
|
# @result[:exception] contains any exception that was thrown
|
51
51
|
end
|
52
52
|
|
data/doc/requirements.md
CHANGED
@@ -2,10 +2,10 @@
|
|
2
2
|
|
3
3
|
To run `octocatalog-diff` you will need these basics:
|
4
4
|
|
5
|
-
- Ruby 2.0
|
5
|
+
- Ruby 2.0 through 2.4 (we test octocatalog-diff with Ruby 2.0, 2.1, 2.2, 2.3, and 2.4)
|
6
6
|
- Mac OS, Linux, or other Unix-line operating system (Windows is not supported)
|
7
7
|
- 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
|
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)
|
9
9
|
|
10
10
|
We recommend that you also have the following to get the most out of `octocatalog-diff`, but these are not absolute requirements:
|
11
11
|
|
@@ -13,7 +13,8 @@ module OctocatalogDiff
|
|
13
13
|
# Constructor: Accepts a key and value.
|
14
14
|
# @param input [Hash] Must contain :key and :value
|
15
15
|
def initialize(input)
|
16
|
-
|
16
|
+
key = input.fetch(:key)
|
17
|
+
@key = key =~ %r{\A/(.+)/\Z} ? Regexp.new(Regexp.last_match(1)) : key
|
17
18
|
@value = parsed_value(input.fetch(:value))
|
18
19
|
end
|
19
20
|
|
@@ -29,6 +30,7 @@ module OctocatalogDiff
|
|
29
30
|
# If input is not a string, we can still construct the object if the key is given.
|
30
31
|
# That input would come directly from code and not from the command line, since inputs
|
31
32
|
# from the command line are always strings.
|
33
|
+
# Also support regular expressions for the key name, if delimited by //.
|
32
34
|
if key.nil? && input.is_a?(String)
|
33
35
|
unless input.include?('=')
|
34
36
|
raise ArgumentError, "Fact override '#{input}' is not in 'key=(data type)value' format"
|
@@ -72,9 +74,9 @@ module OctocatalogDiff
|
|
72
74
|
return value if datatype == 'string'
|
73
75
|
return parse_json(value) if datatype == 'json'
|
74
76
|
return nil if datatype == 'nil'
|
75
|
-
if datatype == 'fixnum'
|
77
|
+
if datatype == 'fixnum' || datatype == 'integer'
|
76
78
|
return Regexp.last_match(1).to_i if value =~ /^(-?\d+)$/
|
77
|
-
raise ArgumentError, "Illegal
|
79
|
+
raise ArgumentError, "Illegal integer '#{value}'"
|
78
80
|
end
|
79
81
|
if datatype == 'float'
|
80
82
|
return Regexp.last_match(1).to_f if value =~ /^(-?\d*\.\d+)$/
|
@@ -10,7 +10,7 @@ module OctocatalogDiff
|
|
10
10
|
# @param options [Hash] Options hash:
|
11
11
|
# :path [String] => Directory to bootstrap
|
12
12
|
# :bootstrap_script [String] => Bootstrap script, relative to directory
|
13
|
-
# @return [Hash] => [
|
13
|
+
# @return [Hash] => [Integer] :status_code, [String] :output
|
14
14
|
def self.bootstrap(options = {})
|
15
15
|
# Options validation
|
16
16
|
unless options[:path].is_a?(String)
|
@@ -530,7 +530,7 @@ module OctocatalogDiff
|
|
530
530
|
|
531
531
|
# Added a new key that points to some kind of data structure that we know how
|
532
532
|
# to handle.
|
533
|
-
if obj[1] =~ /^(.+)\f([^\f]+)$/ && [String,
|
533
|
+
if obj[1] =~ /^(.+)\f([^\f]+)$/ && [String, Integer, Float, TrueClass, FalseClass, Array, Hash].include?(obj[2].class)
|
534
534
|
hashdiff_add_remove.add(obj[1])
|
535
535
|
next
|
536
536
|
end
|
@@ -255,7 +255,7 @@ module OctocatalogDiff
|
|
255
255
|
# Get the diff of two long strings. Call the 'diffy' gem for this.
|
256
256
|
# @param string1 [String] First string (-)
|
257
257
|
# @param string2 [String] Second string (+)
|
258
|
-
# @param depth [
|
258
|
+
# @param depth [Integer] Depth, for correct indentation
|
259
259
|
# @return Array<String> Displayable result
|
260
260
|
def self.diff_two_strings_with_diffy(string1, string2, depth)
|
261
261
|
# Single line strings?
|
@@ -324,8 +324,8 @@ module OctocatalogDiff
|
|
324
324
|
# Get the diff of two hashes. Call the 'diffy' gem for this.
|
325
325
|
# @param hash1 [Hash] First hash (-)
|
326
326
|
# @param hash1 [Hash] Second hash (+)
|
327
|
-
# @param depth [
|
328
|
-
# @param limit [
|
327
|
+
# @param depth [Integer] Depth, for correct indentation
|
328
|
+
# @param limit [Integer] Maximum string length
|
329
329
|
# @param strip_diff [Boolean] Strip leading +/-/" "
|
330
330
|
# @return [Array<String>] Displayable result
|
331
331
|
def self.diff_two_hashes_with_diffy(opts = {})
|
@@ -358,7 +358,7 @@ module OctocatalogDiff
|
|
358
358
|
end
|
359
359
|
|
360
360
|
# Special case: addition only, no truncation
|
361
|
-
# @param depth [
|
361
|
+
# @param depth [Integer] Depth, for correct indentation
|
362
362
|
# @param hash [Hash] Added object
|
363
363
|
# @return [Array<String>] Displayable result
|
364
364
|
def self.addition_only_no_truncation(depth, hash)
|
@@ -383,7 +383,7 @@ module OctocatalogDiff
|
|
383
383
|
|
384
384
|
# Limit length of a string
|
385
385
|
# @param str [String] String
|
386
|
-
# @param limit [
|
386
|
+
# @param limit [Integer] Limit (0=unlimited)
|
387
387
|
# @return [String] Truncated string
|
388
388
|
def self.truncate_string(str, limit)
|
389
389
|
return str if limit.nil? || str.length <= limit
|
@@ -392,7 +392,7 @@ module OctocatalogDiff
|
|
392
392
|
|
393
393
|
# Get the diff between two hashes. This is recursive-aware.
|
394
394
|
# @param obj [diff object] diff object
|
395
|
-
# @param depth [
|
395
|
+
# @param depth [Integer] Depth of nesting, used for indentation
|
396
396
|
# @return Array<String> Printable diff outputs
|
397
397
|
def self.hash_diff(obj, depth, key_in, nested = false)
|
398
398
|
result = []
|
@@ -417,7 +417,7 @@ module OctocatalogDiff
|
|
417
417
|
end
|
418
418
|
|
419
419
|
# Get the diff between two arbitrary objects
|
420
|
-
# @param depth [
|
420
|
+
# @param depth [Integer] Depth of nesting, used for indentation
|
421
421
|
# @param old_obj [?] Old object
|
422
422
|
# @param new_obj [?] New object
|
423
423
|
# @return Array<String> Diff output
|
@@ -432,12 +432,31 @@ module OctocatalogDiff
|
|
432
432
|
|
433
433
|
# Utility Method!
|
434
434
|
# Indent a given text string with a certain number of spaces
|
435
|
-
# @param spaces [
|
435
|
+
# @param spaces [Integer] Number of spaces
|
436
436
|
# @param text [String] Text
|
437
437
|
def self.left_pad(spaces, text = '')
|
438
438
|
[' ' * spaces, text].join('')
|
439
439
|
end
|
440
440
|
|
441
|
+
# Utility Method!
|
442
|
+
# Harmonize equivalent class names for comparison purposes.
|
443
|
+
# @param class_name [String] Class name as input
|
444
|
+
# @return [String] Class name as output
|
445
|
+
def self.class_name_for_diffy(class_name)
|
446
|
+
return 'Integer' if class_name == 'Fixnum'
|
447
|
+
class_name
|
448
|
+
end
|
449
|
+
|
450
|
+
# Utility Method!
|
451
|
+
# `is_a?(class)` only allows one method, but this uses an array
|
452
|
+
# @param object [?] Object to consider
|
453
|
+
# @param classes [Array] Classes to determine if object is a member of
|
454
|
+
# @return [Boolean] True if object is_a any of the classes, false otherwise
|
455
|
+
def self.object_is_any_of?(object, classes)
|
456
|
+
classes.each { |clazz| return true if object.is_a? clazz }
|
457
|
+
false
|
458
|
+
end
|
459
|
+
|
441
460
|
# Utility Method!
|
442
461
|
# Given an arbitrary object, convert it into a string for use by 'diffy'.
|
443
462
|
# This basically exists so we can do something prettier than just calling .inspect or .to_s
|
@@ -445,10 +464,10 @@ module OctocatalogDiff
|
|
445
464
|
# @param obj [?] Object to be stringified
|
446
465
|
# @return [String] String representation of object for diffy
|
447
466
|
def self.stringify_for_diffy(obj)
|
448
|
-
return JSON.pretty_generate(obj) if [Hash, Array]
|
467
|
+
return JSON.pretty_generate(obj) if object_is_any_of?(obj, [Hash, Array])
|
449
468
|
return '""' if obj.is_a?(String) && obj == ''
|
450
|
-
return obj if [String, Fixnum, Float]
|
451
|
-
"#{obj.class}: #{obj.inspect}"
|
469
|
+
return obj if object_is_any_of?(obj, [String, Fixnum, Integer, Float])
|
470
|
+
"#{class_name_for_diffy(obj.class)}: #{obj.inspect}"
|
452
471
|
end
|
453
472
|
|
454
473
|
# Utility Method!
|
@@ -512,8 +531,8 @@ module OctocatalogDiff
|
|
512
531
|
return ['""', 'undef'] if obj2.nil?
|
513
532
|
|
514
533
|
# If one is an integer and the other is a string
|
515
|
-
return [obj1, "\"#{obj2}\""] if obj1.is_a?(
|
516
|
-
return ["\"#{obj1}\"", obj2] if obj1.is_a?(String) && obj2.is_a?(
|
534
|
+
return [obj1, "\"#{obj2}\""] if obj1.is_a?(Integer) && obj2.is_a?(String)
|
535
|
+
return ["\"#{obj1}\"", obj2] if obj1.is_a?(String) && obj2.is_a?(Integer)
|
517
536
|
|
518
537
|
# True and false
|
519
538
|
return [obj1, "\"#{obj2}\""] if obj1.is_a?(TrueClass) && obj2.is_a?(String)
|
@@ -74,8 +74,11 @@ module OctocatalogDiff
|
|
74
74
|
if result.status
|
75
75
|
logger.debug("Success bootstrap_directory for #{result.args[:tag]}")
|
76
76
|
else
|
77
|
+
# Believed to be a bug condition, since error should have already been raised if this happens.
|
78
|
+
# :nocov:
|
77
79
|
errmsg = "Failed bootstrap_directory for #{result.args[:tag]}: #{result.exception.class} #{result.exception.message}"
|
78
80
|
raise OctocatalogDiff::Errors::BootstrapError, errmsg
|
81
|
+
# :nocov:
|
79
82
|
end
|
80
83
|
end
|
81
84
|
end
|
@@ -21,7 +21,7 @@ module OctocatalogDiff
|
|
21
21
|
# Constructor
|
22
22
|
# Options for constructor:
|
23
23
|
# :puppetdb_url [String] PuppetDB Server URLs
|
24
|
-
# :puppetdb_server_url_timeout [
|
24
|
+
# :puppetdb_server_url_timeout [Integer] Timeout (seconds) for puppetdb.conf
|
25
25
|
# :facts [OctocatalogDiff::Facts] Facts object
|
26
26
|
# :fact_file [String] File from which to read facts
|
27
27
|
# :node [String] Node name
|
@@ -99,14 +99,14 @@ module OctocatalogDiff
|
|
99
99
|
|
100
100
|
# Install puppetdb.conf file in temporary directory
|
101
101
|
# @param server_urls [String] String for server_urls in puppetdb.conf
|
102
|
-
# @param server_url_timeout [
|
102
|
+
# @param server_url_timeout [Integer] Value for server_url_timeout in puppetdb.conf
|
103
103
|
def install_puppetdb_conf(logger, server_urls, server_url_timeout = 30)
|
104
104
|
unless server_urls.is_a?(String)
|
105
105
|
raise ArgumentError, "server_urls must be a string, got a: #{server_urls.class}"
|
106
106
|
end
|
107
107
|
|
108
108
|
server_url_timeout ||= 30 # If called with nil argument, supply default
|
109
|
-
unless server_url_timeout.is_a?(
|
109
|
+
unless server_url_timeout.is_a?(Integer)
|
110
110
|
raise ArgumentError, "server_url_timeout must be a fixnum, got a: #{server_url_timeout.class}"
|
111
111
|
end
|
112
112
|
|
@@ -163,9 +163,12 @@ module OctocatalogDiff
|
|
163
163
|
|
164
164
|
if options[:fact_override].is_a?(Array)
|
165
165
|
options[:fact_override].each do |override|
|
166
|
-
|
167
|
-
|
168
|
-
|
166
|
+
keys = override.key.is_a?(Regexp) ? facts.matching(override.key) : [override.key]
|
167
|
+
keys.each do |key|
|
168
|
+
old_value = facts.fact(key)
|
169
|
+
facts.override(key, override.value)
|
170
|
+
logger.debug("Override #{key} from #{old_value.inspect} to #{override.value.inspect}")
|
171
|
+
end
|
169
172
|
end
|
170
173
|
end
|
171
174
|
|
@@ -68,7 +68,8 @@ module OctocatalogDiff
|
|
68
68
|
# enc?
|
69
69
|
if @options[:enc]
|
70
70
|
raise Errno::ENOENT, "Did not find ENC as expected at #{@options[:enc]}" unless File.file?(@options[:enc])
|
71
|
-
cmdline <<
|
71
|
+
cmdline << '--node_terminus=exec'
|
72
|
+
cmdline << "--external_nodes=#{Shellwords.escape(@options[:enc])}"
|
72
73
|
end
|
73
74
|
|
74
75
|
# Future parser?
|
@@ -167,7 +168,7 @@ module OctocatalogDiff
|
|
167
168
|
# the index.
|
168
169
|
# @param cmdline [Array] Existing command line
|
169
170
|
# @param key [String] Key to look up
|
170
|
-
# @return [
|
171
|
+
# @return [Integer] Index of where key is defined (nil if undefined)
|
171
172
|
def key_position(cmdline, key)
|
172
173
|
cmdline.index { |x| x == "--#{key}" || x =~ /\A--#{key}=/ }
|
173
174
|
end
|
@@ -65,8 +65,11 @@ module OctocatalogDiff
|
|
65
65
|
return unless @options[:enc_override].is_a?(Array) && @options[:enc_override].any?
|
66
66
|
content_structure = YAML.load(content)
|
67
67
|
@options[:enc_override].each do |x|
|
68
|
-
|
69
|
-
|
68
|
+
keys = x.key.is_a?(Regexp) ? content_structure.keys.select { |y| x.key.match(y) } : [x.key]
|
69
|
+
keys.each do |key|
|
70
|
+
merge_enc_param(content_structure, key, x.value)
|
71
|
+
logger.debug "ENC override: #{key} #{x.value.nil? ? 'DELETED' : '= ' + x.value.inspect}"
|
72
|
+
end
|
70
73
|
end
|
71
74
|
@content = content_structure.to_yaml
|
72
75
|
end
|
@@ -86,7 +86,12 @@ module OctocatalogDiff
|
|
86
86
|
resources.map! do |resource|
|
87
87
|
if resource_convertible?(resource)
|
88
88
|
path = file_path(resource['parameters']['source'], modulepaths)
|
89
|
-
|
89
|
+
if path.nil?
|
90
|
+
# Pass this through as a wrapped exception, because it's more likely to be something wrong
|
91
|
+
# in the catalog itself than it is to be a broken setup of octocatalog-diff.
|
92
|
+
message = "Errno::ENOENT: Unable to resolve '#{resource['parameters']['source']}'!"
|
93
|
+
raise OctocatalogDiff::Errors::CatalogError, message
|
94
|
+
end
|
90
95
|
|
91
96
|
if File.file?(path)
|
92
97
|
# If the file is found, read its content. If the content is all ASCII, substitute it into
|
@@ -22,7 +22,7 @@ module OctocatalogDiff
|
|
22
22
|
# @param :node [String] REQUIRED: Node name
|
23
23
|
# @param :basedir [String] Directory in which to compile the catalog
|
24
24
|
# @param :pass_env_vars [Array<String>] Environment variables to pass when compiling catalog
|
25
|
-
# @param :retry_failed_catalog [
|
25
|
+
# @param :retry_failed_catalog [Integer] Number of retries if a catalog compilation fails
|
26
26
|
# @param :tag [String] For display purposes, the catalog being compiled
|
27
27
|
# @param :puppet_binary [String] Full path to Puppet
|
28
28
|
# @param :puppet_version [String] Puppet version (optional; if not supplied, it is calculated)
|
@@ -15,7 +15,7 @@ module OctocatalogDiff
|
|
15
15
|
|
16
16
|
# Constructor - See OctocatalogDiff::PuppetDB for additional parameters
|
17
17
|
# @param :node [String] Node name
|
18
|
-
# @param :retry [
|
18
|
+
# @param :retry [Integer] Number of retries, if fetch fails
|
19
19
|
def initialize(options)
|
20
20
|
raise ArgumentError, 'Hash of options must be passed to OctocatalogDiff::Catalog::PuppetDB' unless options.is_a?(Hash)
|
21
21
|
raise ArgumentError, 'node must be a non-empty string' unless options[:node].is_a?(String) && options[:node] != ''
|
@@ -22,17 +22,17 @@ module OctocatalogDiff
|
|
22
22
|
|
23
23
|
# Constructor
|
24
24
|
# @param :node [String] Node name
|
25
|
-
# @param :retry_failed_catalog [
|
25
|
+
# @param :retry_failed_catalog [Integer] Number of retries, if fetch fails
|
26
26
|
# @param :branch [String] Environment to fetch from Puppet Master
|
27
27
|
# @param :puppet_master [String] Puppet server and port number (assumed to be DEFAULT_PUPPET_PORT_NUMBER if not given)
|
28
|
-
# @param :puppet_master_api_version [
|
28
|
+
# @param :puppet_master_api_version [Integer] Puppet server API (default DEFAULT_PUPPET_SERVER_API)
|
29
29
|
# @param :puppet_master_ssl_ca [String] Path to file used to sign puppet master's certificate
|
30
30
|
# @param :puppet_master_ssl_verify [Boolean] Override the CA verification setting guessed from parameters
|
31
31
|
# @param :puppet_master_ssl_client_pem [String] PEM-encoded client key and certificate
|
32
32
|
# @param :puppet_master_ssl_client_p12 [String] pkcs12-encoded client key and certificate
|
33
33
|
# @param :puppet_master_ssl_client_password [String] Path to file containing password for SSL client key (any format)
|
34
34
|
# @param :puppet_master_ssl_client_auth [Boolean] Override the client-auth that is guessed from parameters
|
35
|
-
# @param :timeout [
|
35
|
+
# @param :timeout [Integer] Connection timeout for Puppet master (default=PUPPET_MASTER_TIMEOUT seconds)
|
36
36
|
def initialize(options)
|
37
37
|
raise ArgumentError, 'Hash of options must be passed to OctocatalogDiff::Catalog::PuppetMaster' unless options.is_a?(Hash)
|
38
38
|
raise ArgumentError, 'node must be a non-empty string' unless options[:node].is_a?(String) && options[:node] != ''
|
data/lib/octocatalog-diff/cli.rb
CHANGED
@@ -50,7 +50,7 @@ module OctocatalogDiff
|
|
50
50
|
# @param argv [Array] Use specified arguments (defaults to ARGV)
|
51
51
|
# @param logger [Logger] Logger object
|
52
52
|
# @param opts [Hash] Additional options
|
53
|
-
# @return [
|
53
|
+
# @return [Integer] Exit code: 0=no diffs, 1=something went wrong, 2=worked but there are diffs
|
54
54
|
def self.cli(argv = ARGV, logger = Logger.new(STDERR), opts = {})
|
55
55
|
# Save a copy of argv to print out later in debugging
|
56
56
|
argv_save = argv.dup
|
@@ -14,7 +14,7 @@ OctocatalogDiff::Cli::Options::Option.newoption(:puppet_master_timeout) do
|
|
14
14
|
cli_name: 'puppet-master-timeout',
|
15
15
|
option_name: 'puppet_master_timeout',
|
16
16
|
desc: 'Puppet Master catalog retrieval timeout in seconds',
|
17
|
-
validator: ->(x) { x.to_i > 0 || raise(ArgumentError, 'Specify timeout as
|
17
|
+
validator: ->(x) { x.to_i > 0 || raise(ArgumentError, 'Specify timeout as an integer greater than 0') },
|
18
18
|
translator: ->(x) { x.to_i }
|
19
19
|
)
|
20
20
|
end
|
@@ -120,5 +120,12 @@ module OctocatalogDiff
|
|
120
120
|
@facts['values'][key] = value
|
121
121
|
end
|
122
122
|
end
|
123
|
+
|
124
|
+
# Find all facts matching a particular pattern
|
125
|
+
# @param regex [Regexp] Regular expression to match
|
126
|
+
# @return [Array<String>] Facts that match the regexp
|
127
|
+
def matching(regex)
|
128
|
+
@facts['values'].keys.select { |fact| regex.match(fact) }
|
129
|
+
end
|
123
130
|
end
|
124
131
|
end
|
@@ -17,7 +17,7 @@ module OctocatalogDiff
|
|
17
17
|
|
18
18
|
# Retrieve facts from PuppetDB for a specified node.
|
19
19
|
# @param :puppetdb_url [String|Array] => URL to PuppetDB
|
20
|
-
# @param :retry [
|
20
|
+
# @param :retry [Integer] => Retry after timeout (default 0 retries, can be more)
|
21
21
|
# @param node [String] Node name. (REQUIRED for PuppetDB fact source)
|
22
22
|
# @return [Hash] Facts
|
23
23
|
def self.fact_retriever(options = {}, node)
|
@@ -38,7 +38,7 @@ module OctocatalogDiff
|
|
38
38
|
# Supported arguments:
|
39
39
|
# @param :puppetdb_url [String or Array<String>] PuppetDB URL(s) to try in random order
|
40
40
|
# @param :puppetdb_host [String] PuppetDB hostname, when constructing a URL
|
41
|
-
# @param :puppetdb_port [
|
41
|
+
# @param :puppetdb_port [Integer] Port number, defaults to 8080 (non-SSL) or 8081 (SSL)
|
42
42
|
# @param :puppetdb_ssl [Boolean] defaults to true, because you should use SSL
|
43
43
|
# @param :puppetdb_ssl_ca [String] Path to file containing CA certificate
|
44
44
|
# @param :puppetdb_ssl_verify [Boolean] Override the CA verification setting guessed from parameters
|
@@ -46,7 +46,7 @@ module OctocatalogDiff
|
|
46
46
|
# @param :puppetdb_ssl_client_p12 [String] pkcs12-encoded client key and certificate
|
47
47
|
# @param :puppetdb_ssl_client_password [String] Path to file containing password for SSL client key (any format)
|
48
48
|
# @param :puppetdb_ssl_client_auth [Boolean] Override the client-auth that is guessed from parameters
|
49
|
-
# @param :timeout [
|
49
|
+
# @param :timeout [Integer] Connection timeout for PuppetDB (default=10)
|
50
50
|
def initialize(options = {})
|
51
51
|
@connections =
|
52
52
|
if options.key?(:puppetdb_url)
|
@@ -149,7 +149,7 @@ module OctocatalogDiff
|
|
149
149
|
|
150
150
|
# Parse a URL to determine hostname, port number, and whether or not SSL is used.
|
151
151
|
# @param url [String] URL to parse
|
152
|
-
# @return [Hash] { ssl: true/false, host: <String>, port: <
|
152
|
+
# @return [Hash] { ssl: true/false, host: <String>, port: <Integer> }
|
153
153
|
def parse_url(url)
|
154
154
|
uri = URI(url)
|
155
155
|
raise ArgumentError, "URL #{url} has invalid scheme" unless uri.scheme =~ /^https?$/
|
@@ -99,6 +99,15 @@ module OctocatalogDiff
|
|
99
99
|
# :nocov:
|
100
100
|
end
|
101
101
|
|
102
|
+
# If catalogs failed to compile, report that. Prefer to display an actual failure message rather
|
103
|
+
# than a generic incomplete parallel task message if there is a more specific message present.
|
104
|
+
failures = parallel_catalogs.reject(&:status)
|
105
|
+
if failures.any?
|
106
|
+
f = failures.reject { |r| r.exception.is_a?(OctocatalogDiff::Util::Parallel::IncompleteTask) }.first
|
107
|
+
f ||= failures.first
|
108
|
+
raise f.exception
|
109
|
+
end
|
110
|
+
|
102
111
|
# Construct result hash. Will eventually be in the format
|
103
112
|
# { :from => OctocatalogDiff::Catalog, :to => OctocatalogDiff::Catalog }
|
104
113
|
|
@@ -203,10 +212,12 @@ module OctocatalogDiff
|
|
203
212
|
end
|
204
213
|
else
|
205
214
|
# Something unhandled went wrong, and an exception was thrown. Reveal a generic message.
|
215
|
+
# :nocov:
|
206
216
|
msg = parallel_catalog_obj.exception.message
|
207
217
|
message = "Catalog for '#{key}' (#{branch}) failed to compile with #{parallel_catalog_obj.exception.class}: #{msg}"
|
208
218
|
message += "\n" + parallel_catalog_obj.exception.backtrace.map { |x| " #{x}" }.join("\n") if @options[:debug]
|
209
219
|
raise OctocatalogDiff::Errors::CatalogError, message
|
220
|
+
# :nocov:
|
210
221
|
end
|
211
222
|
end
|
212
223
|
|
@@ -220,22 +231,25 @@ module OctocatalogDiff
|
|
220
231
|
time_start = Time.now
|
221
232
|
catalog.build(logger)
|
222
233
|
time_it_took = Time.now - time_start
|
223
|
-
retries_str = " retries = #{catalog.retries}" if catalog.retries.is_a?(
|
234
|
+
retries_str = " retries = #{catalog.retries}" if catalog.retries.is_a?(Integer)
|
224
235
|
time_str = "in #{time_it_took} seconds#{retries_str}"
|
225
236
|
status_str = catalog.valid? ? 'successfully built' : 'failed'
|
226
237
|
logger.debug "Catalog for #{opts[:branch]} #{status_str} with #{catalog.builder} #{time_str}"
|
227
238
|
catalog
|
228
239
|
end
|
229
240
|
|
230
|
-
#
|
241
|
+
# The catalog validator method can indicate failure one of two ways:
|
242
|
+
# - Raise an exception (this is preferred, since it gives a specific error message)
|
243
|
+
# - Return false (supported but discouraged, since it only surfaces a generic error)
|
231
244
|
# @param catalog [OctocatalogDiff::Catalog] Catalog object
|
232
245
|
# @param logger [Logger] Logger object (presently unused)
|
233
246
|
# @param args [Hash] Additional arguments set specifically for validator
|
234
|
-
# @return [Boolean] true if catalog is valid, false otherwise
|
247
|
+
# @return [Boolean] Return true if catalog is valid, false otherwise
|
235
248
|
def catalog_validator(catalog = nil, _logger = @logger, args = {})
|
236
|
-
|
237
|
-
|
238
|
-
catalog.
|
249
|
+
raise ArgumentError, "Expects a catalog, got #{catalog.class}" unless catalog.is_a?(OctocatalogDiff::Catalog)
|
250
|
+
raise OctocatalogDiff::Errors::CatalogError, "Catalog failed: #{catalog.error_message}" unless catalog.valid?
|
251
|
+
catalog.validate_references if args[:task] == :to # Raises exception for broken references
|
252
|
+
true
|
239
253
|
end
|
240
254
|
end
|
241
255
|
end
|
@@ -118,12 +118,20 @@ module OctocatalogDiff
|
|
118
118
|
else
|
119
119
|
raise ArgumentError, 'SSL client auth enabled but no client keypair specified'
|
120
120
|
end
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
OpenSSL::PKey::RSA.new(result[:pem], result[:pem_password]
|
121
|
+
|
122
|
+
# Make sure there's not a password required, or that if the password is given, it is correct.
|
123
|
+
# This will raise OpenSSL::PKey::RSAError if the key needs a password.
|
124
|
+
if result[:pem] && options[:ssl_client_password]
|
125
|
+
result[:pem_password] = options[:ssl_client_password]
|
126
|
+
_trash = OpenSSL::PKey::RSA.new(result[:pem], result[:pem_password])
|
127
|
+
elsif result[:pem]
|
128
|
+
# Ruby 2.4 requires a minimum password length of 4. If no password is needed for
|
129
|
+
# the certificate, the specified password here is effectively ignored.
|
130
|
+
# We do not want to wait on STDIN, so a password-protected certificate without a
|
131
|
+
# password will cause this to raise an error. There are two checks here, to exclude
|
132
|
+
# an edge case where somebody did actually put '1234' as their password.
|
133
|
+
_trash = OpenSSL::PKey::RSA.new(result[:pem], '1234')
|
134
|
+
_trash = OpenSSL::PKey::RSA.new(result[:pem], '5678')
|
127
135
|
end
|
128
136
|
end
|
129
137
|
|
@@ -1,18 +1,22 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
#
|
3
|
+
# A class to parallelize process executation.
|
4
|
+
# This is a utility class to execute tasks in parallel, with our own forking implementation
|
5
|
+
# that passes through logs and reliably handles errors. If parallel processing has been disabled,
|
6
|
+
# this instead executes the tasks serially, but provides the same API as the parallel tasks.
|
4
7
|
|
5
|
-
require 'parallel'
|
6
8
|
require 'stringio'
|
7
9
|
|
8
10
|
module OctocatalogDiff
|
9
11
|
module Util
|
10
|
-
# This is a utility class to execute tasks in parallel, using the 'parallel' gem.
|
11
|
-
# If parallel processing has been disabled, this instead executes the tasks serially,
|
12
|
-
# but provides the same API as the parallel tasks.
|
13
12
|
class Parallel
|
13
|
+
# This exception is called for a task that didn't complete.
|
14
|
+
class IncompleteTask < RuntimeError; end
|
15
|
+
|
16
|
+
# --------------------------------------
|
14
17
|
# This class represents a parallel task. It requires a method reference, which will be executed with
|
15
18
|
# any supplied arguments. It can optionally take a text description and a validator function.
|
19
|
+
# --------------------------------------
|
16
20
|
class Task
|
17
21
|
attr_reader :description
|
18
22
|
attr_accessor :args
|
@@ -35,10 +39,12 @@ module OctocatalogDiff
|
|
35
39
|
end
|
36
40
|
end
|
37
41
|
|
42
|
+
# --------------------------------------
|
38
43
|
# This class represents the result from a parallel task. The status is set to true (success), false (error),
|
39
44
|
# or nil (task was killed before it could complete). The exception (for failure) and output object (for success)
|
40
45
|
# are readable attributes. The validity of the results, determined by executing the 'validate' method of the Task,
|
41
46
|
# is available to be set and fetched.
|
47
|
+
# --------------------------------------
|
42
48
|
class Result
|
43
49
|
attr_reader :output, :args
|
44
50
|
attr_accessor :status, :exception
|
@@ -51,121 +57,170 @@ module OctocatalogDiff
|
|
51
57
|
end
|
52
58
|
end
|
53
59
|
|
60
|
+
# --------------------------------------
|
61
|
+
# Static methods in the class
|
62
|
+
# --------------------------------------
|
63
|
+
|
54
64
|
# Entry point for parallel processing. By default this will perform parallel processing,
|
55
65
|
# but it will also accept an option to do serial processing instead.
|
56
66
|
# @param task_array [Array<Parallel::Task>] Tasks to run
|
57
67
|
# @param logger [Logger] Optional logger object
|
58
68
|
# @param parallelized [Boolean] True for parallel processing, false for serial processing
|
69
|
+
# @param raise_exception [Boolean] True to raise exception immediately if one occurs; false to return exception in results
|
59
70
|
# @return [Array<Parallel::Result>] Parallel results (same order as tasks)
|
60
|
-
def self.run_tasks(task_array, logger = nil, parallelized = true)
|
71
|
+
def self.run_tasks(task_array, logger = nil, parallelized = true, raise_exception = false)
|
61
72
|
# Create a throwaway logger object if one is not given
|
62
73
|
logger ||= Logger.new(StringIO.new)
|
63
74
|
|
64
|
-
# Validate input - we need an array. If the array is empty then
|
75
|
+
# Validate input - we need an array of OctocatalogDiff::Util::Parallel::Task. If the array is empty then
|
76
|
+
# return an empty array right away.
|
65
77
|
raise ArgumentError, "run_tasks() argument must be array, not #{task_array.class}" unless task_array.is_a?(Array)
|
66
78
|
return [] if task_array.empty?
|
67
79
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
raise ArgumentError, "Element #{
|
80
|
+
invalid_inputs = task_array.reject { |task| task.is_a?(OctocatalogDiff::Util::Parallel::Task) }
|
81
|
+
if invalid_inputs.any?
|
82
|
+
ele = invalid_inputs.first
|
83
|
+
raise ArgumentError, "Element #{ele.inspect} must be a OctocatalogDiff::Util::Parallel::Task, not a #{ele.class}"
|
72
84
|
end
|
73
85
|
|
74
|
-
#
|
75
|
-
|
86
|
+
# Initialize the result array. For now all entries in the array indicate that the task was killed.
|
87
|
+
# Actual statuses will replace this initial status. If the initial status wasn't replaced, then indeed,
|
88
|
+
# the task was killed.
|
89
|
+
result = task_array.map { |x| Result.new(exception: IncompleteTask.new('Killed'), args: x.args) }
|
90
|
+
logger.debug "Initialized parallel task result array: size=#{result.size}"
|
91
|
+
|
92
|
+
# Execute as per the requested method (serial or parallel) and handle results.
|
93
|
+
exception = parallelized ? run_tasks_parallel(result, task_array, logger) : run_tasks_serial(result, task_array, logger)
|
94
|
+
raise exception if exception && raise_exception
|
95
|
+
result
|
76
96
|
end
|
77
97
|
|
78
|
-
#
|
79
|
-
#
|
98
|
+
# Utility method! Not intended to be called from outside this class.
|
99
|
+
# ---
|
100
|
+
# Use a forking strategy to run tasks in parallel. Each task in the array is forked in a child
|
101
|
+
# process, and when that task completes it writes its result (OctocatalogDiff::Util::Parallel::Result)
|
102
|
+
# into a serialized data file. Once children are forked this method waits for their return, deserializing
|
103
|
+
# the output from each data file and updating the `result` array with actual results.
|
104
|
+
# @param result [Array<OctocatalogDiff::Util::Parallel::Result>] Parallel task results
|
80
105
|
# @param task_array [Array<OctocatalogDiff::Util::Parallel::Task>] Tasks to perform
|
81
106
|
# @param logger [Logger] Logger
|
82
|
-
# @return [
|
83
|
-
def self.run_tasks_parallel(task_array, logger)
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
107
|
+
# @return [Exception] First exception encountered by a child process; returns nil if no exceptions encountered.
|
108
|
+
def self.run_tasks_parallel(result, task_array, logger)
|
109
|
+
pidmap = {}
|
110
|
+
ipc_tempdir = Dir.mktmpdir
|
111
|
+
|
112
|
+
# Child process forking
|
113
|
+
task_array.each_with_index do |task, index|
|
114
|
+
# simplecov doesn't see this because it's forked
|
115
|
+
# :nocov:
|
116
|
+
this_pid = fork do
|
117
|
+
task_result = execute_task(task, logger)
|
118
|
+
File.open(File.join(ipc_tempdir, "#{Process.pid}.dat"), 'w') { |f| f.write Marshal.dump(task_result) }
|
119
|
+
Kernel.exit! 0 # Kernel.exit! avoids at_exit from parents being triggered by children exiting
|
120
|
+
end
|
121
|
+
# :nocov:
|
122
|
+
|
123
|
+
pidmap[this_pid] = { index: index, start_time: Time.now }
|
124
|
+
logger.debug "Launched pid=#{this_pid} for index=#{index}"
|
125
|
+
logger.reopen if logger.respond_to?(:reopen)
|
89
126
|
end
|
90
|
-
logger.debug "Initialized parallel task result array: size=#{result.size}"
|
91
127
|
|
92
|
-
#
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
128
|
+
# Waiting for children and handling results
|
129
|
+
while pidmap.any?
|
130
|
+
this_pid, exit_obj = Process.wait2(0)
|
131
|
+
next unless this_pid && pidmap.key?(this_pid)
|
132
|
+
index = pidmap[this_pid][:index]
|
133
|
+
exitstatus = exit_obj.exitstatus
|
134
|
+
raise "PID=#{this_pid} exited abnormally: #{exit_obj.inspect}" if exitstatus.nil?
|
135
|
+
raise "PID=#{this_pid} exited with status #{exitstatus}" unless exitstatus.zero?
|
136
|
+
|
137
|
+
input = File.read(File.join(ipc_tempdir, "#{this_pid}.dat"))
|
138
|
+
result[index] = Marshal.load(input) # rubocop:disable Security/MarshalLoad
|
139
|
+
time_delta = Time.now - pidmap[this_pid][:start_time]
|
140
|
+
pidmap.delete(this_pid)
|
141
|
+
|
142
|
+
logger.debug "PID=#{this_pid} completed in #{time_delta} seconds, #{input.length} bytes"
|
143
|
+
|
144
|
+
next if result[index].status
|
145
|
+
return result[index].exception
|
146
|
+
end
|
147
|
+
|
148
|
+
logger.debug 'All child processes completed with no exceptions raised'
|
149
|
+
|
150
|
+
# Cleanup: Kill any child processes that are still running, and clean the temporary directory
|
151
|
+
# where data files were stored.
|
152
|
+
ensure
|
153
|
+
pidmap.each do |pid, _pid_data|
|
115
154
|
begin
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
Result.new(output: output, status: true, args: ele.args)
|
120
|
-
rescue => exc
|
121
|
-
logger.debug("Failed #{ele.description}: #{exc.class} #{exc.message}")
|
122
|
-
Result.new(exception: exc, status: false, args: ele.args)
|
155
|
+
Process.kill('TERM', pid)
|
156
|
+
rescue Errno::ESRCH # rubocop:disable Lint/HandleExceptions
|
157
|
+
# If the process doesn't exist, that's fine.
|
123
158
|
end
|
124
|
-
# :nocov:
|
125
159
|
end
|
126
160
|
|
127
|
-
|
128
|
-
|
161
|
+
retries = 0
|
162
|
+
while File.directory?(ipc_tempdir) && retries < 10
|
163
|
+
retries += 1
|
164
|
+
begin
|
165
|
+
FileUtils.remove_entry_secure ipc_tempdir
|
166
|
+
rescue Errno::ENOTEMPTY, Errno::ENOENT # rubocop:disable Lint/HandleExceptions
|
167
|
+
# Errno::ENOTEMPTY will trigger a retry because the directory exists
|
168
|
+
# Errno::ENOENT will break the loop because the directory won't exist next time it's checked
|
169
|
+
end
|
170
|
+
end
|
129
171
|
end
|
130
172
|
|
173
|
+
# Utility method! Not intended to be called from outside this class.
|
174
|
+
# ---
|
131
175
|
# Perform the tasks in serial.
|
176
|
+
# @param result [Array<OctocatalogDiff::Util::Parallel::Result>] Parallel task results
|
132
177
|
# @param task_array [Array<OctocatalogDiff::Util::Parallel::Task>] Tasks to perform
|
133
178
|
# @param logger [Logger] Logger
|
134
|
-
|
135
|
-
def self.run_tasks_serial(task_array, logger)
|
136
|
-
# Create an empty array of results. The status is nil and the exception is pre-populated. If the code
|
137
|
-
# runs successfully, all of these default values will be overwritten. If a predecessor task fails, all
|
138
|
-
# later task will have the defined exception.
|
139
|
-
result = task_array.map do |x|
|
140
|
-
Result.new(exception: ::RuntimeError.new('Cancellation - A prior task failed'), args: x.args)
|
141
|
-
end
|
142
|
-
|
179
|
+
def self.run_tasks_serial(result, task_array, logger)
|
143
180
|
# Perform the tasks 1 by 1 - each successful task will replace an element in the 'result' array,
|
144
181
|
# whereas a failed task will replace the current element with an exception, and all later tasks
|
145
182
|
# will not be replaced (thereby being populated with the cancellation error).
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
logger.debug("Failed #{ele.description}: #{exc.class} #{exc.message}")
|
154
|
-
result[task_counter] = Result.new(exception: exc, status: false, args: ele.args)
|
155
|
-
end
|
183
|
+
task_array.each_with_index do |ele, task_counter|
|
184
|
+
result[task_counter] = execute_task(ele, logger)
|
185
|
+
next if result[task_counter].status
|
186
|
+
return result[task_counter].exception
|
187
|
+
end
|
188
|
+
nil
|
189
|
+
end
|
156
190
|
|
157
|
-
|
158
|
-
|
191
|
+
# Utility method! Not intended to be called from outside this class.
|
192
|
+
# ---
|
193
|
+
# Process a single task. Called by run_tasks_parallel / run_tasks_serial.
|
194
|
+
# This method will report all exceptions in the OctocatalogDiff::Util::Parallel::Result object
|
195
|
+
# itself, and not raise them.
|
196
|
+
# @param task [OctocatalogDiff::Util::Parallel::Task] Task object
|
197
|
+
# @param logger [Logger] Logger
|
198
|
+
# @return [OctocatalogDiff::Util::Parallel::Result] Parallel task result
|
199
|
+
def self.execute_task(task, logger)
|
200
|
+
begin
|
201
|
+
logger.debug("Begin #{task.description}")
|
202
|
+
output = task.execute(logger)
|
203
|
+
result = Result.new(output: output, status: true, args: task.args)
|
204
|
+
rescue => exc
|
205
|
+
logger.debug("Failed #{task.description}: #{exc.class} #{exc.message}")
|
206
|
+
# Immediately return without running the validation, since this already failed.
|
207
|
+
return Result.new(exception: exc, status: false, args: task.args)
|
208
|
+
end
|
209
|
+
|
210
|
+
begin
|
211
|
+
if task.validate(output, logger)
|
212
|
+
logger.debug("Success #{task.description}")
|
159
213
|
else
|
160
|
-
|
161
|
-
|
214
|
+
# Preferably the validator method raised its own exception. However if it
|
215
|
+
# simply returned false, raise our own exception here.
|
216
|
+
raise "Failed #{task.description} validation (unspecified error)"
|
162
217
|
end
|
163
|
-
|
164
|
-
|
165
|
-
|
218
|
+
rescue => exc
|
219
|
+
logger.warn("Failed #{task.description} validation: #{exc.class} #{exc.message}")
|
220
|
+
result.status = false
|
221
|
+
result.exception = exc
|
166
222
|
end
|
167
223
|
|
168
|
-
# Return the result
|
169
224
|
result
|
170
225
|
end
|
171
226
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: octocatalog-diff
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- GitHub, Inc.
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2017-05-
|
12
|
+
date: 2017-05-19 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: diffy
|
@@ -53,20 +53,6 @@ dependencies:
|
|
53
53
|
- - ">="
|
54
54
|
- !ruby/object:Gem::Version
|
55
55
|
version: 0.3.0
|
56
|
-
- !ruby/object:Gem::Dependency
|
57
|
-
name: parallel
|
58
|
-
requirement: !ruby/object:Gem::Requirement
|
59
|
-
requirements:
|
60
|
-
- - ">="
|
61
|
-
- !ruby/object:Gem::Version
|
62
|
-
version: 1.11.1
|
63
|
-
type: :runtime
|
64
|
-
prerelease: false
|
65
|
-
version_requirements: !ruby/object:Gem::Requirement
|
66
|
-
requirements:
|
67
|
-
- - ">="
|
68
|
-
- !ruby/object:Gem::Version
|
69
|
-
version: 1.11.1
|
70
56
|
- !ruby/object:Gem::Dependency
|
71
57
|
name: rugged
|
72
58
|
requirement: !ruby/object:Gem::Requirement
|