Classes and defined types
Classes and defined types should follow scope and organization guidelines.
Separate files
Put all classes and resource type definitions (defined types) as separate files in the
manifests
directory of the module. Each file in the manifest directory
should contain nothing other than the class or resource type definition.
Good: etc/puppetlabs/puppet/modules/apache/manifests/init.pp
:
class apache { }Good:
etc/puppetlabs/puppet/modules/apache/manifests/ssl.pp
:class apache::ssl { }Good:
etc/puppetlabs/puppet/modules/apache/manifests/virtual_host.pp
:define apache::virtual_host () { }
Separating classes and defined types into separate files is functionally identical to
declaring them in init.pp
, but has the benefit of
highlighting the structure of the module and making the function and structure more
legible.
When a resource or include statement is placed outside of a class, node definition, or defined type, it is included in all catalogs. This can have undesired effects and is not always easy to detect.
Good: manifests/init.pp
:
# class ntp class ntp { ntp::install } # end of fileBad:
manifests/init.pp
:class ntp { #... } ntp::install
Internal organization of classes and defined types
Structure classes and defined types to accomplish one task.
Documentation comments for Puppet Strings should be included for each class or defined type. If used, documentation comments must precede the name of the element. For complete documentation recommendations, see the Modules section.
Put the lines of code in the following order:
-
First line: Name of class or type.
-
Following lines, if applicable: Define parameters. Parameters should be typed.
-
Next lines: Includes and validation come after parameters are defined. Includes may come before or after validation, but should be grouped separately, with all includes and requires in one group and all validations in another. Validations should validate any parameters and fail catalog compilation if any parameters are invalid. See puppetlabs-ntp for an example.
-
Next lines, if applicable: Should declare local variables and perform variable munging.
-
Next lines: Should declare resource defaults.
-
Next lines: Should override resources if necessary.
The following example follows the recommended style.
In init.pp
:
The
myservice
class installs packages, ensures the state ofmyservice
, and creates a tempfile with given content. If the tempfile contains digits, they are filtered out.@param service_ensure
the wanted state of services.@param package_list
the list of packages to install, at least one must be given, or an error of unsupported OS is raised.@param tempfile_contents
the text to be included in the tempfile, all digits are filtered out if present.class myservice ( Enum['running', 'stopped'] $service_ensure, String $tempfile_contents, Optional[Array[String[1]]] $package_list = undef, ) {
-
Rather than just saying that there was a type mismatch for
$package_list
, this example includes an additional assertion with an improved error message. The list can be "not given", or have an empty list of packages to install. An assertion is made that the list is an array of at least one String, and that the String is at least one character long.assert_type(Array[String[1], 1], $package_list) |$expected, $actual| { fail("Module ${module_name} does not support ${facts['os']['name']} as the list of packages is of type ${actual}") } package { $package_list: ensure => present, } file { "/tmp/${variable}": ensure => present, contents => regsubst($tempfile_contents, '\d', '', 'G'), owner => '0', group => '0', mode => '0644', } service { 'myservice': ensure => $service_ensure, hasstatus => true, } Package[$package_list] -> Service['myservice'] }
In hiera.yaml
: The default values can be merged if you want
to extend with additional packages. If not, use default_hierarchy
instead of hierarchy
.
--- version: 5 defaults: data_hash: yaml_data hierarchy: - name: 'Per Operating System' path: "os/%{os.name}.yaml" - name: 'Common' path: 'common.yaml'In
data/common.yaml
:myservice::service_ensure: runningIn
data/os/centos.yaml
:myservice::package_list: - 'myservice-centos-package'
Public and private
Split your module into public and private classes and defined types where possible. Public classes or defined types should contain the parts of the module meant to be configured or customized by the user, while private classes should contain things you do not expect the user to change via parameters. Separating into public and private classes or defined types helps build reusable and readable code.
Help indicate to the user which classes are which by making sure all public classes have complete comments and denoting public and private classes in your documentation. Use the documentation tags “@api private” and “@api public” to make this clear. For complete documentation recommendations, see the Modules section.
Chaining arrow syntax
Most of the time, use relationship metaparameters rather than chaining arrows. When you have many interdependent or order-specific items, chaining syntax may be used. A chain operator should appear on the same line as its right-hand operand. Chaining arrows must be used left to right.
Good: Points left to right:
Package['httpd'] -> Service['httpd']Good: On the line of the right-hand operand:
Package['httpd'] -> Service['httpd']Bad: Arrows are not all pointing to the right:
Service['httpd'] <- Package['httpd']Bad: Must be on the right-hand operand's line:
Package['httpd'] -> Service['httpd']
Nested classes or defined types
Don't define classes and defined resource types within other classes or defined types. Declare them as close to node scope as possible. If you have a class or defined type which requires another class or defined type, put graceful failures in place if those required classes or defined types are not declared elsewhere.
Bad:
class apache { class ssl { ... } }Bad:
class apache { define config() { ... } }
Display order of parameters
In parameterized class and defined resource type definitions, you can list required parameters before optional parameters (that is, parameters with defaults). Required parameters are parameters that are not set to anything, including undef. For example, parameters such as passwords or IP addresses might not have reasonable default values.
You can also group related parameters, order them alphabetically, or in the order you encounter them in the code. How you order parameters is personal preference.
Note that treating a parameter like a namevar and defaulting it to $title
or $name
does not make it a required
parameter. It should still be listed following the order recommended here.
Good:
class dhcp ( $dnsdomain, $nameservers, $default_lease_time = 3600, $max_lease_time = 86400, ) {}Bad:
class ntp ( $options = "iburst", $servers, $multicast = false, ) {}
Parameter defaults
Adding default values to the parameters in classes and defined types makes your module easier to use. Use Hiera data in your module to set parameter defaults. See Defining classes for details about setting parameter defaults with Hiera data. In simple cases, you can also specify the default values directly in the class or defined type.
Be sure to declare the data type of parameters, as this provides automatic type assertions.
Good: Parameter defaults set in the class with references to Hiera data:
class my_module ( String $source, String $config, ) { # body of class }A
hiera.yaml
in the root of the module sets the
hierarchy for assigning defaults:--- version: 5 default_hierarchy: - name: 'defaults' path: 'defaults.yaml' data_hash: yaml_dataAnd the file
data/defaults.yaml
specifies the actual default
values:my_module::source: 'default source value' my_module::config: 'default config value'
This example places the values in the defaults hierarchy, which means that the defaults are
not merged into overriding values. To merge the defaults into those values, change the
default_hierarchy
to hierarchy
.
If you are maintaining old code created prior to Puppet 4.9,
you might encounter the use of a params.pp
pattern. This
pattern makes maintenance and troubleshooting difficult — refactor such code to use the Hiera data-in-modules pattern instead. See Adding Hiera data to a
module for a detailed example showing how to replace
params.pp
with data.
Bad: params.pp
class my_module ( String $source = $my_module::params::source, String $config = $my_module::params::config, ) inherits my_module::params { # body of class }
Exported resources
Exported resources should be opt-in rather than opt-out. Your module should not be written to use exported resources to function by default unless it is expressly required.
When using exported resources, name the property collect_exported
.
Exported resources should be exported and collected selectively using a search expression, ideally allowing user-defined tags as parameters so tags can be used to selectively collect by environment or custom fact.
Good:
define haproxy::frontend ( $ports = undef, $ipaddress = [$::ipaddress], $bind = undef, $mode = undef, $collect_exported = false, $options = { 'option' => [ 'tcplog', ], }, ) { # body of define }
Parameter indentation and alignment
Parameters to classes or defined types must be uniformly indented in two spaces from the title. The equals sign should be aligned.
Good:
class profile::myclass ( $var1 = 'default', $var2 = 'something else', $another = 'another default value', ) { }Good:
class ntp ( Boolean $broadcastclient = false, Optional[Stdlib::Absolutepath] $config_dir = undef, Enum['running', 'stopped'] $service_ensure = 'running', String $package_ensure = 'present', # ... ) { # ... }
Bad: Too many level of indentation:
class profile::myclass ( $var1 = 'default', $var2 = 'something else', $another = 'another default value', ) { }Bad: No indentation:
class profile::myclass ( $var1 = 'default', $var2 = 'something else', $another = 'another default value', ) { }Bad: Misaligned equals sign:
class profile::myclass ( $var1 = 'default', $var2 = 'something else', $another = 'another default value', ) { }
Class inheritance
In addition to scope and organization, there are some additional guidelines for handling classes in your module.
Don't use class inheritance; use data binding instead of params.pp
pattern. Inheritance is used only for params.pp
, which is not recommended in Puppet
4.
If you use inheritance for maintaining older modules, do not use it across module
namespaces. To satisfy cross-module dependencies in a more portable way, include statements
or relationship declarations. Only use class inheritance for myclass::params
parameter defaults. Accomplish other use cases by adding
parameters or conditional logic.
Good:
class ssh { ... } class ssh::client inherits ssh { ... } class ssh::server inherits ssh { ... }Bad:
class ssh inherits server { ... } class ssh::client inherits workstation { ... } class wordpress inherits apache { ... }
Public modules
When declaring classes in publicly available modules, use include
, contain
, or require
rather than class resource declaration. This avoids
duplicate class declarations and vendor lock-in.
Type signatures
We recommend always using type signatures for class and defined type parameters. Keep the
parameters and =
signs aligned.
When dealing with very long type signatures, you can define type aliases and use short
definitions. Good naming of aliases can also serve as documentation, making your code easier
to read and understand. Or, if necessary, you can turn the 140 line character limit off. For
more information on type signatures, see the Type
data
type.
Related information