Ruby code on Chef as a "ruby_block" not working - ruby

I have a question for the Ruby and Chef hackers.
I have very limited knowledge of Chef and even less on Ruby programming language, however, I need to implement on Chef (chef-solo) something similar to "augeas" (which works with Puppet, but here I need a solution for Chef).
I got the example code below but it's not working and I am now for a few days trying to figure out what is wrong.
Basically I need to be able to select specific strings in a text file and modify these values. I could use sed but perhaps I can do it in a more elegant way using the ruby_block from Chef.
Please let me know what can be possibly wrong with the code below. Why is my /etc/hosts not being updated with new values?
Always when I re-run chef-solo, I get the following error:
NoMethodError
-------------
undefined method `chef' for Chef::Resource::RubyBlock
Thanks for your help.
Follows my default.rb file:
ruby_block "edit etc hosts" do
block do
rc = Chef::Util::FileEdit.new("/etc/hosts")
rc.search_file_replace_line(
/^127\.0\.0\.1 localhost$/,
"127.0.0.1 #{new_fqdn} #{new_hostname} localhost"
)
rc.write_file
end
end

Add this line as the first line of your ruby block:
require 'chef/util/file_edit'
According to your case, you should use the cookbook hostsfile:
hostsfile_entry '127.0.0.1' do
hostname new_hostname
aliases [new_fqdn]
comment 'Append by Recipe X'
action :append
end

It shouldn't be too hard to get Augeas to work with Chef. Augeas is a C library, and Puppet simply uses its Ruby bindings. You just need to make use of these bindings in Chef.
There is a PoC Augeas resource provider for Chef here: https://github.com/craigtracey/augeas.
Note: http://lists.opscode.com/sympa/arc/chef/2013-02/msg00337.html mentions Augeas integration into Chef, but apparently the participants misunderstand Augeas as they mention idempotency issues and deltas. Most uses of Augeas don't lead to managing deltas, but desired states.

Related

chef - how to get path of cookbook in a recipe

From default.rb, I want to access the relative path of the cookbook.
I tried doing:
print "cookbook path: " + run_context.cookbook_collection[cookbook_name].root_dir
but I get
TypeError
---------
no implicit conversion of nil into String
I tried replacing cookbook_name with my cookbook name, but get the same error.
Any help would be appreciated.
This is not a supported API, we do not offer it in an official capacity. Any use of such APIs is at your own risk and will break in the future so you should be comfortable enough with reading the code to find things yourself.
The following worked for me: Chef::Config[:cookbook_path]

Print vars with Test Kitchen - Chef

I'm working with Chef and Test Kitchen, I have some problems installing Apache 2.2 and I was reading the cookbook with my recipe (https://github.com/sous-chefs/apache2/blob/master/recipes/default.rb), I would like to know how this recipe is working when installed Apache and I want print some variables when I launch my recipe:
node['platform_family']
node['platform_version']
An example from Chef official doc:
Chef::Log.fatal('You did not accept the license (set node["splunk"]["accept_license"] to true)')
But I write in my recipe:
Chef::Log.info('PERSONAL-LOG,node["platform_family"]')
I get the log, but exactly with the same text I write, I cant concatenate or call the var directly.
Can someone help me?
Its a case of ruby string interpolation, please use it as:-
Chef::Log.info("PERSONAL-LOG, #{node['platform_family']}")
http://ruby-for-beginners.rubymonstas.org/bonus/string_interpolation.html

Using File::read in a provider's default.rb in Chef

I am trying to create an LWRP that will call the resource that is defined within itself. My cookbook's structure is as follows:
In the machine cookbook's provider, I have a code snippet as follows:
require 'chef/provisioning' # driver for creating machines
require '::File'
def get_environment_json
##environment_template = JSON.parse(File::read(new_resource.template_path + "environment.json"))
return ##environment_template
end
The code is only trying to read a json file and I am using File::read for it.
I keep getting an error as follows:
LoadError
cannot load such file -- ::File
Does anyone know how I can use File::read inside my LWRP's provider?
OK, so the prior two answers are both half right. You have two problems.
First, you can't require ::File as it's already part of Ruby. This is the cause of your error.
Second, if you call File.read you will grab Chef's File not ruby's. You need to do a ::File.read to use Ruby's File class.
require '::File'
Is incorrect and is causing the LoadError. Delete this line. You don't need it. File is part of the Ruby core and doesn't need to be required.
To further explain, the string argument to require represents the file name of the library you want to load. So, it should look like require "file", or require "rack/utils".
It happens becuase Chef already has a file resource. We have to use the Ruby File class in a recipe.We use ::File to use the Ruby File class to fix this issue. For example:
execute 'apt-get-update' do
command 'apt-get update'
ignore_failure true
only_if { apt_installed? }
not_if { ::File.exist?('/var/lib/apt/periodic/update-success-stamp') }
end
Source: https://docs.chef.io/ruby.html#ruby-class

List all the declared packages in chef

I'm working on a infrastructure where some servers don't have access to the internet, so I have to push the packages to the local repo before declaring them to be installed on Chef.
However we've been on a situation where Chef failed to install a package since the package wasn't there on some boxes and it has been successful on some other boxes.
What I want to do is to run a Ruby/RSpec test before applying Chef config on the nodes to make sure the packages declared on the recipes do actually exist on the repo.
In order to do that I need to be able to list all the packages exists in the our recipes.
My question is: Is there anyway to list all the declared packages in Chef? I had a quick look at Chef::Platform and ChefSpec but unfortunately couldn't find anything useful to my problem.
Do you have any idea where is the best place to look at?
If you use ChefSpec you can find all the packages by calling chef_run.find_resources(:package) inside some test. See the source code. Like this:
require 'chefspec'
describe 'example::default' do
let(:chef_run) { ChefSpec::Runner.new.converge(described_recipe) }
it 'does something' do
chef_run.find_resources(:package)...
end
end
You could install one or more of the community ohai plugins. For example the following will return information about installed sofware:
debian
Redhat
windows
Once the plugins are enabled they will add additional node attributes that will be searchable from chef-server.

ChefSpec should not test included recipe

I have built a cookbook for installing Jenkins CI. It uses the key and repository resources from the yum cookbook, so I end up with the following recipe:
yum_key "RPM-GPG-KEY-jenkins" do
url "http://pkg.jenkins-ci.org/redhat/jenkins-ci.org.key"
action :add
end
yum_repository "jenkins" do
description "Jenkins-CI 3rd party repository"
url "http://pkg.jenkins-ci.org/redhat"
key "RPM-GPG-KEY-jenkins"
action :add
end
When I include this recipe in another recipe:
include_recipe 'sp_jenkins::default'
and I test this with the following ChefSpec test
it 'includes the `sp_jenkins::default` recipe' do
expect(chef_run).to include_recipe('sp_jenkins::install')
end
my ChefSpec test fails with the following output:
NameError:
Cannot find a resource for yum_key on chefspec version 0.6.1
(I'm not sure why it says version 0.6.1, gem list tells me it's using 3.0.2)
The sp_jenkins cookbook does depend on the yum cookbook (metadata.rb), and runs fine, however, the cookbook I'm currently writing does not depend on the yum cookbook and therefore doesn't have the yum_key and yum_repository methods available.
Is there a way to prevent ChefSpec from 'descending' into included recipes/cookbooks and just test the current cookbook?
Ohai! Julian is correct - ChefSpec actually does a Chef Solo run in memory on your local machine. It rewrites the provider actions to be a noop, but creates a registry of all the actions taken (including those that would be taken if notifications were executed).
So just like you need the yum cookbook to converge this recipe on a real node, you need it to converge during your unit tests with ChefSpec. The easiest way to accomplish this is by using the Berkshelf or Librarian resolvers. To use the Berkshelf resolver, simply require 'chefspec/berkshelf' after requiring chefspec:
# spec_helper.rb
require 'chefspec'
require 'chefspec/berkshelf'
If you have Berkshelf installed on your system, it will pull all the cookbooks into a temporary directory and run ChefSpec for you.
You may also want to take a look at Strainer, which aims to solve a similar problem.
On a somewhat unrelated note, I am working on a fairly large refactor to the Jenkins cookbook that may better suit your needs.
Sources:
I wrote it...
No, there's no way to prevent it from descending, because it's trying to converge an entire Chef run in memory.
However, if you use the Berkshelf functionality in ChefSpec, the Berkshelf dependency resolver will feed all dependent cookbooks to the in-memory Chef run, and you'll be golden.
It is absolutely valid to expect to test your cookbook in isolation, and not include other projects' code into the scope of your tests. Unfortunately there appears to be no supported, "clean" way to do this, that I can find. I was able to achieve this, but it comes at a price.
To use this technique, do not require 'chefspec/berkshelf' anywhere in your test code, only chefspec itself, as you are intentionally not gathering other cookbook source. Here is a template of my working test module (not my complete test code, as I have omitted RSpec config options):
describe 'mycookbook::recipe' do
let(:chef_run) do
ChefSpec::SoloRunner.new(platform: 'x', version: 'x') {
# ...
}.converge(described_recipe)
end
before :each do
allow_any_instance_of(Chef::RunContext::CookbookCompiler).to receive(:cookbook_order) do
Chef::Log.debug 'Attempt to source external cookbooks blocked'
[described_cookbook]
end
allow_any_instance_of(Chef::Recipe).to receive(:include_recipe) do |recipe|
Chef::Log.debug "Attempt to include #{recipe} blocked"
end
end
it 'works' do
# ...
end
end
You need both of these in your before. The one I had to work for is the intercept of the :cookbook_order method. I had to drill down into the Chef internals to discover this. Keep in mind, this worked for me using Chef 14, but there is no guarantee that this will be future-safe. After upgrading Chef you might have to find another solution, if the implementation of CookbookCompiler ever changes. (The intercept of Chef::Recipe.include_recipe however is a supported API and therefore should be at least somewhat future-safe.)
And, I mention that this comes at a price. (Other than using an unsupported hack!) You will not be able to do any expects for your recipe or attribute includes, except within your own cookbook. A test case like this will fail, because the recipe can't actually be included, as you are preventing that:
it 'includes othercookbook::recipe' do
expect_any_instance_of(Chef::Recipe).to receive(:include_recipe).with('othercookbook::recipe')
end
Also, you must now satisfy in your before blocks all attributes and other preconditions that might otherwise be fulfilled by other recipes in your run list. So you may be signing yourself up for considerable pain by doing this. But, once you have finished, you will have much less brittle tests. (Although to achieve 100% purity regarding external dependencies, you must also surrender fauxhai, which will be even more painful.)

Resources