Search code examples
puppet

Declare dependency between array elements in Puppet


Is there any way to declare in Puppet's language that packages in an array should installed in the order they are given in the array?

I want to automate the installation of CUDA, which requires nvidia-driver-latest-dkms, cuda and cuda-drivers (on RHEL7 as an example) to be installed in that order. I do not want to hard-code the array of packages, because I need to support different distributions, which require different packages. Therefore, an array holding the packages provided via Hiera seemed to be a good idea.

My first solution was ensure_packages($packages_parameter_filled_via_hiera), which stopped working recently (probably due to changes in NVIDIA's packages). The problem seems to be that this code installs the packages in a random order, and you cannot install nvidia-driver-latest-dkms (any more) if any of the other packages is already installed.

My next approach,

$packages.each | $package | {
    ensure_packages($package)
}

has the very same problem as does

$packages.each | $package | {
    package { "cuda-${package}":
        name => $package
    }
}

What I am looking for is something that is equivalent to the -> or ~> operator between the loop instances, or alternatively, some "dangling-pointer" construct that would give me access to the previous $package such that I can write require => Package[$magic_previous_instance] in the package resource. That is, I want to create something equivalent to

package { 'cuda-epel-release': 
    name => 'epel-release'
}
-> package { 'cuda-nvidia-driver-latest-dkms': 
    name => 'nvidia-driver-latest-dkms'
}
-> package { 'cuda-cuda': 
    name => 'cuda'
}
-> package { 'cuda-cuda-drivers': 
    name => 'cuda-drivers'
}

(which actually works, but exactly for RHEL 7) dynamically from Hiera data.


Solution

  • Yes, this is actually possible with a "wrapper" defined resource type:

    define custom::install(Hash $pkg_hash) {
      # iterates through the packages in the hash using their keys, and creates a package default where each requires the previous package
      # index > 0 ensures the first package does not attempt to require a nonexistent previous package
      keys($pkg_hash).each |Integer $index, String $pkg| { if $index > 0 { Package[keys($pkg_hash)[$index - 1]] -> Package[$pkg] } }
    
      create_resources(package, $pkg_hash)
    }
    

    To give credit where credit is due, I was originally pointed in this direction by Henrik Lindberg, so it is not entirely my creation. The input Hash $pkg_hash for the defined resource type is a package hash type analogous to the input argument structure for the create_resources function second argument. For example:

    $pkg_hash = {
      'cuda-epel-release'              => { ensure => installed },
      'cuda-nvidia-driver-latest-dkms' => { ensure => installed }
    }
    

    or in Hiera data YAML format:

    pkg_hash:
      'cuda-epel-release':
        ensure: installed
      'cuda-nvidia-driver-latest-dkms':
        ensure: installed
    

    and then custom::install($pkg_hash) as expected.

    Note that create_resources in the DRT can be replaced as per normal:

    $pkg_hash.each |String $package, Hash $attributes| {
      package { $package: *=> $attributes }
    }
    

    as described in the bottom of the documentation (Puppet doc quick link not working).