Why do this
Gentoo is known for being somewhat complex to manage, making clusters of gentoo machines even more complex in most scenarios. Using the following methods the configuration becomes easier.
By the end of this you should be able to have a default hiera configuration for Gentoo while still being able to override it for specific use cases. What makes the method I chose particularly powerful is the ability to delete default vales entirely, not just setting them to something else.
Most of these methods came from my experience with chef that I thought would apply well to other config engines. While some don't like shoving logic to the configuration template engine, I'm open to suggestions.
Requirements
- Puppet 4.x or puppet-agent with hiera support.
- Puppet's stdlib installed (specifically for delete_values stuff).
- (optional) use puppetserver instead of running this oneoff.
- Hiera configured to use the following configuration.
Hiera config
:merge_behavior: deeper
:deep_merge_options:
:merge_hash_arrays: true
Basic Setup
- Convert the common portage configuraitons to templates.
- Convert the data in those templates to a datastructure.
- Use hiera to write the defaults / node overrides.
- Call the templates via a puppet module.
Datastructures
The easiest way of explaing how this works is to say that the only data stored in the 'deepest' value is ever going to be True
or False
. The reason for this is a because using deep_merge in hiera is an additive process and we need a flag to remove things further down the line.
The datastructure itself is fairly simple, here is an excerpt from my setup.
make_conf:
emerge_default_opts:
"--quiet-build": true
"--changed-use": true
If I wanted to disable --quiet-build
down the line you would just set the value to False
in a higher precidence (the specific node config instead of the general location.
make_conf:
emerge_default_opts:
"--quiet-build": false
Configuration Templates
The templates themselves are epp based, not erb (the old method).
package.keywords
For this one I'll also supply how I auto-set the right archetecture, works for amd64 at least.
Hiera data
"app-admin/paxtest ~%{facts.architecture}": true
Template
<%- |$packages| -%>
# THIS FILE WAS GENERATED BY PUPPET, CHANGES WILL BE OVERWRITTEN
<%- keys(delete_values($packages, false)).each |$package| { -%>
<%= "$package" %>
<%- } -%>
This one is the simplest, if a value for the key (paxtest in this case) is set to false, don't use it, the remaining keys are then set as plan text.
package.use
<%- |$packages| -%>
# THIS FILE WAS GENERATED BY PUPPET, CHANGES WILL BE OVERWRITTEN
<%- keys(delete_values($packages, false)).each |$package| { -%>
<%- if ! empty(keys(delete_values($packages[$package], false))) { -%>
<%= "$package" %> <%= join(keys(delete_values($packages[$package], false)), ' ') %>
<%- } -%>
<%- } -%>
This one is fairly straight forward as well, for each package that isn't disabled, if there are keys for the package (signifying use flags, needed because we remove the unset flags) then set them. This combines the flags set from all levels in hiera.
make.conf
This will be the most complicated one, but it's also likely to be the most important. I'll explain a bit about it after the paste.
<%- |$config| -%>
# THIS FILE WAS GENERATED BY PUPPET, CHANGES WILL BE OVERWRITTEN
CFLAGS="<%= join(keys(delete_values($config['cflags'], false)), ' ') %>"
CXXFLAGS="<%= join(keys(delete_values($config['cxxflags'], false)), ' ') %>"
CHOST="<%= $config['chost'] %>"
MAKEOPTS="<%= join(keys(delete_values($config['makeopts'], false)), ' ') %>"
CPU_FLAGS_X86="<%= join(keys(delete_values($config['cpu_flags_x86'], false)), ' ') %>"
ABI_X86="<%= join(keys(delete_values($config['abi_x86'], false)), ' ') %>"
USE="<%= join(keys(delete_values($config['use'], false)), ' ') %>"
GENTOO_MIRRORS="<%= join(keys(delete_values($config['gentoo_mirrors'], false)), ' ') %>"
<% if has_key($config, 'portage_binhost') { -%>
<%- if $config['portage_binhost'] != false { -%>
PORTAGE_BINHOST="<%= $config['portage_binhost'] %>"
<%- } -%>
<% } -%>
FEATURES="<%= join(keys(delete_values($config['features'], false)), ' ') %>"
EMERGE_DEFAULT_OPTS="<%= join(keys(delete_values($config['emerge_default_opts'], false)), ' ') %>"
PKGDIR="<%= $config['pkgdir'] %>"
PORT_LOGDIR="<%= $config['port_logdir'] %>"
PORTAGE_GPG_DIR="<%= $config['portage_gpg_dir'] %>"
PORTAGE_GPG_KEY='<%= $config['portage_gpg_key'] %>'
GRUB_PLATFORMS="<%= join(keys(delete_values($config['grub_platforms'], false)), ' ') %>"
LINGUAS="<%= join(keys(delete_values($config['linguas'], false)), ' ') %>"
L10N="<%= join(keys(delete_values($config['l10n'], false)), ' ') %>"
PORTAGE_ELOG_CLASSES="<%= join(keys(delete_values($config['portage_elog_classes'], false)), ' ') %>"
PORTAGE_ELOG_SYSTEM="<%= join(keys(delete_values($config['portage_elog_system'], false)), ' ') %>"
PORTAGE_ELOG_MAILURI="<%= $config['portage_elog_mailuri'] %>"
PORTAGE_ELOG_MAILFROM="<%= $config['portage_elog_mailfrom'] %>"
<% if has_key($config, 'accept_licence') { -%>
ACCEPT_LICENSE="<%= join(keys(delete_values($config['accept_licence'], false)), ' ') %>"
<%- } -%>
POLICY_TYPES="<%= join(keys(delete_values($config['policy_types'], false)), ' ') %>"
PAX_MARKINGS="<%= join(keys(delete_values($config['pax_markings'], false)), ' ') %>"
USE_PYTHON="<%= join(keys(delete_values($config['use_python'], false)), ' ') %>"
PYTHON_TARGETS="<%= join(keys(delete_values($config['python_targets'], false)), ' ') %>"
RUBY_TARGETS="<%= join(keys(delete_values($config['ruby_targets'], false)), ' ') %>"
PHP_TARGETS="<%= join(keys(delete_values($config['php_targets'], false)), ' ') %>"
<% if has_key($config, 'source') { -%>
source <%= join(keys(delete_values($config['source'], false)), ' ') %>
<%- } -%>
The basic idea of this is that you pass in the full make.conf datastructre you will generate as a single variable. Everything else is pulled (or elemated from that).
Each option that is selected already has all the options merged, but this could mean both the disabled versions of a given value could be still there, this is removed using the delete_values($config['foo'], false)
bit.
The puppet module itself
It's fairly easy to call, just make sure the template is in the template location and do it as follows.
file { '/etc/portage/make.conf':
content => epp('portage/portage-make_conf.epp', {'config' => hiera_hash('portage')['make_conf']})
}
fin
If you have any questions I'm on freenode as prometheanfire.