Custom faces

With the release of Puppet 2.6, a brand new concept was introduced: Puppet faces.

Faces are an API that allow easy creation of new Puppet (sub) commands: whenever we execute Puppet, we specify at least one command, which provides access to the functionalities of its subsystems.

The most common commands are agent, apply, master, and cert and have existed for a long time but there are a lot more (we can see their full list with puppet help) and most of them are defined via the faces API.

As you can guess, we can easily add new faces and therefore, new subcommands to the Puppet executable just by placing some files in a module of ours.

The typical synopsis of a face reflects the Puppet command's one:

puppet [FACE] [ACTION] [ARGUMENTS] [OPTIONS]

Where [FACE] is the Puppet subcommand to be executed, [ACTION] is the face's action we want to invoke, [ARGUMENTS] is its arguments, and [OPTIONS] is general Puppet options.

To create a face, we have to work on two files: lib/puppet/application/<face_name>.rb and lib/puppet/face/<face_name>.rb. The code in the application directory simply adds the subcommand to Puppet extending the Puppet::Application::FaceBase class; the code in the face directory manages all its logic and what to do for each action.

An interesting point to consider when writing and using faces is that we have access to the whole Puppet environment, its indirectors and termini, and we can interact with its subsystems via the other faces.

A very neat example of this is the secret_agent face, which reproduces as a face what the, much older, agent command does; a quick look at the code in lib/puppet/face/secret_agent.rb, amended of documentation and marginal code, reveals the basic structure of a face and how other faces can be used:

require 'puppet/face'
Puppet::Face.define(:secret_agent, '0.0.1') do
  action(:synchronize) do
    default
    summary "Run secret_agent once."
[...]
    when_invoked do |options|
      Puppet::Face[:plugin, '0.0.1'].download
      Puppet::Face[:facts, '0.0.1'].upload
      Puppet::Face[:catalog, '0.0.1'].download
      report  = Puppet::Face[:catalog, '0.0.1'].apply
      Puppet::Face[:report, '0.0.1'].submit(report)
      return report
    end
  end
end

The Puppet::Face class exposes various methods. Some of them are used to provide documentation, both for the command line and the help pages: summary, arguments, license, copyright, author, notes, and examples. For example, the module face uses these methods to describe what it does in Puppet's core at lib/puppet/face/module.rb:

require 'puppet/face'
require 'puppet/module_tool'
require 'puppet/util/colors'

Puppet::Face.define(:module, '1.0.0') do
  extend Puppet::Util::Colors

  copyright "Puppet Labs", 2012
  license   "Apache 2 license; see COPYING"

  summary "Creates, installs and searches for modules on the Puppet Forge."
  description <<-EOT

This subcommand can find, install and manage modules from the Puppet Forge, which is a repository of user-contributed Puppet code. It can also generate empty modules, and prepare locally developed modules for release on the Forge:

  EOT
  display_global_options "environment", "modulepath"
end

The action method is invoked for each action of a face. Here, we pass the action name as a symbol and a block of code, which implements our action using various other methods:

  • The methods used for documentation and inline help are description, summary, and returns
  • The methods used to manage the parameters used in the command line are option and arguments
  • The methods used to implement specific actions: when_invoked (its return value is the output of the command) and when_rendering

Let's see the implementation of the install action of the module face. The following code is in the lib/puppet/face/module/install.rb file; it's possible to add the code for each action in separate files as in this case, or on the main face file.

We are dealing with a Ruby class that may require other classes:

require 'puppet/forge'
require 'puppet/module_tool/install_directory'
require 'pathname'

This is followed by the face definition and the code applied for the install action:

Puppet::Face.define(:module, '1.0.0') do
  action(:install) do

The description methods are:

    summary "Install a module from the Puppet Forge or a release archive."

    description <<-EOT
      […] 
    EOT

    returns "Pathname object representing the path to the installed module."

    examples <<-'EOT'
      […]
    EOT

Then, the expected arguments and the available options are defined (copied here is only the block relative to the --target-dir option, various other are present in the original file and are defined in a similar way):

    arguments "<name>"

    option "--force", "-f" do
      summary "Force overwrite of existing module, if any."
      description <<-EOT
        Force overwrite of existing module, if any.
      EOT
    end

    option "--target-dir DIR", "-i DIR" do
      summary "The directory into which modules are installed."
      description <<-EOT
        […] 
      EOT
    end

Then, when the install action is called, the when_invoked block is executed. Here is where the real work is done, and in this case are mostly called methods from the Puppet::ModuleTool class and its subclasses:

    when_invoked do |name, options|
      Puppet::ModuleTool.set_option_defaults options
      Puppet.notice "Preparing to install into #{options[:target_dir]} ..."

      forge = Puppet::Forge.new("PMT", self.version)
      install_dir = Puppet::ModuleTool::InstallDirectory.new(Pathname.new(options[:target_dir]))
      installer = Puppet::ModuleTool::Applications::Installer.new(name, forge, install_dir, options)

      installer.run
    end

This action also invokes the when_rendering block to format the console output:

    when_rendering :console do |return_value, name, options|
      if return_value[:result] == :failure
        Puppet.err(return_value[:error][:multiline])
        exit 1
      else
        tree = Puppet::ModuleTool.build_tree(return_value[:installed_modules], return_value[:install_dir])
        return_value[:install_dir] + "
" +
        Puppet::ModuleTool.format_tree(tree)
      end
    end
  end
end

As it happens for many faces, most of the code is in the face directory. The other component of the face, placed in lib/puppet/application/module.rb, is just an extension to the Puppet::Application::FaceBase class:

require 'puppet/application/face_base'

class Puppet::Application::Module < Puppet::Application::FaceBase
end
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset