Chapter 4. Extending Ohai

As we saw in Chapter 3, during the initial stage of the Chef run process (described in Get Configuration Data) Chef runs the Ohai tool to build up a collection of data about the node, which is saved on the Chef server as part of the node object. Ohai is installed as part of the Chef installation process and must be present on a node for chef-client or chef-solo to function correctly.

The core Ohai tool is driven by its flexible and powerful plugin interface. Out of the box, Ohai ships with a number of useful plugins that collect data on various generic aspects of the underlying system, such as the hardware, operating system, and networking configuration.

The power of Ohai’s plugin framework, however, comes from the fact that it allows us to define our own plugins to augment the information stored about our nodes on the Chef server. Say, for example, we want our nodes to define an attribute stating whether they are virtual machines (VMs) or physical servers, so that our recipes only install VM-specific packages on nodes that need them. Perhaps we want to add a node attribute that fetches the physical location of a node from an asset management system so that we can make use of this logic in our recipe code. Out of the box, Ohai does not collect any of this data—but we can easily create our own plugins that do.

Much in the same way as the Chef Recipe domain-specific language (DSL) provides us with a number of useful resource blocks to abstract away the complexities of the underlying Ruby code, Ohai defines its own Ruby-based DSL to simplify the process of writing plugins. In this chapter, we’ll explore this DSL by analyzing the “skeleton code” necessary to create a working plugin, and how to test your plugins and integrate them into Chef runs. We’ll then add functionality to our plugin skeleton to explore some of the different features provided by the Ohai DSL before looking at some of the actual plugins that ship with Ohai.

Caution

This chapter assumes that you are using Ohai version 7.0 or greater, which was released in April 2014 and included with Chef version 11.20.0 forward. Version 7 of Ohai introduced a new DSL for defining plugins, which supersedes the DSL used in previous Ohai versions.

Introduction

Before we start to look at Ohai’s DSL, let’s take a moment to examine the way in which Chef treats attributes gathered by Ohai and how they are given precedence over attributes defined in recipes, roles, and environments.

Ohai Attribute Collection

When ohai runs, it iterates through all of the plugins it knows about to build up a collection of attributes describing the underlying node, which it then passes to the chef-client process. Because these attributes represent information about the node itself rather than the recipes being executed, Chef needs to ensure that they do not get altered during the course of the Chef run—when we use attributes collected by ohai like node[:fqdn] and node[:platform_version] in our recipes, it wouldn’t be particularly desirable to be able to accidentally switch our node’s platform from centos to debian in the middle of a run. To provide this guarantee that ohai attributes will not be changed during the run, Chef assigns them automatic precedence.

As shown in the previous sidebar, automatic attributes take the absolute highest level of precedence. This means that even if we were to try and override an attribute collected by ohai in our recipe, the ohai version would always “win” because it is at a higher precedence level. This enables ohai to guarantee that the attributes it gives us are immutable during the course of a Chef run, and that (providing the Chef run is successful) they will be saved to the node object on the Chef server exactly as collected by ohai at the start of the run.

The Ohai Source Code

Just as the Chef source code is an extremely useful tool for examining the behavior of Chef, the source code of Ohai—particularly its plugins—is a useful (but optional) aid when working through the material in this chapter.

The Ohai source code is located in a separate repository from that of Chef, and is available on GitHub. To clone a copy of the Ohai source code, run the following command (which requires git to be installed):

$>  git clone https://github.com/opscode/ohai.git

We won’t be examining the under-the-hood implementation of Ohai in as much detail as we did that of chef-client in Tracing a chef-client Run, although interested readers may wish to dive a little deeper into its inner workings. We will, however, be looking at the implementation of Ohai plugins, and the source code of the plugins shipped with Ohai can be a useful reference to augment the examples shown in this chapter. These are located in the lib/ohai/plugins directory in the repository.

Now, on to the code!

Ohai Example 1: Plugin Skeleton

