Chef 0.10 Preview: Knife Plugins and UI

Chef 0.10 Preview: Knife Plugins

knife began as a simple thor script
created by Joshua Sierles of 37signals.
As soon as we saw it, we knew it was such a great idea that we had to
incorporate it into Chef. With a bit of a redesign, Chef 0.8 shipped
with knife fully integrated, and it’s since become the primary
interface to Chef for many users.

In Chef 0.10, we’ve completed two long-awaited improvements to knife:
support for custom commands and improved output formatting.

Knife Plugins

A plugin system for knife is something we’ve had on the roadmap for a
long time. Luckily for you, Ryan Davis and
Eric Hodel decided to fast-forward the
roadmap by implementing the feature themselves. With Chef 0.10, knife
will load commands from the following locations:

  • The core set of knife commands shipped with Chef
  • Commands in your home chef directory: ~/.chef/plugins/knife/
  • Commands in a .chef/plugins/knife/ directory in your cookbook repo
  • Commands located in a chef/knife/ directory in a Ruby Gem you have
    installed.

This allows you to conveniently keep a set of knife plugins that you
reuse across projects in your home directory, share plugins with your
team by including them in your cookbook repo, and share plugins with the
whole Chef community by distributing them as Ruby gems.

Writing Knife Plugins

Lets get a taste of what we can do with knife plugins by writing a
simple one. If you’re like me, you use knife search node very frequently
and you would like to do a little less typing when searching for nodes
by Role or tag. We’ll solve the problem by creating a knife grep
command that will search for our nodes by role, tag, fqdn, or IP
address. The code for this plugin is below. To use it, simply copy the
code to ~/.chef/plugins/knife/grep.rb

Knife Grep: The Code

[sourcecode lang=”ruby”]
require ‘chef/knife’

module Kallistec
class Grep < Chef::Knife

deps do
require ‘chef/search/query’
require ‘chef/knife/search’
end

banner "knife grep QUERY"

def run
unless @query = name_args.first
ui.error "You need to specify a query term"
exit 1
end

fuzzier_query = "tags:*#{@query}* OR roles:*#{@query}* OR fqdn:*#{@query}* OR addresses:*#{@query}*"
knife_search = Chef::Knife::Search.new
knife_search.name_args = [‘node’, fuzzier_query]
knife_search.run

end
end
end
[/sourcecode]

Knife Grep: The How

For complete documentation on writing knife plugins, head on over to the
wiki
. We’ll cover
the highlights here.

We start by placing the code in a namespace:

[sourcecode lang=”ruby”]
module Kallistec
end
[/sourcecode]

I’m using my nick as the namespace, but you can use whatever you want.
Next, we create a new knife command class by subclassing Chef::Knife:

[sourcecode lang=”ruby”]
class Grep < Chef::Knife
end
[/sourcecode]

Subclassing Chef::Knife is important, as this lets knife know you’ve
created a new command.

Next, we add our dependency to knife’s lazy loading system:

[sourcecode lang=”ruby”]
deps do
require ‘chef/knife/search’
end
[/sourcecode]

We could have declared all of our dependencies at the top of the file,
but this makes knife load more slowly for all commands, so it’s
recommended to use the lazy loader.

The heart of our knife plugin is the run method. This method will be
called automatically after knife has parsed the command line arguments
and options. Command line arguments are made available through the
name_args Array. In the grep plugin, we simply take the first argument
and use it to generate a search query. We then run a knife search
command via knife’s internal API to perform the query and print results:

[sourcecode lang=”ruby”]
# Create a search query from user input. For input ‘chef’, this is
# equivalent to running
# `knife search node ‘tags:*chef* OR roles:*chef* OR fqdn:*chef* OR addresses:*chef*’
#
fuzzier_query = "tags:*#{@query}* OR roles:*#{@query}* OR fqdn:*#{@query}* OR addresses:*#{@query}*"
# Create a knife search command
knife_search = Chef::Knife::Search.new
# set the command line arguments
knife_search.name_args = [‘node’, fuzzier_query]
# run it!
knife_search.run
[/sourcecode]

Knife Grep: The Grepping

We can then use our new plugin like this:

dan@laptop$ knife grep ghost
1 items found

Node Name:   ghost.local
Environment: production
FQDN:        ghost.local
IP:          172.16.185.135
Run List:    recipe[tmux]
Roles:
Recipes      tmux
Platform:    ubuntu 10.04

