Skip to content

Commit 054967c

Browse files
authored
Fix enum idempotency (#291)
* Fix enum idempotency * fix spec
1 parent 9721f08 commit 054967c

File tree

2 files changed

+38
-3
lines changed

2 files changed

+38
-3
lines changed

lib/puppet/provider/dsc_base_provider/dsc_base_provider.rb

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ def fetch_cached_hashes(cache, hashes)
4444
# @param context [Object] the Puppet runtime context to operate in and send feedback to
4545
# @param resources [Hash] the hash of the resource to canonicalize from either manifest or invocation
4646
# @return [Hash] returns a hash representing the current state of the object, if it exists
47+
# rubocop:disable Metrics/BlockLength, Metrics/MethodLength
4748
def canonicalize(context, resources)
4849
canonicalized_resources = []
4950
resources.collect do |r|
@@ -83,9 +84,18 @@ def canonicalize(context, resources)
8384
downcased_result.each do |key, value|
8485
# Canonicalize to the manifest value unless the downcased strings match and the attribute is not an enum:
8586
# - When the values don't match at all, the manifest value is desired;
86-
# - When the values match case insensitively but the attribute is an enum, prefer the casing of the manifest enum.
87-
# - When the values match case insensitively and the attribute is not an enum, prefer the casing from invoke_get_method
88-
canonicalized[key] = r[key] unless same?(value, downcased_resource[key]) && !enum_attributes(context).include?(key)
87+
# - When the values match case insensitively but the attribute is an enum, and the casing from invoke_get_method
88+
# is not int the enum, prefer the casing of the manifest enum.
89+
# - When the values match case insensitively and the attribute is not an enum, or is an enum and invoke_get_method casing
90+
# is in the enum, prefer the casing from invoke_get_method
91+
is_enum = enum_attributes(context).include?(key)
92+
canonicalized_value_in_enum = if is_enum
93+
enum_values(context, key).include?(canonicalized[key])
94+
else
95+
false
96+
end
97+
match_insensitively = same?(value, downcased_resource[key])
98+
canonicalized[key] = r[key] unless match_insensitively && (canonicalized_value_in_enum || !is_enum)
8999
canonicalized.delete(key) unless downcased_resource.key?(key)
90100
end
91101
# Cache the actually canonicalized resource separately
@@ -104,6 +114,7 @@ def canonicalize(context, resources)
104114
context.debug("Canonicalized Resources: #{canonicalized_resources}")
105115
canonicalized_resources
106116
end
117+
# rubocop:enable Metrics/BlockLength, Metrics/MethodLength
107118

108119
# Attempts to retrieve an instance of the DSC resource, invoking the `Get` method and passing any
109120
# namevars as the Properties to Invoke-DscResource. The result object, if any, is compared to the
@@ -686,6 +697,28 @@ def enum_attributes(context)
686697
context.type.attributes.select { |_name, properties| properties[:type].include?('Enum[') }.keys
687698
end
688699

700+
# Parses the DSC resource type definition to retrieve the values of any attributes which are specified as enums
701+
#
702+
# @param context [Object] the Puppet runtime context to operate in and send feedback to
703+
# @param attribute [String] the enum attribute to retrieve the allowed values from
704+
# @return [Array] returns an array of attribute names as symbols which are enums
705+
def enum_values(context, attribute)
706+
# Get the attribute's type string for the given key
707+
type_string = context.type.attributes[attribute][:type]
708+
709+
# Return an empty array if the key doesn't have an Enum type or doesn't exist
710+
return [] unless type_string&.include?('Enum[')
711+
712+
# Extract the enum values from the type string
713+
enum_content = type_string.match(/Enum\[(.*?)\]/)&.[](1)
714+
715+
# Return an empty array if we couldn't find the enum values
716+
return [] if enum_content.nil?
717+
718+
# Return an array of the enum values, stripped of extra whitespace and quote marks
719+
enum_content.split(',').map { |val| val.strip.delete('\'') }
720+
end
721+
689722
# Look through a fully formatted string, replacing all instances where a value matches the formatted properties
690723
# of an instantiated variable with references to the variable instead. This allows us to pass complex and nested
691724
# CIM instances to the Invoke-DscResource parameter hash without constructing them *in* the hash.

spec/unit/puppet/provider/dsc_base_provider/dsc_base_provider_spec.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,8 @@
179179
end
180180

181181
it 'treats the manifest value as canonical' do
182+
expect(context).to receive(:type).and_return(type)
183+
expect(type).to receive(:attributes).and_return({ dsc_property: { type: "Enum['Dword']" } })
182184
expect(canonicalized_resource.first[:dsc_property]).to eq('Dword')
183185
end
184186
end

0 commit comments

Comments
 (0)