Throughout this chapter we’ll actually be running the example plugin code we create, so at this point I’d recommend creating a directory to place your example plugins in. I’ve used /tmp/ohai_plugins in all of the examples: if you’re on Windows or want to use a different directory path, you’ll need to amend the path used in the examples as appropriate.

Let’s start off by looking at an example of the absolute minimum code necessary to produce a functioning Ohai plugin—it won’t actually add any attributes to our node yet, but it will demonstrate the framework with which we’ll create more functional plugins as the chapter progresses. Paste the code in Example 4-1 into /tmp/ohai_plugins/example1.rb.

Example 4-1. /tmp/ohai_plugins/example1.rb
Ohai.plugin(:Example1) do 1
  provides "awesome_level" 2

  collect_data do 3
    awesome_level 100 4
  end
end
1

This first line tells Ohai what the name of the plugin class will be—under the hood, all Ohai plugins are actually treated as classes that live under the Ohai::NamedPlugin namespace, and Ohai needs to know what the class will be called. The name passed to the Ohai.plugin method (a symbol, because it must be immutable) must be a valid Ruby class name and start with a capital letter. Here we’ve used Example1.

2

Next, we call the provides method with a comma-separated list of all the attributes our plugin will provide. In this example, we’re just creating one attribute called awesome_level.

3

The collect_data block is called by Ohai when it executes each plugin to ask it to collect its data. This block is mandatory: if a collect_data block is not defined in the plugin code, the plugin will not do anything.

4

Inside the collect data block, we’re stating that the value of our awesome_level attribute should be 100. Note that the Ohai DSL does not require us to use the = sign when assigning a value to an attribute—in fact, if you do use an equals sign, the plugin will not work correctly. This variance from normal Ruby behavior is worth remembering because although it makes for shorter plugin code, it can sometimes be confusing when debugging.

This is a nice, simple example of an Ohai plugin, but examples are much more meaningful if they can be observed working in real life. Let’s take a look at the ways in which we can test and run our own Ohai plugins.

Testing and Running Ohai Plugins

There are two ways in which we can try out our custom Ohai plugins. The first is to use Ruby’s irb interactive shell, and the second is to include the plugin during a chef-client run. We’ll look at both of these methods in this section, and when you might want to use them.

Testing Using irb

While Ohai plugins are being developed, running them in the irb Ruby shell is by far the most convenient method. irb allows us to paste lines of Ruby code into a shell prompt, and immediately see the results of each line as it is executed. This functionality allows us to repeatedly run our plugins without the need to perform full Chef runs or alter our node’s Chef configuration.

The irb shell is started by running the irb command in a terminal, and will give you a prompt looking something like this:

irb(main):001:0>

Tip

The exact text shown before the > symbol vary depending on your operating system and Ruby version; in the examples shown in this section I’ve used >> to represent a standard irb prompt.

Let’s look at how we can test our example plugin from Ohai Example 1: Plugin Skeleton using irb. Note that I’ve assumed that you have the example code saved in a file called /tmp/ohai_plugins/example1.rb; if you chose to store your example plugins in a different location, you’ll need to substitute /tmp/ohai_plugins with the path you are using.

In the interactions shown here, each time you see the >> prompt followed by text, this indicates a line of Ruby code that you should paste into your irb shell (don’t copy the >>). The results you should see are shown below each >> line on lines beginning with =>. Where the output of a command is particularly long, I’ve replaced it with <snip>.

Now, let’s try out example1.rb:

$> irb

>> require 'ohai' 1
=> true

>> Ohai::Config[:plugin_path] << '/tmp/ohai_plugins' 2
=> ["/usr/lib64/ruby/gems/1.9.1/gems/ohai-7.0.0/lib/ohai/plugins",
       "/tmp/ohai_plugins"]

>> o = Ohai::System.new 3
=< #<Ohai::System:0x000000045d3028 @plugin_path="", @data={}, <snip>>

