In Puppet the ENC is higher priority than Hiera so anything coming from the ENC (foreman in this case) will override data from hiera. So there are no conflicts so to speak just plain old hierarchy.
Where you will get interesting behavior is with including classes from both places and “when” variables and parameters get set. The order is like this ENC > site.pp > node
inside the node scope you have ENC data too and that overrides data from Hiera https://puppet.com/docs/puppet/latest/lang_scope.html.
Hiera merge strategies do not include ENC node/class data so you cannot merge ENC data inside hiera but you can use the scope function https://puppet.com/docs/puppet/latest/hiera_merging.html#interpolation_functions
to look up ENC data from the top scope inside hiera. You can of course merge data from both places in puppet code later if you want.
We use a number of strategies to get a best of both worlds situation.
- We only include a single module from foreman we called
foreman_enc
, this module has a main class with one line lookup('classes', Array[String], 'unique').include
. This line would normally be in site.pp for an ENC-less hiera set up but putting it in a class included from foreman means that all classes are declared in the expected order avoiding the weird ordering you get if they are not daisy chained. All subsequent class includes are done via hiera and puppet roles and profiles.
- All hostgroups in foreman are are nested inside a single hostgroup which includes the above class. All hosts are automatically added to a hostgroup.
- The other sub classes in our
foreman_enc
module contain no code but just have class parameters. These classes are included in foreman hostgroups where appropriate to make only the parameters we need available in foreman available as smart class parameters in the UI. We have 10 smart class params, 4 of which are global and 9000 nodes. Everything else is in hiera.
- We filter all non necessary puppet classes from foreman to avoid them being added accidentally to nodes and for performance reasons. However due to fixing the ordering above it does not matter where classes are added so you could allow this if you don’t mind everything not being in version control or having to look in two places. You can also use a naming convention or trusted facts or whatever to assign roles directly from hiera, it all works.
The last one is a biggie.
What you loose from the above set up is the ability to move a host from one hostgroup to another to change its config (apart from the 10 smart params of course) because doing so will not change what classes are included or parameters are applied. So how do we fix this to get back the best of both worlds?
Well the first part is we added a hierarchy level to hiera that is based on the ENC parameter$hostgroup
which looks like this Global/Darwin/Workstation
. Like so:
- name: "Foreman Hostgroup specific data"
path: "21_hostgroups/%{hostgroup}.yaml"
Our hiera yaml would then be at hiera/21_hostgroups/Global/Darwin/Workstation.yaml
That was great but in foreman hostgroups inherit data from their parent so to get a similar behavior we would have to duplicate data in each yaml file at that hierarchy level and things quickly got very confusing and difficult to refactor.
What we decided to do was add a template based global smart variable that produced an array of hostgroup paths that would look something like this:
parent_hostgroup_paths:
- Global/Darwin/Workstation
- Global/Darwin
- Global
We could then use hiera’s mapped paths method to create a mini hierarchy inside our main hierarchy with the path array like this (replacing the previous entry)
- name: "Foreman Hostgroup specific data"
mapped_paths: [parent_hostgroup_paths, hostgroup_path, "22_hostgroups/%{hostgroup_path}.yaml"]
That way hiera would look in all hostgroup yaml files and the behaviour is exactly the same as foreman.
The smart variable template is like this
<% parent_hostgroups=@host.hostgroup.to_s.split('/') %><% counter=0 %><%= parent_hostgroups.map! { |g|
counter+=1
if counter == 1
g
else
parent_hostgroups[counter-2] + "/" + g
end
}.reverse %>
That’s the only bit that feels hacky so i suppose the biggest win for us would be trying to get that variable array added as a default ENC variable in foreman.
Anyway I let me know what you think or if you have any qusetions or if you think I am a crazy person.