How do I install dependencies for a chef handler? - ruby

I am trying to install a chef handler via the chef_handler lwrp. This handler (chef-handler-email) comes bundled in a gem. I am trying to install the gem then turn on the handler from within a single recipe that looks like:
chef_gem "chef-handler-mail"
chef_handler "MailHandler" do
source 'chef/handler/mail'
arguments :to_address => "root"
action :nothing
supports :exception => true, :report => false
end.run_action(:enable)
This works fine if the gem is already installed. However, if the Gem is not already installed I receive this error:
[2012-12-09T20:47:56-05:00] FATAL: LoadError: chef_handler[MailHandler] (chef_handler::email line 13) had an error: LoadError: no such file to load -- chef/handler/mail.rb
It appears as though the chef_handler resource is trying to load the handler before chef_gem has executed and installed the gem for the handler. I can obviously do this in a two step manual process where I have a separate recipe for installing the gem, then flip over to another recipe that configures the handler, but I'm hoping to avoid multi-step manual processes. Can it be done via single recipe?

I have a similar recipe for chef minitest-chef-handler:
chef_gem 'minitest'
chef_gem 'minitest-chef-handler'
require 'rubygems'
require 'minitest-chef-handler'
[... some unrelated code ...]
chef_handler "MiniTest::Chef::Handler" do
source "minitest-chef-handler"
arguments :verbose => true
action :nothing
end.run_action( :enable )
Try requiring your gem before creating chef_handler resource, or may be source should be different...

The #run_action call causes the chef_handler resource to be run immediately at "compile" phase while the chef_gem resource is run during the "execute" phase as normally.
So also the gem needs to be installed at compile phase. And it seems that a require statement is also needed (as suggested in another answer) for Chef to load the gem.
chef_gem 'chef-handler-mail' do
action :nothing
end.run_action(:install)
require 'chef/handler/mail'
chef_handler 'MailHandler' do
source 'chef/handler/mail'
# ... other attributes
action :nothing
end.run_action(:enable)

Related

How would I include and configure a ruby library in a Chef recipe?

I want to include the Diplomat gem in my Chef cookbook so that I can perform Consul variable lookups in .erb templates.
I need to configure the Consul URL:
irb(main):015:0> require 'diplomat'
irb(main):016:0> Diplomat.configure do |config|
irb(main):017:1* config.url = "consulurl:80"
irb(main):018:1> end
Set a variable as the URL path:
irb(main):020:0> kv_path = "path/to/variable"
=> "path/to/variable"
And finally, perform the lookup within the templates.
irb(main):022:0> foo = Diplomat::Kv.get(kv_path + '/test_foo_123')
=> "bar"
Where in the cookbook would I need to write the configuration code above such that I can perform variable lookups within .erb templates?
You want to use the chef_gem resource, but make sure to run it during the compile phase:
chef_gem 'diplomat' do
action :nothing
compile_time false
end.run_action(:install)
require 'diplomat'
Installing gems with Chef is relatively painless. Most of the time, you can use the gem_package resource, which behaves very similarly to the native package resource:
gem_package 'httparty'
You can even specify the gem version to install:
gem_package 'httparty' do
version '0.12.0'
end
You may have also seen the chef_gem resource. What's the difference?
The chef_gem and gem_package resources are both used to install Ruby
gems. For any machine on which the chef-client is installed, there are
two instances of Ruby. One is the standard, system-wide instance of
Ruby and the other is a dedicated instance that is available only to
the chef-client. Use the chef_gem resource to install gems into the
instance of Ruby that is dedicated to the chef-client. Use the
gem_package resource to install all other gems (i.e. install gems
system-wide).
source: https://sethvargo.com/using-gems-with-chef/

Local ruby gems fail to install on Windows 2008R2: Errno::EADDRNOTAVAIL

