Skip to content

Commit d1aa01d

Browse files
committed
Initial refactor of catalogs using inheritance
1 parent b850dd9 commit d1aa01d

26 files changed

+201
-202
lines changed

lib/octocatalog-diff/catalog.rb

Lines changed: 83 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -12,88 +12,110 @@
1212
require_relative 'errors'
1313

1414
module OctocatalogDiff
15-
# This class represents a catalog. Generation of the catalog is handled via one of the
15+
# Basic methods for interacting with a catalog. Generation of the catalog is handled via one of the
1616
# supported backends listed above as 'require_relative'. Usually, the 'computed' backend
1717
# will build the catalog from the Puppet command.
1818
class Catalog
19-
# Readable
20-
attr_reader :built, :catalog, :catalog_json
19+
attr_accessor :node
20+
attr_reader :built, :catalog, :catalog_json, :options
2121

2222
# Constructor
23-
# @param :backend [Symbol] If set, this will force a backend
24-
# @param :json [String] JSON catalog content (will avoid running Puppet to compile catalog)
25-
# @param :puppetdb [Object] If set, pull the catalog from PuppetDB rather than building
26-
# @param :node [String] Name of node whose catalog is being built
27-
# @param :fact_file [String] OPTIONAL: Path to fact file (if not provided, look up in PuppetDB)
28-
# @param :hiera_config [String] OPTIONAL: Path to hiera config file (munge temp. copy if not provided)
29-
# @param :basedir [String] OPTIONAL: Base directory for catalog (default base directory of this checkout)
30-
# @param :pass_env_vars [Array<String>] OPTIONAL: Additional environment vars to pass
31-
# @param :convert_file_resources [Boolean] OPTIONAL: Convert file resource source to content
32-
# @param :storeconfigs [Boolean] OPTIONAL: Pass the '-s' flag, for puppetdb (storeconfigs) integration
3323
def initialize(options = {})
24+
unless options.is_a?(Hash)
25+
raise ArgumentError, "#{self.class}.initialize requires hash argument, not #{options.class}"
26+
end
3427
@options = options
3528

29+
# Basic settings
30+
@node = options[:node]
31+
@error_message = nil
32+
@catalog = nil
33+
@catalog_json = nil
34+
3635
# The compilation directory can be overridden, e.g. when testing
3736
@override_compilation_dir = options[:compilation_dir]
3837

3938
# Keep track of whether references have been validated yet. Allow this to be fudged for when we do
4039
# not desire reference validation to happen (e.g., for the "from" catalog that is otherwise valid).
4140
@references_validated = options[:references_validated] || false
4241

43-
# Call appropriate backend for catalog generation
44-
@catalog_obj = backend(options)
42+
# Keep track of whether file resources have been converted.
43+
@file_resources_converted = false
4544

46-
# The catalog is not built yet, except if the backend has no build method
45+
# Keep track of whether it's built yet
4746
@built = false
48-
build unless @catalog_obj.respond_to?(:build)
47+
end
48+
49+
# Guess the backend from the input and return the appropriate catalog object.
50+
# @param :backend [Symbol] If set, this will force a backend
51+
# @param :json [String] JSON catalog content (will avoid running Puppet to compile catalog)
52+
# @param :puppetdb [Object] If set, pull the catalog from PuppetDB rather than building
53+
# @param :node [String] Name of node whose catalog is being built
54+
# @param :fact_file [String] OPTIONAL: Path to fact file (if not provided, look up in PuppetDB)
55+
# @param :hiera_config [String] OPTIONAL: Path to hiera config file (munge temp. copy if not provided)
56+
# @param :basedir [String] OPTIONAL: Base directory for catalog (default base directory of this checkout)
57+
# @param :pass_env_vars [Array<String>] OPTIONAL: Additional environment vars to pass
58+
# @param :convert_file_resources [Boolean] OPTIONAL: Convert file resource source to content
59+
# @param :storeconfigs [Boolean] OPTIONAL: Pass the '-s' flag, for puppetdb (storeconfigs) integration
60+
# @return [OctocatalogDiff::Catalog::<?>] Catalog object from guessed backend
61+
def self.create(options = {})
62+
# Hard-coded backend
63+
if options[:backend]
64+
return OctocatalogDiff::Catalog::JSON.new(options) if options[:backend] == :json
65+
return OctocatalogDiff::Catalog::PuppetDB.new(options) if options[:backend] == :puppetdb
66+
return OctocatalogDiff::Catalog::PuppetMaster.new(options) if options[:backend] == :puppetmaster
67+
return OctocatalogDiff::Catalog::Computed.new(options) if options[:backend] == :computed
68+
return OctocatalogDiff::Catalog::Noop.new(options) if options[:backend] == :noop
69+
raise ArgumentError, "Unknown backend :#{options[:backend]}"
70+
end
71+
72+
# Determine backend based on arguments
73+
return OctocatalogDiff::Catalog::JSON.new(options) if options[:json]
74+
return OctocatalogDiff::Catalog::PuppetDB.new(options) if options[:puppetdb]
75+
return OctocatalogDiff::Catalog::PuppetMaster.new(options) if options[:puppet_master]
76+
77+
# Default is to build catalog ourselves
78+
OctocatalogDiff::Catalog::Computed.new(options)
4979
end
5080

