Managing users with virtual resources
Users are a great example of a resource that may need to be realized by multiple classes. Consider the following situation. To simplify administration of a large number of machines, you defined classes for two kinds of users: developers
and sysadmins
. All machines need to include sysadmins
, but only some machines need developers
:
node 'server' { include user::sysadmins } node 'webserver' { include user::sysadmins include user::developers }
However, some users may be members of both groups. If each group simply declares its members as regular user
resources, this will lead to a conflict when a node includes both developers
and sysadmins
, as in the webserver
example.
To avoid this conflict, a common pattern is to make all users virtual resources, defined in a single class user::virtual
that every machine includes, and then realizing the users where they are needed. This way, there will be no conflict if a user is a member of multiple groups.
How to do it...
Follow these steps to create a user::virtual
class:
- Create the file
modules/user/manifests/virtual.pp
with the following contents:class user::virtual { @user { 'thomas': ensure => present } @user { 'theresa': ensure => present } @user { 'josko': ensure => present } @user { 'nate': ensure => present } }
- Create the file
modules/user/manifests/developers.pp
with the following contents:class user::developers { realize(User['theresa']) realize(User['nate']) }
- Create the file
modules/user/manifests/sysadmins.pp
with the following contents:class user::sysadmins { realize(User['thomas']) realize(User['theresa']) realize(User['josko']) }
- Modify your
nodes.pp
file as follows:node 'cookbook' { include user::virtual include user::sysadmins include user::developers }
- Run Puppet:
cookbook# puppet agent -t Info: Caching catalog for cookbook.example.com Info: Applying configuration version '1413180590' Notice: /Stage[main]/User::Virtual/User[theresa]/ensure: created Notice: /Stage[main]/User::Virtual/User[nate]/ensure: created Notice: /Stage[main]/User::Virtual/User[thomas]/ensure: created Notice: /Stage[main]/User::Virtual/User[josko]/ensure: created Notice: Finished catalog run in 0.47 seconds
How it works...
When we include the user::virtual
class, all the users are declared as virtual resources (because we included the @
symbol):
@user { 'thomas': ensure => present } @user { 'theresa': ensure => present } @user { 'josko': ensure => present } @user { 'nate': ensure => present }
That is to say, the resources exist in Puppet's catalog; they can be referred to by and linked with other resources, and they are in every respect identical to regular resources, except that Puppet doesn't actually create the corresponding users on the machine.
In order for that to happen, we need to call realize
on the virtual resources. When we include the user::sysadmins
class, we get the following code:
realize(User['thomas']) realize(User['theresa']) realize(User['josko'])
Calling realize
on a virtual resource tells Puppet, "I'd like to use that resource now". This is what it does, as we can see from the run output:
Notice: /Stage[main]/User::Virtual/User[theresa]/ensure: created
However, Theresa is in both the developers
and sysadmins
classes! Won't that mean we end up calling realize
twice on the same resource?
realize(User['theresa']) ... realize(User['theresa'])
Yes, it does, and that's fine. You're explicitly allowed to realize resources multiple times, and there will be no conflict. So long as some class, somewhere, calls realize
on Theresa's account, it will be created. Unrealized resources are simply discarded during catalog compilation.
There's more...
When you use this pattern to manage your own users, every node should include the user::virtual
class, as a part of your basic housekeeping configuration. This class will declare all users (as virtual) in your organization or site. This should also include any users who exist only to run applications or services (such as Apache
, www-data
, or deploy
, for example). Then, you can realize them as needed on inpidual nodes or in specific classes.
For production use, you'll probably also want to specify a UID and GID for each user or group, so that these numeric identifiers are synchronized across your network. You can do this using the uid
and gid
parameters for the user
resource.
Note
If you don't specify a user's UID, for example, you'll just get whatever is the next ID number available on a given machine, so the same user on different machines will have a different UID. This can lead to permission problems when using shared storage, or moving files between machines.
A common pattern when defining users as virtual resources is to assign tags to the users based on their assigned roles within your organization. You can then use the collector
syntax instead of realize
to collect users with specific tags applied.
For example, see the following code snippet:
@user { 'thomas': ensure => present, tag => 'sysadmin' } @user { 'theresa': ensure => present, tag => 'sysadmin' } @user { 'josko': ensure => present, tag => 'dev' } User <| tag == 'sysadmin' |>
In the previous example, only users thomas
and theresa
would be included.
See also
- The Using virtual resources recipe in this chapter
- The Managing users' customization files recipe in this chapter