Skip to content
simi edited this page Oct 8, 2012 · 7 revisions

Creating the exporter

To start with you should subclass [Foreman::Export::Base] and your subclass should exist within the [Foreman::Export] namespace.

class Foreman::Export::MyRunitExporter < Foreman::Export::Base
end

Then the only method that you need to implement on your class is the [Foreman::Export::MyRunitExporter#export] method, calling super to setup some sane defaults. Here is some example boilerplate to get you started.

class Foreman::Export::MyRunitExporter < Foreman::Export::Base
  def export
    super

    engine.each_process do |name, process|
      # Creates `n` number of processes based on Foreman’s formation option.
      1.upto(engine.formation[name]) do |num|
        full_name = "#{app}-#{name}-#{num}"          # Construct a unique name for our service
        port      = engine.port_for(process, num)    # Ask Foreman for a unique port
        env       = engine.env.merge("PORT" => port) # Merge our port into our services ENV

        # Construct your service here.
      end
    end
  end
end

From your subclass you have access to the following attr_reader’s:

  • location: Where the user wants you to place the files
  • engine: The [Foreman::Engine] for the current application
  • app: The command line option --app
  • log: The command line option --log
  • user: The command line option --user

You should make sure that your a good citizen and respect all of Foreman’s user configurable options.

There are also a few of private helper methods that will make your life easy:

  • [#error(message)]: Raise a new [Foreman::Export::Exception] with the given message.
  • [#say(message)]: Output given message to the user
  • [#clean(filename)]: Delete the given file (with user notification).
  • [#shell_quote(value)]: Shell escapes the given value and wraps it in quotes.
  • [#export_template(name)]: Checks default Foreman locations (the template command line option, then ~/.foreman/templates, then the gems built-in templates) for a template matching name.
  • [#write_template(name, target, binding)]: Finds template with [#export_template(name)] and outputs it to target.
  • [#chmod(mode, file)]: chomd's file (with user notification).
  • [#create_directory(dir)]: Uses mkdir -p to make the given directory (with user notification).
  • [#write_file(filename, contents)]: Writes the file to disk (with user notification). If the path is relative is resolved relative to the --location option.

Ensuring that your exporter will load

Foreman follows Rails-ish conventions (‘dasherizing', ‘underscoring' and ‘classifying’) when it comes to loading your custom exporters. So continuing our example from above we should invoke our custom exporter using a ‘dasherized’ version of the classname with something like...

bundle exec foreman export my-runit-exporter ~/etc/sv/ --app my-application

Foreman will attempt to require foreman/export/my_runit_exporter and will call your class as expected.

Loading without bundler

If you wanted to use your exporter outside of a Bundler managed application you’ll need to create a wrapper around the Foreman command that ensures your custom exporter is loaded.

#!/usr/bin/env ruby
require 'rubygems'

require 'my-runit-exporter'
require 'foreman/cli'

Foreman::CLI.start

If you are bundling your exporter as a gem you should now be able to include it with gem.executables and use it as a system command (see nature/foreman-export-nature for an example of this in the wild).

More info

To view a full working exporter (as a gem) there is an example over at nature/foreman-export-nature.