Using Hiera to Access Facts of Another Node – Tutorial

hierapuppet

What we're trying to do is generate firewall rules (puppetlabs/firewall) for iptables. Our nodes are grouped like so conceptually:

-- site1
---- shared1
------ specific1
------ specific2
---- shared2
------ specific3
------ specific4

The node "specific4" will always need to access port 8080 on "shared2" and port 10000 on "site1". "specific1" likewise will need to access 8080 on "shared1". The rules are always going to be the same per node but they will rely on which group they are apart of.

I'm struggling to find a way to represent this in hiera without duplication. Is it possible to get facts from a completely separate node?

I think I want to be able to do something like this (simplified):

--
hosts:
  host specific4:
    rules:
      rule:
        port: 8080
        ip: get_ip(get_my_shared())

But obviously, you can't call functions from a yaml file. Would the best way to go about this be using custom facts? I'm haven't actually used hiera yet – so I'm unsure of best practices and what not. Any gentle pushes in the right direction would be most appreciated.

Edit:

This is the solution I've gone with, but if I can use exported resources I can remove the dependency on puppetdb-query.

# helper for creating rules from an array
define firewall_rules($port, $service_type) {
    $source = $name
    firewall { "$port $service_type $source":
        proto       => 'tcp',
        dport       => $port,
        state       => 'NEW',
        source      => "$source",
        action      => 'accept'
    }
}

class profile::specific inherits profile {
    $site = hiera('site')
    $shared = hiera('shared')
    $query = "site=\"$site\" and shared=\"$shared\""
    $shared_hosts = query_nodes($query)
    $specific_port = hiera('specific_ports', '8080')
    firewall_rules { $shared_hosts:
        port           => $specific_port,
        service_type   => 'SPECIFIC'
    }
}

I then export the site and shared facts based on hiera data, and using puppet-stdlib to load them from file resources on the host.

class profile::facts {

    $site       = hiera('site', 'none')
    $shared     = hiera('shared', 'none')
    $specific   = hiera('specific', 'none')
    $role       = hiera('role', 'none')
    $grouping   = "site=$site\nshared=$shared\nspecific=$specific\nrole=$role"

    notify { "facts being set: $grouping ": }

    file { ['/etc/facter/', '/etc/facter/facts.d/']:
        ensure  => directory,
        owner   => 'root',
        group   => 'root'
    }->
    file { '/etc/facter/facts.d/grouping.txt':
        ensure  => file,
        owner   => 'root',
        group   => 'root',
        mode    => '0775',
        content => $grouping
    }
}

As I said, this works, but I'd prefer to use exported resources if possible. The problem I ran into was that the resource doing the exporting couldn't also export its own IP/Host for collection. Perhaps I missed something, but I don't think it's possible, as the exporting happens when the resource is parsed, not when the node that contains that resource is realized.

Best Answer

So you want certain hosts to get information from another host's facts, but which host the facts come from will vary depending on the configuration of the specific host. Is that correct?

If so, I would also recommend using exported resources and using tags to specify the particular resource to use. The reason for this is that there are basically two ways for a host to get at another host's facts. Both require enabling storeconfigs. One is for the puppet master to do explicit lookups on whatever your storeconfigs backend is; I don't know of any modules that encapsulate this, so you might have to write your own. The other is for the source host to export resources that contain their facts; this is easier and the one I'll describe below.

This will be easier if you make your own resource type to wrap the firewall rules. That prevents any collisions with any other classes that happen to be exporting firewall rules.

define site_firewall ($ipaddr) {
  firewall { '500 allow site access':
    chain       => 'OUTPUT',
    destination => $ipaddr,
    proto       => 'tcp',
    port        => 10000,
  }
}

Next, each of your sites should export its own definition for site_firewall:

@@site_firewall { $hostname:
  ipaddr => $ipaddress,
}

In hiera, you define somewhere in your hierarchy the site that each host is a member of:

sitename: site1

Then in your host classes, you instantiate the appropriate site_firewall definition:

Site_firewall <<| name == hiera('sitename', 'default') |>>

A similar setup would apply for the shared hosts.

If you need firewall rules on the site and shared hosts, you should use tags rather than names because there will be multiple firewall rules for a given host. On the specific hosts:

@@firewall { "500 allow site traffic from ${hostname}":
  tag    => hiera('sitename', 'default-site'),
  source => $ipaddress,
  proto  => 'tcp',
  port   => 10000,
}
@@firewall { "500 allow shared traffic from ${hostname}":
  tag    => hiera('sharedname', 'default-shared'),
  source => $ipaddress,
  proto  => 'tcp',
  port   => 8080,
}

On the site hosts, you just need to collect the firewall rules for those hosts:

Firewall <<| tag == $hostname |>>

Edit: Aha. I think I've found the problem you were running into with exported resources. At the very least this is a gotcha that I'll document here for good measure.

If you have a resource with default parameters and you export that resource without explicitly setting those parameters, the parameter defaults are provided by the host realizing the resource, not the one exporting it.

In other words, if you have this resource type definition:

define foo ($bar = $fqdn) {
  notice($bar)
}

And you export it from host baz.example.com:

@@foo { 'title': }

And you realize it on host quux.example.com:

Foo <<| |>>

Then the value of $bar will be "quux.example.com".

If, instead you export it from baz.example.com like this:

@@foo { 'title': bar => $fqdn }

Then the value of $bar will indeed be "baz.example.com".

Related Topic