>> o.all_plugins 4
=> <snip>

>>  o.attributes_print("awesome_level") 5
 => "100"
1

The first thing we need to do is require the ohai gem so we can make use of its objects and methods.

2

Next, we append the path where we stored our example plugins (/tmp/ohai_plugins) to the [:plugin_path] array stored under the Ohai::Config class. Just as Ruby uses the LOAD_PATH variable to tell it where to locate class definition files, this line tells ohai to look for plugins in our examples directory in addition to the default location. The exact directory paths output by this command may vary depending on the versions of Ruby and ohai you have installed, but the important part is that we see /tmp/ohai_plugins at the end of the array.

3

Now we need to create a new instance of the Ohai::System object, and assign it to a variable called o.

4

Next, we call the all_plugins method of o (our Ohai::System object). This method loads all plugins that it finds under the plugin_path that we set in step 2, and executes their collect_data block. I’ve abbreviated the output of this command as it is extremely verbose, but the eventual result of the command is to populate a class instance variable called @data inside our Ohai::System object with the attributes collected and returned by all the plugins. You can run this step repeatedly (without repeating steps 1–3) to re-execute all Ohai plugins, which is handy when making small tweaks to your plugin code.

5

Finally, we call the attributes_print method of our Ohai::System object, passing the name of the attribute we’re interested in—in this case, it’s the awesome_level attribute we created in our example1.rb plugin. This method searches the @data class instance variable mentioned in step 4 to find the method name we’re looking for, and if everything worked correctly we should see the value "100" returned, just as we set it in our plugin. As with step 4, this step can be run repeatedly to verify correct output when tweaking your plugin code.

The code we pasted into irb here is part of the actual code used by ohai when it’s running as part of a Chef client run. Executing this code in irb allows us to quickly and easily test our plugin code to verify that it works correctly, but how do we use it as part of a real Chef run.

Running Using Chef

Once you’re actually ready to run your plugin for real, you need to tell chef-client (or chef-solo) where your plugins are located so that ohai is able to find them when it runs. This is done by adding the Ohai::Config[:plugin_path] << /location/of/plugins line to your client.rb or solo.rb configuration file— the same variable we modified when testing our plugin example with irb.

When chef-client or chef-solo runs, this configuration will be passed to ohai and your plugin will be executed when ohai calls the all_plugins method we saw in the previous section, as ohai will automatically load any plugins it finds under Ohai::Config[:plugin_path]. You also need to make sure that your Ohai plugins are Cheffed out to the correct location on all of your nodes—to make this process easier, Chef, Inc. provides the ohai cookbook, which will automatically Chef out plugins from the configured locations to your nodes, and dynamically update the chef-client configuration to load and run these plugins.

We’ve looked at a very simple example Ohai plugin and seen how to test it and run it on our nodes, but what happens if we want the attributes provided by our plugin to contain more complex data or behave differently on other operating systems? Let’s look at a slightly more advanced example plugin.

Ohai Example 2: Adding More to the Plugin

In Ohai Example 1: Plugin Skeleton, we looked at a very simple plugin that assigned a string value to an attribute. As with many examples, it served well to demonstrate the concepts involved, but in the real world you’re likely to need to write plugins that work across multiple operating systems, and to store more complex and structured attribute data. So how do we do that? Fortunately for us, the Ohai plugin DSL provides convenient functionality to support both of these cases. Consider the code in Example 4-2, which we’ll paste into /tmp/ohai_plugins/example2.rb.