5181
# Build catalog - this method needs to be called to build the catalog. It is separate due to
5282
# the serialization of the logger object -- the parallel gem cannot serialize/deserialize a logger
5383
# object so it cannot be part of any object that is passed around.
5484
# @param logger [Logger] Logger object, initialized to a default throwaway value
5585
def build(logger = Logger.new(StringIO.new))
56-
# Only build once
86+
# If already built, don't build again
5787
return if @built
5888
@built = true
5989

60-
# Call catalog's build method.
61-
if @catalog_obj.respond_to?(:build)
62-
logger.debug "Calling build for object #{@catalog_obj.class}"
63-
@catalog_obj.build(logger)
64-
end
65-
66-
# These methods must exist in all backends
67-
@catalog = @catalog_obj.catalog
68-
@catalog_json = @catalog_obj.catalog_json
69-
@error_message = @catalog_obj.error_message
70-
7190
# The resource hash is computed the first time it's needed. For now initialize it as nil.
7291
@resource_hash = nil
7392

93+
# Invoke the backend's build method, if there is one. There's a stub below in case there's not.
94+
build_catalog(logger)
95+
7496
# Perform post-generation processing of the catalog
7597
return unless valid?
7698

7799
validate_references
78100
return unless valid?
79101

80-
unless @catalog_obj.respond_to?(:convert_file_resources) && @catalog_obj.convert_file_resources == false
81-
if @options.fetch(:compare_file_text, false)
82-
OctocatalogDiff::CatalogUtil::FileResources.convert_file_resources(self, environment)
83-
end
84-
end
102+
convert_file_resources(logger)
103+
end
104+
105+
# Stub method if the backend does not contain a build method.
106+
def build_catalog(_logger)
85107
end
86108

87109
# Compilation environment
88110
# @return [String] Compilation environment (if set), else 'production' by default
89111
def environment
90-
@catalog_obj.respond_to?(:environment) ? @catalog_obj.environment : 'production'
112+
@environment ||= 'production'
91113
end
92114

93115
# For logging we may wish to know the backend being used
94116
# @return [String] Class of backend used
95117
def builder
96-
@catalog_obj.class.to_s
118+
self.class.to_s
97119
end
98120

99121
# Set the catalog JSON
@@ -106,8 +128,7 @@ def catalog_json=(str)
106128
# This retrieves the compilation directory from the catalog, or otherwise the passed-in directory.
107129
# @return [String] Compilation directory
108130
def compilation_dir
109-
return @override_compilation_dir if @override_compilation_dir
110-
@catalog_obj.respond_to?(:compilation_dir) ? @catalog_obj.compilation_dir : @options[:basedir]
131+
@override_compilation_dir || @options[:basedir]
111132
end
112133

113134
# The compilation directory can be overridden, e.g. during testing.
@@ -116,16 +137,16 @@ def compilation_dir=(dir)
116137
@override_compilation_dir = dir
117138
end
118139

119-
# Determine whether the underlying catalog object supports :compare_file_text
120-
# @return [Boolean] Whether underlying catalog object supports :compare_file_text
121-
def convert_file_resources
122-
return true unless @catalog_obj.respond_to?(:convert_file_resources)
123-
@catalog_obj.convert_file_resources
140+
# Stub method for "convert_file_resources" to avoid errors if the backend doesn't support this.
141+
# @return [Boolean] True
142+
def convert_file_resources(logger = Logger.new(StringIO.new))
143+
logger.debug "Disabling --compare-file-text; not supported by #{self.class}"
124144
end
125145

126146
# Retrieve the error message.
127147
# @return [String] Error message (maximum 20,000 characters) - nil if no error.
128148
def error_message
149+
build
129150
return nil if @error_message.nil? || !@error_message.is_a?(String)
130151
@error_message[0, 20_000]
131152
end
@@ -141,12 +162,11 @@ def error_message=(error)
141162
@resource_hash = nil
142163
end
143164

