Chef Recipe Compile Error - ruby

Does anyone know why the following code results the error: undefined method 'tar' for "riak-1.4.2":String
remote_file "/vagrant/usr/src/#{node.default['riak']['version'].tar.gz}" do
source "#{node.default['riak']['url']}"
mode 0755
notifies :run, "bash[extract_riak]", :immediately
end
bash "extract_riak" do
code <<-EOH
# Following is the line which causes the error.
/bin/tar xzf /vagrant/usr/src/#{node.default['riak']['version']}.tar.gz -C /vagrant/usr/src/#{node.default['riak']['version']}
EOH
notifies :run, "bash[make_riak]", :immediately
end

This line is raising the error:
remote_file "/vagrant/usr/src/#{node.default['riak']['version'].tar.gz}"
The .tar.gz should be outside the brackets, like so:
remote_file "/vagrant/usr/src/#{node.default['riak']['version']}.tar.gz"
Everything between the brackets is executed as ruby code and the result takes it's place in the string. node.default['riak']['version'].tar.gz is a chain of function calls, including calling a non-existent tar and gz function at the end. These are part of the filename, and should go outside the brackets.
As a side note, you probably want to use node[:attribute] to get attributes, and only use node.default[:attribute] to set attributes.

I recommend the ark cookbook as better choice for handling archives.
The following example recipe:
include_recipe "ark"
ark "riak" do
url "http://s3.amazonaws.com/downloads.basho.com/riak/1.4/1.4.2/riak-1.4.2.tar.gz"
version "1.4.2"
end
will install riak under the "/usr/local/riak-1.4.2" directory.
Finally, there is a riak cookbook available as well, which reportedly will also install from source.

Instead of:
#{node.default['riak']['version']}.tar.gz
you want:
#{node.default['riak']['version'].tar.gz}

Related

Chef use cookbook_file in ruby block