Example 4-2. /tmp/ohai_plugins/example2.rb
Ohai.plugin(:Example2) do
  provides "awesome" 1

  # Method to initialize object for our attribute
  def create_objects
    # Create a new "Mash" object and assign it to "awesome"
    awesome Mash.new
  end

  # Collect data block with symbol :default
  collect_data(:default) do

    # Call the create_objects method to initialize our "awesome" attribute
    create_objects
    # Assign the value 100 to the :level key of awesome
    awesome[:level] =  100
    # Assign the value "Sriracha" to the :sauce key of awesome
    awesome[:sauce] = "Sriracha"

  end

  # Collect data block with symbol :windows
  collect_data(:windows) do

    # Call the create_objects method to initialize our "awesome" attribute
    create_objects
    # Assign the value 101 to the :level key of awesome
    awesome[:level] =  101
    # Assign the value "Cholula" to the :sauce key of awesome
    awesome[:sauce] = "Cholula"

  end

end
1

This line states that this plugin provides the awesome attribute. This also means that when ohai runs this plugin, the awesome object is in scope for all plugin methods—this is how the collect_data method is able to access an object created in the create_objects method.

I’ve introduced two key new concepts in this plugin example: the Mash object that we’re now using to store our awesome attribute, and the fact that we now have two collect_data blocks, each with a different symbol as its parameter. Let’s examine these one at a time.

The Mash Object

The Mash class, defined in lib/ohai/mash.rb in the repository, is a subclass of Ruby’s built-in Hash class. In our example, we’re using our Mash object awesome to store two related subattributes, level and sauce. This multilevel organizational structure is what allows us to use nested attributes like node[:languages][:ruby][:version] in our recipes—under the hood, ohai collects those attributes into a Mash object.

Mash can mostly be used just as if it were a regular Hash, but there is one important distinction between these classes, which is in fact the reason Mash is used in Chef in the first place. With a standard Hash in Ruby, foo[:bar] and foo["bar"] are actually treated as two different items in the Hash. Try running the following code in irb:

$> irb

>> foo = Hash.new
=> {}

>>foo[:bar] = "hello"
=> "hello"

>>foo["bar"] = "goodbye"
=> "goodbye"

>>foo
=> {:bar=>"hello", "bar"=>"goodbye"}

In this code sample, foo[:bar] has the symbol :bar as its key while foo["bar"] has the string "bar" as its key. To avoid this potentially confusing behavior, the Mash class will treat foo["bar"] and foo[:bar] as the same item by always converting the hash key to a string behind the scenes. Let’s repeat the preceding example in irb using a Mash:

$> irb

>> require 'ohai'

>> foo = Mash.new
=> {}

>>foo[:bar] = "hello"
=> "hello"

>>foo["bar"] = "goodbye"
=> "goodbye"

>>foo
=> {"bar"=>"goodbye"}

As we can see, this time foo[:bar] and foo["bar"] were treated as the same item. In practical terms, you can often treat Mash objects just as if they were standard Hash objects, but it’s useful to be aware of the difference.

Now, let’s move on to the other new concept introduced in Example 4-2: that of having multiple collect_data blocks in our plugin.

Multiple collect_data Methods

As I mentioned in the preamble to this example, in the real world we often want to create Ohai plugins that work on different operating systems. Many of the default Ohai plugins also follow this pattern—for example, the plugin that provides the hostname attribute has to be able to retrieve the hostname from Windows systems as well as Linux systems, and it would not be ideal to have to implement separate plugins for every possible operating system that can run Chef.

Being clever, the folks at Chef, Inc. have provided a neat piece of functionality in ohai to remove the need for this replication of code. Let’s look again at the method definitions we used in our plugin code (Example 4-3).

Example 4-3. Excerpt of /tmp/ohai_plugins/example2.rb
Ohai.plugin(:Example2) do

  ...

  collect_data(:default) do
    ...
  end

  collect_data(:windows) do
    ...
  end

end

We’re actually defining two collect_data blocks here, each with a different symbol as its parameter. This is because ohai allows us to define a collect_data block for each operating system for which we require specific behavior.

The first block with the :default key will be used if no other collect_data block can be found with a more specific symbol for the operating system running the plugin. You may remember that the collect_data block we used in Example 4-1 had no :default symbol—this is because collect_data blocks are implicitly treated as :default if no other symbol has been specified. I’ve added the :default symbol in the this example for clarity, but the plugin will work just as well if the first :default block has no symbol at all.

