Extending the wallaby shell

htcondor
mrg
wallaby
Published

October 21, 2010

The most recent few releases of the Wallaby configuration management service have included some great new features: wallaby console can now be used as an interpreter for shebang scripts, wallaby inventory now supports limiting the nodes it lists with expressive constraints, the wallaby http-server command provides access to node configurations over HTTP, and the wallaby command and its subcommands feature many minor functional and aesthetic improvements. One feature that I’m particularly excited about is under the hood: there is now an API for extending the wallaby shell with your own subcommands. In this post, we’ll look at how to make a new wallaby subcommand.

First, make sure you’re running wallaby 0.9.23 or later. If you’re running Fedora, you can simply git clone the repository, check out the v0.9.23 tag, and run rake rpms to generate installable packages.

Next, you’ll want to have a directory in which to put your custom wallaby shell commands. I recommend putting them somewhere in your home directory. (For the purposes of this discussion, we’ll assume that you’re using ~/.wallaby.) The wallaby command will find and attempt to load every ruby file in this directory that begins with cmd_, so you probably don’t want it to be writable by anyone other than you. Once you have this directory set up, set WALLABY_COMMAND_DIR in your environment to this directory:

export WALLABY_COMMAND_DIR=${HOME}/.wallaby

We’ll start by making a fairly straightforward command that simply prints out the API version supported by the wallaby service. To create a new wallaby command, use wallaby new-command:


Using `wallaby new-command`
wallaby new-command -d "Prints the API version of the Wallaby service." -D $WALLABY_COMMAND_DIR api-version

The -d option allows us to provide a description for the new command (viz., what would show up if we typed wallaby help commands). The -D option allows us to specify a directory in which to create the new command file. (wallaby new-command supports several other options; you can use wallaby help new-command for additional documentation.) After executing this command, we’ll have a documented template file for a new wallaby command, called cmd_api_version.rb, installed in our WALLABY_COMMAND_DIR. It will look something like this:

cmd_api_version.rb Generated code for our simple API version command
# cmd_api_version.rb:  Prints the API version of the Wallaby service.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
 
module Mrg
  module Grid
    module Config
      module Shell
        class ApiVersion < ::Mrg::Grid::Config::Shell::Command
          # opname returns the operation name; for "wallaby foo", it
          # would return "foo".
          def self.opname
            "api-version"
          end
        
          # description returns a short description of this command, suitable 
          # for use in the output of "wallaby help commands".
          def self.description
            "Prints the API version of the Wallaby service."
          end
        
          def init_option_parser
            # Edit this method to generate a method that parses your command-line options.
            OptionParser.new do |opts|
              opts.banner = "Usage:  wallaby #{self.class.opname}\n#{self.class.description}"
        
              opts.on("-h", "--help", "displays this message") do
                puts @oparser
                exit
              end
            end
          end
        
          def act
            # This method is responsible for actually performing the work of
            # the command. It may read the @kwargs instance variable, which
            # should be a hash, and must return an integer, corresponding to
            # the exit code of the command.
        
            # It may access the wallaby store with the "store" method; it will
            # only connect to the wallaby store after the first time "store" is
            # invoked. See the Wallaby client API for more information on
            # methods supported by store and other Wallaby API entities.
        
            # You may exit the command from a callee of act by using the exit!
            # method, which takes a status code and an optional explanation of
            # why you are exiting. For example:
        
            # exit!(1, "Did nothing, unsuccessfully.")
        
            return 0
          end
        end
      end
    end
  end
end

We can now run wallaby help commands and verify that our command shows up. Sure enough, it does:

Verifying that Wallaby can see our new command 
wallaby:master!? % wallaby help commands
Use "wallaby help COMMAND" to get help on COMMAND.
Available commands include:
activate:  Activates pending changes to the pool configuration.
add-param:  Adds a parameter to the store.
api-version:  Prints the API version of the Wallaby service.
 
# ... other commands omitted

We can even run wallaby api-version, although it won’t do anything yet. To make it do something useful, we’re going to edit the act method defined for us in the ApiVersion class. We could do something very simple, like insert puts store.apiVersionNumber before return 0, but it would be nice to allow a user to format the API version number as he or she sees fit, in order to use the result of our command in other scripts.

To let the user supply a format string, we’ll need to add a command-line option, which requires us to edit the init_option_parser method. This method must return an OptionParser object; if you haven’t used Ruby’s option parser class, read up on its documentation. For this example, though, the changes we have to make are pretty minor.

After we’ve added a command-line option and a body for the act method, our cmd_api_version.rb file will look more like this:

# cmd_api_version.rb:  Prints the API version of the Wallaby service.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
 
module Mrg
  module Grid
    module Config
      module Shell
        class ApiVersion < ::Mrg::Grid::Config::Shell::Command
          def self.opname
            "api-version"
          end
        
          def self.description
            "Prints the API version of the Wallaby service."
          end
        
          def init_option_parser
            @format = "The wallaby service is running version %s of the Wallaby API."
 
            OptionParser.new do |opts|
              opts.banner = "Usage:  wallaby #{self.class.opname}\n#{self.class.description}"
        
              opts.on("-h", "--help", "displays this message") do
                puts @oparser
                exit
              end
 
              opts.on("-f", "--format FMT", "format output as FMT (must include '%s')") do |fmt|
                @format = fmt
              end
 
            end
          end
        
          def act
            puts @format % store.apiVersionNumber.to_s
        
            return 0
          end
        end
      end
    end
  end
end

Running this command without arguments will return a string like The wallaby service is running version 20100915 of the Wallaby API. If you supply a format string, you can get simply the raw number (with --format '%s') or something more suited to your taste. (Note that this example script doesn’t do any sanity- or error-checking of the format string, which you’d certainly want to do if you were going to put your command into production.)

We’ll look at a more interesting example wallaby shell command and some of the issues that more interesting commands raise in a future post.​