Take the 2-minute tour ×
Server Fault is a question and answer site for system and network administrators. It's 100% free, no registration required.

Edit: I have come up with what I think is the best way to go about this. Thanks to the people answered my question - it helped along the way. I'm going to post my solution so that others can find it if they want.

Original Question:

I would like to use puppet to auto configure a number of our NFS filesystems. However, I'd like the manifest to make sure it has an IP on the right network before doing so.

Puppet helpfully provides facts about the IPs the machine has assigned through the interfaces and ipaddress_*** facts. The issue is that I need to check all of the interfaces (I'd rather not make assumptions about how they are connected).

Question 1: Can Puppet loop through a fact list? For example:

for $if in $interfaces {
     //do something
}

Question 2: If Puppet could do that, is there a way to evaluate a string as a variable. interfaces provides a really nice way of knowing how many ipaddress_*** and network_***variables there are, but even if I could loop through it's values, I'd need to do something like:

for $if in $interfaces {
    $net_var = eval("network_${if}")
    if $net_var = /^192\.168\.2\.0$/ {
        //do something
    }
}

Is this possible? Am I going about this completely wrong (kinda new to Puppet)?

What I ended up doing:

Puppet allows you to define custom parser functions for a module. These custom functions are just ruby, so you can effectively do whatever you want inside of them, including looping.

You place them in <module_dir>/lib/puppet/parser/functions/. In order to do what I wanted, I created <module_dir>/lib/puppet/parser/functions/has_network.rb with the following contents:

#has_network.rb
require 'ipaddr'

module Puppet::Parser::Functions
    newfunction(:has_network, :type => :rvalue) do |args|

        retcon = ""

        if not /^[0-9]{1,3}(\.[0-9]{1,3}){3}\/[0-2]?[0-9]$/.match(args[0]) then
            raise Puppet::ParseError, "Network provided was not valid. Provide 192.168.0.0/16 format" 
        else
            requested_network = IPAddr.new(args[0])

            interfaces_fact =  lookupvar('interfaces')
            interfaces = interfaces_fact.split(",")
            interfaces.each do |interface|
                ip = IPAddr.new(lookupvar("ipaddress_#{interface}"))
                if requested_network.include?(ip)
                    retcon = "true"
                end
            end
        end

        retcon
    end
end

This adds a new function that I can use in my manifests called has_network. It returns true if one of the machine's IP addresses is on the network provided to the function. Now I can do things like the following:

if has_network("192.168.1.0/24"){
    //do something
}

A couple of notes about custom functions

  • These run on the master server. Note the use of lookupvar to do operations on Facter facts. You cannot do things like create files on the client using one of these.
  • When you change the function, you must restart the puppetmaster. If you do not, the puppetmaster will never load the updated function and you'll be really confused.
share|improve this question

2 Answers 2

up vote 5 down vote accepted

As @Zoredache mentioned, there is no loop in Puppet. But you can get around this by using a definition, something like this:

define show_ip {
    $inf = "ipaddress_${name}"
    $ip = inline_template('<%= scope.lookupvar(inf) %>')

    if $ip =~ /192\.168\.3.*/ {
        notify {"Found IP $ip": }
    }
}

$ifs = split($interfaces, ',')

show_ip { $ifs: }

Belows is the output when directly calling:

# puppet manifests/defines/net.pp 
warning: Implicit invocation of 'puppet apply' by passing files (or flags) directly
to 'puppet' is deprecated, and will be removed in the 2.8 series.  Please
invoke 'puppet apply' directly in the future.

notice: Found IP 192.168.3.118
notice: /Stage[main]//Show_ip[eth1]/Notify[Found IP 192.168.3.118]/message: defined 'message' as 'Found IP 192.168.3.118'
notice: Finished catalog run in 0.07 seconds

Source: https://blog.kumina.nl/tag/puppet-tips-and-tricks/

share|improve this answer
    
It strikes me that this scope.lookupvar is definitely the answer to the second question I had (and safer than Zoredache's). Thanks! –  Chris Oct 28 '11 at 13:55
    
Does define solve your first question? –  quanta Oct 28 '11 at 14:21
    
It'll work - I have to do some more research into how notify works, to see how to get my specific task done, but the basic idea of how to go about it is now there. Thanks –  Chris Oct 28 '11 at 17:57

There is no looping or iteration in puppet.

Played around a bit more, and here an alternate version, that should work for any number of ips on a single interfaces and doesn't use an eval. to_hash.keys returns an array of all the variables names, the find_all filters the list based on the regular expression, a mapping is used to lookup the actual value of the variable, and finally the results are merged back into a string.

$iplist=inline_template('<%= scope.to_hash.keys.find_all{|i| i =~ /ipaddress_.*/}.map{|j| scope.lookupvar(j)}.join(",")%>')
if $iplist =~ /10\.2\.93.*/ {
      notify {"found $iplist": }
}
# on the client with a matching address we see "notice: found 10.2.93.5,127.0.0.1"

I have something that works, but I am not sure it is really the best solution, or safe. Basically I figure you can use an inline_template. Within the inline template we split the interfaces fact which is a list of all the interfaces. We use a mapping to build up a new array of the ip addresses of all the interfaces, and finally we re-join the array into a string.

My biggest concern with this idea is that I am doing an eval, on something that is basically supplied by the client system. I am not entirely sure how evil this is.

This also will break if an interface has multiple addresses, or if an interface has no addresses. Maybe someone else can suggest some improvements. I am just learning ruby.

$iplist=inline_template('<%= interfaces.split(",").map{|x| eval ("ipaddress_"+x)}.join(",")%>')

if $iplist =~ /10\.2\.93.*/ {
      notify {"found $iplist": }
}
# on the client with a matching address we see "notice: found 10.2.93.5,127.0.0.1"

share|improve this answer
    
I updated the answer with a cleaner solution. –  Zoredache Oct 28 '11 at 17:29

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.