Module design practices

Consistent module design practices makes module contributions easier.

Spacing, indentation, and whitespace

Module manifests should follow best practices for spacing, indentation, and whitespace.

Manifests:

  • Must use two-space soft tabs.
  • Must not use literal tab characters.
  • Must not contain trailing whitespace.
  • Must include trailing commas after all resource attributes and parameter definitions.
  • Must end the last line with a new line.
  • Must use one space between the resource type and opening brace, one space between the opening brace and the title, and no spaces between the title and colon.

    Good:

    file { '/tmp/sample':
    Bad: Space between title and colon:
    file { '/tmp/sample' :
    Bad: No spaces:
    file{'/tmp/sample':
    Bad: Too many spaces:
    file     { '/tmp/sample':

  • Should not exceed a 140-character line width, except where such a limit would be impractical.

  • Should leave one empty line between resources, except when using dependency chains.

  • May align hash rockets (=>) within blocks of attributes, one space after the longest resource key, arranging hashes for maximum readability first.

Arrays and hashes

To increase readability of arrays and hashes, it is almost always beneficial to break up the elements on separate lines.

Use a single line only if that results in overall better readability of the construct where it appears, such as when it is very short. When breaking arrays and hashes, they should have:

  • Each element on its own line.

  • Each new element line indented one level.

  • First and last lines used only for the syntax of that data type.

Good: Array with multiple elements on multiple lines:

service { 'sshd':
  require => [
    Package['openssh-server'],
    File['/etc/ssh/sshd_config'],
  ],
}
Good: Hash with multiple elements on multiple lines:
$myhash = {
  key       => 'some value',
  other_key => 'some other value',
}
Bad: Array with multiple elements on same line:
service { 'sshd':
  require => [ Package['openssh-server'], File['/etc/ssh/sshd_config'], ],
}
Bad: Hash with multiple elements on same line:
$myhash = { key => 'some value', other_key => 'some other value', }
Bad: Array with multiple elements on different lines, but syntax and element share a line:
service { 'sshd':
  require => [ Package['openssh-server'],
    File['/etc/ssh/sshd_config'],
  ],
}
Bad: Hash with multiple elements on different lines, but syntax and element share a line:
$myhash = { key => 'some value',
  other_key     => 'some other value',
}
Bad: Array with an indention of elements past two spaces:
service { 'sshd':
  require => [
              Package['openssh-server'],
              File['/etc/ssh/sshd_config'],
  ],
}

Quoting

As long you are consistent, strings may be enclosed in single or double quotes, depending on your preference.

Regardless of your preferred quoting style, all variables MUST be enclosed in braces when interpolated in a string.

For example:

Good:

"/etc/${file}.conf"
"${facts['operatingsystem']} is not supported by ${module_name}"

Bad:

"/etc/$file.conf"

 

Option 1: Prefer single quotes

Modules that adopt this string quoting style MUST enclose all strings in single quotes, except as listed below.

For example:

Good:

owner => 'root'
Bad:
owner => "root"

A string MUST be enclosed in double quotes if it:

  • Contains variable interpolations.

    • Good:

      "/etc/${file}.conf"

    • Bad:

      '/etc/${file}.conf'

  • Contains escaped characters not supported by single-quoted strings.

    • Good:

      content => "nameserver 8.8.8.8\n"

    • Bad:

      content => 'nameserver 8.8.8.8\n'

A string SHOULD be enclosed in double quotes if it:

  • Contains single quotes.

    • Good:

      warning("Class['apache'] parameter purge_vdir is deprecated in favor of purge_configs")

    • Bad:

      warning('Class[\'apache\'] parameter purge_vdir is deprecated in favor of purge_configs')

 

Option 2: Prefer double quotes

Modules that adopt this string quoting style MUST enclose all strings in double quotes, except as listed below.

For example:

Good:

owner => "root"

Bad:

owner => 'root'

A string SHOULD be enclosed in single quotes if it does not contain variable interpolations AND it:

  • Contains double quotes.

    • Good:

      warning('Class["apache"] parameter purge_vdir is deprecated in favor of purge_configs')

    • Bad:

      warning("Class[\"apache\"] parameter purge_vdir is deprecated in favor of purge_configs") 

  • Contains literal backslash characters that are not intended to be part of an escape sequence.

    • Good:

      path => 'c:\windows\system32'

    • Bad:

      path => "c:\\windows\\system32"

If a string is a value from an enumerable set of options, such as present and absent, it SHOULD NOT be enclosed in quotes at all.

For example:

Good:

ensure => present
Bad:
ensure => "present"

Escape characters

Use backslash (\) as an escape character.

For both single- and double-quoted strings, escape the backslash to remove this special meaning: \\ This means that for every backslash you want to include in the resulting string, use two backslashes. As an example, to include two literal backslashes in the string, you would use four backslashes in total.

Do not rely on unrecognized escaped characters as a method for including the backslash and the character following it.

Unicode character escapes using fewer than 4 hex digits, as in \u040, results in a backslash followed by the string u040. (This also causes a warning for the unrecognized escape.) To use a number of hex digits not equal to 4, use the longer u{digits} format.

Comments

Comments must be hash comments (# This is a comment). Comments should explain the why, not the how, of your code.

Do not use /* */ comments in Puppet code.

Good:

# Configures NTP
file { '/etc/ntp.conf': ... }
Bad:
/* Creates file /etc/ntp.conf */
file { '/etc/ntp.conf': ... }

Include documentation comments for Puppet Strings for each of your classes, defined types, functions, and resource types and providers. If used, documentation comments precede the name of the element. For documentation recommendations, see the Modules section of this guide.

Functions

Avoid the inline_template() and inline_epp() functions for templates of more than one line, because these functions don’t permit template validation. Instead, use the template() and epp() functions to read a template from the module. This method allows for syntax validation.

You should avoid using calls to Hiera functions in modules meant for public consumption, because not all users have implemented Hiera. Instead, we recommend using parameters that can be overridden with Hiera.

Improving readability when chaining functions

In most cases, especially if blocks are short, we recommend keeping functions on the same line. If you have a particularly long chain of operations or block that you find difficult to read, you can break it up on multiples lines to improve readability. As long as your formatting is consistent throughout the chain, it is up to your own judgment.

For example, this:

$foodgroups.fruit.vegetables
Is better than this:
   $foodgroups
           .fruit
           .vegetables
But, this:
$foods = {
 "avocado"    => "fruit",
 "eggplant"   => "vegetable",
 "strawberry" => "fruit",
 "raspberry"  => "fruit",
}
 
$berries = $foods.filter |$name, $kind| {
 # Choose only fruits
 $kind == "fruit"
}.map |$name, $kind| {
 # Return array of capitalized fruits
 String($name, "%c")
}.filter |$fruit| {
 # Only keep fruits named "berry"
 $fruit =~ /berry$/
}
Is better than this:
$foods = {
 "avocado"    => "fruit",
 "eggplant"   => "vegetable",
 "strawberry" => "fruit",
 "raspberry"  => "fruit",
}
 
$berries = $foods.filter |$name, $kind| { $kind == "fruit" }.map |$name, $kind| { String($name, "%c") }.filter |$fruit| { $fruit =~ /berry$/ }

Related information