lib/puppet/resource_api.rb in puppet-resource_api-1.0.3 vs lib/puppet/resource_api.rb in puppet-resource_api-1.1.0
- old
+ new
@@ -143,80 +143,91 @@
defaultto options[:default]
end
end
type = Puppet::Pops::Types::TypeParser.singleton.parse(options[:type])
- validate do |value|
- if options[:behaviour] == :read_only
- raise Puppet::ResourceError, "Attempting to set `#{name}` read_only attribute value to `#{value}`"
+ if param_or_property == :newproperty
+ define_method(:should) do
+ if type.is_a? Puppet::Pops::Types::PBooleanType
+ # work around https://tickets.puppetlabs.com/browse/PUP-2368
+ rs_value ? :true : :false # rubocop:disable Lint/BooleanSymbol
+ else
+ rs_value
+ end
end
- return true if type.instance?(value)
+ define_method(:should=) do |value|
+ @shouldorig = value
+ # Puppet requires the @should value to always be stored as an array. We do not use this
+ # for anything else
+ # @see Puppet::Property.should=(value)
+ @should = [Puppet::ResourceApi.mungify(type, value, "#{definition[:name]}.#{name}")]
+ end
- if value.is_a? String
- # when running under `puppet resource`, we need to try to coerce from strings to the real type
- case value
- when %r{^-?\d+$}, Puppet::Pops::Patterns::NUMERIC
- value = Puppet::Pops::Utils.to_n(value)
- when %r{\Atrue|false\Z}
- value = value == 'true'
+ # used internally
+ # @returns the final mungified value of this property
+ define_method(:rs_value) do
+ @should ? @should.first : @should
+ end
+ else
+ define_method(:value) do
+ @value
+ end
+
+ define_method(:value=) do |value|
+ if options[:behaviour] == :read_only
+ raise Puppet::ResourceError, "Attempting to set `#{name}` read_only attribute value to `#{value}`"
end
- return true if type.instance?(value)
- inferred_type = Puppet::Pops::Types::TypeCalculator.infer_set(value)
- error_msg = Puppet::Pops::Types::TypeMismatchDescriber.new.describe_mismatch("#{definition[:name]}.#{name}", type, inferred_type)
- raise Puppet::ResourceError, error_msg
+ @value = Puppet::ResourceApi.mungify(type, value, "#{definition[:name]}.#{name}")
end
+
+ # used internally
+ # @returns the final mungified value of this parameter
+ define_method(:rs_value) do
+ @value
+ end
end
if type.instance_of? Puppet::Pops::Types::POptionalType
type = type.type
end
- # provide better handling of the standard types
+ # puppet symbolizes some values through puppet/parameter/value.rb (see .convert()), but (especially) Enums
+ # are strings. specifying a munge block here skips the value_collection fallback in puppet/parameter.rb's
+ # default .unsafe_munge() implementation.
+ munge { |v| v }
+
+ # provide hints to `puppet type generate` for better parsing
case type
when Puppet::Pops::Types::PStringType
# require any string value
Puppet::ResourceApi.def_newvalues(self, param_or_property, %r{})
- # rubocop:disable Lint/BooleanSymbol
when Puppet::Pops::Types::PBooleanType
Puppet::ResourceApi.def_newvalues(self, param_or_property, 'true', 'false')
+ # rubocop:disable Lint/BooleanSymbol
aliasvalue true, 'true'
aliasvalue false, 'false'
aliasvalue :true, 'true'
aliasvalue :false, 'false'
-
- munge do |v|
- case v
- when 'true', :true
- true
- when 'false', :false
- false
- else
- v
- end
- end
- # rubocop:enable Lint/BooleanSymbol
+ # rubocop:enable Lint/BooleanSymbol
when Puppet::Pops::Types::PIntegerType
Puppet::ResourceApi.def_newvalues(self, param_or_property, %r{^-?\d+$})
- munge do |v|
- Puppet::Pops::Utils.to_n(v)
- end
when Puppet::Pops::Types::PFloatType, Puppet::Pops::Types::PNumericType
Puppet::ResourceApi.def_newvalues(self, param_or_property, Puppet::Pops::Patterns::NUMERIC)
- munge do |v|
- Puppet::Pops::Utils.to_n(v)
- end
end
+ if param_or_property == :newproperty
+ # stop puppet from trying to call into the provider when
+ # no pre-defined values have been specified
+ # "This is not the provider you are looking for." -- Obi-Wan Kaniesobi.
+ def call_provider(value); end
+ end
+
case options[:type]
when 'Enum[present, absent]'
Puppet::ResourceApi.def_newvalues(self, param_or_property, 'absent', 'present')
- # puppet symbolizes these values through puppet/paramter/value.rb (see .convert()), but Enums are strings
- # specifying a munge block here skips the value_collection fallback in puppet/parameter.rb's
- # default .unsafe_munge() implementation
- munge { |v| v }
end
end
end
define_singleton_method(:instances) do
@@ -264,13 +275,12 @@
result
end
define_method(:flush) do
# puts 'flush'
- target_state = Hash[@parameters.map { |k, v| [k, v.value] }]
- # remove puppet's injected metaparams
- target_state.delete(:loglevel)
+ # skip puppet's injected metaparams
+ target_state = Hash[@parameters.reject { |k, _v| [:loglevel, :noop].include? k }.map { |k, v| [k, v.rs_value] }]
target_state = my_provider.canonicalize(context, [target_state]).first if feature_support?('canonicalize')
retrieve unless @rapi_current_state
# require 'pry'; binding.pry
@@ -382,7 +392,90 @@
else
values.each do |v|
this.newvalue(v) {}
end
end
+ end
+
+ # Validates and munges values coming from puppet into a shape palatable to the provider.
+ # This includes parsing values from strings, e.g. when running in `puppet resource`.
+ # @param type[Puppet::Pops::Types::TypedModelObject] the type to check/clean against
+ # @param value the value to clean
+ # @param error_msg_prefix[String] a prefix for the error messages
+ # @return [type] the cleaned value
+ # @raise [Puppet::ResourceError] if `value` could not be parsed into `type`
+ def self.mungify(type, value, error_msg_prefix)
+ cleaned_value, error = try_mungify(type, value, error_msg_prefix)
+
+ raise Puppet::ResourceError, error if error
+
+ cleaned_value
+ end
+
+ # Recursive implementation part of #mungify. Uses a multi-valued return value to avoid excessive
+ # exception throwing for regular usage
+ # @return [Array] if the mungify worked, the first element is the cleaned value, and the second
+ # element is nil. If the mungify failed, the first element is nil, and the second element is an error
+ # message
+ # @private
+ def self.try_mungify(type, value, error_msg_prefix)
+ case type
+ when Puppet::Pops::Types::PArrayType
+ if value.is_a? Array
+ conversions = value.map do |v|
+ try_mungify(type.element_type, v, error_msg_prefix)
+ end
+ # only convert the values if none failed. otherwise fall through and rely on puppet to render a proper error
+ if conversions.all? { |c| c[1].nil? }
+ value = conversions.map { |c| c[0] }
+ end
+ end
+ when Puppet::Pops::Types::PBooleanType
+ value = case value
+ when 'true', :true # rubocop:disable Lint/BooleanSymbol
+ true
+ when 'false', :false # rubocop:disable Lint/BooleanSymbol
+ false
+ else
+ value
+ end
+ when Puppet::Pops::Types::PIntegerType, Puppet::Pops::Types::PFloatType, Puppet::Pops::Types::PNumericType
+ if value =~ %r{^-?\d+$} || value =~ Puppet::Pops::Patterns::NUMERIC
+ value = Puppet::Pops::Utils.to_n(value)
+ end
+ when Puppet::Pops::Types::PEnumType, Puppet::Pops::Types::PStringType, Puppet::Pops::Types::PPatternType
+ if value.is_a? Symbol
+ value = value.to_s
+ end
+ when Puppet::Pops::Types::POptionalType
+ return value.nil? ? [nil, nil] : try_mungify(type.type, value, error_msg_prefix)
+ when Puppet::Pops::Types::PVariantType
+ # try converting to anything except string first
+ string_type = type.types.find { |t| t.is_a? Puppet::Pops::Types::PStringType }
+ conversion_results = (type.types - [string_type]).map do |t|
+ try_mungify(t, value, error_msg_prefix)
+ end
+
+ # only consider valid results
+ conversion_results = conversion_results.select { |r| r[1].nil? }.to_a
+
+ # use the conversion result if unambiguous
+ return conversion_results[0] if conversion_results.length == 1
+
+ # return an error if ambiguous
+ return [nil, "#{error_msg_prefix} #{value.inspect} is not unabiguously convertable to #{type}"] if conversion_results.length > 1
+
+ # try to interpret as string
+ return try_mungify(string_type, value, error_msg_prefix) if string_type
+
+ # fall through to default handling
+ end
+
+ # a match!
+ return [value, nil] if type.instance?(value)
+
+ # an error :-(
+ inferred_type = Puppet::Pops::Types::TypeCalculator.infer_set(value)
+ error_msg = Puppet::Pops::Types::TypeMismatchDescriber.new.describe_mismatch(error_msg_prefix, type, inferred_type)
+ return [nil, error_msg] # the entire function is using returns for clarity # rubocop:disable Style/RedundantReturn
end
end