The second block has the :windows symbol as its parameter. When ohai detects that it is being run on a Windows system, this collect_data method will be executed instead of the :default block. It’s important to remember that within a plugin you can only define one collect_data block for each operating system—ohai will throw an error if you try to define two blocks with the :windows symbol, for example.

Tip

Currently, ohai allows you to specify the following symbols (hence supported operating systems) as parameters to collect_data blocks:

:aix, :darwin, :hpux, :linux, :freebsd, :openbsd, :netbsd,
:solaris2, :windows

Running example2.rb

Now that we’ve examined the new concepts introduced in Example 4-2, let’s run our new plugin in irb on both Linux (Example 4-4) and Windows (Example 4-5) to see it in action.

Example 4-4. Running example2.rb on Linux

$> irb

>> require 'ohai'
=> true

>> Ohai::Config[:plugin_path] << '/tmp/ohai_plugins'
=> ["/usr/lib64/ruby/gems/1.9.1/gems/ohai-7.0.0/lib/ohai/plugins",
       "/tmp/ohai_plugins"]

>> o = Ohai::System.new
=> <snip>

>> o.all_plugins
=> <snip>

>>  o.attributes_print("awesome")
=> "{\n  \"level\": 100,\n  \"sauce\": \"Sriracha\"\n}"

>>  o.attributes_print("awesome/sauce") 1
 => "[\n  \"Sriracha\"\n]"
1

When calling the attributes_print method on a Mash, we use the / character to indicate when we want to “descend” through the Mash. For example, here we’re asking for awesome/sauce, which is the equivalent of awesome[:sauce] if we were referring to this attribute inside our plugin. Note that the attributes_print method includes \n newline characters, as this method is typically used to output “pretty printed” formatted output to a terminal.

Those results were exactly what we expected. Since no collect_data block was defined with the :linux symbol, the collect_data block with the :default symbol was executed, giving us an awesome Mash containing two attributes: level, with the value 100, and sauce, with the value Sriracha. Now let’s try running the same plugin in irb on a Windows machine, as shown in Example 4-5, and see what happens.

Example 4-5. Running example2.rb on Windows
C:\> irb

>> require 'ohai'
=> true

>> Ohai::Config[:plugin_path] << 'C:\tmp\ohai_plugins'
=> ["C:\ruby\gems\1.9.1\gems\ohai-7.0.0\lib\ohai\plugins", "C:\tmp\ohai_plugins"]

>> o = Ohai::System.new
=> <snip>

>> o.all_plugins
=> <snip>

>>  o.attributes_print("awesome")
=> "{\n  \"level\": 101,\n  \"sauce\": \"Cholula\"\n}"

>>  o.attributes_print("awesome/sauce")
 => "[\n  \"Cholula\"\n]"

As we see here, when the plugin is run on a Windows machine, the collect_data block with the :windows symbol is used instead of the :default block. We again get an awesome Mash containing our two attributes, but this time level has the value 101 and sauce has the value Cholula. If you have both Windows and another operating system available for testing, why not try running this plugin on both systems to replicate these results?

With the addition of Mash objects and multiple operating system–specific collect_data methods to our toolbox, we’re now getting to the stage where we can start to write more useful Ohai plugins—but what if you don’t just want to write a single plugin, but rather a family of plugins that provide different “branches” of the same attribute structure? Let’s look at some real examples from the plugins that actually ship with Ohai.

Ohai Example 3: Multilevel Plugins

As we worked through Ohai Example 1: Plugin Skeleton and Ohai Example 2: Adding More to the Plugin, we progressed from a bare-bones Ohai plugin with minimal functionality to a more advanced plugin that implements OS-specific behavior and a nested attribute structure. But how far does that leave us from the functionality contained in the real plugins that Ohai ships with? To finish off this chapter we’re going to look at one final feature of Ohai, its dependency system, to learn how to create a single attribute structure that contains information collected from a variety of related but functionally different tools.

