Qual é a maneira correta de fazer loop em uma receita do Chef (solo)?

8

Alguém pode me explicar como funciona o chef? Essa é uma questão bastante ampla, então, para reduzi-la, tenho essa receita muito simples que faz um loop sobre uma lista de usuários e cria cada um deles, se eles ainda não existirem. Não funciona.

Pelo que eu posso dizer, o loop parece estar acontecendo como eu esperava. Uma vez que o loop tenha completado meus comandos bash para criar cada usuário são executados, uma vez para cada iteração no loop. No entanto, quando os comandos bash são executados, eles parecem ter apenas o valor do usuário da primeira iteração do loop.

Qual é a maneira correta de escrever uma receita que faça um loop sobre dados variáveis semelhantes a este exemplo?

Aqui está a receita:

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

Estou testando isso usando o Vagrant com a seguinte configuração:

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

As mensagens geradas pelas declarações puts na receita são assim:

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
    
por Matthew J Morrison 08.03.2013 / 03:14

2 respostas

5

torne seu nome de script exclusivo ...

bash "create_user_#{user}" do

FWIW, eu usei o link várias vezes, o que permite que você crie / remova usuários com base em atributos e databags.

    
por 08.03.2013 / 06:20
10

O comportamento que você está vendo pode ser explicado pela compreensão da diferença entre dois dos principais estágios de uma execução do cliente Chef: compilação e convergência.

Durante a fase de "compilação", o cliente Chef executa o código em suas receitas para criar uma coleção de recursos . Esta é uma lista dos Recursos que você disse ao Chef para gerenciar em seu sistema, junto com o estado de destino deles. Por exemplo, um recurso do Diretório para dizer que /tmp/foo deve existir e ser de propriedade de root:

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

Durante a fase "convergir", o cliente Chef usa provedores para carregar o estado atual de cada recurso e compara isso com o estado de destino. Se forem diferentes, o Chef atualizará o sistema. Para nosso recurso de diretório, o Chef criaria o diretório, se ele não existisse, e alteraria seu proprietário para "root", se necessário.

Os recursos são identificados exclusivamente por seu nome e tipo - nosso Diretório seria directory[/tmp/foo] . Coisas estranhas acontecerão quando você tiver dois recursos com o mesmo nome, mas atributos diferentes - isso explica seu problema e pode ser corrigido usando a resposta 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

No entanto, neste caso em particular, você se beneficiaria do uso do recurso Usuário do Chef. Aqui está um substituto para sua receita (sem as mensagens de depuração):

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

Por que isso é melhor que um conjunto de recursos básicos?

  1. O recurso Usuário funciona da mesma maneira em várias plataformas - a mesma receita funcionará em sistemas operacionais que usam algo diferente de "useradd" para criar usuários.
  2. Os provedores responsáveis por isso sabem como verificar se o usuário já existe, então você não precisa de not_if.
  3. Os mesmos provedores também podem ser usados para remover usuários, bloquear ou desbloquear suas senhas e atualizar outros atributos em contas de usuários existentes.

Mas a melhor razão para usar os recursos certos é que ele comunica mais claramente sua intenção. Seu objetivo provavelmente não é executar um monte de comandos shell - é para garantir que alguns usuários estejam presentes em seu sistema. Os comandos usados para fazer isso são apenas um detalhe de implementação.

Os provedores encapsulam esses detalhes, deixando-nos livres para nos concentrar em descrever o que queremos.

    
por 08.03.2013 / 14:20