Generating Puppet Resources From Simple Arrays

Steve Maddison

Puppet’s create_resources() function is a great way to cut down on unnecessary code in your manifests. With your parameters neatly stored in a Hiera hash, it’s possible to create huge configurations in just a line of two of code. Sometimes, however, you just have a simple array of items for which the exact same configuration applies.

For example, maybe you have a list of hosts and you want to generate firewall rules to allow each of these to access some particular port on your server. So somewhere in your manifest we end up with a list of IP addresses, all of which are to be allowed access to the web server on our machine.

    # These should generally come from Hiera but, for the sake of simplicity,
    # let's just use a literal array for now.
    $ip_addresses = ['','']

And for each of these we want to declare a resource like this, where “???” is the IP address in question.

    firewall_rule { "Allow access to web server":
      source_ip        = ???,
      destination_port = 80,
      protocol         = 'tcp',

There are two problems with this. Firstly, until we can all start using the new iteration features in Puppet 3.2, there’s no simple way of defining a resource for each item in a simple array. Secondly, we somehow need to ensure that each resource we make has a different title, otherwise Puppet is going to complain about duplicate resource definitions. The custom function below solves both these problems.

    module Puppet::Parser::Functions
      newfunction(:generate_resource_hash, :type => :rvalue, :doc => <<-'ENDHEREDOC') do |args|
        Converts an array of values to a hash of hashes suitable for use with
        the create_resources() function. The keys op the top-level hash are
        the positions of the original array members, optionally prefixed
        by a fixed string. Each of the elements of this top-level hash is
        populated with a single key => value pair; the key being the second
        parameter passed to this function, the value being the corresponding
        value from the original array.

        For example:

            $my_array = ['one','two']
            $my_hash = generate_resource_hash($my_array,'foo','bar')
            # The resulting hash is equivalent to:
            # $my_hash = {
            #   'bar1' => {
            #     'foo' => 'one'
            #   }
            #   'bar2' => {
            #     'foo' => 'two'
            #   }
            # }


        if args.length < 2
          raise Puppet::ParseError, ("generate_resource_hash(): wrong number of args (#{args.length}; must be at least 2)")

        my_array = args[0]
        unless my_array.is_a?(Array)
          raise(Puppet::ParseError, 'generate_resource_hash(): first arg must be an array')

        param = args[1]
        unless param.is_a?(String)
          raise(Puppet::ParseError, 'generate_resource_hash(): second arg must be a string')

        prefix = args[2] if args[2]
        if prefix
          unless prefix.is_a?(String)
            raise(Puppet::ParseError, 'generate_resource_hash(): third arg must be a string')

        # The destination hash we'll be filling.
        generated =
        pos = 1

        my_array.each do |value|
          id = prefix + pos.to_s
          generated[id] =
          generated[id][param] = value
          pos = pos + 1

        # Return the new hash

When fed our array of IP addresses, the function creates a hash for us, which we can send straight to create_resources().

    $ip_addresses = ['','']
    $rule_hash    = generate_resource_hash($ip_addresses, 'source_ip')
    create_resources(firewall_rule, $rule_hash)

The resulting hash is:

    $rule_hash = {
      '1' => {
        'source_ip' => '',
      '2' => {
        'source_ip' => '',

As you can see, we're not quite there yet as the missing paramters (destination_port and protocol) need to be fed to the resources. Additionally, the strings "1" and "2" are going to clash with other resources if we perform the same trick again. How about this:

    $ip_addresses  = ['','']
    # We can prefix the generated hash keys with an arbitrary string.
    $rule_hash     = generate_resource_hash($ip_addresses, 'source_ip',
                     'Allow HTTP from web client ')
    # Stick all the common stuff in this extra hash:
    $rule_defaults = {
      'destination_port' => 80,
      'protocol'         => 'tcp',
    create_resources(firewall_rule, $rule_hash, $rule_defaults)

We now end up with this much more useful set of resources in the catalog:

    firewall_rule { 'Allow HTTP from web client 1':
      source_ip        => '',
      destination_port => 80,
      protocol         => 'tcp',
    firewall_rule { 'Allow HTTP from web client 2':
      source_ip        => '',
      destination_port => 80,
      protocol         => 'tcp',



you might also like