Rather than looking at another canned example to illustrate Ohai plugin dependency, we’re going to look at three plugins that ship with Ohai and provide various attributes that live under the languages attribute, which stores information about any installed programming language tools. You can see an example of the attributes stored under languages in Get Configuration Data.

The first of these plugins is the languages.rb plugin, shown in Example 4-6.

Example 4-6. lib/ohai/plugins/languages.rb
Ohai.plugin(:Languages) do
  provides "languages"

  collect_data do
    languages Mash.new
  end
end

This plugin is actually fairly close to the code we saw in Ohai Example 1: Plugin Skeleton. It defines a single collect_data method, and initializes the top-level languages attribute with a new Mash object. Note that it does not itself set any attributes beyond initializing languages—this is a commonly used pattern in Ohai when defining a plugin that will sit at the “top” of a dependency tree. But how does the languages Mash get populated with attributes that store information on a number of different programming languages? That’s where Ohai’s dependency system comes in.

Let’s now look at the code of the php.rb plugin (shown in Example 4-7, with comments added), which provides attributes related to the PHP programming language.

Example 4-7. lib/ohai/plugins/php.rb
Ohai.plugin(:PHP) do
  provides "languages/php" 1

  depends "languages" 2

  collect_data do

    # Set the output variable to nil
    output = nil

    # Create a new Mash object called php
    php = Mash.new

    # Execute the shell command "php -v" and store the results in
    # the variable so
    so = shell_out("php -v")

    # If the exit code of the command was 0 (i.e., successful)...
    if so.exitstatus == 0

      # Parse the php version command's output to extract the data we care about
      output = /PHP (\S+).+built: ([^)]+)/.match(so.stdout)

      # If the above step produced any results
      if output
        # Set the :version and :builddate keys of our php Mash
        php[:version] = output[1]
        php[:builddate] = output[2]
      end
      # If the php Mash has a :version key, assign the php Mash to the :php key
      # of the languages Mash
      languages[:php] = php if php[:version] 3
    end
  end
end
1

This line tells ohai that this plugin is only providing the php key of the languages Mash. As we saw when requesting a nested ohai attribute in Running example2.rb, the / character is used to indicate different levels of the languages Mash. Essentially, this plugin is stating that it does not provide the entire languages attribute, just a single element of it called php.

2

This line states that this plugin depends on the languages plugin. When Ohai evaluates this line, it looks for a plugin named languages.rb to satisfy the dependency. Any attributes defined in the languages plugin are available to all plugins that depend on it. In this case, this means that the languages Mash we declared in languages.rb is available to php.rb—this allows php.rb to add PHP-specific attributes to the languages Mash, as we’ll see later in the plugin.

3

This line sets the value of the :php key of the languages Mash to the php Mash if there is a non-nil php[:version] key. Note that the languages Mash is actually defined in languages.rb, rather than in this plugin—this is where the Ohai plugin dependency model comes in handy. We can declare separate plugins to provide different elements of the same top-level ohai attribute.

Now let’s look at the code of the perl.rb plugin (shown in Example 4-8, again with comments added), which provides attributes related to the Perl programming language.

Example 4-8. lib/ohai/plugins/perl.rb
Ohai.plugin(:Perl) do
  provides "languages/perl" 1

  depends "languages" 2

  collect_data do

    # Set the output variable to nil
    output = nil

    # Create a new Mash object called perl
    perl = Mash.new

    # Execute the shell command "perl -V:version -V:archname" and store the results in
    # the variable so
    so = shell_out("perl -V:version -V:archname")

    # If the exit code of the command was 0 (i.e., successful)
    if so.exitstatus == 0

      # Parse the perl version command's output to extract the data we care about
      so.stdout.split(/\r?\n/).each do |line|
        case line
        when /^version=\'(.+)\';$/
          perl[:version] = $1
        when /^archname=\'(.+)\';$/
          perl[:archname] = $1
        end
      end
    end

    # If the exit code of the command was 0 (i.e., successful)
    if so.exitstatus == 0
      # Assign the perl Mash to the :perl key of the languages Mash
      languages[:perl] = perl 3
    end
  end