Edit: Bug in rubygems 2.4.4. (fixed in 2.4.5)
I'm having trouble installing gems with the embedded ruby that comes with Chef Client v12.2.1, using the chef_gem resource:
Mixlib::ShellOut::ShellCommandFailed
------------------------------------
chef_gem[zabbixapi] (generic_server_win::libzabbix-deps line 6) had an error: Mixlib::ShellOut::ShellCommand Failed: Expected process to exit with [0], but received '1'
---- Begin output of C:/opscode/chef/embedded/bin/gem install c:/chef/cache/zabbixapi-2.2.2.gem -q --no-rdoc --no-ri -v "2.2.2" ----
STDOUT:
STDERR: ERROR: While executing gem ... (Errno::EADDRNOTAVAIL)
The requested address is not valid in its context. - connect(2)
---- End output of C:/opscode/chef/embedded/bin/gem install c:/chef/cache/zabbixapi-2.2.2.gem -q --no-rdoc --no-ri -v "2.2.2" ----
Ran C:/opscode/chef/embedded/bin/gem install c:/chef/cache/zabbixapi-2.2.2.gem -q --no-rdoc --no-ri -v "2.2.2" returned 1
Also:
Same result when running the command manually on the command-line as an Administrator with the --local option
Same error occurs for other gems.
The servers that I'm trying to run this on have no internet access
I'm unable to reproduce the problem on a newly installed test machine (with internet access)
The version of ruby used is 2.0.0: ruby 2.0.0p451 (2014-02-24) [i386-mingw32]
Here is my Chef recipe:
cookbook_file "#{Chef::Config[:file_cache_path]}/zabbixapi-2.2.2.gem" do
source 'zabbixapi-2.2.2.gem'
end
chef_gem "zabbixapi" do
source "#{Chef::Config[:file_cache_path]}/zabbixapi-2.2.2.gem"
end
You're problem is that the chef_gem resource is special in the way it enforce the use of the embedded ruby in chef installation and that it is run before convergence to allow the gem to be required in recipes. documentation about it here
To use a local source deployed with chef you have to ensure the file is present before, if not the gem command will try to download it (and fail with no internet access).
To ensure your local file is present before the chef_gem call you have to ensure the cookbook_file resource is called at compile time with this trick
in your specific case this should do:
cookbook_file "#{Chef::Config[:file_cache_path]}/zabbixapi-2.2.2.gem" do
action :nothing
source 'zabbixapi-2.2.2.gem'
end.run_action(:create)
chef_gem "zabbixapi" do
source "#{Chef::Config[:file_cache_path]}/zabbixapi-2.2.2.gem"
end
The action nothing in the resource is to avoid having it called twice (once in compile phase and once in converge phase, even if the later won't have any impact, it's better to save time no evaluating it twice) then calling the action :create at end of the definition will trigger the action in the compile phase and the file will be present for the chef_gem call later.

Install gem from S3 in OpsWorks Chef recipe

