Puppet:Mastering Infrastructure Automation
上QQ阅读APP看书,第一时间看更新

Creating Puppet 4 functions

The Puppet 3 functions API has some limitations and is missing features. The new function API in Puppet 4 improves upon that substantially.

Some of the limitations of the old functions are as follows:

  • The functions had no automatic type checking
  • These functions had to have a unique name due to a flat namespace
  • These functions were not private and hence could be used anywhere
  • The documentation could not be retrieved without running the Ruby code

Running on Puppet 3 requires functions to be in a module in the lib/puppet/parser/functions directory. Therefore, people referred to these functions as parser functions. But this name is misleading. Functions are unrelated to the Puppet parser.

In Puppet 4, functions have to be put into a module in path lib/puppet/functions.

This is how you create a function that will return the hostname of the Puppet Master:

# modules/utils/lib/puppet/functions/resolver.rb
require 'socket'
Puppet::Functions.create_function(:resolver) do
  def resolver()
    Socket.gethostname
  end
end

Using dispatch adds type checking for attributes. Depending on desired functionality, one might have multiple dispatch blocks (checking for different data types). Each dispatch can refer to another defined Ruby method inside the function. This reference is possible by using the same names for dispatch and the Ruby method.

The following example code should get additional functionality; depending on the type of argument, the function should either return the hostname of the local system, or use DNS to get the hostname from an IPv4 address or the ipaddress for a given hostname:

require 'resolv'
require 'socket'
Puppet::Functions.create_function(:resolver) do
  dispatch :ip_param do
     param 'Pattern[/^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/]', :ip
  end
  dispatch :fqdn_param do
     param 'Pattern[/^([a-z0-9\.].*$/]', :fdqn
  end
  dispatch :no_param do
  end

  def no_param
    Socket.gethostname
  end
  def ip_param(ip)
    Resolv.getname(ip)
  end
  def fqdn_param(fqdn)
    Resolv.getaddress(fqdn)
  end
end

At the beginning of the file, we have to load some Ruby modules to allow the DNS name resolution and finding the local hostname.

The first two dispatch sections check for the data type of the parameter value and set a unique symbol. The last dispatch section does not check for data types, which matches when no parameter was given.

Each defined Ruby method uses the name of the according dispatch and executes Ruby code depending on the parameter type.

Now the resolver function can be used from inside the Puppet manifest code in three different ways:

class resolver {
  $localname = resolver()
  notify { "Without argument resolver returns local hostname: ${localname}": }

  $remotename = resolver('puppetlabs.com')
  notify { "With argument puppetlabs.com: ${remotename}": }

  $remoteip = resolver('8.8.8.8')
  notify { "With argument 8.8.8.8: ${remoteip}": }
}

When declaring this class, the following output will show up:

puppet apply -e 'include resolver'
Notice: Compiled catalog for puppetmaster.example.net in environment production in 0.35 seconds
...
Notice: Without argument resolver returns local hostname: puppetmaster

Notice: With argument puppetlabs.com: 52.10.10.141

Notice: With argument 8.8.8.8: google-public-dns-a.google.com

Notice: Applied catalog in 0.04 seconds

With Puppet 3 functions, it was impossible to have two functions of the same name. One always had to check whether duplicate functions appeared when making use of a new module.

The Puppet 4 functions now offer the possibility of using namespacing just like classes.

Let's migrate our function into the class namespace:

# modules/utils/lib/puppet/functions/resolver/resolve.rb
require 'resolv'
require 'socket'
Puppet::Functions.create_function(:'resolver::resolve') do
  # the rest of the function is identical to the example given # above
end

In the example, the code needs to be in resolver/lib/puppet/functions/resolver/resolve.rb which corresponds to function name: 'resolver::resolve'.

Functions with namespaces are invoked as usual:

class resolver {
  $localname = resolver::resolve()

  $remotename = resolver::resolve('puppetlabs.com')

  $remoteip = resolver::resolve('8.8.8.8')
}