octocatalog-diff 1.3.0 → 1.4.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 +9 -0
- data/doc/advanced-catalog-validation.md +3 -1
- data/doc/dev/api/v1/calls/catalog-diff.md +1 -5
- data/doc/dev/api/v1/calls/catalog.md +1 -5
- data/lib/octocatalog-diff/catalog-util/builddir.rb +1 -1
- data/lib/octocatalog-diff/catalog.rb +89 -69
- data/lib/octocatalog-diff/catalog/computed.rb +37 -45
- data/lib/octocatalog-diff/catalog/json.rb +18 -7
- data/lib/octocatalog-diff/catalog/noop.rb +5 -5
- data/lib/octocatalog-diff/catalog/puppetdb.rb +7 -20
- data/lib/octocatalog-diff/catalog/puppetmaster.rb +15 -22
- data/lib/octocatalog-diff/errors.rb +0 -1
- data/lib/octocatalog-diff/util/catalogs.rb +9 -6
- data/lib/octocatalog-diff/util/parallel.rb +1 -1
- data/lib/octocatalog-diff/util/scriptrunner.rb +1 -1
- metadata +18 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5f0b3862bc04c59a30e41e90cf689f508cb68dd6
|
4
|
+
data.tar.gz: 4e67157b3a0ba173d2d1099d1e0aec79027a8f3b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: aa208efdde5e60a3bfdd1ced747632bc00d0fec7f916fed1765f9c8e7fe8de99160a7ef673e694fa043a90713af3df2c6eec620f9e792d2c0332864d04baf694
|
7
|
+
data.tar.gz: 228113ef4f77899dfe3185b839b9d67f41db4ae9db0901f9aa7aa3307d0c67d8983982118c8e2dff433f37f428cccc8c1ca755f83c41e9a8a9965a6027bd4952
|
data/.version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.
|
1
|
+
1.4.0
|
data/doc/CHANGELOG.md
CHANGED
@@ -8,6 +8,15 @@
|
|
8
8
|
</tr>
|
9
9
|
</thead><tbody>
|
10
10
|
<tr valign=top>
|
11
|
+
<td>1.4.0</td>
|
12
|
+
<td>2017-08-03</td>
|
13
|
+
<td>
|
14
|
+
<li><a href="https://github.com/github/octocatalog-diff/pull/135">#135</a>: (Enhancement) Puppet 5 compatibility</li>
|
15
|
+
<li><a href="https://github.com/github/octocatalog-diff/pull/140">#140</a>: (Internal) Prefix tmpdirs with ocd-</li>
|
16
|
+
<li><a href="https://github.com/github/octocatalog-diff/pull/138">#138</a>: (Internal) Refactor catalog class with proper inheritance</li>
|
17
|
+
</td>
|
18
|
+
</tr>
|
19
|
+
<tr valign=top>
|
11
20
|
<td>1.3.0</td>
|
12
21
|
<td>2017-06-09</td>
|
13
22
|
<td>
|
@@ -4,12 +4,14 @@
|
|
4
4
|
|
5
5
|
Catalog validation features include:
|
6
6
|
|
7
|
-
- Validate references: Ensure resources targeted by `before`, `notify`, `require`, and/or `subscribe` exist in the catalog
|
7
|
+
- Validate references: Ensure resources targeted by `before`, `notify`, `require`, and/or `subscribe` exist in the catalog for Puppet 4 and below.
|
8
8
|
|
9
9
|
## Validate references
|
10
10
|
|
11
11
|
`octocatalog-diff` includes the ability to validate references by ensuring resources targeted by `before`, `notify`, `require`, and/or `subscribe` parameters also exist in the catalog.
|
12
12
|
|
13
|
+
Puppet 5 already has this checking built in, so the `--validate-references` option described in this section will be ignored if Puppet 5 is being used. The same exception (`OctocatalogDiff::Errors::CatalogError`) is raised for a missing reference, whether the problem was detected by octocatalog-diff or Puppet 5.
|
14
|
+
|
13
15
|
Consider the following Puppet code:
|
14
16
|
|
15
17
|
```
|
@@ -194,7 +194,7 @@ The following exceptions may occur during the compilation of a catalog within th
|
|
194
194
|
|
195
195
|
- `OctocatalogDiff::Errors::CatalogError`
|
196
196
|
|
197
|
-
Catalog failed to compile. Please note that whenever possible, a `OctocatalogDiff::API::V1::Catalog` object is still constructed for a failed catalog, with `#valid?` returning false.
|
197
|
+
Catalog failed to compile. Please note that whenever possible, a `OctocatalogDiff::API::V1::Catalog` object is still constructed for a failed catalog, with `#valid?` returning false. It's also possible that the catalog contained broken references -- see [Catalog validation](/doc/advanced-catalog-validation.md).
|
198
198
|
|
199
199
|
- `OctocatalogDiff::Errors::GitCheckoutError`
|
200
200
|
|
@@ -203,7 +203,3 @@ The following exceptions may occur during the compilation of a catalog within th
|
|
203
203
|
- `OctocatalogDiff::Errors::PuppetVersionError`
|
204
204
|
|
205
205
|
The version of Puppet could not be determined, generally because the Puppet binary was not found, or does not respond as expected to `puppet version`.
|
206
|
-
|
207
|
-
- `OctocatalogDiff::Errors::ReferenceValidationError`
|
208
|
-
|
209
|
-
See [Catalog validation](/doc/advanced-catalog-validation.md).
|
@@ -100,7 +100,7 @@ The following exceptions may occur during the compilation of a catalog:
|
|
100
100
|
|
101
101
|
- `OctocatalogDiff::Errors::CatalogError`
|
102
102
|
|
103
|
-
Catalog failed to compile. Please note that whenever possible, a `OctocatalogDiff::API::V1::Catalog` object is still constructed for a failed catalog, with `#valid?` returning false.
|
103
|
+
Catalog failed to compile. Please note that whenever possible, a `OctocatalogDiff::API::V1::Catalog` object is still constructed for a failed catalog, with `#valid?` returning false. It's also possible that the catalog contained broken references -- see [Catalog validation](/doc/advanced-catalog-validation.md).
|
104
104
|
|
105
105
|
- `OctocatalogDiff::Errors::GitCheckoutError`
|
106
106
|
|
@@ -109,7 +109,3 @@ The following exceptions may occur during the compilation of a catalog:
|
|
109
109
|
- `OctocatalogDiff::Errors::PuppetVersionError`
|
110
110
|
|
111
111
|
The version of Puppet could not be determined, generally because the Puppet binary was not found, or does not respond as expected to `puppet version`.
|
112
|
-
|
113
|
-
- `OctocatalogDiff::Errors::ReferenceValidationError`
|
114
|
-
|
115
|
-
See [Catalog validation](/doc/advanced-catalog-validation.md).
|
@@ -38,7 +38,7 @@ module OctocatalogDiff
|
|
38
38
|
# @param options [Hash] Options for class; see above description
|
39
39
|
def initialize(options = {}, logger = nil)
|
40
40
|
@options = options.dup
|
41
|
-
@tempdir = Dir.mktmpdir
|
41
|
+
@tempdir = Dir.mktmpdir('ocd-builddir-')
|
42
42
|
at_exit { FileUtils.rm_rf(@tempdir) if File.directory?(@tempdir) }
|
43
43
|
|
44
44
|
@factdir = nil
|
@@ -12,14 +12,42 @@ require_relative 'catalog-util/fileresources'
|
|
12
12
|
require_relative 'errors'
|
13
13
|
|
14
14
|
module OctocatalogDiff
|
15
|
-
#
|
15
|
+
# Basic methods for interacting with a catalog. Generation of the catalog is handled via one of the
|
16
16
|
# supported backends listed above as 'require_relative'. Usually, the 'computed' backend
|
17
17
|
# will build the catalog from the Puppet command.
|
18
18
|
class Catalog
|
19
|
-
|
20
|
-
attr_reader :built, :catalog, :catalog_json
|
19
|
+
attr_accessor :node
|
20
|
+
attr_reader :built, :catalog, :catalog_json, :options
|
21
21
|
|
22
22
|
# Constructor
|
23
|
+
def initialize(options = {})
|
24
|
+
unless options.is_a?(Hash)
|
25
|
+
raise ArgumentError, "#{self.class}.initialize requires hash argument, not #{options.class}"
|
26
|
+
end
|
27
|
+
@options = options
|
28
|
+
|
29
|
+
# Basic settings
|
30
|
+
@node = options[:node]
|
31
|
+
@error_message = nil
|
32
|
+
@catalog = nil
|
33
|
+
@catalog_json = nil
|
34
|
+
@retries = nil
|
35
|
+
|
36
|
+
# The compilation directory can be overridden, e.g. when testing
|
37
|
+
@override_compilation_dir = options[:compilation_dir]
|
38
|
+
|
39
|
+
# Keep track of whether references have been validated yet. Allow this to be fudged for when we do
|
40
|
+
# not desire reference validation to happen (e.g., for the "from" catalog that is otherwise valid).
|
41
|
+
@references_validated = options[:references_validated] || false
|
42
|
+
|
43
|
+
# Keep track of whether file resources have been converted.
|
44
|
+
@file_resources_converted = false
|
45
|
+
|
46
|
+
# Keep track of whether it's built yet
|
47
|
+
@built = false
|
48
|
+
end
|
49
|
+
|
50
|
+
# Guess the backend from the input and return the appropriate catalog object.
|
23
51
|
# @param :backend [Symbol] If set, this will force a backend
|
24
52
|
# @param :json [String] JSON catalog content (will avoid running Puppet to compile catalog)
|
25
53
|
# @param :puppetdb [Object] If set, pull the catalog from PuppetDB rather than building
|
@@ -30,18 +58,25 @@ module OctocatalogDiff
|
|
30
58
|
# @param :pass_env_vars [Array<String>] OPTIONAL: Additional environment vars to pass
|
31
59
|
# @param :convert_file_resources [Boolean] OPTIONAL: Convert file resource source to content
|
32
60
|
# @param :storeconfigs [Boolean] OPTIONAL: Pass the '-s' flag, for puppetdb (storeconfigs) integration
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
61
|
+
# @return [OctocatalogDiff::Catalog::<?>] Catalog object from guessed backend
|
62
|
+
def self.create(options = {})
|
63
|
+
# Hard-coded backend
|
64
|
+
if options[:backend]
|
65
|
+
return OctocatalogDiff::Catalog::JSON.new(options) if options[:backend] == :json
|
66
|
+
return OctocatalogDiff::Catalog::PuppetDB.new(options) if options[:backend] == :puppetdb
|
67
|
+
return OctocatalogDiff::Catalog::PuppetMaster.new(options) if options[:backend] == :puppetmaster
|
68
|
+
return OctocatalogDiff::Catalog::Computed.new(options) if options[:backend] == :computed
|
69
|
+
return OctocatalogDiff::Catalog::Noop.new(options) if options[:backend] == :noop
|
70
|
+
raise ArgumentError, "Unknown backend :#{options[:backend]}"
|
71
|
+
end
|
38
72
|
|
39
|
-
#
|
40
|
-
|
41
|
-
|
73
|
+
# Determine backend based on arguments
|
74
|
+
return OctocatalogDiff::Catalog::JSON.new(options) if options[:json]
|
75
|
+
return OctocatalogDiff::Catalog::PuppetDB.new(options) if options[:puppetdb]
|
76
|
+
return OctocatalogDiff::Catalog::PuppetMaster.new(options) if options[:puppet_master]
|
42
77
|
|
43
|
-
#
|
44
|
-
|
78
|
+
# Default is to build catalog ourselves
|
79
|
+
OctocatalogDiff::Catalog::Computed.new(options)
|
45
80
|
end
|
46
81
|
|
47
82
|
# Build catalog - this method needs to be called to build the catalog. It is separate due to
|
@@ -49,43 +84,42 @@ module OctocatalogDiff
|
|
49
84
|
# object so it cannot be part of any object that is passed around.
|
50
85
|
# @param logger [Logger] Logger object, initialized to a default throwaway value
|
51
86
|
def build(logger = Logger.new(StringIO.new))
|
52
|
-
#
|
87
|
+
# If already built, don't build again
|
53
88
|
return if @built
|
54
89
|
@built = true
|
55
90
|
|
56
|
-
# Call catalog's build method.
|
57
|
-
if @catalog_obj.respond_to?(:build)
|
58
|
-
logger.debug "Calling build for object #{@catalog_obj.class}"
|
59
|
-
@catalog_obj.build(logger)
|
60
|
-
end
|
61
|
-
|
62
|
-
# These methods must exist in all backends
|
63
|
-
@catalog = @catalog_obj.catalog
|
64
|
-
@catalog_json = @catalog_obj.catalog_json
|
65
|
-
@error_message = @catalog_obj.error_message
|
66
|
-
|
67
91
|
# The resource hash is computed the first time it's needed. For now initialize it as nil.
|
68
92
|
@resource_hash = nil
|
69
93
|
|
94
|
+
# Invoke the backend's build method, if there is one. There's a stub below in case there's not.
|
95
|
+
logger.debug "Calling build for object #{self.class}"
|
96
|
+
build_catalog(logger)
|
97
|
+
|
70
98
|
# Perform post-generation processing of the catalog
|
71
99
|
return unless valid?
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
100
|
+
|
101
|
+
validate_references
|
102
|
+
return unless valid?
|
103
|
+
|
104
|
+
convert_file_resources if @options[:compare_file_text]
|
105
|
+
|
106
|
+
true
|
107
|
+
end
|
108
|
+
|
109
|
+
# Stub method if the backend does not contain a build method.
|
110
|
+
def build_catalog(_logger)
|
77
111
|
end
|
78
112
|
|
79
113
|
# Compilation environment
|
80
114
|
# @return [String] Compilation environment (if set), else 'production' by default
|
81
115
|
def environment
|
82
|
-
@
|
116
|
+
@environment ||= 'production'
|
83
117
|
end
|
84
118
|
|
85
119
|
# For logging we may wish to know the backend being used
|
86
120
|
# @return [String] Class of backend used
|
87
121
|
def builder
|
88
|
-
|
122
|
+
self.class.to_s
|
89
123
|
end
|
90
124
|
|
91
125
|
# Set the catalog JSON
|
@@ -98,8 +132,7 @@ module OctocatalogDiff
|
|
98
132
|
# This retrieves the compilation directory from the catalog, or otherwise the passed-in directory.
|
99
133
|
# @return [String] Compilation directory
|
100
134
|
def compilation_dir
|
101
|
-
|
102
|
-
@catalog_obj.respond_to?(:compilation_dir) ? @catalog_obj.compilation_dir : @options[:basedir]
|
135
|
+
@override_compilation_dir || @options[:basedir]
|
103
136
|
end
|
104
137
|
|
105
138
|
# The compilation directory can be overridden, e.g. during testing.
|
@@ -108,16 +141,16 @@ module OctocatalogDiff
|
|
108
141
|
@override_compilation_dir = dir
|
109
142
|
end
|
110
143
|
|
111
|
-
#
|
112
|
-
#
|
113
|
-
def convert_file_resources
|
114
|
-
|
115
|
-
@catalog_obj.convert_file_resources
|
144
|
+
# Stub method for "convert_file_resources" -- returns false because if the underlying class does
|
145
|
+
# not implement this method, it's not supported.
|
146
|
+
def convert_file_resources(_dry_run = false)
|
147
|
+
false
|
116
148
|
end
|
117
149
|
|
118
150
|
# Retrieve the error message.
|
119
151
|
# @return [String] Error message (maximum 20,000 characters) - nil if no error.
|
120
152
|
def error_message
|
153
|
+
build
|
121
154
|
return nil if @error_message.nil? || !@error_message.is_a?(String)
|
122
155
|
@error_message[0, 20_000]
|
123
156
|
end
|
@@ -133,12 +166,11 @@ module OctocatalogDiff
|
|
133
166
|
@resource_hash = nil
|
134
167
|
end
|
135
168
|
|
136
|
-
#
|
137
|
-
# compiled by running Puppet (e.g., it was read in from JSON or puppetdb), then this returns the
|
138
|
-
# puppet version optionally passed in to the constructor. This can also be nil.
|
169
|
+
# Stub method to return the puppet version if the back end doesn't support this.
|
139
170
|
# @return [String] Puppet version
|
140
171
|
def puppet_version
|
141
|
-
|
172
|
+
build
|
173
|
+
@options[:puppet_version]
|
142
174
|
end
|
143
175
|
|
144
176
|
# This allows retrieving a resource by type and title. This is intended for use when a O(1) lookup is required.
|
@@ -147,6 +179,7 @@ module OctocatalogDiff
|
|
147
179
|
# @return [Hash] Resource item
|
148
180
|
def resource(opts = {})
|
149
181
|
raise ArgumentError, ':type and :title are required' unless opts[:type] && opts[:title]
|
182
|
+
build
|
150
183
|
build_resource_hash if @resource_hash.nil?
|
151
184
|
return nil unless @resource_hash[opts[:type]].is_a?(Hash)
|
152
185
|
@resource_hash[opts[:type]][opts[:title]]
|
@@ -155,6 +188,7 @@ module OctocatalogDiff
|
|
155
188
|
# This is a compatibility layer for the resources, which are in a different place in Puppet 3.x and Puppet 4.x
|
156
189
|
# @return [Array] Resource array
|
157
190
|
def resources
|
191
|
+
build
|
158
192
|
raise OctocatalogDiff::Errors::CatalogError, 'Catalog does not appear to have been built' if !valid? && error_message.nil?
|
159
193
|
raise OctocatalogDiff::Errors::CatalogError, error_message unless valid?
|
160
194
|
return @catalog['data']['resources'] if @catalog['data'].is_a?(Hash) && @catalog['data']['resources'].is_a?(Array)
|
@@ -165,16 +199,17 @@ module OctocatalogDiff
|
|
165
199
|
# :nocov:
|
166
200
|
end
|
167
201
|
|
168
|
-
#
|
202
|
+
# Stub method of the the number of retries necessary to compile the catalog. If the underlying catalog
|
169
203
|
# generation backend does not support retries, nil is returned.
|
170
204
|
# @return [Integer] Retry count
|
171
205
|
def retries
|
172
|
-
|
206
|
+
nil
|
173
207
|
end
|
174
208
|
|
175
209
|
# Determine if the catalog build was successful.
|
176
210
|
# @return [Boolean] Whether the catalog is valid
|
177
211
|
def valid?
|
212
|
+
build
|
178
213
|
!@catalog.nil?
|
179
214
|
end
|
180
215
|
|
@@ -182,11 +217,19 @@ module OctocatalogDiff
|
|
182
217
|
# Raise a OctocatalogDiff::Errors::ReferenceValidationError for any found to be missing.
|
183
218
|
# Uses @options[:validate_references] to influence which references are checked.
|
184
219
|
def validate_references
|
220
|
+
# If we've already done the validation, don't do it again
|
221
|
+
return if @references_validated
|
222
|
+
@references_validated = true
|
223
|
+
|
185
224
|
# Skip out early if no reference validation has been requested.
|
186
225
|
unless @options[:validate_references].is_a?(Array) && @options[:validate_references].any?
|
187
226
|
return
|
188
227
|
end
|
189
228
|
|
229
|
+
# Puppet 5 has reference validation built-in and enabled, so there won't even be a valid catalog if
|
230
|
+
# there were invalid references. It's pointless to perform validation of our own.
|
231
|
+
return if puppet_version && puppet_version >= '5.0.0'
|
232
|
+
|
190
233
|
# Iterate over all the resources and check each one that has one of the attributes being checked.
|
191
234
|
# Keep track of all references that are missing for ultimate inclusion in the error message.
|
192
235
|
missing = []
|
@@ -204,7 +247,7 @@ module OctocatalogDiff
|
|
204
247
|
# At this point there is at least one broken/missing reference. Format an error message and raise.
|
205
248
|
errors = format_missing_references(missing)
|
206
249
|
plural = errors =~ /;/ ? 's' : ''
|
207
|
-
|
250
|
+
self.error_message = "Catalog has broken reference#{plural}: #{errors}"
|
208
251
|
end
|
209
252
|
|
210
253
|
private
|
@@ -265,29 +308,6 @@ module OctocatalogDiff
|
|
265
308
|
end
|
266
309
|
end
|
267
310
|
|
268
|
-
# Private method: Choose backend based on passed-in options
|
269
|
-
# @param options [Hash] Options passed into constructor
|
270
|
-
# @return [Object] OctocatalogDiff::Catalog::<whatever> object
|
271
|
-
def backend(options)
|
272
|
-
# Hard-coded backend
|
273
|
-
if options[:backend]
|
274
|
-
return OctocatalogDiff::Catalog::JSON.new(options) if options[:backend] == :json
|
275
|
-
return OctocatalogDiff::Catalog::PuppetDB.new(options) if options[:backend] == :puppetdb
|
276
|
-
return OctocatalogDiff::Catalog::PuppetMaster.new(options) if options[:backend] == :puppetmaster
|
277
|
-
return OctocatalogDiff::Catalog::Computed.new(options) if options[:backend] == :computed
|
278
|
-
return OctocatalogDiff::Catalog::Noop.new(options) if options[:backend] == :noop
|
279
|
-
raise ArgumentError, "Unknown backend :#{options[:backend]}"
|
280
|
-
end
|
281
|
-
|
282
|
-
# Determine backend based on arguments
|
283
|
-
return OctocatalogDiff::Catalog::JSON.new(options) if options[:json]
|
284
|
-
return OctocatalogDiff::Catalog::PuppetDB.new(options) if options[:puppetdb]
|
285
|
-
return OctocatalogDiff::Catalog::PuppetMaster.new(options) if options[:puppet_master]
|
286
|
-
|
287
|
-
# Default is to build catalog ourselves
|
288
|
-
OctocatalogDiff::Catalog::Computed.new(options)
|
289
|
-
end
|
290
|
-
|
291
311
|
# Private method: Build the resource hash to be used used for O(1) lookups by type and title.
|
292
312
|
# This method is called the first time the resource hash is accessed.
|
293
313
|
def build_resource_hash
|
@@ -4,6 +4,7 @@ require 'fileutils'
|
|
4
4
|
require 'json'
|
5
5
|
require 'stringio'
|
6
6
|
|
7
|
+
require_relative '../catalog'
|
7
8
|
require_relative '../catalog-util/bootstrap'
|
8
9
|
require_relative '../catalog-util/builddir'
|
9
10
|
require_relative '../catalog-util/command'
|
@@ -15,9 +16,7 @@ module OctocatalogDiff
|
|
15
16
|
class Catalog
|
16
17
|
# Represents a Puppet catalog that is computed (via `puppet master --compile ...`)
|
17
18
|
# By instantiating this class, the catalog is computed.
|
18
|
-
class Computed
|
19
|
-
attr_reader :node, :error_message, :catalog, :catalog_json, :retries
|
20
|
-
|
19
|
+
class Computed < OctocatalogDiff::Catalog
|
21
20
|
# Constructor
|
22
21
|
# @param :node [String] REQUIRED: Node name
|
23
22
|
# @param :basedir [String] Directory in which to compile the catalog
|
@@ -28,14 +27,10 @@ module OctocatalogDiff
|
|
28
27
|
# @param :puppet_version [String] Puppet version (optional; if not supplied, it is calculated)
|
29
28
|
# @param :puppet_command [String] Full command to run Puppet (optional; if not supplied, it is calculated)
|
30
29
|
def initialize(options)
|
31
|
-
|
32
|
-
raise ArgumentError, 'Node name must be passed to OctocatalogDiff::Catalog::Computed' unless options[:node].is_a?(String)
|
30
|
+
super
|
33
31
|
|
34
|
-
|
35
|
-
|
36
|
-
@error_message = nil
|
37
|
-
@catalog = nil
|
38
|
-
@catalog_json = nil
|
32
|
+
raise ArgumentError, 'Node name must be passed to OctocatalogDiff::Catalog::Computed' unless options[:node].is_a?(String)
|
33
|
+
raise ArgumentError, 'Branch is undefined' unless options[:branch]
|
39
34
|
|
40
35
|
# Additional class variables
|
41
36
|
@pass_env_vars = options.fetch(:pass_env_vars, [])
|
@@ -44,31 +39,15 @@ module OctocatalogDiff
|
|
44
39
|
@puppet_binary = options[:puppet_binary]
|
45
40
|
@puppet_version = options[:puppet_version]
|
46
41
|
@puppet_command = options[:puppet_command]
|
47
|
-
@retries = nil
|
48
42
|
@builddir = nil
|
49
43
|
@facts_terminus = options.fetch(:facts_terminus, 'yaml')
|
50
|
-
|
51
|
-
# Pass through the input for other access
|
52
|
-
@opts = options
|
53
|
-
raise ArgumentError, 'Branch is undefined' unless @opts[:branch]
|
54
|
-
end
|
55
|
-
|
56
|
-
# Actually build the catalog (populate @error_message, @catalog, @catalog_json)
|
57
|
-
def build(logger = Logger.new(StringIO.new))
|
58
|
-
if @facts_terminus != 'facter'
|
59
|
-
facts_obj = OctocatalogDiff::CatalogUtil::Facts.new(@opts, logger)
|
60
|
-
logger.debug "Start retrieving facts for #{@node} from #{self.class}"
|
61
|
-
@opts[:facts] = facts_obj.facts
|
62
|
-
logger.debug "Success retrieving facts for #{@node} from #{self.class}"
|
63
|
-
end
|
64
|
-
build_catalog(logger)
|
65
44
|
end
|
66
45
|
|
67
46
|
# Get the Puppet version
|
68
47
|
# @return [String] Puppet version
|
69
48
|
def puppet_version
|
70
49
|
raise ArgumentError, '"puppet_binary" was not passed to OctocatalogDiff::Catalog::Computed' unless @puppet_binary
|
71
|
-
@puppet_version ||= OctocatalogDiff::Util::PuppetVersion.puppet_version(@puppet_binary, @
|
50
|
+
@puppet_version ||= OctocatalogDiff::Util::PuppetVersion.puppet_version(@puppet_binary, @options)
|
72
51
|
end
|
73
52
|
|
74
53
|
# Compilation directory
|
@@ -80,7 +59,14 @@ module OctocatalogDiff
|
|
80
59
|
|
81
60
|
# Environment used to compile catalog
|
82
61
|
def environment
|
83
|
-
@
|
62
|
+
@options.fetch(:environment, 'production')
|
63
|
+
end
|
64
|
+
|
65
|
+
# Convert file resources source => "puppet:///..." to content => "actual content of file".
|
66
|
+
def convert_file_resources(dry_run = false)
|
67
|
+
return @options.key?(:basedir) if dry_run
|
68
|
+
return false unless @options[:basedir]
|
69
|
+
OctocatalogDiff::CatalogUtil::FileResources.convert_file_resources(self, environment)
|
84
70
|
end
|
85
71
|
|
86
72
|
private
|
@@ -105,29 +91,29 @@ module OctocatalogDiff
|
|
105
91
|
return if @builddir
|
106
92
|
|
107
93
|
# Fill options for creating and populating the temporary directory
|
108
|
-
tmphash = @
|
94
|
+
tmphash = @options.dup
|
109
95
|
|
110
96
|
# Bootstrap directory if needed
|
111
|
-
if !@
|
112
|
-
raise Errno::ENOENT, "Invalid dir #{@
|
113
|
-
tmphash[:basedir] = @
|
114
|
-
elsif @
|
115
|
-
if @
|
116
|
-
tmphash[:basedir] =
|
97
|
+
if !@options[:bootstrapped_dir].nil?
|
98
|
+
raise Errno::ENOENT, "Invalid dir #{@options[:bootstrapped_dir]}" unless File.directory?(@options[:bootstrapped_dir])
|
99
|
+
tmphash[:basedir] = @options[:bootstrapped_dir]
|
100
|
+
elsif @options[:branch] == '.'
|
101
|
+
if @options[:bootstrap_current]
|
102
|
+
tmphash[:basedir] = Dir.mktmpdir('ocd-bootstrap-basedir-')
|
117
103
|
at_exit { cleanup_checkout_dir(tmphash[:basedir], logger) }
|
118
104
|
|
119
|
-
FileUtils.cp_r File.join(@
|
105
|
+
FileUtils.cp_r File.join(@options[:basedir], '.'), tmphash[:basedir]
|
120
106
|
|
121
|
-
o = @
|
107
|
+
o = @options.reject { |k, _v| k == :branch }.merge(path: tmphash[:basedir])
|
122
108
|
OctocatalogDiff::CatalogUtil::Bootstrap.bootstrap_directory(o, logger)
|
123
109
|
else
|
124
|
-
tmphash[:basedir] = @
|
110
|
+
tmphash[:basedir] = @options[:basedir]
|
125
111
|
end
|
126
112
|
else
|
127
|
-
checkout_dir = Dir.mktmpdir
|
113
|
+
checkout_dir = Dir.mktmpdir('ocd-bootstrap-checkout-')
|
128
114
|
at_exit { cleanup_checkout_dir(checkout_dir, logger) }
|
129
115
|
tmphash[:basedir] = checkout_dir
|
130
|
-
OctocatalogDiff::CatalogUtil::Bootstrap.bootstrap_directory(@
|
116
|
+
OctocatalogDiff::CatalogUtil::Bootstrap.bootstrap_directory(@options.merge(path: checkout_dir), logger)
|
131
117
|
end
|
132
118
|
|
133
119
|
# Create and populate the temporary directory
|
@@ -136,8 +122,14 @@ module OctocatalogDiff
|
|
136
122
|
|
137
123
|
# Private method: Build catalog by running Puppet
|
138
124
|
# @param logger [Logger] Logger object
|
139
|
-
def build_catalog(logger
|
140
|
-
|
125
|
+
def build_catalog(logger)
|
126
|
+
if @facts_terminus != 'facter'
|
127
|
+
facts_obj = OctocatalogDiff::CatalogUtil::Facts.new(@options, logger)
|
128
|
+
logger.debug "Start retrieving facts for #{@node} from #{self.class}"
|
129
|
+
@options[:facts] = facts_obj.facts
|
130
|
+
logger.debug "Success retrieving facts for #{@node} from #{self.class}"
|
131
|
+
end
|
132
|
+
|
141
133
|
bootstrap(logger)
|
142
134
|
result = run_puppet(logger)
|
143
135
|
@retries = result[:retries]
|
@@ -168,10 +160,10 @@ module OctocatalogDiff
|
|
168
160
|
@puppet_command_obj ||= begin
|
169
161
|
raise ArgumentError, '"puppet_binary" was not passed to OctocatalogDiff::Catalog::Computed' unless @puppet_binary
|
170
162
|
|
171
|
-
command_opts = @
|
163
|
+
command_opts = @options.merge(
|
172
164
|
node: @node,
|
173
165
|
compilation_dir: @builddir.tempdir,
|
174
|
-
parser: @
|
166
|
+
parser: @options.fetch(:parser, :default),
|
175
167
|
puppet_binary: @puppet_binary,
|
176
168
|
fact_file: @builddir.fact_file,
|
177
169
|
dir: @builddir.tempdir,
|
@@ -201,7 +193,7 @@ module OctocatalogDiff
|
|
201
193
|
# Set up the ScriptRunner
|
202
194
|
scriptrunner = OctocatalogDiff::Util::ScriptRunner.new(
|
203
195
|
default_script: 'puppet/puppet.sh',
|
204
|
-
override_script_path: @
|
196
|
+
override_script_path: @options[:override_script_path]
|
205
197
|
)
|
206
198
|
|
207
199
|
begin
|
@@ -1,21 +1,25 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative '../catalog'
|
4
|
+
require_relative '../catalog-util/fileresources'
|
5
|
+
|
3
6
|
require 'json'
|
4
7
|
|
5
8
|
module OctocatalogDiff
|
6
9
|
class Catalog
|
7
10
|
# Represents a Puppet catalog that is read in directly from a JSON file.
|
8
|
-
class JSON
|
9
|
-
attr_accessor :node
|
10
|
-
attr_reader :error_message, :catalog, :catalog_json
|
11
|
-
|
11
|
+
class JSON < OctocatalogDiff::Catalog
|
12
12
|
# Constructor
|
13
13
|
# @param :json [String] REQUIRED: Content of catalog, will be parsed as JSON
|
14
14
|
# @param :node [String] Node name (if not supplied, will be determined from catalog)
|
15
15
|
def initialize(options)
|
16
|
-
|
17
|
-
|
18
|
-
|
16
|
+
super
|
17
|
+
|
18
|
+
unless options[:json].is_a?(String)
|
19
|
+
raise ArgumentError, "Must supply :json as string in options: #{options[:json].class}"
|
20
|
+
end
|
21
|
+
|
22
|
+
@catalog_json = options.fetch(:json)
|
19
23
|
begin
|
20
24
|
@catalog = ::JSON.parse(@catalog_json)
|
21
25
|
@error_message = nil
|
@@ -27,6 +31,13 @@ module OctocatalogDiff
|
|
27
31
|
@catalog_json = nil
|
28
32
|
end
|
29
33
|
end
|
34
|
+
|
35
|
+
# Convert file resources source => "puppet:///..." to content => "actual content of file".
|
36
|
+
def convert_file_resources(dry_run = false)
|
37
|
+
return @options.key?(:basedir) if dry_run
|
38
|
+
return false unless @options[:basedir]
|
39
|
+
OctocatalogDiff::CatalogUtil::FileResources.convert_file_resources(self, environment)
|
40
|
+
end
|
30
41
|
end
|
31
42
|
end
|
32
43
|
end
|
@@ -1,16 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative '../catalog'
|
4
|
+
|
3
5
|
require 'json'
|
4
6
|
|
5
7
|
module OctocatalogDiff
|
6
8
|
class Catalog
|
7
9
|
# Represents a null Puppet catalog.
|
8
|
-
class Noop
|
9
|
-
attr_accessor :node
|
10
|
-
attr_reader :error_message, :catalog, :catalog_json
|
11
|
-
|
12
|
-
# Constructor
|
10
|
+
class Noop < OctocatalogDiff::Catalog
|
13
11
|
def initialize(options)
|
12
|
+
super
|
13
|
+
|
14
14
|
@catalog_json = '{"resources":[]}'
|
15
15
|
@catalog = { 'resources' => [] }
|
16
16
|
@error_message = nil
|
@@ -3,43 +3,30 @@
|
|
3
3
|
require 'json'
|
4
4
|
require 'stringio'
|
5
5
|
|
6
|
+
require_relative '../catalog'
|
6
7
|
require_relative '../errors'
|
7
8
|
require_relative '../puppetdb'
|
8
9
|
|
9
10
|
module OctocatalogDiff
|
10
11
|
class Catalog
|
11
12
|
# Represents a Puppet catalog that is read from PuppetDB.
|
12
|
-
class PuppetDB
|
13
|
-
attr_accessor :node
|
14
|
-
attr_reader :error_message, :catalog, :catalog_json, :retries, :convert_file_resources
|
15
|
-
|
13
|
+
class PuppetDB < OctocatalogDiff::Catalog
|
16
14
|
# Constructor - See OctocatalogDiff::PuppetDB for additional parameters
|
17
15
|
# @param :node [String] Node name
|
18
16
|
# @param :retry [Integer] Number of retries, if fetch fails
|
19
17
|
def initialize(options)
|
20
|
-
|
21
|
-
raise ArgumentError, 'node must be a non-empty string' unless options[:node].is_a?(String) && options[:node] != ''
|
22
|
-
@node = options[:node]
|
23
|
-
@catalog = nil
|
24
|
-
@error_message = nil
|
25
|
-
@retries = nil
|
26
|
-
|
27
|
-
# Cannot convert file resources from this type of catalog
|
28
|
-
@convert_file_resources = false
|
18
|
+
super
|
29
19
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
def build(logger = Logger.new(StringIO.new))
|
35
|
-
fetch_catalog(logger)
|
20
|
+
unless @options[:node].is_a?(String) && @options[:node] != ''
|
21
|
+
raise ArgumentError, 'node must be a non-empty string'
|
22
|
+
end
|
36
23
|
end
|
37
24
|
|
38
25
|
private
|
39
26
|
|
40
27
|
# Private method: Get catalog from PuppetDB. Sets @catalog / @catalog_json or @error_message
|
41
28
|
# @param logger [Logger object] Logger object
|
42
|
-
def
|
29
|
+
def build_catalog(logger)
|
43
30
|
# Use OctocatalogDiff::PuppetDB to interact with puppetdb
|
44
31
|
puppetdb_obj = OctocatalogDiff::PuppetDB.new(@options)
|
45
32
|
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative '../catalog'
|
3
4
|
require_relative '../catalog-util/facts'
|
4
5
|
require_relative '../external/pson/pure'
|
5
6
|
require_relative '../util/httparty'
|
@@ -11,10 +12,7 @@ require 'stringio'
|
|
11
12
|
module OctocatalogDiff
|
12
13
|
class Catalog
|
13
14
|
# Represents a Puppet catalog that is obtained by contacting the Puppet Master.
|
14
|
-
class PuppetMaster
|
15
|
-
attr_accessor :node
|
16
|
-
attr_reader :error_message, :catalog, :catalog_json, :convert_file_resources, :options, :retries
|
17
|
-
|
15
|
+
class PuppetMaster < OctocatalogDiff::Catalog
|
18
16
|
# Defaults
|
19
17
|
DEFAULT_PUPPET_PORT_NUMBER = 8140
|
20
18
|
DEFAULT_PUPPET_SERVER_API = 3
|
@@ -34,32 +32,29 @@ module OctocatalogDiff
|
|
34
32
|
# @param :puppet_master_ssl_client_auth [Boolean] Override the client-auth that is guessed from parameters
|
35
33
|
# @param :timeout [Integer] Connection timeout for Puppet master (default=PUPPET_MASTER_TIMEOUT seconds)
|
36
34
|
def initialize(options)
|
37
|
-
|
38
|
-
|
39
|
-
unless options[:
|
35
|
+
super
|
36
|
+
|
37
|
+
unless @options[:node].is_a?(String) && @options[:node] != ''
|
38
|
+
raise ArgumentError, 'node must be a non-empty string'
|
39
|
+
end
|
40
|
+
|
41
|
+
unless @options[:branch].is_a?(String) && @options[:branch] != ''
|
40
42
|
raise ArgumentError, 'Environment must be a non-empty string'
|
41
43
|
end
|
42
|
-
|
44
|
+
|
45
|
+
unless @options[:puppet_master].is_a?(String) && @options[:puppet_master] != ''
|
43
46
|
raise ArgumentError, 'Puppet Master must be a non-empty string'
|
44
47
|
end
|
45
48
|
|
46
|
-
@node = options[:node]
|
47
|
-
@catalog = nil
|
48
|
-
@error_message = nil
|
49
|
-
@retries = nil
|
50
49
|
@timeout = options.fetch(:puppet_master_timeout, options.fetch(:timeout, PUPPET_MASTER_TIMEOUT))
|
51
50
|
@retry_failed_catalog = options.fetch(:retry_failed_catalog, 0)
|
52
|
-
|
53
|
-
# Cannot convert file resources from this type of catalog right now.
|
54
|
-
# FIXME: This is possible with additional API calls but is current unimplemented.
|
55
|
-
@convert_file_resources = false
|
56
|
-
|
57
|
-
options[:puppet_master] += ":#{DEFAULT_PUPPET_PORT_NUMBER}" unless options[:puppet_master] =~ /\:\d+$/
|
58
|
-
@options = options
|
51
|
+
@options[:puppet_master] += ":#{DEFAULT_PUPPET_PORT_NUMBER}" unless @options[:puppet_master] =~ /\:\d+$/
|
59
52
|
end
|
60
53
|
|
54
|
+
private
|
55
|
+
|
61
56
|
# Build method
|
62
|
-
def
|
57
|
+
def build_catalog(logger = Logger.new(StringIO.new))
|
63
58
|
facts_obj = OctocatalogDiff::CatalogUtil::Facts.new(@options, logger)
|
64
59
|
logger.debug "Start retrieving facts for #{@node} from #{self.class}"
|
65
60
|
@facts = facts_obj.facts
|
@@ -67,8 +62,6 @@ module OctocatalogDiff
|
|
67
62
|
fetch_catalog(logger)
|
68
63
|
end
|
69
64
|
|
70
|
-
private
|
71
|
-
|
72
65
|
# Returns a hash of parameters for each supported version of the Puppet Server Catalog API.
|
73
66
|
# @return [Hash] Hash of parameters
|
74
67
|
#
|
@@ -11,7 +11,6 @@ module OctocatalogDiff
|
|
11
11
|
class BootstrapError < RuntimeError; end
|
12
12
|
class CatalogError < RuntimeError; end
|
13
13
|
class PuppetVersionError < RuntimeError; end
|
14
|
-
class ReferenceValidationError < RuntimeError; end
|
15
14
|
class GitCheckoutError < RuntimeError; end
|
16
15
|
|
17
16
|
# Error classes for retrieving facts
|
@@ -63,15 +63,15 @@ module OctocatalogDiff
|
|
63
63
|
# build the catalog.
|
64
64
|
result = {}
|
65
65
|
catalog_tasks.each do |x|
|
66
|
-
result[x[0]] = OctocatalogDiff::Catalog.
|
66
|
+
result[x[0]] = OctocatalogDiff::Catalog.create(x[1].args)
|
67
67
|
@logger.debug "Initialized #{result[x[0]].builder} for #{x[0]}-catalog"
|
68
68
|
end
|
69
69
|
|
70
70
|
# Disable --compare-file-text if either (or both) of the chosen backends do not support it
|
71
71
|
if @options.fetch(:compare_file_text, false)
|
72
|
-
result.each do |_key,
|
73
|
-
next
|
74
|
-
@logger.debug "Disabling --compare-file-text; not supported by #{
|
72
|
+
result.each do |_key, builder_obj|
|
73
|
+
next if builder_obj.convert_file_resources(true)
|
74
|
+
@logger.debug "Disabling --compare-file-text; not supported by #{builder_obj.builder}"
|
75
75
|
@options[:compare_file_text] = false
|
76
76
|
catalog_tasks.map! do |x|
|
77
77
|
x[1].args[:compare_file_text] = false
|
@@ -147,6 +147,7 @@ module OctocatalogDiff
|
|
147
147
|
retry_failed_catalog: @options.fetch(:retry_failed_catalog, 0),
|
148
148
|
parser: @options["parser_#{key}".to_sym]
|
149
149
|
)
|
150
|
+
args[:basedir] ||= args[:bootstrapped_dir]
|
150
151
|
|
151
152
|
# If any options are in the form of 'to_SOMETHING' or 'from_SOMETHING', this sets the option to
|
152
153
|
# 'SOMETHING' for the catalog if it matches this key. For example, when compiling the 'to' catalog
|
@@ -157,6 +158,9 @@ module OctocatalogDiff
|
|
157
158
|
args.delete(opt_key)
|
158
159
|
end
|
159
160
|
|
161
|
+
# Skip reference validation in the from-catalog by saying we already performed it.
|
162
|
+
args[:references_validated] = (key == :from)
|
163
|
+
|
160
164
|
# The task is a OctocatalogDiff::Util::Parallel::Task object that contains the method to execute,
|
161
165
|
# validator method, text description, and arguments to provide when calling the method.
|
162
166
|
task = OctocatalogDiff::Util::Parallel::Task.new(
|
@@ -250,10 +254,9 @@ module OctocatalogDiff
|
|
250
254
|
# @param logger [Logger] Logger object (presently unused)
|
251
255
|
# @param args [Hash] Additional arguments set specifically for validator
|
252
256
|
# @return [Boolean] Return true if catalog is valid, false otherwise
|
253
|
-
def catalog_validator(catalog = nil, _logger = @logger,
|
257
|
+
def catalog_validator(catalog = nil, _logger = @logger, _args = {})
|
254
258
|
raise ArgumentError, "Expects a catalog, got #{catalog.class}" unless catalog.is_a?(OctocatalogDiff::Catalog)
|
255
259
|
raise OctocatalogDiff::Errors::CatalogError, "Catalog failed: #{catalog.error_message}" unless catalog.valid?
|
256
|
-
catalog.validate_references if args[:task] == :to # Raises exception for broken references
|
257
260
|
true
|
258
261
|
end
|
259
262
|
end
|
@@ -107,7 +107,7 @@ module OctocatalogDiff
|
|
107
107
|
# @return [Exception] First exception encountered by a child process; returns nil if no exceptions encountered.
|
108
108
|
def self.run_tasks_parallel(result, task_array, logger)
|
109
109
|
pidmap = {}
|
110
|
-
ipc_tempdir = Dir.mktmpdir
|
110
|
+
ipc_tempdir = Dir.mktmpdir('ocd-ipc-')
|
111
111
|
|
112
112
|
# Child process forking
|
113
113
|
task_array.each_with_index do |task, index|
|
@@ -91,7 +91,7 @@ module OctocatalogDiff
|
|
91
91
|
# @return [String] Path to tempfile containing script
|
92
92
|
def temp_script(script)
|
93
93
|
raise Errno::ENOENT, "Script '#{script}' not found" unless File.file?(script)
|
94
|
-
temp_dir = Dir.mktmpdir
|
94
|
+
temp_dir = Dir.mktmpdir('ocd-scriptrunner-')
|
95
95
|
at_exit do
|
96
96
|
begin
|
97
97
|
FileUtils.remove_entry_secure temp_dir
|
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.4.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-
|
12
|
+
date: 2017-08-03 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: diffy
|
@@ -137,32 +137,18 @@ dependencies:
|
|
137
137
|
- - '='
|
138
138
|
- !ruby/object:Gem::Version
|
139
139
|
version: 0.48.1
|
140
|
-
- !ruby/object:Gem::Dependency
|
141
|
-
name: puppetdb-terminus
|
142
|
-
requirement: !ruby/object:Gem::Requirement
|
143
|
-
requirements:
|
144
|
-
- - '='
|
145
|
-
- !ruby/object:Gem::Version
|
146
|
-
version: 3.2.4
|
147
|
-
type: :development
|
148
|
-
prerelease: false
|
149
|
-
version_requirements: !ruby/object:Gem::Requirement
|
150
|
-
requirements:
|
151
|
-
- - '='
|
152
|
-
- !ruby/object:Gem::Version
|
153
|
-
version: 3.2.4
|
154
140
|
- !ruby/object:Gem::Dependency
|
155
141
|
name: simplecov
|
156
142
|
requirement: !ruby/object:Gem::Requirement
|
157
143
|
requirements:
|
158
|
-
- - "
|
144
|
+
- - "~>"
|
159
145
|
- !ruby/object:Gem::Version
|
160
146
|
version: 0.14.1
|
161
147
|
type: :development
|
162
148
|
prerelease: false
|
163
149
|
version_requirements: !ruby/object:Gem::Requirement
|
164
150
|
requirements:
|
165
|
-
- - "
|
151
|
+
- - "~>"
|
166
152
|
- !ruby/object:Gem::Version
|
167
153
|
version: 0.14.1
|
168
154
|
- !ruby/object:Gem::Dependency
|
@@ -193,6 +179,20 @@ dependencies:
|
|
193
179
|
- - "~>"
|
194
180
|
- !ruby/object:Gem::Version
|
195
181
|
version: 4.10.0
|
182
|
+
- !ruby/object:Gem::Dependency
|
183
|
+
name: puppetdb-terminus
|
184
|
+
requirement: !ruby/object:Gem::Requirement
|
185
|
+
requirements:
|
186
|
+
- - '='
|
187
|
+
- !ruby/object:Gem::Version
|
188
|
+
version: 3.2.4
|
189
|
+
type: :development
|
190
|
+
prerelease: false
|
191
|
+
version_requirements: !ruby/object:Gem::Requirement
|
192
|
+
requirements:
|
193
|
+
- - '='
|
194
|
+
- !ruby/object:Gem::Version
|
195
|
+
version: 3.2.4
|
196
196
|
description: |
|
197
197
|
Octocatalog-Diff assists with Puppet development and testing by enabling the user to
|
198
198
|
compile 2 Puppet catalogs and compare them. It is possible to compare different
|