example42 blog

Puppet Tip 105 - The lazy tagging issue and how to work around

In Puppet trainings I usually tell people that a lazy admin is a good admin. A lazy admin will automate repetitive tasks.

Additionally I like to refer to Puppet as also being lazy: Puppet always checks the actual state and compares it with the desired state.

In case that actual state is correct, Puppet will go to sleep again.

But there are parts where being lazy is kind of a problem. One of these is what I prefer to name “the lazy tagging” in Puppet.

What are tags?

Puppet offers the possibility to add arbitrary names to resources or classes.

Let’s have a look at some examples:

You can add tags to resource declarations using meta parameter:

package { 'foobar':
  ensure => present,
  tag    => 'barfoo',
}

Or you can have a class and you add an additional name to it using the tag function:

class foobar {
  tag('barfoo')
}

For classes Puppet also does automatic tagging using the class name.

class profile::database::mysql {
  tag('profile::database::mysql')  # not needed, done by Puppet internally
}

These tags can then be used to limit the resources the Puppet agent is checking and managing by using the --tag cli option:

puppet agent --test --tags=barfoo

The agent will still receive the whole catalog of resources for a node. But the agent will only work on resources with the provided tags, which may be a comma separated list.

Tags and collectors

When using virtual resources and collectors, one might think, that the tags are usable also during catalog compilation. But this is not true, as tags are usually handled by the Puppet agent, not the server.

Let’s assume that you want to manage users locally.

You have some people which always get access (the sysadmins) and you have staff being responsible for specific applications. Maybe some of your people manage more than one application.

First you consider placing all the user information into hiera and you tag the individual users:

# data/common.yaml
profile::usermanagement::users:
  'ben':
    uid: '1044'
    shell:'/bin/zsh'
    tag:
      - 'sysadmin'
  'bob':
    uid: '1045'
    shell: '/bin/bash'
    tag:
      - 'sysadmin'
  'rob':
    uid: '1046'
    shell: '/bin/bash'
    tag:
      - 'app3b'
      - 'billing2'
  'fin':
    uid: '1047'
    shell: '/bin/bash'
    tag:
      - 'proxy'
      - 'billing2'

Now you want to create users based on the node role. Let’s assume that we have the following roles:

  • app3b
  • billing2
  • proxy

In this case you might consider using virtual resources and declare/collect only users which have a node role tag set (and of course all of your sysadmin people).

class profile::usermanagement (
  Hash $users = {},
) {
  $users.each |String $key, Hash $value| {
    @user { $key:
      * => $value,
    }
  }

  User <| tag == $::role or tag == 'sysadmin' |>
}

But what will happen?

Puppet will always create ALL users which you listed in hiera.

The reason is that the lazy tagging gets evaluated on the Puppet agent and not on the Puppet master.

Work around the lazy tagging issue

Using Lambdas will allow you to run a function which is executed at the compiler to remove unneeded users.

Here we can use the filter function:

class profile::usermanagement (
  Hash $users = {},
) {
  $sysadmins = $users.filter |$key, $value| { 'sysadmin' in $value['tag'] }
  $roleadmins = $users.filter |$key, $value| { $::role in $value['tag'] }

  $all_admins = $sysadmin.merge($roleadmins)

  $all_admins.each | $key, $value | {
    user { $key:
      * => $value,
    }
  }
}

As you have removed all users you don’t want to have on systems using the filter function, you can now directly declare the users instead of using virtual resources and collectors.

example42 wishes everybody a healthy and successful new year.

Martin Alfke