Jürgen Paul
Jürgen Paul

Reputation: 15007

Make chef cookbook recipe only run once

So I use the following recipe:

include_recipe "build-essential"

node_packages = value_for_platform(
  [ "debian", "ubuntu" ]                      => { "default" => [ "libssl-dev" ] },
  [ "amazon", "centos", "fedora", "centos" ]  => { "default" => [ "openssl-devel" ] },
  "default"   => [ "libssl-dev" ]
)

node_packages.each do |node_package|
  package node_package do
    action :install
  end
end

bash "install-node" do
  cwd Chef::Config[:file_cache_path]
  code <<-EOH
    tar -xzf node-v#{node["nodejs"]["version"]}.tar.gz
    (cd node-v#{node["nodejs"]["version"]} && ./configure --prefix=#{node["nodejs"]["dir"]} && make && make install)
  EOH
  action :nothing
  not_if "#{node["nodejs"]["dir"]}/bin/node --version 2>&1 | grep #{node["nodejs"]["version"]}"
end

remote_file "#{Chef::Config[:file_cache_path]}/node-v#{node["nodejs"]["version"]}.tar.gz" do
  source node["nodejs"]["url"]
  checksum node["nodejs"]["checksum"]
  notifies :run, resources(:bash => "install-node"), :immediately
end

It successfully installed nodejs on my Vagrant VM but on restart it's getting executed again. How do I prevent this? I'm not that good in reading ruby code.

Upvotes: 4

Views: 8937

Answers (5)

Anuj Biyani
Anuj Biyani

Reputation: 329

In my experience remote_file always runs when executing chef-client, even if the target file already exists. I'm not sure why (haven't dug into the Chef code to find the exact cause of the bug), though.

You can always write a not_if or only_if to control the execution of the remote_file resource, but usually it's harmless to just let it run every time.

The rest of your code looks like it's already idempotent, so there's no harm in running the client repeatedly.

There's an action you can specify for remote_file that will make it run conditionally:

remote_file 'target' do
  source 'wherever'
  action :create_if_missing
end

See the docs.

Upvotes: 1

AlbertFont
AlbertFont

Reputation: 36

You can run a recipe only once overriding the run-list with -o modifier.

sudo chef-client -o "recipe[cookbook::recipe]"

-o RunlistItem,RunlistItem..., Replace current run list with specified items

--override-runlist

Upvotes: 1

whummer
whummer

Reputation: 497

If you want to test whether your recipe is idempotent, you may be interested in ToASTER, a framework for systematic testing of Chef scripts.

http://cloud-toaster.github.io/

Chef recipes are executed with different configurations in isolated container environments (Docker VMs), and ToASTER reports various metrics such as system state changes, convergence properties, and idempotence issues.

Upvotes: 0

Holger Just
Holger Just

Reputation: 55758

To make the remote_file resource idempotent (i.e. to not download a file already present again) you have to correctly specify the checksum of the file. You do this in your code using the node["nodejs"]["checksum"] attribute. However, this only works, if the checksum is correctly specified as the SHA256 hash of the downloaded file, no other algorithm (esp. not MD5) is supported.

If the checksum is not correct, your recipe will still work. However, on the next run, Chef will notice that the checksum of the existing file is different from the one you specified and will download the file again, thus notify the install node ressource and do the whole compile stuff.

Upvotes: 8

Ian McMahon
Ian McMahon

Reputation: 1700

With chef, it's important that recipes be idempotent. That means that they should be able to run over and over again without changing the outcome. Chef expects to be able to run all the recipes on a node periodically, and that should be ok.

Do you have a way of knowing which resource within that recipe is causing you problems? The remote_file one is the only one I'm suspicious of being non-idempotent, but I'm not sure offhand.

Looking at the Chef wiki, I find this:

Deprecated Behavior In Chef 0.8.x and earlier, Remote File is also used to fetch files from the files/ directory in a cookbook. This behavior is now provided by #Cookbook File, and use of Remote File for this purpose is deprecated (though still valid) in Chef 0.9.0 and later.

Anyway, the way chef tends to work, it will look to see if whatever "#{Chef::Config[:file_cache_path]}/node-v#{node["nodejs"]["version"]}.tar.gz" resolves to exists, and if it does, it should skip that resource. Is it possible that install-node deletes that file when it's finished installing? If so, chef will re-fetch it every time.

Upvotes: 2

Related Questions