Skip to content

Commit

Permalink
Merge pull request #140 from enterprisemodules/parameter_and_properties
Browse files Browse the repository at this point in the history
Move parameter and property logic to separate classes
  • Loading branch information
DavidS authored Dec 6, 2018
2 parents 9dd87a9 + 83f99db commit 3099283
Show file tree
Hide file tree
Showing 9 changed files with 689 additions and 150 deletions.
191 changes: 41 additions & 150 deletions lib/puppet/resource_api.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
require 'pathname'
require 'puppet/resource_api/data_type_handling'
require 'puppet/resource_api/glue'
require 'puppet/resource_api/parameter'
require 'puppet/resource_api/property'
require 'puppet/resource_api/puppet_context' unless RUBY_PLATFORM == 'java'
require 'puppet/resource_api/read_only_parameter'
require 'puppet/resource_api/type_definition'
require 'puppet/resource_api/value_creator'
require 'puppet/resource_api/version'
require 'puppet/type'
require 'puppet/util/network_device'
Expand Down Expand Up @@ -174,13 +178,23 @@ def to_resource
# TODO: using newparam everywhere would suppress change reporting
# that would allow more fine-grained reporting through context,
# but require more invest in hooking up the infrastructure to emulate existing data
param_or_property = if [:read_only, :parameter, :namevar].include? options[:behaviour]
:newparam
else
:newproperty
end
if [:parameter, :namevar].include? options[:behaviour]
param_or_property = :newparam
parent = Puppet::ResourceApi::Parameter
elsif options[:behaviour] == :read_only
param_or_property = :newparam
parent = Puppet::ResourceApi::ReadOnlyParameter
else
param_or_property = :newproperty
parent = Puppet::ResourceApi::Property
end

send(param_or_property, name.to_sym) do
# This call creates a new parameter or property with all work-arounds or
# customizations required by the Resource API applied. Under the hood,
# this maps to the relevant DSL methods in Puppet::Type. See
# https://puppet.com/docs/puppet/6.0/custom_types.html#reference-5883
# for details.
send(param_or_property, name.to_sym, parent: parent) do
unless options[:type]
raise Puppet::DevError, "#{definition[:name]}.#{name} has no type"
end
Expand All @@ -191,144 +205,32 @@ def to_resource
warn("#{definition[:name]}.#{name} has no docs")
end

if options[:behaviour] == :namevar
isnamevar
end

# read-only values do not need type checking, but can have default values
if options[:behaviour] != :read_only && options.key?(:default)
if options.key? :default
if options[:default] == false
# work around https://tickets.puppetlabs.com/browse/PUP-2368
defaultto :false # rubocop:disable Lint/BooleanSymbol
elsif options[:default] == true
# work around https://tickets.puppetlabs.com/browse/PUP-2368
defaultto :true # rubocop:disable Lint/BooleanSymbol
else
# marshal the default option to decouple that from the actual value.
# we cache the dumped value in `marshalled`, but use a block to unmarshal
# everytime the value is requested. Objects that can't be marshalled
# See https://stackoverflow.com/a/8206537/4918
marshalled = Marshal.dump(options[:default])
defaultto { Marshal.load(marshalled) } # rubocop:disable Security/MarshalLoad
end
end
# The initialize method is called when puppet core starts building up
# type objects. The core passes in a hash of shape { resource:
# #<Puppet::Type::TypeName> }. We use this to pass through the
# required configuration data to the parent (see
# Puppet::ResourceApi::Property, Puppet::ResourceApi::Parameter and
# Puppet::ResourceApi::ReadOnlyParameter).
define_method(:initialize) do |resource_hash|
super(definition[:name], self.class.data_type, name, resource_hash)
end

if name == :ensure
def insync?(is)
rs_value.to_s == is.to_s
end
# get pops data type object for this parameter or property
define_singleton_method(:data_type) do
@rsapi_data_type ||= Puppet::ResourceApi::DataTypeHandling.parse_puppet_type(
name,
options[:type],
)
end

type = Puppet::ResourceApi::DataTypeHandling.parse_puppet_type(
name,
options[:type],
# from ValueCreator call create_values which makes alias values and
# default values for properties and params
Puppet::ResourceApi::ValueCreator.create_values(
self,
data_type,
param_or_property,
options,
)

if param_or_property == :newproperty
define_method(:should) do
if name == :ensure && rs_value.is_a?(String)
rs_value.to_sym
elsif rs_value == false
# work around https://tickets.puppetlabs.com/browse/PUP-2368
:false # rubocop:disable Lint/BooleanSymbol
elsif rs_value == true
# work around https://tickets.puppetlabs.com/browse/PUP-2368
:true # rubocop:disable Lint/BooleanSymbol
else
rs_value
end
end

define_method(:should=) do |value|
@shouldorig = value

if name == :ensure
value = value.to_s
end

# 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::DataTypeHandling.mungify(
type,
value,
"#{definition[:name]}.#{name}",
Puppet::ResourceApi.caller_is_resource_app?,
),
]
end

# 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

@value = Puppet::ResourceApi::DataTypeHandling.mungify(
type,
value,
"#{definition[:name]}.#{name}",
Puppet::ResourceApi.caller_is_resource_app?,
)
end

# used internally
# @returns the final mungified value of this parameter
define_method(:rs_value) do
@value
end
end

# 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
if type.instance_of? Puppet::Pops::Types::POptionalType
type = type.type
end

