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
end
end
munge do |value|
case value
when :mauve, :violet # are these colors really any different?
:purple
else
super
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
Your type can specify automatic relationships it can have with resources.
autorequire
, autobefore
, autonotify
, and autosubscribe
, which all require a resource type as an argument, and your code should return a list of resource names that your resource could be related to.autorequire(:user) do
self[:user]
end
This won’t throw an error if resources with those names do not exist. The purpose of this hook is to make sure that if any required resources are being managed, they get applied before the requiring resource.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.