Levvel Blog - Levvel Up Your DevOps Game With Chef Custom Ohai Plugins

Levvel Up Your DevOps Game With Chef Custom Ohai Plugins

Chef is one of my favorite DevOps technologies. Besides having an easy Ruby-based DSL, a solid testing framework, great reporting/compliance tools, and tons of community support in the form of community cookbooks, Chef is really flexible and extendible.

Assumptions: You have ChefDk installed along with Vagrant and VirtualBox. If you’re going to be slinging Chef code, I’d recommend starting with those, preferably on a **nix/MacOSX distro although Windows is coming into the fray.

I want to talk about one of the more interesting and unfortunately not well-documented parts of Chef: Custom Ohai plugins.

Ohai is a tool included in Chef that is used to detect attributes on a node (VM/server), and then provides these attributes to the chef-client at the start of every chef-client run. Ohai is required by the chef-client and must be present on a node. Ohai attributes include all the nitty-gritty details SysAdmins love about hardware/software. But what if our compliance officers or tech leads want to collect more information and make that globally available and query-able via the Chef Server?

Well we can write custom Ohai plugins for pretty much anything and then load that into Ohai’s list of attributes.

Let’s start of with a fairly standard cookbook. This cookbook will install tCServer and it’s dependencies on a local Vagrant VM.

Clone this repository:

$ git clone git@github.com:xacaxulu/tcserver-cookbook-1.git

CD into it and run:

$ chef exec kitchen converge

Once this converges, or runs the cookbooks instructions on the newly minted VM, we’ll see that a JDK was installed, and that a tcserver instance called ‘myserver’ was instantiated and started as root. Let’s login to our Vagrant vm and double check:

$ chef exec kitchen login

Using the tcruntime-ctl utility, we can check the status of a given instance.

$ sudo su – root
$ /opt/vmware/vfabric-tc-server-standard/myserver/bin/tcruntime-ctl.sh status

Now we see some cool output from the instance.

[root@default-centos-66 ~]# /opt/vmware/vfabric-tc-server-standard/myserver/bin/tcruntime-ctl.sh status
Instance name:         myserver
Runtime version:       7.0.50.C.RELEASE
tc Runtime Base:       /opt/vmware/vfabric-tc-server-standard/myserver
Status:                RUNNING as PID=3836

But what we want to do is capture this data and access or retrieve it later as an Ohai attribute.

ENTER THE CUSTOM OHAI PLUGIN

We can create an Ohai plugin that parses the output of such a command and then loads it into Ohai, making it available for any number of applications within your organization.

First we’ll need to make a few additions to the cookbook you’ve just cloned down.

In metadata.rb, you’ll need to add a line that tells our VM that it can’t run without the ohai cookbook:

depends 'ohai'

In your recipes/default.rb you’ll need to add the following at the bottom:

include_recipe 'ohai'
ohai 'reload_tcserver' do
  plugin 'tcserver'
  action :nothing
end

cookbook_file '/etc/chef/ohai_plugins/tcserver.rb' do
  source 'plugins/tcserver.rb'
  action :create
  owner 'root'
  group 'root'
  mode 0644
  notifies :reload, 'ohai[reload_tcserver]', :immediately
end

If you have a __Berksfile __(and you should definitely consider using Berkshelf as your dependency manager) , add a this line as well.

cookbook 'ohai'

SO WHAT DOES ALL THIS DO?

Well, in recipes/default.rb, we put in an include_recipe statement that will load and run the “ohai” cookbook, laying down the necessary prerequisites for us to make a working custom plugin. Then we added 2 resources, an oha resource that will reload the data when the cookbook_file resource writes our plugin file to the desired node at /etc/chef/ohai_plugins/tcserver.rb.

We’ve got everything set up except for the actual Ohai plugin itself.

Create the directories and plugin file in the root of your cookbook as follows:

$ mkdir -p files/default/plugins
$ touch files/default/plugins/tcserver.rb

Now files/default/plugins/tcserver.rb will be empty but we’re going to add the code necessary to instantiate a new Ohai plugin, run a command to get the tcserver running instance status, then parse the output of that command and load it as a readable Ohai attribute.

Ohai.plugin(:Tcserver) do
  provides 'tcserver'
  collect_data do
    tcserver Mash.new unless tcserver
    inst = []
    instances = []
    # this allows loading data for multiple instances
    # assuming a common Dir where you store instances
    filenames = Dir['/opt/vmware/vfabric-tc-server-standard/my*']

    filenames.each do |file|
      cmd = Mixlib::ShellOut.new("#{file}/bin/tcruntime-ctl.sh status", user: 'root').run_command.stdout.chomp.split("\n")
      cmd.each { |i| i.downcase!.gsub!(/\s+/, ' ').gsub!(': ', ':').gsub!(/\s/,'_') }
      instances << cmd.map { |i|i.split(':') }.flatten
    end

  instances.each { |i| inst << Hash[*i] }
    tcserver['instances'] = inst.reverse
  end
end

You can cut and paste this and it should work just fine, but I’d like to explain the Ohai plugin just a bit so you know why things are here.

Ohai.plugin(:Tcserver) is necessary to name and locate the plugin. I know the capitalization thing is weird and misleading for Rubyists but it’s correct in Chef-land.

The method provides is necessary to define an attribute, which we name the same as the plugin, ‘tcserver’. *collect_data *is a block of Ruby code that is called by Ohai when it runs and is where the magic happens. Inside this block we instantiate the all-important Mash.new object which must be instantiated then used to contain all the data we come up with.

The rest of the code is really specific to the task you have at hand. In my case, I needed to grab the output of a command, which is why I used the Mixlib::ShellOut module. Mixlib::ShellOut is super handy for getting command line info from within the Chef paradigm.

Outside of that, you’re free to parse, smash, transform, etc. your data any way you like it. I prefer to keep the output looking like the rest of Ohai’s output (JSON hash) but it’s up to you.

Now we can run a chef exec kitchen converge again. After a successful converge, we’ll log into the running VM with a chef exec kitchen login. Then we’ll invoke Ohai from the command line and tell it to check if our plugin is working.

$ chef exec kitchen converge
$ chef exec kitchen login
$ sudo ohai –d /etc/chef/ohai_plugins/

At the end of the Ohai output we see a JSON hash of the information that we asked for and parsed in the Ohai plugin located in files/default/plugins/tcserver.rb.

In this simple way, we can collect, parse and store any kind of data that can be accessed from the command line in a custom Ohai plugin, making this a really powerful part of the Chef ecosystem.

If you’re interested in expanding your team’s knowledge on how to use Chef more efficiently, or if you’d like an in-depth review of your cookbooks, CI/CD processes and overall DevOps capabilities, we here at Levvel would be excited to begin that conversation with you.

James Denman

VP of Technology

Related Posts