Skip to content

Module Development

This chapter covers the pieces that turn a basic module into a full-featured one: defined types, type aliases, custom facts, functions, custom types and providers, tasks, and plans.

Module anatomy

A basic module has this structure:

modulename/
├── metadata.json
├── manifests/
│   ├── init.pp
│   └── subclass.pp
├── files/
├── templates/
├── README.md
├── lib/
│   └── facter/
├── facts.d/
├── examples/
│   ├── init.pp
│   └── subclass.pp
├── Gemfile
├── Rakefile
└── spec/
    ├── classes/
    │   └── init_spec.rb
    └── spec_helper.rb

Module fundamentals

A more advanced module adds directories for the extensions covered in this chapter:

modulename/
├── types/
├── manifests/
│   ├── init.pp
│   ├── subclass.pp
│   └── resourcetype.pp
├── functions/
├── lib/
│   ├── facter/
│   └── puppet/
│       ├── functions/
│       ├── type/
│       └── provider/
└── spec/
    ├── acceptance/
    │   ├── init_spec.rb
    │   └── nodesets/
    │       └── default.yml
    ├── classes/
    │   └── init_spec.rb
    └── spec_helper.rb

This adds types/ (type aliases), resourcetype.pp (defined resource types), functions/ (native functions), lib/puppet/ (Ruby functions, custom types, and providers), and spec/acceptance/ (acceptance tests).

Note

The Puppet language uses the word "type" for several different things — data types, type aliases, defined resource types, and custom resource types. Watch the context.

Type aliases

Type aliases create custom data types based on the built-in data types:

# mymodule/types/manifest.pp
type Mymodule::Manifest = Pattern[/^(.*\/)?\w+\.pp$/]

Type aliases

Defined resource types

Defined resource types are blocks of native Puppet code that work like built-in resources:

# mymodule/manifests/gem.pp
define mymodule::gem (
  String $ensure = 'installed',
) {
  package { $title:
    ensure   => $ensure,
    provider => 'gem',
  }
}

They are also referred to as defined types or just defines. Scaffold a new one with:

$ jig new defined_type typename

Defined types

Functions

Functions take optional arguments and return a result. They are executed at compile time — on the OpenVox server in a typical server/agent configuration. OpenVox supports native (Puppet-language) functions and Ruby functions.

Function basics

Native functions

Native functions are written in the Puppet language and live in a module's functions/ directory:

# mymodule/functions/first_interface.pp
# Usage:
#   mymodule::first_interface($facts['networking']['interfaces'])
function mymodule::first_interface(Hash $ifaces) >> String {
  $iface = $ifaces.filter | $i | {
    $i[1].dig('bindings', 0, 'address') != undef
  }.keys().delete_regex('^lo$|\.[0-9]+$|^docker[0-9]')
  $iface[0]
}

Writing functions in the Puppet language

Ruby functions

Ruby functions live in lib/puppet/functions/ in your module. They can live in a module's namespace, but are not required to.

Ruby function overview · Signatures · Implementation

Custom facts

Facts are Ruby scripts that add data to the $facts hash. Unlike functions, facts are executed on the OpenVox agent:

# dnsmasq/lib/facter/dnsmasq_version.rb
Facter.add(:dnsmasq_version) do
  setcode do
    begin
      value = `dnsmasq --version`
      if $CHILD_STATUS.exitstatus != 0
        nil
      else
        match = %r{^Dnsmasq version\s+(\S+)\s}.match(value)
        match[1]
      end
    rescue
      nil
    end
  end
end

Custom facts

Custom types and providers

Custom types extend the resource types available in OpenVox. Providers are implementations of custom types that allow for portability across platforms. They are included in modules in these locations:

  • lib/puppet/type/
  • lib/puppet/provider/

Custom types · Provider development

Tasks

A task consists of two parts: the script itself, and a JSON file containing metadata about the script (such as the available parameters). Scaffold one with:

$ jig new task mytask

This creates tasks/mytask.sh (the script) and tasks/mytask.json (the metadata):

{
  "puppet_task_version": 1,
  "supports_noop": false,
  "description": "A short description of this task",
  "parameters": {
  }
}

Tasks can be written in any language available on the agent. They are namespaced to the module they ship with, so the example above would be called mymodule::mytask.

Writing tasks

Plans

Plans are written in the Puppet language but are executed by the bolt command-line tool (OpenBolt). Plans can include logic, and they can execute tasks or apply Puppet code to one or more nodes in a sequence defined by the plan.

Writing plans