I have the following code to figure out where Java is located on the box. Java comes with our application and what Java version that is included with the application differs.
def app_java_home
if Dir.exist?("#{app_home}/jre-server/linux")
Dir.chdir("#{app_home}/jre-server/linux") do
Dir.glob('jdk*').select { |f| File.directory? f }[0]
end
end
end
Then, in my cookbook I have
aws_s3_file "#{app_download_path}/#{app_s3['archive_file']}" do
bucket app_s3['bucket']
remote_path app_s3['remote_path']
region aws_region
not_if { ::Dir.exists?(app_bin_dir) }
not_if { ::File.exists?("#{app_download_path}/#{app_s3['archive_file']}") }
end
execute 'extract' do
user 'root'
command "unzip #{app_download_path}/#{app_s3['archive_file']} > /dev/null"
not_if { ::Dir.exists?("#{app_home}/ourapp") }
only_if { ::File.exists?("#{app_download_path}/#{app_s3['archive_file']}") }
end
execute 'move' do
user 'root'
command "mv #{app_download_path}/ourapp/ #{app_install_path}"
not_if { ::Dir.exists?(app_home) }
end
cookbook_file "#{app_java_home}/jre/lib/security/local_policy.jar" do
source %W[#{app_release}/local_policy.jar default/local_policy.jar]
owner app_user_name
group app_group_name
mode 0755
end
cookbook_file "#{app_java_home}/jre/lib/security/US_export_policy.jar" do
source %W[#{app_release}/US_export_policy.jar default/US_export_policy.jar]
owner app_user_name
group app_group_name
mode 0755
end
However, the two cookbook_file resources fails because it can't find the directory:
No such file or directory # dir_chdir - /ourapp/jre-server/linux/
After a lot of googling, I've come to the conclusion that it's a .. "missmatch" (?) between compile time and run time of the recipes. Basically, if I understand it correctly, it tries to run the cookbook_file resource(s) first but fails. So never downloads, unpacks and installs the app artefact.
I've tried running app_java_home when the directory exists, and it does seem to work the way I want it..
I tried putting the cookbook_file resources in a ruby_block, but then I instead get:
undefined method `cookbook_file' for Chef::Resource::RubyBlock
The app_java_home .. function (?) used to look like this:
def app_java_home
"#{app_home}/jre-server/linux/#{jdk_version}"
end
Where jdk_version came from the databag. This worked fine, but we have a long standing bug/feature request in our system where it sometimes happens that "they" get the version they put in the databag wrong, causing all sorts of problems.. So they want a way to remove this dependency and instead "figure this out" dynamically.
Ruby and Chef isn't my forte, so I'm not sure what to try next. I have found references to Chef::Resources::CookbookFile (which, if I understand it, could/should be used inside ruby_blocks), but can't find any examples or documentation about it. The link on RubyDocs is broken.
Adding an answer here for a better explanation.
Any (Ruby) code that is not within any of the Chef resources, will run in Compile phase
All resource declarations will run in Convergence phase in the order they are defined
Thankfully, there is a way to make resources run in Compile phase if so required. Though IMHO it should be done sparingly and in exceptional cases.
As per your comment aws_s3_file and execute resources are the ones that unpack the app (and create the directory). In this case, it seems you want them to run in compile phase.
Prior to Chef client 16.0
Use the run_action option with the action that should be performed at the compile time. For example execute resource takes action :run:
# Note action ":nothing" and "run_action"
execute 'extract' do
user 'root'
command "unzip #{app_download_path}/#{app_s3['archive_file']} > /dev/null"
not_if { ::Dir.exists?("#{app_home}/ourapp") }
only_if { ::File.exists?("#{app_download_path}/#{app_s3['archive_file']}") }
action :nothing
end.run_action(:run)
Chef client 16.0 onwards
We can add a common property to the resources. Example with execute resource:
# Note the extra property "compile_time"
execute 'extract' do
user 'root'
command "unzip #{app_download_path}/#{app_s3['archive_file']} > /dev/null"
not_if { ::Dir.exists?("#{app_home}/ourapp") }
only_if { ::File.exists?("#{app_download_path}/#{app_s3['archive_file']}") }
compile_time true
end
And finally to answer the subject of the question:
Chef use cookbook_file in ruby block
This is not possible. Refer to the first point on the top. If we want Ruby code to run during converge (instead of compile), we put it within the ruby_block resource. So it can contain code like (for example):
ruby_block 'get directory' do
block do
def app_java_home
"#{app_home}/jre-server/linux/#{jdk_version}"
end
end
end
With the help of #seshadri_c, I finally managed to solve the problem! It took some doing, because I kept misunderstanding the suggestions etc.
So this is what I came up with (for posterity):
def jdk_version(required = true)
base_dir = "#{app_home}/jre-server/linux"
if Dir.exist?("#{base_dir}")
Dir.chdir("#{app_home}/jre-server/linux") do
Dir.glob("jdk*").each do |f|
if File.directory?(f)
return "#{f}"
end
end
end
end
end
def app_java_home
return "#{app_home}/jre-server/linux/#{jdk_version}"
end
Turns out I need to get just the version, individually, as well, so I rearranged the functions a bit. I'm sure it could be written much cleaner, but here the trick was to use return instead of puts/print! Well, I'm a programmer, but not a Ruby programmer so didn't know that was an option..
Then, in the cookbook, I added the .run_action() where needed. I didn't need them for the cookbook_file, which simplified things a bit:
aws_s3_file "#{app_download_path}/#{app_s3['archive_file']}" do
bucket app_s3['bucket']
remote_path app_s3['remote_path']
region aws_region
not_if { ::Dir.exists?(app_bin_dir) }
not_if { ::File.exists?("#{app_download_path}/#{app_s3['archive_file']}") }
end.run_action(:create)
execute 'extract' do
user 'root'
command "unzip #{app_download_path}/#{app_s3['archive_file']} > /dev/null"
not_if { ::Dir.exists?("#{app_home}/app") }
only_if { ::File.exists?("#{app_download_path}/#{app_s3['archive_file']}") }
end.run_action(:run)
execute 'move' do
user 'root'
command "mv #{app_download_path}/app/ #{app_install_path}"
not_if { ::Dir.exists?(app_home) }
end.run_action(:run)
# JCE Unlimited Strength Jurisdiction Policy Files
cookbook_file "#{app_java_home}/jre/lib/security/local_policy.jar" do
source %W[#{app_release}/local_policy.jar default/local_policy.jar]
owner app_user_name
group app_group_name
mode 0755
end
cookbook_file "#{app_java_home}/jre/lib/security/US_export_policy.jar" do
source %W[#{app_release}/US_export_policy.jar default/US_export_policy.jar]
owner app_user_name
group app_group_name
mode 0755
end
With all that, everything is running exactly when they're supposed to and everything seems to be working.

Ruby Guard ignore files

I would like to run the requirejs optimization script when any .js file is changed (except for built.js).
I read there is an ignore method. I have the following in my Gaurdfile (which is causing an infinite loop).
guard 'shell', :ignore => 'built.js' do
watch(/script\/(.*).js/) { `node r.js -o build.js` }
end
My question is: How do I configure my Guardfile to ignore the file built.js?
First, assuming you already have the guard-shell gem installed...
I think this gives you something to work from given what you are trying to do.
It will ignore the script/build.js file and trigger a shell command when any other .js file changes.
ignore /script\/build.js/
guard :shell do
watch /.*\.js/ do |m|
`yourcommandhere`
msg = "Processed #{m[0]}"
n msg, 'mycustomshellcommand'
"-> #{msg}"
end
end
See this link for Guardfile examples.
See this link for the syntax of guard-shell.

Using a different name for calling a ruby gem from the command line

Is there any way to change the name that the user has to use when calling from the command line? For example, I have a Thor command line app called super_awesome_gem. I want my gem to be called super_awesome_gem, but when the user calls it from the command line I just want them to be able to call sup_awe or something.
I've tried editing the gemspec file and the file and folder names, but I can't figure out what the proper way to do this would be, or even if there is one.
Is there a way to name a gem one way and have the command line call be a different name?
Your gem name and the executables it bundles don't have to be the same at all. In your gemspec, you can define a list of executables via executables:
Gem::Specification.new do |s|
s.name = "super_awesome_gem"
# other gemspec stuff
s.executables = ["sup_awe"]
end
As long as sup_awe is listed in the gemspec's files list, and is executable, that will be in the user's path after they install your gem. Bundler, when bootstrapping your gemspec, makes this even simpler
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
Anything in bin/ will be treated as an executable.
That is the long way of saying that your exectuable/bin file can be named whatever you want, and doesn't have to be named for your gem.
Another way to achieve this is an alias:
alias my_command=original_command
Just place it where it fits you best.
A third way is to use a wrapper_script, which is a script with the desired name which then calls the original command and passes it all arguments it got:
#!/bin/sh
original_command $#
or in cmd.exe on windows:
#echo off
original_command %*

Chef - create template with dynamic variable?

I'm having a bit of a challenge on a Chef recipe. I'm new to Chef, so please bear with me.
Step 1: My chef recipe installs Ruby Passenger, then compiles the Passenger nginx module along with Nginx.
# Install passenger and nginx module
bash "Install Passenger" do
code <<-EOF
source /usr/local/rvm/scripts/rvm
gem install passenger
EOF
user "root"
not_if { `gem list`.lines.grep(/^passenger \(.*\)/).count > 0 }
end
# Install passenger
# Note that we have to explicitly include the RVM script otherwise it won't setup the environment correctly
bash "Install passenger nginx module and nginx from source" do
code <<-EOF
source /usr/local/rvm/scripts/rvm
passenger-install-nginx-module --auto --prefix=/opt/nginx --auto-download
EOF
user "root"
not_if { File.directory? "/opt/nginx" }
end
Step 2: After that, I create the nginx config file using a template. This configuration requires the location of Passenger, which is dependent on step 1 completing.
template "/opt/nginx/conf/nginx.conf" do
source "nginx.conf.erb"
action :create
variables(
deploy_user: deploy_user,
passenger_root: `bash -c "source /usr/local/rvm/scripts/rvm; passenger-config --root"`.chomp,
passenger_ruby: `bash -c "source /usr/local/rvm/scripts/rvm; which ruby"`.chomp,
passenger: node[:passenger]
)
end
Problem: Chef appears to compile templates at th ebeginning of the run. So what ends up happening is that Step 2 is actually compiled before Step 1 is run. This means that the passenger_root variable is blank. It needs Step 1 to complete before being able to get the passenger_root, then run the template.
I tried wrapping the step 2 code in a ruby_block but that doesn't work: undefined methodtemplate' for Chef::Resource::RubyBlock`.
Not sure what to do here, or what is the best practice for Chef for something like this?
Thanks in advance,
Leonard
A cleaner and recommended way is to use Lazy Attribute Evaluation.
template "/opt/nginx/conf/nginx.conf" do
source "nginx.conf.erb"
action :create
variables lazy {
{
deploy_user: deploy_user,
passenger_root: `bash -c "source /usr/local/rvm/scripts/rvm; passenger-config --root"`.strip,
passenger_ruby: `bash -c "source /usr/local/rvm/scripts/rvm; which ruby"`.strip,
passenger: node[:passenger]
}
}
end
Also, I'd suggest using strip instead of chomp [thanks Draco].
As soon as you wrap your code in ruby_block you cannot use ordinary recipe resource declaration anymore. You have to write pure ruby code:
ruby_block "create /opt/nginx/conf/nginx.conf from template" do
block do
res = Chef::Resource::Template.new "/opt/nginx/conf/nginx.conf", run_context
res.source "nginx.conf.erb"
res.variables(
deploy_user: deploy_user,
passenger_root: `bash -c "source /usr/local/rvm/scripts/rvm; passenger-config --root"`.chomp,
passenger_ruby: `bash -c "source /usr/local/rvm/scripts/rvm; which ruby"`.chomp,
passenger: node[:passenger]
)
res.run_action :create
end
end
PS. And I guess you want to use strip instead of chomp to remove whitespace.
Yeah, Chef is a beast. I think part of the problem is there are a million ways to do the same things but there really is no documentation detailing the best way. What you probably want is to use Notifications, so that the block 1 runs first than notifies the block 2 to run. This means block 2 needs action :none so that it does not trigger until it gets notified.
I added the notify to your example in block 1 and added the action :none to block 2.
bash "Install Passenger" do
code <<-EOF
source /usr/local/rvm/scripts/rvm
gem install passenger
EOF
user "root"
not_if { `gem list`.lines.grep(/^passenger \(.*\)/).count > 0 }
notifies :run, 'bash[Install passenger nginx module and nginx from source]', :immediately
end
bash "Install passenger nginx module and nginx from source" do
code <<-EOF
source /usr/local/rvm/scripts/rvm
passenger-install-nginx-module --auto --prefix=/opt/nginx --auto-download
EOF
user "root"
action :none
end

Unexpected tIDENTIFIER in template path

I'll preface this by saying that I know a little Ruby but not very well, so this may well be a common Ruby mistake (but I can't seem to find an answer!)
I have this Chef recipe (run using Vagrant):
package "php5-fpm" do
action :install
end
template "/etc/php5-fpm/pool.d/site" do
source "php-fpm.erb"
owner "root"
group "root"
mode 0644
action :create
only_if "dpkg --get-selections | grep php5-fpm"
end
service "php5-fpm" do
action :restart
end
This fails with the following message:
================================================================================
Recipe Compile Error in /tmp/.../cookbooks/site/recipes/php-fpm.rb
================================================================================
SyntaxError
-----------
compile error
/tmp/.../cookbooks/site/recipes/php-fpm.rb:1: syntax error, unexpected
tIDENTIFIER, expecting $end
template "/etc/php5-fpm/pool.d/site" d...
^
Cookbook Trace:
---------------
/tmp/.../cookbooks/dosos/recipes/php-fpm.rb:1:in `from_file'
Relevant File Content:
----------------------
/tmp/.../cookbooks/dosos/recipes/php-fpm.rb:
end 2: :restart" do-selections | grep php5-fpm"
This doesn't make a lot of sense to me because the "relevant file content" appears to be munged from different parts of the file.
Is there something Ruby has about putting forward slashes in strings (and if so, why not error on the preceding 3 slashes, unless it's running right-to-left?)
I was using Vagrant to provision my server using the Chef scripts. The Chef scripts were edited on Windows, which were available on the Linux VM via a shared folder.
The mistake was that my editor on Windows was not configured to use Unix line-endings, which confused Ruby. Stripping carriage returns (^M) allowed the script to run normally.

Resources