Ova

What is a Defined Resource in Puppet?

Published in Puppet Defined Types 5 mins read

A defined resource in Puppet is a powerful, custom-built abstraction that allows you to encapsulate common configuration patterns into reusable blocks of Puppet code. These blocks can be evaluated multiple times with different parameters, effectively acting like a new, user-defined resource type. Once defined, you can cause this block to be evaluated by simply declaring a resource of that new, custom type, much like you would a native Puppet resource.

Understanding Defined Resource Types

Defined resource types, often simply called "defines" or "defined types," are fundamental for managing configuration complexity and promoting consistency across your infrastructure. They enable you to group several native Puppet resources (like file, package, service, user) into a single, logical unit that represents a higher-level concept, such as a "web application," "database user," or "Nginx virtual host."

How Defined Resources Work

Creating and using defined resources involves two main steps: defining them and then declaring them.

Defining a Defined Resource Type

You define a defined resource type using the define keyword, giving it a unique name and specifying any parameters it might accept. These parameters allow you to customize the behavior of each instance of the defined type.

Example Definition:

define mymodule::webapp (
  String $port,
  String $docroot,
  String $ensure_service = 'running',
) {
  # This block of code will be evaluated for each declaration.
  # The $name variable automatically contains the title of the declared resource.

  # Manage the web server package
  package { 'nginx':
    ensure => 'present',
  }

  # Create a configuration file for the web application
  file { "/etc/nginx/sites-available/${name}.conf":
    ensure  => 'file',
    content => template('mymodule/webapp.conf.epp'), # Uses a template
    mode    => '0644',
    owner   => 'root',
    group   => 'root',
    notify  => Service['nginx'],
  }

  # Ensure the configuration is enabled
  file { "/etc/nginx/sites-enabled/${name}.conf":
    ensure => 'link',
    target => "/etc/nginx/sites-available/${name}.conf",
    notify => Service['nginx'],
  }

  # Manage the Nginx service
  service { 'nginx':
    ensure => $ensure_service,
    enable => true,
    hasstatus => true,
    hasrestart => true,
  }
}

In this example, mymodule::webapp is the name of the defined resource type, and it takes parameters like $port, $docroot, and $ensure_service.

Declaring a Defined Resource Type

Once a defined resource type is created, you can declare it in your manifests as if it were a built-in resource type. Each declaration causes the code block within the define to be evaluated, with the provided parameters customizing its execution.

Example Declaration:

node 'webserver.example.com' {
  # Deploy a blog application
  mymodule::webapp { 'blog_app':
    port    => '80',
    docroot => '/var/www/blog',
  }

  # Deploy an admin panel
  mymodule::webapp { 'admin_panel':
    port    => '8080',
    docroot => '/var/www/admin',
    ensure_service => 'stopped', # Example of overriding default parameter
  }
}

Here, blog_app and admin_panel are distinct instances of the mymodule::webapp defined type, each configured with specific settings.

Key Benefits and Use Cases

Defined resources are a cornerstone of effective Puppet code management, offering significant advantages:

  • Encapsulation: They allow you to bundle multiple related resources (e.g., a user, their home directory, and their SSH key) into a single logical unit. This makes your manifests cleaner and easier to understand.
  • Reusability: You can define a configuration pattern once and then declare it many times with different parameters, avoiding repetitive code. This is ideal for scenarios like creating multiple virtual hosts, users, or application instances.
  • Abstraction: Defined types abstract away the underlying complexity of managing individual native resources, letting you focus on the higher-level goals (e.g., "I need a web app," not "I need a file, a service, and a package").
  • Maintainability: Changes to a common pattern only need to be made in one place (the defined type definition), simplifying updates and reducing the risk of inconsistencies.
  • Readability: By using descriptive defined type names, your Puppet code becomes more self-documenting, making it easier for others (or your future self) to understand what's being configured.

Defined Types vs. Other Puppet Concepts

Understanding how defined types fit into the broader Puppet ecosystem is crucial.

Defined Types vs. Native Resource Types

Native resource types are the built-in fundamental building blocks of Puppet, directly interacting with the operating system. Defined types build upon these.

Feature Native Resource Types (e.g., file, package, service) Defined Resource Types (e.g., mymodule::webapp)
Source Built-in Puppet language, core modules, community modules User-created Puppet code or specific module definitions
Purpose Manage atomic OS components (e.g., a single file) Encapsulate common patterns of multiple native resources
Declaration Declare direct instances (e.g., file { '/etc/fstab': ... }) Declare instances that run a block of code with parameters
Complexity Level Low-level, single task High-level abstraction of multiple tasks
Reusability Focus Individual resource management Pattern replication with variation

Defined Types vs. Classes

While both classes and defined types are ways to organize and reuse Puppet code, they serve slightly different purposes:

  • Classes are generally used for unique, singleton configurations that apply once per node (e.g., configuring an entire Apache server, installing a specific monitoring agent). They are typically declared using the include function or resource-like declaration without a title (e.g., class { 'apache': }).
  • Defined types are designed for multiple, repeatable instances of a configuration pattern on a single node or across many nodes, where each instance has different parameters and a unique title.

Conclusion

Defined resources are indispensable for writing modular, maintainable, and scalable Puppet code. By abstracting complex configuration logic into reusable blocks, they empower administrators to manage infrastructure efficiently, reduce redundancy, and ensure consistent application deployments across diverse environments. They are a cornerstone of advanced Puppet development, enabling the creation of custom, high-level abstractions that perfectly match the unique needs of your infrastructure.