Unexpected tIDENTIFIER in template path - ruby

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.

Related

Chef compile error when capturing shell output

I have a chef recipe that looks something like this:
package 'build-essential' do
action :install
end
cmd = Mixlib::ShellOut.new("gcc -dumpversion")
cmd.run_command
gcc_version = cmd.stdout.strip()
If I execute the recipe on a system where gcc is installed, the recipe runs fine without errors. However, if I run the recipe on a system which doesn't have gcc install I get the error 'no such file or directory - gcc'.
I came to know about the chef two-phases stuff when trying to find a solution to my problem. I was expecting the package installation to satisfy the gcc requirement. How can I tell chef that this requirement will be satisfied later and not throw an error at compile time?
I tried the following, but the attribute does not get updated.
Chef::Resource::RubyBlock.send(:include, Chef::Mixin::ShellOut)
ruby_block "gcc_version" do
block do
s = shell_out("gcc -dumpversion")
node.default['gcc_version'] = s.stdout.strip()
end
end
echo "echo #{node[:gcc_version]}" do
command "echo #{node[:gcc_version]}"
end
Any help is appreciated. Thanks.
So okay, a few issues here. First, forget that Chef::Resource::whatever.send(:include trick. Never do it, literally never. In this case, the ShellOut mixin is already available in all the places anyway.
Next, and more importantly, you've still got a two-pass confusion issue. See https://coderanger.net/two-pass/ for details but basically the strings in that echo resource (I assume that said execute originally and you messed up the coping?) get interpolated at compile time. You haven't said what you are trying to do, but you probably need to use the lazy{} helper method.
And last, don't store things in node attributes like that, it's super brittle and hard to work with.

Keeping files updated with a Chef recipe

The challenge prompt is above, and my latest attempt is below. The directories and files are created as expected, and the read-out after executing chef-apply multipleCopies.rb tells me the files are linked, but when I update any one of the files, the others do not follow suit. Any ideas? Here is my code:
for x in 1..3
directory "multipleCopy#{x}" do
mode '0755'
action :create
end
end
file "multipleCopy1/secret.txt" do
mode '0755'
action :create
end
for x in 2..3
link "multipleCopy#{x}/secret.txt" do
to "multipleCopy1/secret.txt"
link_type :hard
subscribes :reload, "multipleCopy1/secret.txt", :immediately
end
end
Note: For less headache, I am testing the recipe locally before uploading to the ubuntu server referenced in the prompt, which is why my file paths are different and why I have not yet included the ownership properties.
So a file hard link doesn't seem to be what the question is going for (though I would say your solution is maybe better since this is really not what Chef is for, more on that later). Instead they seem to want you to have three actually different files, but sync the contents.
So first the easy parts, creating the directories and the empty initial files. It's rare to see those for loops used in Ruby code, though it is syntactically valid:
3.times do |n|
directory "/var/save/multipleCopy#{n+1}" do
owner "ubuntu"
group "root"
mode "755"
end
file "/var/save/multipleCopy#{n+1}/secret.txt" do
owner "root
group "root"
mode "755"
end
end
But that doesn't implement the hard part of sync'ing the files. For that we need to first analyze the mtimes on the files and use the most recent as the file content to set.
latest_file = 3.times.sort_by { |n| ::File.mtime("/var/save/multipleCopy#{n+1}/secret.txt") rescue 0 }
latest_content = ::File.read("/var/save/multipleCopy#{latest_file+1}/secret.txt") rescue nil
and then in the file resource:
file "/var/save/multipleCopy#{n+1}/secret.txt" do
owner "root
group "root"
mode "755"
content latest_content
end
As for this not being a good use of Chef: Chef is about writing code which asserts the desired state of the machine. In the case of files like this, rather than doing this kind of funky stuff to check if a file has been edited, you would just say that Chef owns the file content for all three and if you want to update it, you do it via your cookbook (and then usually use a template or cookbook_file resource).

Using a Chef InSpec resource before deleting a file

I am running Chef InSpec command resource which matches output of the command with some content from a file. Then I am deleting that file after using the following resource. But the following command returns "" and test fails.
describe command("some command") do
its ('stdout') {should match /some_regex/}
end
But when I do not delete the file, above command returns the expected output. Is there any issue with InSpec resources and deleting a file?
Rspec (and thus InSpec) has its own two-pass loading model similar (but unrelated and distinct) to Chef's. That means you are probably deleting it before the test gets run, even though it looks like it is after in the code. Try using an after(:all) block? That's the way to do it in normal RSpec, but InSpec deviates from Rspec in some places so I'm not 100% it will work.

Have Vagrant+Chef run bail out if template files have been checked out with CRLF line endings

I keep getting bit by an annoying gotcha that happens when Windows developers check out cookbooks from my Git repo with Git's autocrlf set to true. When they run vagrant up to bring up a Linux VM, the cookbook files are mapped into the VM with CRLF line endings, which causes no end of obscure errors when the shell and other POSIX utilities try to operate on the (now invalid) template files that have been copied into the VM.
The fix for that is simple enough: re-clone the repository after changing the autocrlf setting to input or false.
My problem is that, when you have the wrong line endings, the only symptoms are errors in strange places that in no way point to there being a problem with line endings.
How can I have Chef† check for the wrong line endings in, say, the cookbook template files and throw an error if it finds one? I think a simple Ruby snippet that does an assertion on the line endings in a given file that I can put at the top of a recipe would work.
Note: in the particular case of my repo, the sequence of steps is:
Developer checks out repo
Developer runs vagrant up
Vagrant kicks off a Chef run in the VM
Finally, the repo's build script runs in the VM
† Or really anything else included in the repo
Rubocop can be used to enforce unix style line endings (among many other things).
For example (from the command line, within the guest):
gem install rubocop
rubocop --only Style/EndOfLine # only check line endings
Or it could be done from within the context of chef itself with something like the following recipe:
chef_gem 'rubocop'
ruby_block 'check line endings' do
block do
# It's probably better to call rubo cop code directly, rather than
# shelling out, but that can be an exercise for the reader ;-)
rubocop_cmd = Mixlib::ShellOut.new(
'rubocop --only Style/EndOfLine',
:cwd => 'dir_to_check'
)
rubocop_cmd.run_command
# Raise an exception if it didn't exit with 0.
rubocop_cmd.error!
end
end
The uncaught exception will cause the chef run to bail out.
Here's a Ruby snippet that you can put in any Chef recipe:
def cookbook_supporting_files(*cookbooks)
cookbooks = cookbooks.map {|name| run_context.cookbook_collection[name]}
cookbooks.flat_map do |cb|
(cb.manifest[:files] + cb.manifest[:templates]) \
.map {|f| ::File.join(cb.root_dir, f['path']) }
end
end
def dos_eol?(f)
::File.open(f, 'rb').read(4096).include? "\r\n"
end
cookbook_supporting_files(cookbook_name).each do |f|
if dos_eol? f
raise "Cookbook template '#{f}' contains CRLF line endings"
end
end
This will check the line endings of the cookbook files and templates that exist in whatever cookbook the above snippet is placed in.
If you want to have the same snippet check other cookbooks, simply replace:
cookbook_supporting_files(cookbook_name)
With the list of the cookbooks you want to check:
cookbook_supporting_files('some_cookbook', 'another_cookbook')

Chef Recipe Compile Error

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}

Resources