Module: Puppet::DataTypes

Defined in:
lib/puppet/datatypes.rb

Overview

Data types in the Puppet Language can have implementations written in Ruby and distributed in puppet modules. A data type can be declared together with its implementation by creating a file in ‘lib/puppet/functions/<modulename>’. The name of the file must be the downcased name of the data type followed by the extension ‘.rb’.

A data type is created by calling name>) and passing it a block that defines the data type interface and implementation.

Data types are namespaced inside the modules that contains them. The name of the data type is prefixed with the name of the module. As with all type names, each segment of the name must start with an uppercase letter.

The above example does not declare an implementation which makes it equivalent to adding the following contents in a file named ‘user.pp’ under the ‘types’ directory of the module root.

type Auth::User = Object[
  attributes => {
    name => String,
    email => String
  }]

Both declarations are valid and will be found by the module loader.

Structure of a data type


A Data Type consists of an interface and an implementation. Unless a registered implementation is found, the type system will automatically generate one. An automatically generated implementation is all that is needed when the interface fully defines the behaviour (for example in the common case when the data type has no other behaviour than having attributes).

When the automatically generated implementation is not sufficient, one must be implemented and registered. The implementation can either be done next to the interface definition by passing a block to ‘implementation`, or map to an existing implementation class by passing the class as an argument to `implementation_class`. An implementation class does not have to be special in other respects than that it must implemented the type’s interface. This makes it possible to use existing Ruby data types as data types in the puppet language.

Note that when using ‘implementation_class` there can only be one such implementation across all environments managed by one puppet server and you must handle and install these implementations as if they are part of the puppet platform. In contrast; the type implementations that are done inside of the type’s definition are safe to use in different versions in different environments (given that they do not need additional external logic to be loaded).

When using an ‘implementation_class` it is sometimes desirable to load this class from the ’lib’ directory of the module. The method ‘load_file` is provided to facilitate such a load. The `load_file` will use the `Puppet::Util::Autoload` to search for the given file in the ’lib’ directory of the current environment and the ‘lib’ directory in each included module.

Assumes the following class is declared under ‘lib/puppetx/auth’ in the module:

class PuppetX::Auth::User
  attr_reader :name, :year_of_birth
  def initialize(name, year_of_birth)
    @name = name
    @year_of_birth = year_of_birth
  end

  def age
    DateTime.now.year - @year_of_birth
  end

  def send_text(sender, text)
    sender.send_text_from(@name, text)
  end
end

Then the type declaration can look like this:

Puppet::DataTypes.create_type('Auth::User') do
  interface <<-PUPPET
    attributes => {
      name => String,
      email => String,
      year_of_birth => Integer,
      age => { type => Integer, kind => derived }
    },
    functions => {
      send_text => Callable[Sender, String[1]]
    }
    PUPPET

  # This load_file is optional and only needed in case
  # the implementation is not loaded by other means.
  load_file 'puppetx/auth/user'

  implementation_class PuppetX::Auth::User
end

Examples:

A simple data type

Puppet::DataTypes.create_type('Auth::User') do
  interface <<-PUPPET
    attributes => {
      name => String,
      email => String
    }
  PUPPET
end

Adding implementation on top of the generated type using ‘implementation`

Puppet::DataTypes.create_type('Auth::User') do
  interface <<-PUPPET
    attributes => {
      name => String,
      email => String,
      year_of_birth => Integer,
      age => { type => Integer, kind => derived }
    }
    PUPPET

  implementation do
    def age
      DateTime.now.year - @year_of_birth
    end
  end
end

Appointing an already existing implementation class

Defined Under Namespace

Classes: Error, TypeBuilder, TypeBuilderAPI

Class Method Summary collapse

Class Method Details

.create_loaded_type(type_name, loader, &block) ⇒ Object



138
139
140
141
142
143
# File 'lib/puppet/datatypes.rb', line 138

def self.create_loaded_type(type_name, loader, &block)
  builder = TypeBuilder.new(type_name.to_s)
  api = TypeBuilderAPI.new(builder).freeze
  api.instance_eval(&block)
  builder.create_type(loader)
end

.create_type(type_name, &block) ⇒ Object



126
127
128
129
130
131
132
133
134
135
136
# File 'lib/puppet/datatypes.rb', line 126

def self.create_type(type_name, &block)
  # Ruby < 2.1.0 does not have method on Binding, can only do eval
  # and it will fail unless protected with an if defined? if the local
  # variable does not exist in the block's binder.
  #

  loader = block.binding.eval('loader_injected_arg if defined?(loader_injected_arg)')
  create_loaded_type(type_name, loader, &block)
rescue StandardError => e
  raise ArgumentError, _("Data Type Load Error for type '%{type_name}': %{message}") % { type_name: type_name, message: e.message }
end