end
1

This line tells ohai that this plugin is only providing the perl key of the languages Mash.

2

This line states that, like php.rb, this plugin depends on the languages plugin.

3

This line sets the value of the :perl key of the languages Mash to the perl Mash. Again, the languages Mash is actually defined in languages.rb rather than in this plugin.

In this example, we see another plugin that also provides part of the languages attribute, but implements entirely different behavior to fetch the data needed to provide the perl key.

These three plugins combine to provide a useful demonstration of the power and flexibility of Ohai’s plugin interface. We see a top-level languages.rb plugin that defines the parent attribute, named languages, and two “dependent” plugins (php.rb and perl.rb) that implement totally separate behavior specific to the programming language tools they describe, and set specific and separate elements of the parent languages attribute.

When we actually run ohai and look at the results, this hierarchical plugin dependency is hidden from us—what we see is a single languages attribute containing all of the information set by the various plugins that depend on languages.

To see this behavior in action, let’s run ohai in irb again, as illustrated in Example 4-9, and look at the languages attribute in the output.

Tip

Note that because this time we’re only running plugins that ship with Ohai by default, we don’t need to add our example plugin path to Ohai::Config[:plugin_path]. It you do not have the same programming languages I do installed on your machine, the output you see when running this example locally might vary.

Example 4-9. Exploring the language attribute
$> irb

>> require 'ohai'
=> true

>> o = Ohai::System.new
=> <snip>

>> o.all_plugins
=> <snip>

>>  o.attributes_print("languages")
 => "{\n  \"lua\": {\n    \"version\": \"5.1.4\"\n  },\n
          \"nodejs\": {\n    \"version\": \"0.10.21\"\n  },\n
          \"perl\": {\n    \"version\": \"5.16.2\",\n
            \"archname\": \"darwin-thread-multi-2level\"\n  },\n
          \"php\": {\n    \"version\": \"5.4.17\",\n
            \"builddate\": \"Aug 25 2013\"\n  },\n  }\n}"
           <<snip>>

>>  o.attributes_print("languages/php")
 => "{\n  \"version\": \"5.4.17\",\n  \"builddate\": \"Aug 25 2013\"\n}"

>>  o.attributes_print("languages/perl")
 => "{\n  \"version\": \"5.16.2\",\n
             \"archname\": \"darwin-thread-multi-2level\"\n}"

We can see here that the languages attribute contains data on a number of different programming languages—although the output of ohai doesn’t show us this, as we saw earlier, this output is provided by a number of different plugins. In our sample output, for example, we see the specific attributes set by the PHP and Perl plugins we explored in this section, in the same languages attribute as attributes describing the Lua and Node.js programming languages.

Summary

Ohai’s flexible and extensible plugin interface allows us to leverage any information source we choose to store information about our Chef nodes. If we want to store information about a new programming language, for example, we can extend one of the existing Ohai plugins to make use of its existing data structures, or we can define entirely new attributes of our own choosing.

In this chapter we worked through a number of examples, starting with the minimal code necessary for a functioning Ohai plugin, then moving on to a plugin that implemented operating-system-specific behavior and nested data structures, before finally looking at some of the plugins that actually ship with Ohai to explore its dependency model. It’s my hope that these examples have served to give you an idea of the flexibility and power that Ohai plugins give you to store information about your nodes in Chef, as well as some ideas about the kinds of information you might be able to store about your nodes to enhance your Chef recipes.

In the next chapter, we’ll examine how Chef allows us to specify custom behavior on the success or failure of our Chef runs through the use of handlers.

Get Customizing Chef now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.