I need net-ssh and net-scp as part of a custom OpsWorks chef recipe.
Getting occasional failures from rubygems.org failing to provide the gems, so I would like to host them myself on S3.
chef_gem has the 'source' argument, but it seems to require the local file exist prior to chef being started (so I can't download the file immediately before chef_gem using remote_file)
$gemSsh = "#{Chef::Config[:file_cache_path]}/net-ssh.gem"
$gemScp = "#{Chef::Config[:file_cache_path]}/net-scp.gem"
remote_file $gemSsh do
source "https://s3-us-west-2.amazonaws.com/****/net-ssh-2.9.1.gem"
action :nothing
end.run_action(:create)
remote_file $gemScp do
source "https://s3-us-west-2.amazonaws.com/****/net-scp-1.2.1.gem"
action :nothing
end.run_action(:create)
chef_gem "net-ssh" do
action :nothing
source $gemSsh
end.run_action(:install)
chef_gem "net-scp" do
action :nothing
source $gemScp
end.run_action(:install)
(Note: the run_action(:install) is based on comments here https://tickets.opscode.com/browse/CHEF-4843)
This fails with the following error:
NoMethodError
-------------
undefined method `name' for "/var/lib/aws/opsworks/cache.stage2/net-scp.gem":String
Cookbook Trace:
---------------
/var/lib/aws/opsworks/cache.stage2/cookbooks/opsworks_commons/libraries/monkey_patch_rubygems_provider.rb:55:in `install'
/var/lib/aws/opsworks/cache.stage2/cookbooks/****/recipes/default.rb:24:in `from_file'
You can use "--local" flag which is provided by gem install (You can find other options with gem install --help).
Basic command would be somethig like gem install --local path_to_gem/filename.gem. So your recipe in that case would be:
....
chef_gem "net-ssh" do
action :nothing
options("--local #{$gemSsh}")
end.run_action(:install)
chef_gem "net-scp" do
action :nothing
options("--local #{$gemScp}")
end.run_action(:install)

chef gem_package not using the right binary

I'm trying to install the gems "sensu_plugin" and "mixlib-shellout", on a windows server 2008r2, from a recipe. In this last one, I have the following code :
%w{ sensu-plugin mixlib-shellout }.each do |gem_plugin|
gem_package gem_plugin do
gem_binary("C:/opt/sensu/embedded/bin/gem")
options("--no-rdoc --no-ri")
action :install
end
end
Yet, when running the recipe on my machine, I receive the error "ERROR: While executing gem ... (Errno::ENOENT) No such file or directory - U:/". What I found out is that whenever I try installing a gem package using the gem binary located in the chef-client directory (C:\opscode\chef\embedded\bin\gem), it raised this last error.
The thing is that the gem I wish to use to installed the gem packages is actually located in C:/opt/sensu/embedded/bin/gem, which I have declared in the gem_binary option.
In the end it looks like chef is ignoring the gem_binary option and trying to install it from its own gem binary.
I can't see what's wrong with this config. Is the gem_binary really indicating what binary to use?

After installing a gem within a script, how do I load the gem?

I have a small Ruby script that I'm writing to automate the preparation of a development environment on local machines. Because I can't be certain that the rubyzip2 library is present on all of the machines, I'm having the script install it when needed.
Currently, my script is doing the following:
begin
require 'zip/zip'
rescue LoadError
system("gem install rubyzip2")
end
Once the gem has been installed, the script continues execution; however, the gem hasn't been loaded so all code requiring rubyzip2 halts the execution.
How do I load the gem into memory so that the script can continue running after installation?
Instead of doing require 'thegem' and rescuing error, you should check the gem availability before, and then, if needed, install it. After, you can require it.
Take a look at this post for the gem availability
Or this post
EDIT
After installation, you need to clear gem paths if you don't want to reload your script.
You could achieve this with this method :
Gem.clear_paths
There are already answered questions here
So your code should looks like this ( for example ) :
begin
gem "rubyzip2"
rescue LoadError
system("gem install rubyzip2")
Gem.clear_paths
end
require 'zip/zip'
With bundler version higher than 1.10 (to update just run gem install bundler) you can use its new 'inline' feature as described here.
require 'bundler/inline'
gemfile(true) do
source 'https://rubygems.org'
gem 'catpix'
end
puts Catpix::VERSION
First parameter of gemfile method is whether gems that aren't already installed on the user's system should be installed.
Use Bundler with your application/library. This will allow users to run bundle and all the gems will be fetched and ready for you to use.
Ok so you may want to use Bundler and set up a Gemfile then have bundler do a bundle install, bundler will fetch out all the gems and install them if it is not already installed and you can then require all the gems in the gem file. Read the documentation in the link for more information.
But what you are looking to do specifically in your question is to use the retry keyword. This keyword will retry the loop after the rescue was called.
So if you require the gem and it fails and the Load Error Exception is called. The Begin Block will rescue, the system call will install the gem, then it will retry and require the gem. Just cautious because this may lead to an infinite loop unless you want to set up a condition to maybe retry it only once.
begin
require 'zip/zip'
rescue LoadError
system("gem install rubyzip2")
retry
end

Resources