Ruby gems in recipes

I require a gem; tiny_tds in a recipe for it to run. I have installed the gem on my local using gem install tiny_tds and the chef zero works fine.
How do I make it dependency proof as a cookbook to be run on a node?

1 Like

Unfortunately there isn’t a simple answer. Or rather there is, add gem 'tiny_tds' to your cookbook’s metadata.rb, but it probably won’t work. That system works great for pure-Ruby gems, but tiny_tds is a C-based extension library so to install it you need a compiler, Ruby headers, the tds library and all its headers, etc. The next best thing is probably to use the chef_gem resource to install it an compile time, but you’ll also need to ensure all those other dependencies are also installed at compile time (the build-essential cookbook can help with some of that). You’ll also need to make sure you don’t try to require the library before compile time, meaning you can’t use it at the base level of custom resources or libraries (though you can still use it in those kinds of things, just requires lazy loading to delay the usage until after it is installed). I would recommend looking very hard to see if there is a non-ext library available that will do similar things, or consider deploying the gem outside of Chef (possibly via a binary gem installer or custom system package).

I found that TinyTDS is available and included in a cookbook in the supermarket (cookbook ‘sql_server’, ‘= 1.0.4’) as per the readme. It doesn’t find mention in the latest cookbook version’s Readme.
I checked the source code on github but I guess the sourcecode is only for the latest version. I don’t find TinyTDS anywhere, checked all files. This does show that it is possible and someone did it in a cookbook before.

The install of that gem as part of the cookbook was removed long ago. But https://github.com/chef-cookbooks/sql_server/blob/v2.4.0/recipes/client.rb#L45

I am curious to know how did it work in the sql_server cookbook earlier. I tried to put the same lines: Is it because I am running chef client locally and berks and metadata is not read in this manner?

gem_package "tiny_tds" do
  gem_binary '/opt/chef/embedded/bin/gem'
  action :install
end

But This one doesn't work

Installing Cookbook Gems:
Compiling Cookbooks...

Recipe Compile Error in C:/Users/testuser/.chef/local-mode-cache/cache/cookbooks/dbcookbook/recipes/default.rb

LoadError

cannot load such file -- tiny_tds

System Info:

chef_version=13.6.4
platform=windows
platform_version=6.3.9600
ruby=ruby 2.4.2p198 (2017-09-14 revision 59899) [x64-mingw32]
program_name=C:/opscode/chef/bin/chef-client
executable=C:/opscode/chef/bin/chef-client

Running handlers:
[2018-01-03T10:55:30+08:00] ERROR: Running exception handlers
[2018-01-03T10:55:30+08:00] ERROR: Running exception handlers
Running handlers complete
[2018-01-03T10:55:30+08:00] ERROR: Exception handlers complete
[2018-01-03T10:55:30+08:00] ERROR: Exception handlers complete
Chef Client failed. 0 resources updated in 03 seconds
[2018-01-03T10:55:30+08:00] FATAL: Stacktrace dumped to C:/Users/testuser/.chef/local-mode-cache/cache/chef-stacktrace
.out
[2018-01-03T10:55:30+08:00] FATAL: Stacktrace dumped to C:/Users/testuser/.chef/local-mode-cache/cache/chef-stacktrace
.out
[2018-01-03T10:55:30+08:00] FATAL: Please provide the contents of the stacktrace.out file if you file a bug report
[2018-01-03T10:55:30+08:00] FATAL: Please provide the contents of the stacktrace.out file if you file a bug report
[2018-01-03T10:55:30+08:00] FATAL: LoadError: cannot load such file -- tiny_tds
[2018-01-03T10:55:30+08:00] FATAL: LoadError: cannot load such file -- tiny_tds

why I am so bent up on requiring tiny_tds is, I can do this: with tiny_tds but not anyway else:
if you can’t help me find another way to do selects and loop through them. I will really appreciate it.

require 'tiny_tds'    
servername = 'testserver'
login = node['autosqlpatch2012']['login']
pswd = node['autosqlpatch2012']['password']
     
client = TinyTds::Client.new username: "#{login}" , password: "#{pswd}",  
host: "#{servername}", port: 1433,  
database: 'master'
#Get all Databases and loop through them to take their backups.
results = client.execute("SELECT name from sys.databases where name not in ('tempdb')")  
results.each do |row|  
    check_command = 'Invoke-Sqlcmd -Query "select datediff(hour, MAX(msdb.dbo.backupset.backup_finish_date), getdate()) as col1 FROM   msdb.dbo.backupmediafamily  
    INNER JOIN msdb.dbo.backupset' +  ' ON msdb.dbo.backupmediafamily.media_set_id = msdb.dbo.backupset.media_set_id' + ' WHERE  msdb..backupset.type = \'D\' 
    and database_name= \'' + "#{row['name']}" + '\' GROUP BY  msdb.dbo.backupset.database_name" -ServerInstance ' + "#{servername}"


    backup_command = 'Backup-SqlDatabase -ServerInstance ' + "#{servername}"  + ' -Database ' + "#{row['name']}" +' -BackupAction Database'
    powershell_script 'Backup DB of ' + "#{row['name']}" do
    code <<-EOH
    #{backup_command}

    EOH

Hi,

Think about it like a Hen-Egg problem. When chef runs, one of the first steps for it is to load the libraries. If you require a library in there, that is not yet installed, you will of course get a LoadError. That is why Chef introduced gem dependencies in the metadata.rb [1]. This would install the gem first and only then load the libraries, so it could solve your problem. However, since the gem requires compiling native C libraries you will need all these dependencies already installed on your system.
There are ways around this but it’s not so simple. The docker cookbook did it for example in older versions: https://github.com/chef-cookbooks/docker/blob/v2.5.9/libraries/_autoload.rb

[1] https://docs.chef.io/config_rb_metadata.html