Is there a way to iterate through node.run_state data in a Chef recipe?

994 views Asked by At

Is there a way to iterate through node.run_state data? This is in a RHEL environment with Active Directory users. I have a ruby block that populates node.run_state. I have to have this run at converge time, because the overall cookbook will be used for build automation. On the very first run, the cookbook installs Centrify, then later needs to run adquery to gather user info for populating home directories with SSH keys.

On a chef-client run, I see this:

       Compiling Cookbooks...
       {}

Obviously, that's the puts running in compile time against an empty hash. At converge time, nothing happens in the loop with the 2 directory and 1 template resource.

Here is the relevant piece of the recipe:

ruby_block 'set uid, gid, and homedir for users' do
  block do
    base_attr['ssh_keys'].each do |user, pubkeys|
  # next unless Dir.exist?(homedir)
      node.run_state[user] = {}
      puts "Checking user #{user}..."
      if local_users.key?(user)
        node.run_state[user]['homedir'] = local_users[user]['homedir']
        node.run_state[user]['uid'] = local_users[user]['uid'].to_i
        node.run_state[user]['gid'] = local_users[user]['gid'].to_i
      elsif centrify_users.key?(user)
        node.run_state[user]['homedir'] = centrify_users[user]['homedir']
        node.run_state[user]['uid'] = centrify_users[user]['uid'].to_i
        node.run_state[user]['gid'] = centrify_users[user]['gid'].to_i
      else
        puts "user #{user} not found."
        # Place holder values.
        node.run_state[user]['homedir'] = "/tmp/#{user}"
        node.run_state[user]['uid'] = 0
        node.run_state[user]['gid'] = 0
      end
    end
  end
end
  # Dir.exist? guard should bypass compile-time error.
  # "name is a required property"
  # next unless Dir.exist?(homedir)
puts node.run_state
node.run_state.each do |user|
  directory node.run_state[user]['homedir'] do
    owner node.run_state[user]['uid']
    group node.run_state[user]['gid']
    mode '0700'
  end

  directory "#{node.run_state[user]['homedir']}/.ssh" do
    owner node.run_state[user]['uid']
    group node.run_state[user]['gid']
    mode '0700'
  end

  template "#{node.run_state[user]['homedir']}/.ssh/authorized_keys" do
    owner node.run_state[user]['uid']
    group node.run_state[user]['gid']
    mode '0600'
    source 'authorized_keys.erb'
    variables(
      sshkeys: base_attr['ssh_keys'][user]
    )
  end
end

Any ideas how to make this work?

3

There are 3 answers

1
TX_Mike68 On BEST ANSWER

My subconscious mind told me this morning to pull the path inside the directory resource, and THEN use lazy on it. So I have the working solution. node.run_state in the ruby_block, and lazy blocks around the requisite attributes in the subsequent resources that use node.run_state. Thanks for the feedback on this issue.

2
slashpai On

Add lazy {} block around node.run_state where you are utilizing it. So in your directory and template resource. lazy {} ensures that the resource evaluates during the execution phase of a Chef Infra Client run (as opposed to during the compile phase). You can read more about lazy block here

0
Draco Ater On

As you are setting node.run_state in the execution phase, you also need to read it in execution phase. Currently your node.run_state.each is skipped, because, as you noted correctly, node.run_state is empty.

So you need to move this node.run_state.each execution into execution phase. One possibility is to create a ruby_block and move your cycle there, unfortunately you can't use the resource methods there as in recipe, but you can use resource classes directly:

ruby_block 'Create ssh authorized keys' do
  block do
    node.run_state.each do |user|
      d = Chef::Resource::Directory.new(node.run_state[user]['homedir'], run_context)
      d.owner node.run_state[user]['uid']
      d.group node.run_state[user]['gid']
      d.mode '0700'
      d.run_action :create

      d = Chef::Resource::Directory.new("#{node.run_state[user]['homedir']}/.ssh", run_context)
      d.owner node.run_state[user]['uid']
      d.group node.run_state[user]['gid']
      d.mode '0700'
      d.run_action :create

      t = Chef::Resource::Template.new("#{node.run_state[user]['homedir']}/.ssh/authorized_keys", run_context)
      t.owner node.run_state[user]['uid']
      t.group node.run_state[user]['gid']
      t.mode '0600'
      t.source 'authorized_keys.erb'
      t.variables(sshkeys: base_attr['ssh_keys'][user])
      t.run_action :create
    end
  end
end