Tuesday 3 January 2017

Chapter 12. Search

Chef search provides the ability to query data indexed on Chef Server. The search query runs on Chef Server and search results are returned to clients. Queries can be invoked by using knife on the command line or from within a recipe. Typical queries are usually inventory related, such as a count and system names of all the computers that have particular operating system configurations or software installed. For example, searches for versions of the openssl library vulnerable to the Heartbleed Virus were quite popular as we were writing this book.

Search from the Command Line

Let’s start by performing a search query with knife on the command line. Use the chef-playground directory you created in Chapter 11. Use the same dual command prompt setup you used there. Start the chef-zeroserver on an open port in one window. We will be using port 9501 in the examples in this chapter:
$ chef-zero --port 9501
Then, in the other window, make the chef-playground directory the current working directory and run the knife upload nodes command to populate Chef Server with some test data about nodes:
$ cd chef-playground
$ knife upload nodes
Created nodes/susu.json
Created nodes/atwood.json
Created nodes/snowman.json

Search from the Command Line with Knife

You can also perform searches in production on the command line with the knife search command. The search query syntax with knife is in the following form:
$ knife search <index> <search_query>
The index can be one of the following:
  • node
  • client
  • environment
  • role
  • <name of data bag>
In this chapter, we’ll use node for the index field. Figure 12-1 shows an example of a search for nodes that have an IP address beginning with 10.1.1.
Knife search query syntax
Figure 12-1. Knife search query syntax
Chef uses Apache Solr for searching and search indexing. The following command performs a search query for all nodes and returns the results using the Solr search query string "*:*.” The results contain the test data we just populated with knife upload, showing three nodes registered to be managed by Chef.
$ knife search node "*:*"
3 items found

Node Name:   atwood
Environment: _default
FQDN:        atwood.playground.local
IP:          192.168.33.31
Run List:    recipe[apache], recipe[motd]
Roles:
Recipes:     apache, motd
Platform:    centos 6.5
Tags:

Node Name:   snowman
Environment: _default
FQDN:        snowman.playground.local
IP:          192.168.33.32
Run List:    recipe[apache], recipe[motd], recipe[motd-attributes]
Roles:
Recipes:     apache, motd, motd-attributes
Platform:    ubuntu 14.04
Tags:

Node Name:   susu
Environment: _default
FQDN:        susu.playground.local
IP:          192.168.33.33
Run List:    recipe[apache], recipe[motd]
Roles:
Recipes:     apache, motd
Platform:    centos 6.5
Tags:
Chef search queries use the Solr “<attribute>:<search_pattern>” form:
knife search node "ipaddress:192.168.33.32"
Use an asterisk (“*”) within a search query to perform a wildcard search matching 0 or more characters:
knife search node "ipaddress:192.*"
knife search node "platfo*:centos"
Use a question mark (“?”) to match any single character:
knife search node "platform_version:14.0?"
You can add specific key-value pairs in the query part of the knife search command line. The following query will return the item where node == snowman:
$ knife search node "hostname:snowman"
1 items found

Node Name:   snowman
Environment: _default
FQDN:        snowman.playground.local
IP:          192.168.33.32
Run List:    recipe[apache], recipe[motd], recipe[motd-attributes]
Roles:
Recipes:     apache, motd, motd-attributes
Platform:    ubuntu 14.04
Tags:

NOTE

To obtain a list of attribute names to use in a search, run the knife <index> show command using the --long option. It will show you all the available attributes. For example, we ran the following command to determine that the attribute for Node Name: was hostname:
knife node snowman --long
Note that our test data doesn’t include all the possible attributes. You’ll need to run knife node showagainst a real Chef Server setup. Another way to collect this information for a node without needing a real setup is to run ohai and look through the results. Most of the attributes about a node come from ohai.
Multiple key-value pairs can be specified using boolean values, such as OR. For example, the following query would return the items where the id is alice OR bob:
$ knife search node "name:susu OR name:atwood"
2 items found

