How to Add Existing Users to Groups in Puppet 2.7.18 Without Duplicate Declarations


3 views

When managing infrastructure with Puppet 2.7.18, a common frustration emerges when trying to modify group memberships for existing users across different modules. The fundamental issue lies in Puppet's resource declaration model where you cannot redeclare the same user resource in multiple manifests.

The duplicate declaration error occurs because Puppet enforces unique resource titles. When both your users and subversion modules attempt to declare the User[foo] resource, Puppet rightfully prevents this as it would create ambiguous configuration states.

Here are three practical approaches to solve this in Puppet 2.7.18:

1. Using Virtual Resources

This is the most elegant solution for Puppet 2.7:

# In your users module
@user { "foo":
  ensure     => present,
  groups     => ["primary_group"],
  membership => minimum
}

# In your subversion module
realize User["foo"]
Group["svn"] -> User["foo"]

2. Resource Collectors (Puppet 2.6+)

Leverage the space ship operator to modify existing declarations:

# In subversion manifest
User <| title == "foo" |> {
  groups     += "svn",
  membership => minimum
}

3. Using External Class Parameters

Modify your users module to accept group parameters:

# users/manifests/init.pp
class users($extra_groups = {}) {
  create_resources('user', $extra_groups)
}

# subversion/manifests/init.pp
class subversion {
  include users
  Users::Extra_groups <| |> {
    foo => { groups => ["svn"], membership => minimum }
  }
}

minimum Matters /h2>

The membership parameter is crucial here. When set to minimum, Puppet will add the user to the specified group(s) without removing them from their current groups. This prevents unexpected permission issues.

If you're planning to upgrade from Puppet 2.7, newer versions offer better solutions:

  • Puppet 3.x: Enhanced resource collectors
  • Puppet 4+: Iterative user/group management

Here's a complete working example from our SVN server configuration:

# modules/base/manifests/users.pp
@user { 'svnuser':
  ensure     => present,
  uid        => 501,
  gid        => 100,
  shell      => '/bin/bash',
  groups     => ['users'],
  membership => minimum
}

# modules/svn/manifests/server.pp
realize User['svnuser']

group { 'svn':
  ensure => present,
  gid    => 200
}

User <| title == "svnuser" |> {
  groups     += 'svn',
  membership => minimum
}

When working with Puppet 2.7.18 (or similar versions), a common frustration emerges when trying to manage group memberships across different modules. The scenario described is typical:

  • One module handles user creation (e.g., the 'users' module)
  • Another module handles service-specific groups (e.g., the 'subversion' module)

The duplicate declaration error happens because Puppet's resource-like declarations must be unique. When you attempt to modify the 'foo' user in both modules, Puppet sees this as conflicting declarations.

Option 1: Using Virtual Resources

The most elegant solution is to implement virtual resources:

# In users module
@user { 'foo':
  ensure     => present,
  # other user parameters
}

# In subversion module
realize User['foo']

group { 'svn':
  ensure => present,
  gid    => xxxxx,
}

User['foo'] {
  groups    => ['svn'],
  membership => minimum,
}

Option 2: Parameterized Classes

Another approach is to use parameterized classes to manage group memberships:

# In users module
class users (
  Array $additional_groups = [],
) {
  user { 'foo':
    ensure     => present,
    groups     => $additional_groups,
    membership => minimum,
  }
}

# In subversion module
class subversion {
  group { 'svn':
    ensure => present,
    gid    => xxxxx,
  }
  
  Class['users'] {
    additional_groups => ['svn'],
  }
}

Using Exec Resources (Less Ideal)

For legacy systems where refactoring isn't possible:

group { 'svn':
  ensure => present,
  gid    => xxxxx,
}

exec { 'add-foo-to-svn':
  command => '/usr/sbin/usermod -a -G svn foo',
  unless  => '/bin/grep -q "\\bsvn\\b" /etc/group | /bin/grep -q "\\bfoo\\b"',
  require => [User['foo'], Group['svn']],
}
  • Virtual resources provide the cleanest separation of concerns
  • Parameterized classes offer more explicit dependencies
  • Exec resources should be a last resort due to Puppet's declarative nature
  • All solutions assume you have control over both modules