My first custom puppet function
This experience turned out to be a lot harder than expected. Not writing some custom function - that is dead easy. Getting done what I wanted was very hard :-)
What I had: data in hiera in a nice to maintain way
What I needed: resources for a puppet module
What I had to do: convert the data from hiera to data useful as parameters for create_resources
e.a.
While usually glueing hiera to puppet resources is not the most complex thing, I had a caveat: one particular hiera_hash
had to be filtered and restructured and passed as a parameter for another Puppet resource.
What I had (simplified)
profile::firewalld::zones:
internal:
description: Interface for management
short: Interface for management
public:
description: Interface for public access
short: Interface for public access
profile::firewalld::rich_rules:
allow_from_server_1
zone: public
family: ipv6
source:
address: $IP_FROM_SERVER_1
port:
portid: 1234
protocol: tcp
action:
action_type: accept
allow_from_server_2
zone: public
family: ipv6
source:
address: $IP_FROM_SERVER_2
port:
portid: 1234
protocol: tcp
action:
action_type: accept
What I needed
profile::firewalld::zones:
internal:
description: Interface for management
short: Interface for management
public:
description: Interface for public access
short: Interface for public access
rich_rules:
- family: ipv6
source:
address: $IP_FROM_SERVER_1
port:
portid: 1234
protocol: tcp
action:
action_type: accept
- family: ipv6
source:
address: $IP_FROM_SERVER_2
port:
portid: 1234
protocol: tcp
action:
action_type: accept
What I had to do
The trick was thus for each profile::firewalld::zone
, creating a copy of the hiera_hash
profile::firewalld::rich_rules
that:
- contains only the elements for that zone (based on the
zone
keys from the values) - has not the
zone
keys from the values ofprofile::firewalld::rich_rules
- has not the key names (
allow_from_server_x
)
I tried with default Pupppet 3 manifest syntax but found no way to get it done. So I decided to build my first custom function (rich_rules_for($zone)
) - I am well versed in Ruby, so technically this should be no problem. However, I found no clear information on how to retrieve any type of resources from inside a custom function, or how to return the results to be used in other resources. Thus I hit a few obstacles along the way.
First step was easy: extend the manifests/firewalld/zone.pp
manifest:
define profile::firewalld::zone (
[...]
) {
[...]
$rich_rules = rich_rules_for($name)
::firewalld::zone { $name:
[...],
rich_rules => $rich_rules,
}
}
I also created a class to get the rich rules from hiera and a definition to hold a rich rule.
The file manifests/firewalld/rich_rules.pp
class profile::firewalld::rich_rules {
$rich_rules = hiera_hash('profile::firewalld::rich_rules', {})
create_resources('profile::firewalld::rich_rule', $rich_rules)
}
The file manifests/firewalld/rich_rule.pp
define profile::firewalld::rich_rule (
$zone,
[...]
) {
# Yes, the body of the definition is empty - it is meant purely as
# a holder for data
}
Now finally the hard part. I will put comments inside the code to explain why I did some things.
The file lib/puppet/parser/functions/rich_rules_for.rb
module Puppet::Parser::Functions
newfunction(:rich_rules_for, :type => :rvalue) do |args|
zone = args[0] # I expect a single parameter, the zone
result = [] # This will contain the filtered and converted rules for the zone
excludes = [:zone] # These are the keys to be removed from every item
# To get all resources in the catalog, use catalog.resources
# I did not find a way to get only resources of a specific type, so I had to
# 'manually' filter all resources and select only the relevant ones. At the
# same time, I could filter for the zone.
catalog.resources.select{|r|
r.type == 'Profile::Firewalld::Rich_rule' and
r['zone'] == zone
}.each do |rule|
# We will build a new element to add to the results
clean_rule = {}
# We iterate over all the keys/values in the original element and decide
# whether/how to add it to the new element. 'to_hash' gives you all the
# properties from the manifest or from hiera as a hash:
# {
# :key1 => valueA,
# :key2 => valueB,
# }
rule.to_hash.each do |key, value|
# Here we get rid of unwanted keys
unless excludes.include?(key)
# And we need to convert the symbols from 'to_hash' to strings again,
# otherwise the puppet provider gets confused (:key1 != 'key1')
clean_rule["#{key}"] = value
end
end
# Then we add the new element to the list of results
result << clean_rule
end
# And finally, we return the list of results
return result
end
end
I hope anyone is saved a lot of research now - I found no clean cut examples to do something similar to this…
comments powered by Disqus