Skip to content

Move parameter and property logic to separate classes #140

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Dec 6, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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