Node Name:   atwood
Environment: _default
FQDN:        atwood.playground.local
IP:          192.168.33.31
Run List:    recipe[apache], recipe[motd]
Roles:
Recipes:     apache, motd
Platform:    centos 6.5
Tags:

Node Name:   susu
Environment: _default
FQDN:        susu.playground.local
IP:          192.168.33.33
Run List:    recipe[apache], recipe[motd]
Roles:
Recipes:     apache, motd
Platform:    centos 6.5
Tags:
Logical AND can be used as well, if you want to return results matching all value criteria:
$ knife search node "ipaddress:192* AND platform:ubuntu"
1 items found

Node Name:   snowman
Environment: _default
FQDN:        snowman.playground.local
IP:          192.168.33.32
Run List:    recipe[apache], recipe[motd], recipe[motd-attributes]
Roles:
Recipes:     apache, motd, motd-attributes
Platform:    ubuntu 14.04
Tags:
Search results can be filtered with the -a parameter. For example, -a ipaddress only returns the value for the ipaddress attribute:
$ knife search node "*:*" -a ipaddress
3 items found

atwood:
  ipaddress: 192.168.33.31

snowman:
  ipaddress: 192.168.33.32

susu:
  ipaddress: 192.168.33.33

Search in a Recipe Using Test Kitchen

You can also perform search queries in your Chef code. In this section we’ll write a cookbook using Test Kitchen and chef_zero that performs search queries. Create the directory chef-playground/cookbooks, and make sure it is the current working directory:
Linux/Mac OS X:
$ cd chef-playground/cookbooks
Windows:
> cd chef-playground\cookbooks
Now, generate a nodes cookbook in the chef-playground/cookbooks directory.
Chef Development Kit:
$ chef generate cookbook nodes
$ cd nodes
Chef Client:
$ knife cookbook create nodes --cookbook-path .
$ cd nodes
$ kitchen init --create-gemfile
$ bundle install
Edit the .kitchen.yml to make sure you are using the chef_zero provisioner and our favorite basebox image as shown in Example 12-1. Notice there is a new addition to the provisioner: stanza, the nodes_path:
provisioner:
  name: chef_zero
  nodes_path: ../../nodes
nodes_path is a relative path pointing to the chef-playground/nodes directory we created with our test data in Chapter 11. Test data is normally located somewhere outside the main cookbook source code so it doesn’t get inadvertently uploaded to the Chef server.
Example 12-1. chefdk/chef-playground/cookbooks/nodes/.kitchen.yml
---
driver:
  name: vagrant

provisioner:
  name: chef_zero
  nodes_path: ../../nodes

platforms:
  - name: centos65
    driver:
      box: learningchef/centos65
      box_url: learningchef/centos65

suites:
  - name: default
    run_list:
      - recipe[nodes::default]
    attributes:
Let’s write a recipe that performs a search query on Chef Server for all nodes, like we did in the previous section.Chef provides a search() method that you can use in your Chef code. It takes two parameters, similar to the two command line parameters used in knife search:
search(index, search_query):
index
Possible values for index are nodeclientenvironmentrole, and <name of data bag>. In this example, we’ll be using node.
search_query
Apache Solr search query.
Make sure recipes/default.rb matches the code contained in Example 12-2. It contains Chef code that queries Chef Server for all items in the users data bag.
Example 12-2. chefdk/chef-playground/cookbooks/nodes/recipes/default.rb
#
# Cookbook Name:: users
# Recipe:: default
#
# Copyright (C) 2014
#
#
#

# Print every node name matching the search pattern
search("node", "*:*").each do |matching_node|
  log matching_node.to_s
end
The .each statement in recipes/default.rb is a looping construct specific to Ruby. If you are familiar with other programming languages, look at the following code for something that might be a little more familiar. The following code creates a counter variable, incrementing it by 1 until it equals 5:
counter = 0
while counter < 5
  puts counter
  counter = counter + 1
