1

I'm creating a user via Chef. His properties are stored in data bag:

{
    "id": "developer",
    "home": "/home/developer",
    "shell": "/bin/zsh",
    "password": "s3cr3t"
}

The recipe is:

developer = data_bag_item('users', 'developer')

user developer['id'] do
  action :create

  supports :manage_home => true
  home developer['home']
  comment developer['comment']
  shell developer['shell']
  password developer['password']
end

The problem is that if zsh is not installed on node, I cannot login as developer. So, I want to conditionally apply argument for user resource, like:

user developer['id'] do
  action :create

  supports :manage_home => true
  home developer['home']
  comment developer['comment']
  if installed?(developer['shell'])
    shell developer['shell']
  end
  password developer['password']
end

How can I achieve this?

madhead
  • 31,729
  • 16
  • 153
  • 201
  • Is installing zsh with the package resource not an option ? – Tensibai Apr 28 '15 at 16:05
  • @Tensibai, well, actually I install `zsh` using that cookbook. I just do not want to depend on it tightly. – madhead Apr 28 '15 at 21:13
  • In this case @mudasobwa answer is the correct one (I had upvoted if the answer included a small explanation of how the ruby code works for future readers) – Tensibai Apr 28 '15 at 21:16
  • @MarkO'Connor, I'm not expert in Ruby / Chef, but looks like `opscode/users` [has the same problem](https://github.com/opscode-cookbooks/users/blob/aa63d35cd37bb491958326ea54816f135b383a18/providers/manage.rb#L99): if specified shell does not exist, the user is "broken". – madhead Apr 28 '15 at 21:16
  • @Tensibai, I've just tested it and it actually does not work in case if you run `zsh` recipe and recipe from my question during one chef-run. [Looks like in this case `File.exist? developer['shell']` evaluates to false](http://stackoverflow.com/a/25981182/750510), then `zsh` is installed and then my recipe is run. I'm trying to solve this, and will comment @mudasobwa's answer. – madhead Apr 28 '15 at 22:07
  • Indeed, you have to be clever and use lazy evaluation as when chef compile the shell does not already exist – Tensibai Apr 28 '15 at 22:09
  • Something along the line `shell lazy { File.exists? Developer['shell'] ? Developer['shell'] : "/bin/bash" }` – Tensibai Apr 28 '15 at 22:12
  • @Tensibai, that's it! I'll accept your answer if you post it. – madhead Apr 28 '15 at 22:30
  • I'll do this tomorow, it's quite hard from phone :) – Tensibai Apr 28 '15 at 22:31

2 Answers2

4

To complement @mudasobwa's answer the proper way to do it in chef and avoid missing the shell if it's installed by another recipe or a package resource in the same recipe you have to use lazy attribute evaluation.

Long version for thoose interested on the how and why:

This is a side effect on how chef works, there's a first time compiling the resources to build a collection, at this phase any ruby code in a recipe (outside of a ruby_block resource) if evaluated. Once that is done the resources collection is converged (the desired state is compared to the actual state and relevant actions are done).

The following recipe would do:

package "zsh" do
  action :install
end

user "myuser" do
  action :create
  shell lazy { File.exists? "/bin/zsh" ? "/bin/zsh" : "/bin/bash" }
end

What hapens here is that the evaluation of the shell attribute value is delayed to the converge phase, we have to use a if-then-else construction (here with a ternary operator as I find it more readable) to fallback to a shell we're sure will be present (I used /bin/bash, but a failsafe value would be /bin/sh) or the shell attribute will be nil, which is not allowed.

With this delayed evaluation the test on the presence of "/bin/zsh" is done after the package has been installed and the file should be present. In case there was a problem within the package, the user resource will still create the user but with "/bin/bash"

Tensibai
  • 15,557
  • 1
  • 37
  • 57
1

The easiest way to achieve what you want is to check for the shell existence explicitly:

shell developer['shell'] if File.exist? developer['shell']
Aleksei Matiushkin
  • 119,336
  • 10
  • 100
  • 160