144-
# This retrieves the version of Puppet used to compile a catalog. If the underlying catalog was not
145-
# compiled by running Puppet (e.g., it was read in from JSON or puppetdb), then this returns the
146-
# puppet version optionally passed in to the constructor. This can also be nil.
165+
# Stub method to return the puppet version if the back end doesn't support this.
147166
# @return [String] Puppet version
148167
def puppet_version
149-
@catalog_obj.respond_to?(:puppet_version) ? @catalog_obj.puppet_version : @options[:puppet_version]
168+
build
169+
@options[:puppet_version]
150170
end
151171

152172
# This allows retrieving a resource by type and title. This is intended for use when a O(1) lookup is required.
@@ -155,6 +175,7 @@ def puppet_version
155175
# @return [Hash] Resource item
156176
def resource(opts = {})
157177
raise ArgumentError, ':type and :title are required' unless opts[:type] && opts[:title]
178+
build
158179
build_resource_hash if @resource_hash.nil?
159180
return nil unless @resource_hash[opts[:type]].is_a?(Hash)
160181
@resource_hash[opts[:type]][opts[:title]]
@@ -163,6 +184,7 @@ def resource(opts = {})
163184
# This is a compatibility layer for the resources, which are in a different place in Puppet 3.x and Puppet 4.x
164185
# @return [Array] Resource array
165186
def resources
187+
build
166188
raise OctocatalogDiff::Errors::CatalogError, 'Catalog does not appear to have been built' if !valid? && error_message.nil?
167189
raise OctocatalogDiff::Errors::CatalogError, error_message unless valid?
168190
return @catalog['data']['resources'] if @catalog['data'].is_a?(Hash) && @catalog['data']['resources'].is_a?(Array)
@@ -173,16 +195,17 @@ def resources
173195
# :nocov:
174196
end
175197

176-
# This retrieves the number of retries necessary to compile the catalog. If the underlying catalog
198+
# Stub method of the the number of retries necessary to compile the catalog. If the underlying catalog
177199
# generation backend does not support retries, nil is returned.
178200
# @return [Integer] Retry count
179201
def retries
180-
@retries = @catalog_obj.respond_to?(:retries) ? @catalog_obj.retries : nil
202+
nil
181203
end
182204

183205
# Determine if the catalog build was successful.
184206
# @return [Boolean] Whether the catalog is valid
185207
def valid?
208+
build
186209
!@catalog.nil?
187210
end
188211

@@ -281,29 +304,6 @@ def resources_missing_from_catalog(resources_to_check)
281304
end
282305
end
283306

284-
# Private method: Choose backend based on passed-in options
285-
# @param options [Hash] Options passed into constructor
286-
# @return [Object] OctocatalogDiff::Catalog::<whatever> object
287-
def backend(options)
288-
# Hard-coded backend
289-
if options[:backend]
290-
return OctocatalogDiff::Catalog::JSON.new(options) if options[:backend] == :json
291-
return OctocatalogDiff::Catalog::PuppetDB.new(options) if options[:backend] == :puppetdb
292-
return OctocatalogDiff::Catalog::PuppetMaster.new(options) if options[:backend] == :puppetmaster
293-
return OctocatalogDiff::Catalog::Computed.new(options) if options[:backend] == :computed
294-
return OctocatalogDiff::Catalog::Noop.new(options) if options[:backend] == :noop
295-
raise ArgumentError, "Unknown backend :#{options[:backend]}"
296-
end
297-
298-
# Determine backend based on arguments
299-
return OctocatalogDiff::Catalog::JSON.new(options) if options[:json]
300-
return OctocatalogDiff::Catalog::PuppetDB.new(options) if options[:puppetdb]
301-
return OctocatalogDiff::Catalog::PuppetMaster.new(options) if options[:puppet_master]
302-
303-
# Default is to build catalog ourselves
304-
OctocatalogDiff::Catalog::Computed.new(options)
305-
end
306-
307307
# Private method: Build the resource hash to be used used for O(1) lookups by type and title.
308308
# This method is called the first time the resource hash is accessed.
309309
def build_resource_hash
@@ -317,5 +317,16 @@ def build_resource_hash
317317
end
318318
end
319319
end
320+
321+
# Private method: A common way of running convert_file_resources for backends that allow it.
322+
def convert_file_resources_real(logger = Logger.new(StringIO.new))
323+
return false unless @options[:compare_file_text]
324+
if @options[:basedir]
325+
OctocatalogDiff::CatalogUtil::FileResources.convert_file_resources(self, environment)
326+
else
327+
logger.debug "Disabling --compare-file-text; not supported by #{self.class} without basedir"
328+
false
329+
end
330+
end
320331
end
321332
end