case type
when Puppet::Pops::Types::PStringType
# require any string value
Puppet::ResourceApi.def_newvalues(self, param_or_property, %r{})
when Puppet::Pops::Types::PBooleanType
Puppet::ResourceApi.def_newvalues(self, param_or_property, 'true', 'false')
aliasvalue true, 'true'
aliasvalue false, 'false'
aliasvalue :true, 'true' # rubocop:disable Lint/BooleanSymbol
aliasvalue :false, 'false' # rubocop:disable Lint/BooleanSymbol

when Puppet::Pops::Types::PIntegerType
Puppet::ResourceApi.def_newvalues(self, param_or_property, %r{^-?\d+$})
when Puppet::Pops::Types::PFloatType, Puppet::Pops::Types::PNumericType
Puppet::ResourceApi.def_newvalues(self, param_or_property, Puppet::Pops::Patterns::NUMERIC)
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')
end
end
end

Expand Down Expand Up @@ -582,17 +484,6 @@ def self.class_name_from_type_name(type_name)
type_name.to_s.split('_').map(&:capitalize).join
end

# Add the value to `this` property or param, depending on whether param_or_property is `:newparam`, or `:newproperty`
def self.def_newvalues(this, param_or_property, *values)
if param_or_property == :newparam
this.newvalues(*values)
else
values.each do |v|
this.newvalue(v) {}
end
end
end

def self.caller_is_resource_app?
caller.any? { |c| c.match(%r{application/resource.rb:}) }
end
Expand Down
46 changes: 46 additions & 0 deletions lib/puppet/resource_api/parameter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
require 'puppet/util'
require 'puppet/parameter'

module Puppet; module ResourceApi; end; end # predeclare the main module # rubocop:disable Style/Documentation,Style/ClassAndModuleChildren

# Class containing parameter functionality for ResourceApi.
class Puppet::ResourceApi::Parameter < Puppet::Parameter
attr_reader :value

# This initialize takes arguments and sets up new parameter.
# @param type_name the name of the Puppet Type
# @param data_type the data type of parameter instance
# @param attribute_name the name of attribue of the parameter
# @param resource_hash the resource hash instance which is passed to the
# parent class.
def initialize(type_name, data_type, attribute_name, resource_hash)
@type_name = type_name
@data_type = data_type
@attribute_name = attribute_name
super(resource_hash) # Pass resource to parent Puppet class.
end

# This method assigns value to the parameter and cleans value.
# @param value the value to be set and clean
# @return [type] the cleaned value
def value=(value)
@value = Puppet::ResourceApi::DataTypeHandling.mungify(
@data_type,
value,
"#{@type_name}.#{@attribute_name}",
Puppet::ResourceApi.caller_is_resource_app?,
)
end

# used internally
# @returns the final mungified value of this parameter
def rs_value
@value
end

# 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 }
end
86 changes: 86 additions & 0 deletions lib/puppet/resource_api/property.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
require 'puppet/util'
require 'puppet/property'

module Puppet; module ResourceApi; end; end # predeclare the main module # rubocop:disable Style/Documentation,Style/ClassAndModuleChildren

# Class containing property functionality for ResourceApi.
class Puppet::ResourceApi::Property < Puppet::Property
# This initialize takes arguments and sets up new property.
# @param type_name the name of the Puppet Type
# @param data_type the data type of property instance
# @param attribute_name the name of attribue of the property
# @param resource_hash the resource hash instance which is passed to the
# parent class.
def initialize(type_name, data_type, attribute_name, resource_hash)
@type_name = type_name
@data_type = data_type
@attribute_name = attribute_name
# Define class method insync?(is) if the name is :ensure
def_insync? if @attribute_name == :ensure && self.class != Puppet::ResourceApi::Property
# Pass resource to parent Puppet class.
super(resource_hash)
end

# This method returns value of the property.
# @return [type] the property value
def should
if @attribute_name == :ensure && rs_value.is_a?(String)
rs_value.to_sym
elsif rs_value == false
# work around https://tickets.puppetlabs.com/browse/PUP-2368
:false # rubocop:disable Lint/BooleanSymbol
elsif rs_value == true
# work around https://tickets.puppetlabs.com/browse/PUP-2368
:true # rubocop:disable Lint/BooleanSymbol
else
rs_value
end
end

# This method sets and returns value of the property and sets @shouldorig.
# @param value the value to be set and clean
# @return [type] the property value
def should=(value)
@shouldorig = value

if @attribute_name == :ensure
value = value.to_s
end

# 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::DataTypeHandling.mungify(
@data_type,
value,
"#{@type_name}.#{@attribute_name}",
Puppet::ResourceApi.caller_is_resource_app?,
),
]
end

# used internally
# @returns the final mungified value of this property
def rs_value
@should ? @should.first : @should
end

# method overloaded only for the :ensure property, add option to check if the
# rs_value matches is. Only if the class is child of
# Puppet::ResourceApi::Property.
def def_insync?
define_singleton_method(:insync?) { |is| rs_value.to_s == is.to_s }
end

# 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 }

# 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
19 changes: 19 additions & 0 deletions lib/puppet/resource_api/read_only_parameter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
require 'puppet/util'
require 'puppet/resource_api/parameter'

# Class containing read only parameter functionality for ResourceApi.
class Puppet::ResourceApi::ReadOnlyParameter < Puppet::ResourceApi::Parameter
# This method raises error if the there is attempt to set value in parameter.
# @return [Puppet::ResourceError] the error with information.
def value=(value)
raise Puppet::ResourceError,
"Attempting to set `#{@attribute_name}` read_only attribute value " \
"to `#{value}`"
end

# used internally
# @returns the final mungified value of this parameter
def rs_value
@value
end
end
Loading

0 comments on commit 3099283

Please sign in to comment.