Resource implementation: the provider

To make changes, a resource requires an implementation, or provider. It is the code used to retrieve, update and delete the resources of a certain type.

The two fundamental operations to manage resources are reading and writing system state. These operations are implemented as get and set. The implementation itself is a Ruby class in the Puppet::Provider namespace, named after the type using CamelCase.

Due to the way Puppet autoload works, this is in a file called puppet/provider/<type_name>/<type_name>.rb. The class also has the CamelCased type name twice.

At runtime, the current and intended system states a specific resource. These are represented as Ruby hashes of the resource's attributes and applicable operational parameters:

class Puppet::Provider::AptKey::AptKey
  def get(context)
    [
      {
        name: 'name',
        ensure: 'present',
        created: '2017-01-01',
        # ...
      },
      # ...
    ]
  end

  def set(context, changes)
    changes.each do |name, change|
      is = change.has_key? :is ? change[:is] : get_single(name)
      should = change[:should]
      # ...
    end
  end
end

The get method reports the current state of the managed resources. It returns an enumerable of all existing resources. Each resource is a hash with attribute names as keys, and their respective values as values. It is an error to return values not matching the type specified in the resource type. If a requested resource is not listed in the result, it is considered to not exist on the system. If the get method raises an exception, the provider is marked as unavailable during the current run, and all resources of this type fails in the current transaction. The exception message is reported.

The set method updates resources to a new state. The changes parameter gets passed a hash of change requests, keyed by the resource's name. Each value is another hash with the optional :is and :should keys. At least one of the two must be specified. The values are of the same shape as those returned by get. After the set, all resources are in the state defined by the :should values.

A missing :should entry indicates that a resource will be removed from the system. Even a type implementing the ensure => [present, absent] attribute pattern must react correctly on a missing :should entry. An :is key might contain the last available system state from a prior get call. If the :is value is nil, the resources were not found by get. If there is no :is key, the runtime did not have a cached state available.

The set method should always return nil. Signaling progress through the logging utilities described below. If the set method throws an exception, all resources that should change in this call and haven't already been marked with a definite state, are marked as failed. The runtime only calls the set method if there are changes to be made, especially when resources are marked with noop => true (either locally or through a global flag). The runtime does not pass them to set. See supports_noop for changing this behavior if required.

Both methods take a context parameter which provides utilties from the runtime environment, and is described in more detail there.

Implementing simple providers

In many cases, the resource type follows the conventional patterns of Puppet, and does not gain from the complexities around batch-processing changes. For those cases, the SimpleProvider class supplies a proven foundation that reduces the amount of code necessary to get going.

SimpleProvider requires that your type follows these common conventions:

  • name is the name of your namevar attribute.

  • ensure attribute is present and has the Enum[absent, present] type.

To start using SimpleProvider, inherit from the class like this:

require 'puppet/resource_api/simple_provider'

# Implementation for the wordarray type using the Resource API.
class Puppet::Provider::AptKey::AptKey < Puppet::ResourceApi::SimpleProvider
  # ...

Next, instead of the set method, the provider needs to implement the create, update or delete methods:

  • create(context, name, should): Called to create a resource.

    • context: provides utilities from the runtime environment.

    • name: the name of the new resource.

    • should: a hash of the attributes for the new instance.

  • update(context, name, should): Called to update a resource.

    • context: provides utilties from the runtime environment.

    • name: the name of the resource to change.

    • should: a hash of the desired state of the attributes.

  • delete(context, name): Called to delete a resource.

    • context: provides utilities from the runtime environment.

    • name: the name of the resource that to be deleted.

The SimpleProvider does basic logging and error handling.

In this section: