Quelle est la bonne façon de boucler une recette de chef (solo)?

8

Quelqu'un peut-il m'expliquer comment fonctionne le chef? C'est une question assez large, donc pour la réduire, j'ai cette recette très simple qui fait une boucle sur une liste d'utilisateurs et crée chacun d'eux s'ils n'existent pas déjà. Ça ne marche pas.

D'après ce que je peux dire, la boucle semble se produire comme je m'y attendais. Une fois la boucle terminée, mes commandes bash pour créer chaque utilisateur sont exécutées, une fois pour chaque itération de la boucle. Cependant, lorsque les commandes bash sont exécutées, elles ne semblent avoir la valeur utilisateur qu'à partir de la première itération de boucle.

Quelle est la bonne façon d'écrire une recette qui boucle sur des données variables similaires à cet exemple?

Voici la recette:

node[:users].each do |user|
  puts "in loop for #{user['username']}"
  bash "create_user" do
    user "root"
    code do
      puts "running 'useradd' for #{user['username']}"
      "useradd #{user['username']}"
    end
    not_if do
      puts "checking /etc/passwd for #{user['username']}"
      "cat /etc/passwd | grep #{user['username']}"
    end
  end
end

Je teste cela en utilisant Vagrant avec la configuration suivante:

Vagrant::Config.run do |config|
  config.vm.box = "precise32"
  config.vm.box_url = "http://files.vagrantup.com/precise32.box"
  config.vm.provision :chef_solo do |chef|
    chef.add_recipe "sample"
    chef.json = {
      :users => [
        {:username => 'testA'},
        {:username => 'testB'},
        {:username => 'testC'},
        {:username => 'testD'},
        {:username => 'testE'},
      ],
    }
  end
end

Les messages générés par les instructions put dans la recette ressemblent à ceci:

2013-03-08T01:03:46+00:00] INFO: Start handlers complete.
in loop for testA

in loop for testB

in loop for testC

in loop for testD

in loop for testE

[2013-03-08T01:03:46+00:00] INFO: Processing bash[create_user] action run (sample::default line 5)
checking /etc/passwd for testA

[2013-03-08T01:03:46+00:00] INFO: Processing bash[create_user] action run (sample::default line 5)
checking /etc/passwd for testA

[2013-03-08T01:03:46+00:00] INFO: Processing bash[create_user] action run (sample::default line 5)
checking /etc/passwd for testA

[2013-03-08T01:03:46+00:00] INFO: Processing bash[create_user] action run (sample::default line 5)
checking /etc/passwd for testA

[2013-03-08T01:03:46+00:00] INFO: Processing bash[create_user] action run (sample::default line 5)
checking /etc/passwd for testA

[2013-03-08T01:03:46+00:00] INFO: Chef Run complete in 0.026071 seconds
Matthew J Morrison
la source

Réponses:

5

rendre le nom de votre script unique ...

bash "create_user_#{user}" do

FWIW, j'ai utilisé https://github.com/fnichol/chef-user plusieurs fois, ce qui vous permet de créer / supprimer des utilisateurs en fonction des attributs et des sacs de données.

Darrin Holst
la source
Cela semble si simple, merci! La recette de création d'utilisateurs n'était qu'un exemple que j'utilisais pour comprendre pourquoi ma boucle ne fonctionnait pas dans d'autres recettes. Merci encore!
Matthew J Morrison
10

Le comportement que vous voyez peut être expliqué en comprenant la différence entre deux des étapes clés d'une exécution client Chef: la compilation et la convergence.

Pendant la phase de "compilation", le client Chef exécute le code dans vos recettes pour créer une collection de ressources . Il s'agit d'une liste des ressources que vous avez demandé à Chef de gérer sur votre système, ainsi que leur état cible. Par exemple, une ressource d'annuaire pour dire qu'elle /tmp/foodevrait exister et appartenir à root:

directory "/tmp/foo" do
  owner "root"
end

Pendant la phase de "convergence", le client Chef utilise des fournisseurs pour charger l'état actuel de chaque ressource, puis le compare à l'état cible. S'ils sont différents, Chef mettra à jour le système. Pour notre ressource Annuaire, Chef créerait l'annuaire s'il n'existait pas et changerait son propriétaire en "root" si nécessaire.

Les ressources sont identifiées de manière unique par leur nom et leur type - ce serait notre répertoire directory[/tmp/foo]. Des choses étranges se produisent lorsque vous avez deux ressources avec le même nom mais des attributs différents - cela explique votre problème, et il peut être résolu en utilisant la réponse de Darrin Holst:

node[:users].each do |user|
  puts "in loop for #{user['username']}"
  bash "create_user_#{user}" do
    user "root"
    code do
      puts "running 'useradd' for #{user['username']}"
      "useradd #{user['username']}"
    end
    not_if do
      puts "checking /etc/passwd for #{user['username']}"
      "cat /etc/passwd | grep #{user['username']}"
    end
  end
end

Cependant, dans ce cas particulier, vous auriez avantage à utiliser la ressource Utilisateur de Chef. Voici un remplacement pour votre recette (sans les messages de débogage):

node[:users].each do |u|
  user u['username'] do
    action :create
  end
end

Pourquoi est-ce mieux qu'un ensemble de ressources bash?

  1. La ressource Utilisateur fonctionne de la même manière sur différentes plates-formes - la même recette fonctionnera sur les systèmes d'exploitation qui utilisent autre chose que "useradd" pour créer des utilisateurs.
  2. Les fournisseurs responsables de cela savent comment vérifier si l'utilisateur existe déjà, vous n'avez donc pas besoin de not_if.
  3. Les mêmes fournisseurs peuvent également être utilisés pour supprimer des utilisateurs, verrouiller ou déverrouiller leurs mots de passe et mettre à jour d'autres attributs sur des comptes d'utilisateurs existants.

Mais la meilleure raison d'utiliser les bonnes ressources est qu'elle communique plus clairement votre intention. Votre objectif n'est probablement pas d'exécuter un tas de commandes shell - c'est de vous assurer que certains utilisateurs sont présents sur votre système. Les commandes utilisées pour ce faire ne sont qu'un détail d'implémentation.

Les fournisseurs résument ces détails, nous laissant libres de nous concentrer sur la description de ce que nous voulons.

zts
la source
1
Merci, cela clarifie un peu les choses. La recette de création d'utilisateur n'était qu'un exemple avec lequel je pouvais jouer pour mieux comprendre pourquoi les boucles que j'avais ailleurs ne fonctionnaient pas comme prévu.
Matthew J Morrison