octocatalog-diff 1.1.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|