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

8

Alguém por favor pode me explicar como o chef funciona? Essa é uma pergunta bastante ampla, então, para restringir, tenho uma receita muito simples que percorre uma lista de usuários e cria cada um, se eles ainda não existirem. Não funciona.

Pelo que posso dizer, o loop parece estar acontecendo como eu esperaria. Depois que o loop é concluído, 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 na primeira iteração do loop.

Qual é a maneira correta de escrever uma receita que circula 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 instruções put 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
Matthew J Morrison
fonte

Respostas:

5

torne seu nome de script único ...

bash "create_user_#{user}" do

FWIW, eu usei https://github.com/fnichol/chef-user várias vezes, o que permite criar / remover usuários com base em atributos e bancos de dados.

Darrin Holst
fonte
Parece tão simples, obrigado! A receita de criação de usuários foi apenas um exemplo que eu estava usando para entender por que meu loop não estava funcionando em outras receitas. Obrigado novamente!
Matthew J Morrison
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 "compilar", 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. Por exemplo, um recurso do Diretório para dizer que /tmp/foodeveria existir e pertencer à raiz:

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 depois o compara ao estado de destino. Se forem diferentes, o Chef atualizará o sistema. Para nosso recurso de diretório, o Chef criaria o diretório se não existisse e mudaria 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 com 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, nesse 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 do que um conjunto de recursos do bash?

  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, para que você não precise_se.
  3. Os mesmos provedores também podem ser usados ​​para remover usuários, bloquear ou desbloquear suas senhas e atualizar outros atributos nas contas de usuário existentes.

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

Os fornecedores encapsulam esses detalhes, deixando-nos à vontade para descrever o que queremos.

zts
fonte
1
Obrigado, isso esclarece bastante as coisas. A receita de criação de usuário era apenas um exemplo com o qual eu poderia brincar para entender melhor por que os loops que eu tinha em outro lugar não funcionaram como eu esperava.
Matthew J Morrison