Generating Puppet Resources From Simple Arrays

Steve Maddison
26-11-2013

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 = ['192.168.1.24','192.168.1.25']

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'
            #   }
            # }
            create_resources(foobar,$my_hash)

        ENDHEREDOC

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

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

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

        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')
          end
        end

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

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

        # Return the new hash
        generated
      end
    end

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

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

The resulting hash is:

    
    $rule_hash = {
      '1' => {
        'source_ip' => '192.168.1.24',
      },
      '2' => {
        'source_ip' => '192.168.1.25',
      },
    }

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  = ['192.168.1.24','192.168.1.25']
    # 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        => '192.168.1.24',
      destination_port => 80,
      protocol         => 'tcp',
    }
    firewall_rule { 'Allow HTTP from web client 2':
      source_ip        => '192.168.1.25',
      destination_port => 80,
      protocol         => 'tcp',
    }

LEAVE A REPLY

3 Comments

you might also like