Cloud Commands are now Plugins

In order to reduce the commands shipped with knife to only those useful
to everyone, we’ve removed the cloud computing commands from core and
made them available as plugins:

In addition to these, we have new functionality available in two new
plugins:

All of these plugins are available as gems; you can install them via:

(sudo) gem install knife-ec2 # or whichever plugin you want

Prettier Knife Output

knife‘s default JSON output has been a double edged, er, knife. While
it’s great for integrating with other tools, it isn’t optimal for human
consumption. So in 0.10 we’ve added a new “summary” output format and
made it the default. You already got a taste of the summary output above
when we demoed the knife grep command. knife node show produces the
same output:

dan@laptop$ knife node show ghost.local
Node Name:   ghost.local
Environment: production
FQDN:        ghost.local
IP:          172.16.185.135
Run List:    recipe[tmux]
Roles:
Recipes      tmux
Platform:    ubuntu 10.04

Data bag items look pretty nifty as well:

dan@laptop$ knife data bag show users-example charles
id:     charles
key:    -----BEGIN RSA PRIVATE KEY-----
        MIIEowIBAAKCAQEAw8PkvRWOVONSByxseLhKrOH9EigRizutlfwk0/LwUvnM4Ffb
        HcZ4lD1LWzewfnQsNLv6+gdhMk7gcRTFxeNLf9+//VsN5vODcLdDdd3DGKrJ6uvx
        N5hs1t8fHf6B5N1Z823CsTBAspbAGqkeoiJVDfk9LKU/W+YM+GS0h2pp88H9Y9p4
        ilwu55xXSH1/RKunniTvQWEdDcs/zpk5+EKoaBoaZCpItMLKMk7RziaAkYNfwiWr
        6zhUpjyUbhg6giO4TKFy0HKODejFhlz4i9syYxh3fSKr+2HiUruT1Mw2RrdyFXiI
        too long, didn't read...
        3T8+Tb6UjZ0hMSSVsfeUlwKBgGqfvp5CQsub1HIv1YZMnZYMMDGFQm5B6CxE0Eal
        frFUtlNszk2PDjCY2IWaNCyrGCVhP/ra+y+5PzO1utrob/KiRuX9D8fs3yIClsQd
        2R8EomzZq554+W9UcMBrU90PTdgrMvXMGHsR/6j0hEqYT1ILje+neY6eBiiv5mPh
        4vSpAoGBAKcuAifcR62UxKklaE5d6QSvq6r3OlxIJ6R/LL2yMpPkbMvaEpzN6lpH
        oF3GSz78K8wPr/1Zhhm/g/VJyZA9E0n67HS+SRyTHrSCHXS9PrBGQJiV+AMvF5gf
        uFP1Kv0FYJ1VS1FgFPDSTUMFTwzPvj0G8PnJi4kHcEePgpUk/ost
        -----END RSA PRIVATE KEY-----
shell:  zsh

There are many more examples I could show you, but you really need to
use knife in 0.10 for a while to get the full effect.

Help!

For the final touch, we’ve made the manual pages accessible via knife
help
, and greatly expanded the content. We now have a manual page for
each command category, in addition to one for general knife usage. To get
started, run knife help list to get a list of help topics, or run
knife help knife for general knife documentation. By the way,
improving documentation is an easy way to get started contributing to
Chef, so if you see something in the docs that you’d like to improve,
dive right in—the manpages are written in friendly
markdown

and generated with ronn.

Try it Today!

If you’re using the Opscode
Platform
, you can start using
the new knife right now by installing the 0.10 Release Candidate to your
laptop/workstation:

(sudo) gem install chef --pre

If you run into any bugs, you can seemlessly downgrade via gem
uninstall
(don’t forget to file a bug
report
though!)

If you’re running you’re own server, you’ll need to upgrade the server
before you can start using the new knife. You can install a 0.10 server
for testing by bootstrapping with chef-solo:

[sourcecode lang=”text”]
sudo chef-solo -j chef.json -c solo.rb -r https://s3.amazonaws.com/chef-solo/bootstrap-0.10.0.rc.0.tar.gz
[/sourcecode]

You can also find upgrade instructions on the
wiki.
,
and, as always, feel free to ask for help on our mailing
list
and on our IRC channel
(irc.freenode.net#chef).

Author Dan DeLeo