Develop types and providers with the Resource API

The recommended method to create custom types and providers is to use the Resource API, which is built on top of Puppet core. It is easier, faster, and safer than the old types and providers method.

To get started developing types and providers with the Resource API:
  1. Download Puppet Development Kit (PDK) appropriate to your operating system and architecture.
  2. Create a new module with PDK, or work with an existing PDK-enabled module. To create a new module, run pdk new module <MODULE_NAME> from the command line, specifying the name of the module. Respond to the dialog questions.
  3. To add the puppet-resource_api gem and enable modern rspec-style mocking, open the .sync.yml file in your editor, and add the following content:
    # .sync.yml
    ---
    Gemfile:
      optional:
        ':development':
          - gem: 'puppet-resource_api'
    spec/spec_helper.rb:
      mock_with: ':rspec'
  4. Apply these changes by running pdk update
  5. To create the required files for a new type and provider in the module, run: pdk new provider <provider_name>
    You will get the following response:
    $ pdk new provider foo
    pdk (INFO): Creating '.../example/lib/puppet/type/foo.rb'from template.
    pdk (INFO): Creating '.../example/lib/puppet/provider/foo/foo.rb' from template.
    pdk (INFO): Creating '.../example/spec/unit/puppet/provider/foo/foo_spec.rb' from template.
    $
    The three generated files are the type (resource definition), the provider (resource implementation), and the unit tests. The default template contains an example that demonstrates the basic workings of the Resource API. This allows the unit tests to run immediately after creating the provider, which looks like this:
    $ pdk test unit
    [✔] Preparing to run the unit tests.
    [✔] Running unit tests. 
    Evaluated 4 tests in 0.012065973 seconds: 0 failures, 0 pending.
    [✔] Cleaning up after running unit tests.
    $

Writing the type and provider

Write a type to describe the resource and define its metadata, and a provider to gather information about the resource and implement changes.

Writing the type

The type contains the shape of your resources. The template provides the necessary name and ensure attributes. You can modify their description and the name's type to match your resource. Add more attributes as you need.
# lib/puppet/type/yum.rb
require 'puppet/resource_api'

Puppet::ResourceApi.register_type(
  name: 'yum',
  docs: <<-EOS,
      This type provides Puppet with the capabilities to manage ...
    EOS
  attributes: {
    ensure: {
      type:    'Enum[present, absent]',
      desc:    'Whether this apt key should be present or absent on the target system.',
      default: 'present',
    },
    name: {
      type:      'String',
      desc:      'The name of the resource you want to manage.',
      behaviour: :namevar,
    },
  },
)
The following keys are available for defining attributes:
  • type: the Puppet 4 data type allowed in this attribute. You can use all data types matching Scalar and Data.

  • desc: a string describing this attribute. This is used in creating the automated API docs with puppet-strings.

  • default: a default value used by the runtime environment; when the caller does not specify a value for this attribute.

  • behaviour / behavior: how the attribute behaves. Available values include:

    • namevar: marks an attribute as part of the primary key or identity of the resource. A given set of namevar values must distinctively identify an instance.

    • init_only: this attribute can only be set during the creation of the resource. Its value is reported going forward, but trying to change it later leads to an error. For example, the base image for a VM or the UID of a user.

    • read_only: values for this attribute are returned by get(), but set() is not able to change them. Values for this should never be specified in a manifest. For example, the checksum of a file, or the MAC address of a network interface.

    • parameter: values for this attributes are not returned by get(). You can use this attribute to influence how the provider behaves. For example, you can influence the managehome attribute when creating a user.

Writing the provider

The provider is the most important part of your new resource, as it reads and enforces state. When you generate a provider with the pdk new provider command, PDK generates a provider file like this generated yum.rb file
require 'puppet/resource_api/simple_provider'

# Implementation for the yum type using the Resource API.
class Puppet::Provider::provider_name::Yum < Puppet::ResourceApi::SimpleProvider
  def get(_context)
    [
      {
        name: 'foo',
        ensure: 'present',
      },
      {
        name: 'bar',
        ensure: 'present',
      },
    ]
  end

  def create(context, name, should)
    context.notice("Creating '#{name}' with #{should.inspect}")
  end

  def update(context, name, should)
    context.notice("Updating '#{name}' with #{should.inspect}")
  end

  def delete(context, name)
    context.notice("Deleting '#{name}'")
  end
end
                

The optional initialize method can be used to set up state that is available throughout the execution of the catalog. This is most often used for establishing a connection when talking to a service, such as when you are managing a database.

The get(context) method returns a list of hashes describing the resources that are on the target system. The basic example would return an empty list. For example, these resources could be returned from this:
[
  {
    name: 'a',
    ensure: 'present',
  },
  {
    name: 'b',
    ensure: 'present',
  },
]

The create, update, and delete methods are called by the SimpleProvider base class to change the system as requested by the catalog. The name argument is the name of the resource that is being processed. should contains the attribute hash — in the same format as get returns — with the values in the catalog.

When adding Ruby code to a module, follow these guidelines:
  • For small sized blocks of code, put the code in the provider.

  • For medium sized blocks of code, put the code into a separate file in lib/puppet_x/$forgeuser/$modulename.rb. puppet_x is optional, but it helps keep the file name unique and reduces the risk of a file overwriting code from an unknown dependency.

  • For large sized blocks of code, put the code in a separate gem.

Unit testing

The generated unit tests in spec/unit/puppet/provider/<PROVIDER_NAME>_spec.rb are evaluated when you run pdk test unit.