What Ruby Features Are Used in Chef Recipes

what ruby features are used in chef recipes?

Chef is written in Ruby and makes an extensive use of Ruby ability to design custom DSL. Almost every chef configuration file is written with a Ruby-based DSL.

This means that in order to use chef effectively you should be familiar with the basic of Ruby syntax including

  • Grammar
  • Data types (the main difference compared to other languages are Symbols)
  • Blocks

You don't need to know a lot about metaprogramming in Ruby.

The case of the code you posted is an excellent example of a Ruby based DSL. Let me explain it a little bit.

# Call the method directory passing the path and a block
# containing some code to be evaluated
directory "/home/test/mydir" do

# chown the directory to the test user
owner "test"

# set the permissions to 0555
mode "0755"

# create the directory if it does not exists
action :create

# equivalent of -p flag in the mkdir
recursive true

end

Blocks are a convenient way to specify a group of operations (in this case create, set permissions, etc) to be evaluated in a single context (in this case in the context of that path).

Ruby blocks as used in Chef, trouble understanding syntax

I answered the same question at https://stackoverflow.com/a/20569738/123527

# Call the method directory passing the path and a block
# containing some code to be evaluated in the given context
template "/etc/profile.d/golang.sh" do

# Use the ERB template defined at "golang.sh.erb"
source "golang.sh.erb"

# chown the file to the user root
owner "root"
group "root"

# set the permissions to 0555
mode "0755"
end

Blocks are a convenient way to specify a group of operations (in this case create, set permissions, etc) to be evaluated in a single context (in this case in the context of that path).

template, source, owner, group, etc are all Ruby methods.

Override chef attributes globally from a cookbook

I've solved this by using my cookbook as a wrapper for the rbenv cookbook. I moved the logic above into the default attributes file, and used include_recipe to include the original recipes in my own. For some reason, the new attributes still weren't being picked up, so I moved the original values into node[:rbenv_wrapper][:rubies], and used override to set the new ones in node[:rbenv][:rubies], after which everything worked as expected.

Ruby code blocks and Chef

If you come from another language (not Ruby), this syntax might seem very strange. Let's break down things.

When calling a method with parameters, in most cases the parentheses are optional:

  • foo(bar) is equivalent to foo bar
  • foo(bar, baz) is equivalent to foo bar, baz

A Ruby block of code can be wrapped in curly braces ({}) or inside a do..end block and can be passed to a method as its last parameters (but note that there's no comma and if you're using parentheses it goes after them. Some examples:

foo(bar) { # code here }

foo(bar) do
# code here
end

foo bar do
# code here
end

foo do
# code here
end

In some cases, code blocks can receive parameters, but in Chef the resources' blocks never do. Just for reference, the syntax for that is:

foo(bar) do |baz, qux|
baz + qux
end

Specifically about Chef resources, their syntax is usually:

resource_type(name) do
attribute1 value1
attribute2 value2
end

This means that, when you say:

log "a debug string" do
level :debug
end

you're actually creating a log resource whose name attribute is set to "a debug string". It can later be referred to (in other resources, for example) using log[a debug string].

AFAIK, the name attribute is mandatory for every Chef resource type as it's what makes it unique, and allows you to, among other things, call actions on it after it has been declared.


Side note: The ruby block is usually optional for a Chef resource. If you do something like:

directory "/some/path"

Chef will compile that resource using its default attributes (among which is action :create), and try to create the named directory using those.

Merge attributes from different Chef cookbooks or recipes

In cookbook 2, you want to take advantage of Ruby's Hash#merge:

node.default[:bamboo][:agent][:attributes] = node[:bamboo][:agent][:attributes].merge(
'system.attr-3' => 'test3'
)

And then ensure cookbook 2 depends on cookbook 1 (so the attributes are loaded first).

Write to Chef::Log from a template script?

Chef::Log is a global, so technically you can just call its methods directly from inside a template, but this is really weird and you probably shouldn't. Do whatever logging you need from the recipe code instead. If you need to log some values, compute them in the recipe and them pass them in using variables.



Related Topics



Leave a reply



Submit