lib/octocatalog-diff/catalog/computed.rb

Lines changed: 16 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
require 'json'
55
require 'stringio'
66

7+
require_relative '../catalog'
78
require_relative '../catalog-util/bootstrap'
89
require_relative '../catalog-util/builddir'
910
require_relative '../catalog-util/command'
@@ -15,9 +16,7 @@ module OctocatalogDiff
1516
class Catalog
1617
# Represents a Puppet catalog that is computed (via `puppet master --compile ...`)
1718
# 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
2120
# Constructor
2221
# @param :node [String] REQUIRED: Node name
2322
# @param :basedir [String] Directory in which to compile the catalog
@@ -28,14 +27,9 @@ class Computed
2827
# @param :puppet_version [String] Puppet version (optional; if not supplied, it is calculated)
2928
# @param :puppet_command [String] Full command to run Puppet (optional; if not supplied, it is calculated)
3029
def initialize(options)
31-
raise ArgumentError, 'Usage: OctocatalogDiff::Catalog::Computed.initialize(options_hash)' unless options.is_a?(Hash)
32-
raise ArgumentError, 'Node name must be passed to OctocatalogDiff::Catalog::Computed' unless options[:node].is_a?(String)
30+
super
3331

34-
# Standard readable variables
35-
@node = options[:node]
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)
3933

4034
# Additional class variables
4135
@pass_env_vars = options.fetch(:pass_env_vars, [])
@@ -53,17 +47,6 @@ def initialize(options)
5347
raise ArgumentError, 'Branch is undefined' unless @opts[:branch]
5448
end
5549

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-
end
66-
6750
# Get the Puppet version
6851
# @return [String] Puppet version
6952
def puppet_version
@@ -83,6 +66,11 @@ def environment
8366
@opts.fetch(:environment, 'production')
8467
end
8568

69+
# Convert file source => ... to content => ... if a basedir is given.
70+
def convert_file_resources(logger = Logger.new(StringIO.new))
71+
convert_file_resources_real(logger)
72+
end
73+
8674
private
8775

8876
# Private method: Clean up a checkout directory, if it exists
@@ -137,7 +125,13 @@ def bootstrap(logger)
137125
# Private method: Build catalog by running Puppet
138126
# @param logger [Logger] Logger object
139127
def build_catalog(logger = nil)
140-
return nil unless @catalog.nil? && @error_message.nil?
128+
if @facts_terminus != 'facter'
129+
facts_obj = OctocatalogDiff::CatalogUtil::Facts.new(@opts, logger)
130+
logger.debug "Start retrieving facts for #{@node} from #{self.class}"
131+
@opts[:facts] = facts_obj.facts
132+
logger.debug "Success retrieving facts for #{@node} from #{self.class}"
133+
end
134+
141135
bootstrap(logger)
142136
result = run_puppet(logger)
143137
@retries = result[:retries]

lib/octocatalog-diff/catalog/json.rb

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
11
# frozen_string_literal: true
22

3+
require_relative '../catalog'
4+
require_relative '../catalog-util/fileresources'
5+
36
require 'json'
47

58
module OctocatalogDiff
69
class Catalog
710
# 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
1212
# Constructor
1313
# @param :json [String] REQUIRED: Content of catalog, will be parsed as JSON
1414
# @param :node [String] Node name (if not supplied, will be determined from catalog)
1515
def initialize(options)
16-
raise ArgumentError, 'Usage: OctocatalogDiff::Catalog::JSON.initialize(options_hash)' unless options.is_a?(Hash)
16+
super
1717
raise ArgumentError, "Must supply :json as string in options: #{options[:json].class}" unless options[:json].is_a?(String)
18-
@catalog_json = options[:json]
18+
19+
@catalog_json = options.fetch(:json)
1920
begin
2021
@catalog = ::JSON.parse(@catalog_json)
2122
@error_message = nil
@@ -27,6 +28,11 @@ def initialize(options)
2728
@catalog_json = nil
2829
end
2930
end
31+
32+
# Convert file source => ... to content => ... if a basedir is given.
33+
def convert_file_resources(logger = Logger.new(StringIO.new))
34+
convert_file_resources_real(logger)
35+
end
3036
end
3137
end
3238
end

0 commit comments

Comments
 (0)