Type development
When you define a resource type, focus on what the resource can do, not how it does it.
Creating types
newtype
method
on the Puppet::Type
class:# lib/puppet/type/database.rb
Puppet::Type.newtype(:database) do
@doc = "Create a new database."
# ... the code ...
end
The name of the type is the only required argument to newtype
. The name must be a Ruby symbol, and the name of the file containing the
type must match the type's name.The newtype
method also requires a block of
code, specified with either curly braces ({ ... }
) or
the do ... end
syntax. The code block implements
the type, and contains all of the properties and parameters. The block will not be passed
any arguments.
You can optionally specify a self-refresh option for the type by putting :self_refresh => true
after the name. Doing so causes resources
of this type to refresh (as if they had received an event through a
notify-subscribe relationship) whenever a change is made to the resource. A notable use of
this option is in the core mount
type.
Documenting types
Write a description for the custom resource type in the
type's @doc
instance variable.
The description can be extracted by the puppet doc --reference type
command, which generates a complete
type reference which includes your new type, and by the puppet describe
command, which outputs information about
specific types.
Puppet::Type.newtype(:database) do
@doc = %q{Creates a new database. Depending
on the provider, this might create relational
databases or NoSQL document stores.
Example:
database {'mydatabase':
ensure => present,
owner => root,
}
}
end
In
this example, any whitespace would be trimmed from the first line (in this case, it’s zero
spaces), then the greatest common amount would be trimmed from remaining lines. Three lines
have four leading spaces, two lines have six, and two lines have eight, so four leading
spaces would be trimmed from each line. This leaves the example code block indented by four
spaces, and thus doesn’t break the Markdown formatting.Properties and parameters
The bulk of a type definition consists of properties and parameters, which become the resource attributes available when declaring a resource of the new type.
- Properties correspond to something measurable on the target system. For example, the UID and GID of a user account are properties, because their current state can be queried or changed. In practical terms, setting a value for a property causes a method to be called on the provider.
-
Parameters change how Puppet manages a resource, but do not necessarily map directly to something measurable. For example, the
user
type’smanagehome
attribute is a parameter — its value affects what Puppet does, but the question of whether Puppet is managing a home directory isn’t an innate property of the user account.
Additionally, there are a few special attributes called metaparameters, which are supported by all resource types. These don’t need to be handled when creating new types; they’re implemented elsewhere.
A type definition typically has multiple properties, and must have at least one parameter.
Properties
A custom type's properties are at the heart of defining how the resource works. In most cases, it’s the properties that interact with your resource’s providers.
If you define a property named owner
, then when you are
retrieving the state of your resource, then the owner
property calls the owner
method on the provider. In turn, when you are setting the
state (because the resource is out of sync), then the owner
property calls the owner=
method to set the state on disk.
ensure
property is special because it’s used to create and destroy
resources. You can set this property up on your resource type just by calling the ensurable
method in your type
definition:Puppet::Type.newtype(:database) do
ensurable
...
end
This
property uses three methods on the provider: create
, destroy
, and exists?
. The last method, somewhat obviously, is a Boolean to determine if the
resource exists. If a resource’s ensure
property is out of sync, then no other properties are checked or
modified.You can modify how ensure
behaves, such as by adding other valid values and
determining what methods get called as a result; see types like package
for examples.
newproperty
method, which should
be called on the
type:Puppet::Type.newtype(:database) do
ensurable
newproperty(:owner) do
desc "The owner of the database."
...
end
end
Note
the call to desc
; this sets
the documentation string for this property, and for Puppet
types that get distributed with Puppet, it is extracted as
part of the Type reference.ensure
, but it works for
other properties,
too:newproperty(:enable) do
newvalue(:true)
newvalue(:false)
end
You
can attach code to the value definitions (this code would be called instead of the property=
method), but it’s
normally unnecessary.newproperty(:owner) do
validate do |value|
unless value =~ /^\w+/
raise ArgumentError, "%s is not a valid user name" % value
end
end
end
Note
that the order in which you define your properties can be important: Puppet keeps track of the definition order, and it always
checks and fixes properties in the order they are defined.Customizing behavior
-
It is considered in sync if any of those values matches the current value.
-
If none of those values match, the first one is used when syncing the property.
newproperty(:minute, :array_matching => :all) do # :array_matching defaults to :first
...
end
You can also customize how information about your property gets logged. You can create
an is_to_s
method to change how the current values
are described, should_to_s
to change how the
desired values are logged, and change_to_s
to
change the overall log message for changes. See current types for examples.
Handling property values
@should
instance variable. You can retrieve
those values directly by calling should
on your resource
(although note that when :array_matching
is set
to :first
you get the first value in the array;
otherwise you get the whole
array):myval = should(:color)
When
you’re not sure (or don’t care) whether you’re dealing with a property or parameter, it’s
best to use value
:myvalue = value(:color)
Parameters
Parameters are defined the same way as properties. The difference between them is that parameters never result in methods being called on providers.
newparam
method. This method takes the name of the parameter (as a symbol) as
its argument, as well as a block of code. You can and should provide documentation for each
parameter by calling the desc
method inside its block. Tools that generate docs from this
description trim leading whitespace from multiline strings, as described for type
descriptions.newparam(:name) do
desc "The name of the database."
end
Namevar
Every type must have at least one mandatory parameter: the namevar. This parameter uniquely identifies each resource of the type on the target system — for example, the path of a file on disk, the name of a user account, or the name of a package.
If the user doesn’t specify a value for the namevar when declaring a resource, its value defaults to the title of the resource.
-
Create a parameter whose name is
:name
. Because most types just use:name
as the namevar, it gets special treatment and automatically becomes the namevar.newparam(:name) do desc "The name of the database." end
-
Provide the
:namevar => true
option as an additional argument to thenewparam
call. This allows you to use a namevar with a different, more descriptive name, such as thefile
type’spath
parameter.newparam(:path, :namevar => true) do ... end
-
Call the
isnamevar
method (which takes no arguments) inside the parameter’s code block. This allows you to use a namevar with a different, more descriptive name. There is no practical difference between this and option 2.newparam(:path) do isnamevar ... end
Specifying allowed values
newparam(:color) do
newvalues(:red, :green, :blue, :purple)
end
You can specify regular expressions in addition to literal values; matches
against regex always happen after equality comparisons against literal values, and those
matches are not converted to symbols. For instance, given the following
definition:newparam(:color) do
desc "Your color, and stuff."
newvalues(:blue, :red, /.+/)
end
If you provide blue as the value, then your parameter is set to :blue
, but if you provide green, then it is set to "green"
.Validation and munging
newparam(:color) do
desc "Your color, and stuff."
newvalues(:blue, :red, /.+/)
validate do |value|
if value == "green"
raise ArgumentError,
"Everyone knows green databases don't have enough RAM"
else
super(value)
end
end
munge do |value|
case value
when :mauve, :violet # are these colors really any different?
:purple
else
super(value)
end
end
end
The default validate
method looks for values
defined using newvalues
and if there are any values defined
it accepts only those values (this is how allowed values are validated). The default munge
method converts any values that are specifically allowed
into symbols. If you override either of these methods, note that you lose this value
handling and symbol conversion, which you’ll have to call super
for.Values are always validated before they’re munged.
Lastly, validation and munging only happen when a value is assigned. They have no role to play at all during use of a given value, only during assignment.
Boolean parameters
require 'puppet/parameter/boolean'
# ...
newparam(:force, :boolean => true, :parent => Puppet::Parameter::Boolean)
There
are two parts here. The :parent =>
Puppet::Parameter::Boolean
part configures the parameter to accept lots of
names for true and false, to make things easy for your users. The :boolean => true
creates a boolean
method on the type class to return the value of the parameter. In this
example, the method would be named force?
.Automatic relationships
Use automatic relationships to define the ordering of resources.
By default, Puppet includes and processes
resources in the order they are defined in their manifest. However, there are times when
resources need to be applied in a different order. The Puppet
language provides ways to express explicit ordering such as relationship metaparameters
(require
, before
, etc),
chaining arrows and the require
and contain
functions.
require
relationship from your custom type to a configuration file that it depends on, add the
following to your custom
type:autorequire(:file) do
['/path/to/file']
end
:file
refers to the type of resource you want to require
, and the array contains resource title(s) with which to create the
require
relationship(s). The effect is nearly equivalent
to using an explicit require
relationship:custom { <CUSTOM RESOURCE>:
ensure => present,
require => File['/path/to/file']
}
An important difference between automatic and explicit relationships is that automatic relationships do not require the other resources to exist, while explicit relationships do.
Agent-side pre-run resource validation
A resource can have prerequisites on the target, without which it cannot be synced. In some cases, if the absence of these prerequisites would be catastrophic, you might want to halt the catalog run if you detect a missing prerequisite.
In this situation, define a method in
your type named pre_run_check
. This method can do any check you want. It should take no
arguments, and should raise a Puppet::Error
if the catalog run should be halted.
If a type has a pre_run_check
method, Puppet agent and
puppet apply
runs the
check for every resource of the type before attempting to apply the catalog. It collects any
errors raised, and presents all of them before halting the catalog run.
Puppet::Type.newtype(:thing) do
newparam :name, :namevar => true
def pre_run_check
if(rand(6) == 0)
raise Puppet::Error, "Puppet roulette failed, no catalog for you!"
end
end
end
How types and providers interact
The type definition declares the features that a provider
must have and what’s required to make them work. Providers can either be tested for whether they
suffice, or they can declare that they have the features. Because a type's properties call
getter and setter methods on the providers, the providers must define getters and setters for
each property (except ensure
).
newtype(:coloring) do
feature :paint, "The ability to paint.", :methods => [:paint]
feature :draw, "The ability to draw."
newparam(:color, :required_features => %w{paint}) do
...
end
end
The
first argument to the feature method is the name of the feature, the second argument is its
description, and after that is a hash of options that help Puppet determine whether the feature is available. The only
option currently supported is specifying one or more methods that must be defined on the
provider. If no methods are specified, then the provider needs to specifically declare that
it has that
feature:Puppet::Type.type(:coloring).provide(:drawer) do
has_feature :draw
end
The
provider can specify multiple available features at the same time with has_features
.-
feature?
: Passed a feature name, returns true if the feature is available or false otherwise. -
features
: Returns a list of all supported features on the provider. -
satisfies?
: Passed a list of feature, returns true if they are all available, false otherwise.
Additionally, each feature gets a separate Boolean method, so the
above example would result in a paint?
method on the provider.