end
Although the preceding is completely valid Ruby, a more idiomatic way is to use the .each iterator to return all the elements in an array one-by-one. Ruby shines in having really compact ways to specify common coding constructs.
The do..end block construct is more sophisticated than we’ve covered so far. It can be used to define a method with no name. Also, you can pass this nameless method one or more parameters enclosed by two vertical bars (||), such as |counter| in the following example.
In the following example, we pass a code block to (0..5).each. When you pass a code block to an iterator method, it will execute the specified method for each item. In this case, each item of the range (0..5) will be passed to our code block as the |counter| variable. The block uses this variable to print out each value in the range:
(0..5).each do |counter|
  puts counter
end
We’re doing something similar in Example 12-2, iterating through each node item returned by search() and returning the item content in the matching_node variable. matching_node is a hash containing the key-value pairs in the node item.
The code just reads these values from the matching_node hash and uses the to_s method to print a string representation of the object using log, which is the node name.
Run kitchen converge. If all goes well, you should notice that Test Kitchen uploads the cookbook code to the sandbox environment and creates node entries in a chef-zero instance. Then it runs the cookbook code, which performs a query for all nodes, printing out the following results:
$ kitchen converge
-----> Starting Kitchen (v1.2.2.dev)
-----> Converging <default-centos65>...
       Preparing files for transfer
       Resolving cookbook dependencies with Berkshelf 3.1.3...
       Removing non-cookbook files before transfer
       Preparing nodes
...
       Converging 4 resources
       Recipe: nodes::default
         * log[node[atwood]] action write
       [2014-07-27T13:05:24-07:00] INFO: Processing log[node[atwood]] action
       write (nodes::default line 12)
       [2014-07-27T13:05:24-07:00] INFO: node[atwood]


         * log[node[default-centos65]] action write[2014-07-27T13:05:24-07:00]
         INFO: Processing log[node[default-centos65]] action write
         (nodes::default line 12)
       [2014-07-27T13:05:24-07:00] INFO: node[default-centos65]


         * log[node[snowman]] action write[2014-07-27T13:05:24-07:00] INFO:
         Processing log[node[snowman]] action write (nodes::default line 12)
       [2014-07-27T13:05:24-07:00] INFO: node[snowman]


         * log[node[susu]] action write[2014-07-27T13:05:24-07:00] INFO:
         Processing log[node[susu]] action write (nodes::default line 12)
       [2014-07-27T13:05:24-07:00] INFO: node[susu]


       [2014-07-27T13:05:24-07:00] INFO: Chef Run complete in 0.068921558
       seconds

       Running handlers:
       [2014-07-27T13:05:24-07:00] INFO: Running report handlers
       Running handlers complete

       [2014-07-27T13:05:24-07:00] INFO: Report handlers complete
       Chef Client finished, 4/4 resources updated in 2.158656241 seconds
       Finished converging <default-centos65> (0m5.12s).
-----> Kitchen is finished. (0m5.57s)

NOTE

Sharp-eyed readers might notice that the search returns four results instead of the three we received running knife using the command line. The search result returns the nodes atwoodsnowmansusu, and default-centos65. The additional node entry is the sandbox node Test Kitchen creates for us. When Test Kitchen performs a kitchen converge, it automatically registers the sandbox instance as a node with its own chef-zero instance.
You have been introduced to Chef search. We’re done with the chef-zero instance on your host and the Test Kitchen sandbox environment. Hit Ctrl-C in the window in which you launched chef-zero to stop the Chef Zero server. Make sure the chef-playground/cookbooks/nodes directory with your cookbook is the current working directory, then run:
$ kitchen destroy

Summary

In this chapter we introduced you to Chef search. Chef Server uses Apache Solr behind the scenes to add support for searching and indexing. The Apache Solr "<attribute>:<query>" syntax is also used for search queries. You can search from anything that can talk with Chef Server, given that Chef Server’s search capability is implemented as an API. We showed you how to search from the command line using knife node search and how to search within a recipe using the search() method.
In the next chapter, we’ll cover data bags. Data bags contain data that can be accessed by more than one node. Data bags were added as a feature in Chef Server to get around the limitation that node attributes can be read only by the node that created the data, not by any other node.

No comments:

Post a Comment