epp(): Invalid EPP: This Variable has no effect. A value was produced and then forgotten

Contexte : Puppet 5.1.0, template au format EPP

Message d’erreur à l’exécution :

Error: Evaluation Error: Error while evaluating a Function Call, epp(): Invalid EPP: This Variable has no effect. A value was produced and then forgotten (one or more preceding expressions may have the wrong form) at /opt/puppetlabs/puppet/modules/monitoring/templates/mongodb.epp:11:98

Cette erreur cryptique indique qu’une variable dans le template EPP ne sert pas.

Exemple :

<% $user %>

$user n’est ni affichée (ce serait ), ni dans une condition ni utilisée pour une boucle.

La plupart du temps, c’est afficher la variable que l’on souhaite, ne manque donc que le = :

<%= $user %>

Puppet : Autorise %wheel à passer root sans mot de passe

Puppet offre plusieurs façons de manipuler /etc/sudoers : en remplaçant le fichier entier (via une ressource file), en éditant ou en ajoutant une ligne précise (via file_line) ou via la ressource augeas.

Méconnu, augeas est un utilitaire – non lié à Puppet – permettant de manipuler un grand nombre de types de fichiers avec une syntaxe similaire à https://fr.wikipedia.org/wiki/XPathXPath. Contrairement à file et file_line, il a l’immense avantage de ne pas provoquer d’erreur de syntaxe.

Voici ce que cela donne pour éditer /etc/sudoers :

augeas { 'sudoers-wheel-nopasswd':
  incl    => '/etc/sudoers',
  lens    => 'Sudoers.lns',
  changes => 'set spec[user=\'%wheel\']/host_group/command/tag NOPASSWD',
  require => Package['augeas'],
}

Requière d’installer préalablement le package augeas :

if ! defined(Package['augeas']) {
  package { 'augeas': ensure => present, }
}

A noter qu’il existe un module tout fait pour éditer sudoers : saz/puppet-sudo, lequel s’appuie sur Augeas.

Puppet : contain vs include

Si votre code Puppet s’organise avec des rôles et des profiles (ce qui est très fortement recommandé), il y a de fortes chances que vous ayez du code ressemblant à ceci :

class roles::webserver {
   include profiles::base
   include profiles::apache
}

A l’exécution, surprise, l’ordre d’exécution n’est pas forcément celui attendu. Dans l’exemple ci-dessus, profiles::apache pourrait très bien passer avant.

Pour forcer l’ordre, on va donc utiliser des chaining arrows :

class roles::webserver {
   include profiles::base
   include profiles::apache

   Class['profiles::base'] ->
   Class['profiles::apache']
}

Cette approche se voit régulièrement et pose pourtant un problème de taille : l’ordre ne pas récursif, autrement dit il ne s’applique pas aux include que contiennent profiles::base et profiles::apache.

Ainsi, si profiles::apache inclut lui-même apache, il est tout fait possible que apache vienne s’exécuter avant ce qu’il y a dans profiles::base.

La bonne approche est d’utiliser le mot-clé contain qui, couplé avec les chaining arrows, garanti l’ordre d’exécution comme attendu (d’abord profiles::base puis profiles::apache) :

class roles::webserver {
   contain profiles::base
   contain profiles::apache

   Class['profiles::base'] ->
   Class['profiles::apache']
}

Note : si votre code est bien fait, la question de l’ordre d’exécution ne devrait pas se poser. Lorsque vous écrivez votre code, pensez à bien mettre un require/notify sur toutes les ressources qui dépendent de quelque chose.

Petite astuce pour vérifier que les dépendances (require et notify) sont correctement définies, exécuter Puppet avec l’option ordering à random. Avec cette option, Puppet « secoue tout » et, à chaque exécution, change l’ordre (en tenant compte des dépendances, heureusement :)). rspec-puppet a aussi un réglage pour activer ce comportement.

Puppet : Convertir une chaîne en nombre

Puppet permet de convertir une chaîne en nombre de plusieurs façons.

Implicitement :

# cat test.pp
$nombre = '8080'
$type = type($nombre)
notify { "Type: ${type}": }
# puppet apply --verbose test.pp
Notice: Type: Integer[8080, 8080]

Chose qui ne fonctionne pas toujours, notamment avec Hiera :

# cat /etc/puppetlabs/code/environments/production/hieradata/common.yaml 
foo::port: '8080'
# cat test.pp
class foo(Any $port) {
    $type = type($port)
    notify { "Type: ${type}": }
}
include foo
# puppet apply --verbose test.pp
Notice: Type: String

On peut cependant lui forcer la main en ajoutant 0 – méthode que l’on peut retrouver dans la documentation officielle :

# cat test.pp
class foo(Any $port) {
    $port_int = 0 + $port
    $type = type($port_int)
    notify { "Type: ${type}": }
}
include foo
# puppet apply --verbose test.pp
Notice: Type: Integer[8080, 8080]

Procéder de la sorte génère le warning suivant depuis Puppet 4.10.2 :

Warning: The string '8080' was automatically coerced to the numerical value 8080

En mode strict, Puppet va même transformer cela en erreur.

La seule et unique méthode désormais est d’utiliser Numeric (apparu avec Puppet 4.1), Integer ou Float :

# cat test.pp 
class foo(Any $port) {
    $port_int = Numeric($port)
    $type = type($port_int)
    notify { "Type: ${type}": }
}
include foo
# puppet apply --verbose test.pp
Notice: Type: Integer[8080, 8080]

Puppet : The argument signature (String format, [String timezone]) is deprecated for #strfime

Pour obtenir un timestamp avec Puppet, je faisais jusqu’à présent :

$timestamp = strftime('%s')

Cela fonctionnait bien mais générait le warning suivant :

Puppet : The argument signature (String format, [String timezone]) is deprecated for #strfime

La bonne pratique est désormais :

$timestamp = Timestamp().strftime('%s')

Pour rappel, strftime est fourni nativement depuis Puppet 4.8. Ce n’est plus puppetlabs-stdlib qui est utilisé. Il y a d’ailleurs un changement dans la syntaxe entre les deux, cf PUP-6724.

Source : https://docs.puppet.com/puppet/latest/function.html#strftime