Как правильно сделать цикл в рецепте шеф-повара (соло)?

8

Может кто-нибудь объяснить мне, как работает шеф-повар? Это довольно широкий вопрос, поэтому, чтобы сузить его, у меня есть очень простой рецепт, который перебирает список пользователей и создает каждого из них, если они еще не существуют. Это не работает.

Из того, что я могу сказать, цикл, кажется, происходит так, как я и ожидал. После завершения цикла выполняются команды bash для создания каждого пользователя, по одному разу для каждой итерации цикла. Однако когда команды bash выполняются, кажется, что они имеют пользовательское значение только с первой итерации цикла.

Как правильно написать рецепт, который перебирает переменные данные, как в этом примере?

Вот рецепт:

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

Я тестирую это с помощью Vagrant со следующей настройкой:

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

Сообщения, генерируемые инструкциями put в рецепте, выглядят так:

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
Мэтью Дж Моррисон
источник

Ответы:

5

сделайте имя вашего скрипта уникальным ...

bash "create_user_#{user}" do

Кстати, я использовал https://github.com/fnichol/chef-user несколько раз, что позволяет создавать / удалять пользователей на основе атрибутов и пакетов данных.

Даррин Холст
источник
Это кажется таким простым, спасибо! Рецепт создания пользователей был лишь примером, который я использовал, чтобы понять, почему мой цикл не работает в других рецептах. Еще раз спасибо!
Мэтью Дж Моррисон
10

Поведение, которое вы видите, можно объяснить, понимая разницу между двумя ключевыми этапами запуска клиента Chef: компиляцией и конвергенцией.

На этапе «компиляции» клиент Chef запускает код в ваших рецептах для создания коллекции ресурсов . Это список ресурсов, которые вы указали Chef для управления в вашей системе, вместе с их целевым состоянием. Например, ресурс Справочника, который /tmp/fooдолжен существовать и принадлежать пользователю root:

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

На этапе «конвергенции» клиент Chef использует провайдеров для загрузки текущего состояния каждого ресурса, а затем сравнивает его с целевым состоянием. Если они разные, Chef обновит систему. Для нашего ресурса Directory Chef создаст каталог, если он не существует, и при необходимости изменит его владельца на «root».

Ресурсы однозначно идентифицируются по их имени и типу - наш каталог будет directory[/tmp/foo]. Странные вещи произойдут, когда у вас есть два ресурса с одинаковыми именами, но разными атрибутами, - это объясняет вашу проблему, и это можно исправить с помощью ответа Даррина Холста:

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

Тем не менее, в этом конкретном случае вы бы выиграли от использования ресурса пользователя Chef. Вот замена для вашего рецепта (без сообщений отладки):

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

Почему это лучше, чем набор ресурсов Bash?

  1. Пользовательский ресурс работает одинаково на разных платформах - тот же рецепт будет работать в операционных системах, которые используют что-то отличное от «useradd» для создания пользователей.
  2. Поставщики, ответственные за это, знают, как проверить, существует ли пользователь, поэтому вам не нужно not_if.
  3. Эти же провайдеры могут также использоваться для удаления пользователей, блокировки или разблокировки их паролей и обновления других атрибутов существующих учетных записей пользователей.

Но лучшая причина для использования правильных ресурсов заключается в том, что они более четко отражают ваши намерения. Ваша цель, вероятно, состоит не в том, чтобы запускать несколько команд оболочки, а в том, чтобы обеспечить присутствие некоторых пользователей в вашей системе. Команды, используемые для этого, являются просто деталями реализации.

Поставщики инкапсулируют эти детали, что позволяет нам сосредоточиться на описании того, что мы хотим.

ЗТС
источник
1
Спасибо, это немного проясняет ситуацию. Рецепт создания пользователя был просто примером, с которым я мог поиграть, чтобы лучше понять, почему циклы, которые у меня были в другом месте, не работали, как я ожидал.
Мэтью Дж Моррисон