Chapter 16. Documenting Modules

In this chapter, we’re going to discuss how to document your manifests well. Good documentation ensures that others can use your module. It also enables people to build modules that depend on yours in a manner that you can support.

Don’t skimp on documentation if it’s an internal module that will never be published. Your own users in the company are far more important than random people on the Internet. Give your own users the very best.

Don’t forget that you are creating this module based on active needs you understand today. Make notes to remind yourself of the requirements you are trying to solve, so that you can recall what you were thinking when you come back to refactor the class a year later. You’ll have a whole new set of requirements in your head, and what you did last year won’t make any sense. Trust me, this happens.

Learning Markdown

Puppet is moving away from RDoc format to the widely used Markdown format. Markdown is much easier to learn than RDoc, and is utilized today by the Puppet Forge, GitHub, Sourceforge, Stack Exchange, and many other code repositories and forums.

While you should absolutely read the Markdown documentation, it is entirely possible to build a valid and working README document with just the following simple rules:

  • Paragraphs should be typed as-is with no special formatting.
  • Code blocks should be indented four spaces or surrounded by ```.
  • Headers start with one # sign for each level: #heading1 ##heading2.
  • Bullet lists start with a leading asterisk, dash, or plus sign.
  • Number lists start with a leading number and period.
  • Use spaces to indent for list and code block hierarchy.
  • Add two spaces to the end of a line to insert a line feed.
  • Surround words with *single asterisks* for italic text.
  • Surround words with **double asterisks** for bold text.

These nine rules provide more than enough syntax to create valid README documents.

Markdown supports a lot more syntax than this. You can find complete documentation of the format at Markdown: Syntax.

Writing a Good README

An initial README.md template is generated by the puppet module generate command. Replace the example content with details specific to your module.

This is documentation for users of your module, so be clear and unambiguous about how to use it. A well-written README will inform a user what your module does, and how to use it without reading its manifests. In particular, make sure you include:

  • A list of all dependencies required by your module
  • A list of all parameters used by your module
  • An example Hiera configuration for a common use case

You can find Puppet Labs’ latest recommendations for style on its site at “README Style Notes”. These are well worth reading, and will greatly improve the usability of your module.

Documenting the Classes and Types

Each class and defined type in your module should be documented in the manifest.

Puppet 4 has deprecated RDoc syntax documentation in favor of using Markdown for class documentation. Therefore you’ll need to use the formats shown in this book instead of the RDoc format you may find in older modules.

Installing YARD and Puppet Strings

Puppet 4 has moved away from RDoc in favor of Markdown format documentation for consistency. You can use Markdown format even if your module is used by Puppet 3 users, as Markdown is easy to read. Puppet 3 users can also install the puppet-strings module to generate HTML and PDF documentation.

Note
puppet doc no longer generates module documentation in Puppet 4. Module documentation is generated by puppet strings, which you can make available by installing the puppetlabs-strings module.

Install the Yard gem as a gem used by the AIO puppet installation:

$ sudo puppet resource package yard provider=puppet_gem
Notice: /Package[yard]/ensure: created
package { 'yard':
  ensure => ['0.8.7.6'],
}

Install the Puppet Labs strings module. You can install this in the production environment, or within your personal Puppet directory:

$ puppet module install puppetlabs-strings
Notice: Preparing to install into /home/vagrant/.puppetlabs/etc/code/modules ...
Notice: Created target directory /home/vagrant/.puppetlabs/etc/code/modules
Notice: Downloading from https://forgeapi.puppetlabs.com ...
Notice: Installing -- do not interrupt ...
/home/vagrant/.puppetlabs/etc/code/modules
└── puppetlabs-strings (v0.3.0)

Finally, ensure that the .yardopts file exists in the root of each module. If you are updating older modules not generated from a recent skeleton, you may need to add this file:

$ echo '--markup markdown' > .yardopts

Fixing the Headers

At the time this book was written, the default Puppet module skeleton included headers tagged with a valid but less common Markdown header. If these don’t appear as headers in your documentation, then you may be missing the aforementioned .yardopts file:

#
# Authors
# -------

It is also completely valid to replace those headers with the same Markdown headers used in the README file. Use the proper number of # characters for your header depth and leave no spaces before the start of the header:

#
##Authors
#

When updating an older module, change the heading for Examples to a comment, as Yard includes its own special heading for examples in the generated output.

Listing Parameters

Parameters are documented with the @param meta tag. Follow this tag with the name of the parameter and a description of its use. The following lines should document the default and expected values, as shown here:

# @param [Type] sample_parameter Description of parameter
# * `sample parameter`
# Description of the parameter
Tip
If you are updating an older module, there will be no examples of @param in the manifest documentation.

I have found that it displays better if you put two spaces after the parameter bullet so the description falls on the following line. It is no longer necessary to document the type or default value, as this is shown clearly in the Parameter Summary lower on the page.

Here is an example for documenting the puppet::client class we built earlier in the book:

# @param [Enum['running','stopped']] status Whether Puppet runs as a daemon
# * `status`
#    Whether Puppet agent should be running as a daemon
#
# @param [Boolean] enabled Whether Puppet client should start at boot
# * `enabled`
#    Whether Puppet agent should start at boot
#
# @param [Hash] config Hash of configuration options for the [agent] section
# * `config`
#    Hash containing key/value pairs of Puppet configuration directives

class puppet::agent(
  Enum['running','stopped'] $status  = 'running',
  Boolean $enabled                   = true,
  Hash $config                       = {},
) {

Every parameter listed in the class invocation must be listed with a @param option, or the documentation will reflect the discrepancy with marked-out lines and (TBD) flags.

Documenting Variable References

In the Variables section of the documentation, list any variables declared by this class that are not input parameters. This can include:

  • Internal variables created by the class
  • Direct access of variables from other modules and classes
  • Direct Hiera lookups of data not passed in as parameters

Variables are documented with the same syntax used for parameters, but without the @param tag. Follow the bullet line with a freeform description of how the variable is used. As before, adding two spaces after the bullet line provides the best display.

Here is an example for documenting the puppet::agent class we built earlier in the book:

##Variables
#
# * `puppet::package_version`  
#   This uses the common $package_version variable provided by the base class
#
class puppet::agent(...) {
  include '::puppet'

  package { 'puppet-agent':
    version => $::puppet::package_version,
  }

Showing Examples

Examples are documented with the @examples meta tag. Follow this tag with the name of the parameter and a description of its use. The following lines should document how to use the module, the required values, and the most likely use case.

Here is an example for documenting how to use the puppet::agent class we built earlier in the book:

# Examples here (no header required)
#
# @examples Hiera data for using puppet::agent
#   classes:
#     - puppet::agent
#
#   puppet::agent::status = 'running'
#   puppet::agent::enabled = true

As always in Markdown, add two spaces to any empty line you wish to preserve in the output.

Documenting Functions

Document function parameters using the @param meta tag followed by the input value type. If the function returns any values they should be defined using @returns followed by the type.

The following lines provide the format for parameter and return values:

# @param [Type] sample_parameter - Description of parameter
# @returns [Type] - Description of the result
# @since [String] - Description of when the function became available

It is not necessary to document a default value, as this is shown in the Parameter Summary lower on the page.

Here is an example for documenting the get_subnet class built earlier in the book:

##Function: get_subnet
#
# @param [String] address - an IP address
# @param [String] netmask - the subnet mask
# @returns [String] - the network address of the subnet
# @since 1.2.2

Puppet::Functions.create_function(:'mymodule::get_subnet') do

Every parameter listed in the class invocation must be listed with a @param option, or the documentation will reflect the discrepancy with marked-out lines and (TBD) markers.

Functions can include @example blocks exactly the same as Puppet classes.

If the function is private and you want to warn others against making use of it, you can mark it as such and a big warning label will be included in the documentation alerting others not to use it:

# @api private
Puppet::Functions.create_function(:'mymodule::get_subnet') do

Generating Documentation

Generate module documentation by changing into the module path and running puppet strings. strings reads each manifest and plugin, and creates the final documentation from tags in them. It places the HTML documentation in the doc/ folder within the module.

If you run this against your Puppet module, you should see output like this:

$ cd /etc/puppetlabs/code/environments/test/modules/puppet
$ puppet strings
Files:           1
Modules:         0 (    0 undocumented)
Classes:         0 (    0 undocumented)
Constants:       0 (    0 undocumented)
Methods:         0 (    0 undocumented)
Puppet Classes:     3 (    0 undocumented)
Puppet Types:     1 (    0 undocumented)
 100.00% documented
true

If it points out that any of your classes or defined types aren’t documented, you know what to do.

You can read the documentation for a module you are using by opening up the index.html file within the module’s doc/ directory.

You can generate documentation for all modules by running puppet strings from the modules directory:

$ cd /etc/puppetlabs/code/environments/test/modules
$ puppet strings

Updating Module Metadata

An initial metadata.json file is generated by the puppet module generate command. You should go through each of these sections and replace the example content with details specific to your module.

Tip
As this file is standard (picky) JSON format, use double quotes for everything and remove trailing commas from the last entry in each item.

You can find Puppet Labs’ latest recommendations for names and values at “Writing a metadata.json File” on its site. However, we will review some important guidelines next.

Identifying the License

If you are publishing on the Puppet Forge, you must select a license that allows others to use the module, such as one of the following:

If you need help finding the right license for your needs, the Choose an OSS License website provides a choose-your-path approach to finding a license that meets your needs. Once you have chosen the license, find the appropriate identifier on the SPDX License List and use that for the value, like so:

  "license": "Apache-2.0",

Generate a license for your module by following the instructions included with the license selected. This generally involves adding a copyright year, a copyright owner, and perhaps the project name to the boilerplate text provided. Place this in a file named LICENSE.

Promoting the Project

An important thing to get right is the source, project, and issues tags. These are used to create links in the Puppet Forge and private forges. They inform users how to find documentation and submit issues related to the project:

  "source": "https://github.com/exampleorg/exampleorg-puppet",
  "project_page": "https://forge.puppetlabs.com/exampleorg/puppet",
  "issues_url": "https://github.com/exampleorg/exampleorg-puppet/issues",

Indicating Compatibility

Operating system compatibility informs the viewer which operating systems and versions your module is known to work on. This is defined as an array of hashes in the metadata. A match for any one hash indicates success.

Each hash contains two values:

operatingsystem
This is the value of $facts['os']['name']
operatingsystemrelease
An array of possible values, which are matched against either $facts['os']['release']['major'] or "$facts['os']['release']['major'].$facts['os']['release']['minor']"

Here’s an example of compatibility that supports recent Enterprise Linux and Debian/Ubuntu versions:

 "operatingsystem_support": [
    {
      "operatingsystem":"RedHat",
      "operatingsystemrelease":[ "6", "7" ]
    },
    {
      "operatingsystem":"CentOS",
      "operatingsystemrelease":[ "6", "7" ]
    },
    {
      "operatingsystem":"Amazon",
      "operatingsystemrelease":[ "2017.09", "2017.03", "2016.09" ]
    },
    {
      "operatingsystem":"OracleLinux",
      "operatingsystemrelease":[ "6", "7" ]
    },
    {
      "operatingsystem":"Scientific",
      "operatingsystemrelease":[ "6", "7" ]
    },
    {
      "operatingsystem":"Debian",
      "operatingsystemrelease":[ "6", "7", "8" ]
    },
    {
      "operatingsystem": "Ubuntu",
      "operatingsystemrelease": [ "17.10", "16.10", "16.04", "14.04"]
    }
  ],

Defining Requirements

Requirements are an array of hashes, each item of which identifies a valid Puppet version. A match for any one hash indicates success:

name
This is the value of the production. At this time, I think only puppet and pe (Puppet Enterprise) are supported.
version_requirement

This is a comparison expression that can utilize both <= and >= operators:

  "requirements": [
    { "name": "pe",     "version_requirement": ">= 2015.0.0" },
    { "name": "puppet", "version_requirement": ">= 4.0.0"    }
  ],

A module that requires Puppet 4/PE 2015 or greater might use the preceding example, whereas a module that supports only Puppet 3 might use >= 3.7.0 < 4.0.0.

Tip
Puppet Enterprise versions use a single digit after the period, but the version_requirement attribute requires two periods in the version number. Append an extra .0 to the end of the Puppet Enterprise version number.

I wish there were a way to suggest that Puppet 3 versions that utilized the future parser (the prototype for Puppet 4’s parsing engine) were compatible, but I’ve found no way to express that.

Listing Dependencies

List all modules required by this module in Dependencies. Indicate the range of acceptable versions with comparison operators. puppet module uses this information to automatically install dependencies when the module is installed:

name
This is the name of the module as shown on the Puppet Forge, but with the prefix and the module name separated by a slash (/).
version_requirement

This expression can utilize multiple >= and <= operators to indicate a valid range of matching versions:

  "dependencies": [
    { "name": "puppetlabs/stdlib",  "version_requirement": ">= 3.2.0" },
    { "name": "puppetlabs/inifile", "version_requirement": ">= 1.0.2" }
  ],
}

Identifying a Module Data Source

If the module uses a data provider as documented in “Binding Data Providers in Modules”, list the data provider type (valid values are hiera or function):

  "data_provider": "function"

You can leave out this variable or use none to indicate that there is no data provider specific to this module.

Updating Old Metadata

The following changes are necessary when updating on an older module:

  • The types field has been obsoleted, and should be removed from the file.
  • Puppet Enterprise version numbers have changed format.
    • Puppet Enterprise 3.8 was the final version based on Puppet 3.
    • Puppet Enterprise 2015.2 was the first version based on Puppet 4.

Maintaining the Change Log

The CHANGELOG.md file isn’t generated by default. Create this file and update it with every version change in your module. For each version change, include something like this:

##YYYY-MM-DD - Release X.Y.Z
###Summary

This release ...

####Features
- Added new...
- Revised...

####Deprecations
- Removed support for...

####Bugfixes
- Fixed bug where...

Maintaining CHANGELOG.md will save you a tremendous amount of bug reports and frustrated users. Consider this a worthwhile investment.

Evolving and Improving

The migration to Markdown format is an evolving effort that has iterated a few times during the development of this book, and will continue after the book has gone to press.

You can find the latest updates for recommended style at the puppetlabs-strings module on GitHub.

It can be useful to refer to the YARD Documentation Guides and Resources for how-to guides and other resources. The following tags are supported at this time:

@example
Show an example snippet of code for an object. The first line is an optional title.
@param
Documents a single function or class parameter with a given name, type, and optional description.
@returns
Describes the return value and type of a function or fact.
@since
Lists the version in which the fact or function was first added.

Keep in mind that not all YARD tags are parsed by Puppet strings. The following YARD tags were not supported when this book was last updated:

@author
Special formatting for the author of the code.
@note
Emphasize a note about the class, type, or method.
@options
List valid options for a hash.
@see
Refer to a web page.
@todo
Comment about things that need to be completed.
@version
Show version of the specific class, type, or function.

Best Practices for Documenting Modules

Let’s review some of the best practices for module documentation covered in this chapter:

  • Document every manifest using Markdown markup within the file.
  • Document each input parameter’s type and acceptable values.
  • Update the module version for every change released.
  • Update the README and CHANGELOG Markdown documents for each version of the module.

You can find more detailed guidelines in “Documenting Modules” on the Puppet docs site.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset