Fourth refactor: Manually manage the user account

We manage a lot of user accounts in our infrastructure, so we handle them in a unified way. The profile::server class pulls in virtual::users, which has a lot of virtual resources we can selectively realize depending on who needs to log into a given machine.

This has a cost — it's action at a distance, and you need to read more files to see which users are enabled for a given profile. But we decided the benefit was worth it: because all user accounts are written in one or two files, it's easy to see all the users that might exist, and ensure that they're managed consistently.

We're accepting difficulty in one place (where we can comfortably handle it) to banish difficulty in another place (where we worry it would get out of hand). Making this choice required that we know our colleagues and their comfort zones, and that we know the limitations of our existing code base and supporting services.

So, for this example, we change the Jenkins profile to work the same way; we manage the jenkins user alongside the rest of our user accounts. While we're doing that, we also manage a few directories that can be problematic depending on how Jenkins is packaged.

Some values we need are used by Jenkins agents as well as controllers, so we're going to store them in a params class, which is a class that sets shared variables and manages no resources. This is a heavyweight solution, so wait until it provides real value before using it. In our case, we had a lot of OS-specific agent profiles (not shown in these examples), and they made a params class worthwhile.

Just as before, "don't repeat yourself" is in tension with "keep it readable." Find the balance that works for you.
  # We rely on virtual resources that are ultimately declared by profile::server.
  include profile::server

  # Some default values that vary by OS:
  include profile::jenkins::params
  $jenkins_owner          = $profile::jenkins::params::jenkins_owner
  $jenkins_group          = $profile::jenkins::params::jenkins_group
  $controller_config_dir      = $profile::jenkins::params::controller_config_dir

  file { '/var/run/jenkins': ensure => 'directory' }

  # Because our account::user class manages the '${controller_config_dir}' directory
  # as the 'jenkins' user's homedir (as it should), we need to manage
  # `${controller_config_dir}/plugins` here to prevent the upstream
  # rtyler-jenkins module from trying to manage the homedir as the config
  # dir. For more info, see the upstream module's `manifests/plugin.pp`
  # manifest.
  file { "${controller_config_dir}/plugins":
    ensure  => directory,
    owner   => $jenkins_owner,
    group   => $jenkins_group,
    mode    => '0755',
    require => [Group[$jenkins_group], User[$jenkins_owner]],
  }

  Account::User <| tag == 'jenkins' |>

  class { 'jenkins':
    lts                => true,
    repo               => true,
    direct_download    => $direct_download,
    version            => 'latest',
    service_enable     => true,
    service_ensure     => running,
    configure_firewall => true,
    install_java       => $install_jenkins_java,
    manage_user        => false,                    # <-- here
    manage_group       => false,                    # <-- here
    manage_datadirs    => false,                    # <-- here
    port               => $jenkins_port,
    config_hash        => {
      'HTTP_PORT'    => { 'value' => $jenkins_port },
      'JENKINS_PORT' => { 'value' => $jenkins_port },
    },
  }

Three things to notice in the code above:

  • We manage users with a homegrown account::user defined type, which declares a user resource plus a few other things.
  • We use an Account::User resource collector to realize the Jenkins user. This relies on profile::server being declared.
  • We set the Jenkins class's manage_user, manage_group, and manage_datadirs parameters to false.
  • We're now explicitly managing the plugins directory and the run directory.

Diff of fourth refactor

@@ -5,6 +5,33 @@ class profile::jenkins::controller (
   Boolean                     $install_jenkins_java = true,
 ) {

+  # We rely on virtual resources that are ultimately declared by profile::server.
+  include profile::server
+
+  # Some default values that vary by OS:
+  include profile::jenkins::params
+  $jenkins_owner          = $profile::jenkins::params::jenkins_owner
+  $jenkins_group          = $profile::jenkins::params::jenkins_group
+  $controller_config_dir      = $profile::jenkins::params::controller_config_dir
+
+  file { '/var/run/jenkins': ensure => 'directory' }
+
+  # Because our account::user class manages the '${controller_config_dir}' directory
+  # as the 'jenkins' user's homedir (as it should), we need to manage
+  # `${controller_config_dir}/plugins` here to prevent the upstream
+  # rtyler-jenkins module from trying to manage the homedir as the config
+  # dir. For more info, see the upstream module's `manifests/plugin.pp`
+  # manifest.
+  file { "${controller_config_dir}/plugins":
+    ensure  => directory,
+    owner   => $jenkins_owner,
+    group   => $jenkins_group,
+    mode    => '0755',
+    require => [Group[$jenkins_group], User[$jenkins_owner]],
+  }
+
+  Account::User <| tag == 'jenkins' |>
+
   class { 'jenkins':
     lts                => true,
     repo               => true,
@@ -14,6 +41,9 @@ class profile::jenkins::controller (
     service_ensure     => running,
     configure_firewall => true,
     install_java       => $install_jenkins_java,
+    manage_user        => false,
+    manage_group       => false,
+    manage_datadirs    => false,
     port               => $jenkins_port,
     config_hash        => {
       'HTTP_PORT'    => { 'value' => $jenkins_port },