Anton Dollmaier

Anton Dollmaier


puppet syntax: using for-loops

20 Apr 2020 »

Since attending OSDC in 2014, I’ve been relying on Puppet to deploy, manage and maintain our servers.

Although Puppet does have it’s advantages, there were also downsides.

The Puppet language did initially not have the possibility of loops - which resulted in funny structures to overcome this limitation.

The first solution have been the define code block: resembling a class, they instead allow to be defined multiple times with different parameters.

Combine this with create_resources() and you have a basic loop:

define demo::module (
  String $title = $name,
  String $number,
) {
  file{"/demo/${title}":
    ensure  => "file",
    content => $number,
  }
}

class demo (
  $modules = {
    'test1' => {
      number => "1",
    },
    'test2' => {
      number => "2",
    },
  },
) {
  create_resources(
    'demo::module',
    $modules,
  )
}

What doesn’t work with this is approach is a simpler for-loop: repeat a given block for a defined number of times, e.g. to define a number of services running for systemd.

The recent changes in the puppet language allow to do exactly this. I’ve not found a mention of this concept in the official documentation, so I’d like to present it here.

By leveraging the combined powers of slice() and each(), a simple for-loop can be created:

class demo (
  Integer $count_modules = 3,
) {
  slice(Integer[1,$count_modules], 1).each|$n| {
    $number = $n[0]
    file{"/demo/${number}":
      ensure  => "file",
      content => $number,
    }
  }
}

References of the used functions:

In practice, I’ve been relying on this to define scrape jobs of the puppet-prometheus module:

class scraper (
  Hash[String,Integer] $jobs = {},
  Integer $metrics_port = 5000,
  String $scrape_host = $facts['fqdn'],
  String $job_name = 'scraper',
) {
  $jobs.each|String $process, Integer $counts| {
    slice(Integer[1,$counts], 1).each|$n| {
      $number = $n[0]
      @@prometheus::scrape_job { "${facts['fqdn']}:${metrics_port}_${process}_${number}":
        job_name => $job_name,
        targets  => [
          "${scrape_host}:${metrics_port}",
        ],
        labels   => {
          '__metrics_path__' => "/${process}${number}/metrics",
        },
      }
    }
  }
}

Defining the desired scrape jobs is now only a matter of some lines of yaml in hiera:

scraper::jobs:
  pictures: 5
  videos: 3

This simplifies deployment greatly, reduces the risk of errors - and adds to readability, especially for developers already familiar with code.

© Anton Dollmaier