You are here

March 2010

Writing install triggers for cobbler

Cobbler has the ability to run triggers at specific times. Two of those times include before installing a new machine ("pre-install triggers") and after installing a new machine ("post-install triggers").

"Old-style" triggers involve running executable binaries or scripts (e.g. shell scripts) in specific locations. Pre-install triggers are placed in the directory /var/lib/cobbler/triggers/install/pre/ and post-install triggers are placed in the directory /var/lib/cobbler/triggers/install/post/. The trigger will be passed three pieces of information: the object type, e.g. "system," the name of the object, and the object's IP. (A comment in the run_install_triggers method in remote.py says this passes the name, MAC, and IP but this does not appear to match the name of the variables.) If the trigger requires more information, it will need to pull it from elsewhere or parse cobbler's output. For all but simple tasks, this is probably not a convenient way to go.

Note: There is a bug in cobbler 2.0.3.1 which prevents running "old-style" triggers. See ticket #530 for more information and a possible fix.

"New-style" triggers are written in Python as modules. They reside in cobbler's module directory. (On my system, this is /usr/lib/python2.4/site-packages/cobbler/modules/.) Each module is required to define at least the functions register and run.

The register function takes no arguments and returns a string corresponding to the directory the module would reside in if it were an "old-style" trigger. For a pre-install trigger, it would be:

  1. def register():
  2.     return "/var/lib/cobbler/triggers/install/post/*"

For a post-install trigger, it would be:

  1. def register():
  2.     return "/var/lib/cobbler/triggers/install/post/*"

The run function is where the actual code for the trigger should reside. It takes three arguments: A cobbler API reference, an array containing arguments, and a logger reference. The argument array contains the same three values as for "old-style" triggers, i.e. the object type, the name, and the IP address. The logger reference may be set to None and the code should handle that. (In cobbler 2.0.3.1, this will be set to None. This may be fixed when the issue for "old-style" install triggers is.)

For an example of a run function, let's look at one I wrote (based on the trigger in install_post_report.py that is included with cobbler) to automatically sign Puppet certificates:

  1. def run(api, args, logger):

This starts the method. Note the signature.

  1.     settings = api.settings()
  2.  
  3.     if not str(settings.sign_puppet_certs_automatically).lower() in [ "1", "yes", "y", "true"]:
  4.         return 0

This retrieves the settings from /etc/cobbler/settings. To control the trigger, I added another option there named sign_puppet_certs_automatically. If this value either does not exist or is not set to one of the required values showing its enabled, the trigger returns a success code (since it's not supposed to run, it shouldn't return a failure code) and exits.

I also added another option to the cobbler settings called puppetca_path which contains the path to the puppetca command.

  1.     objtype = args[0] # "target" or "profile"
  2.     name    = args[1] # name of target or profile
  3.  
  4.     if objtype != "system":
  5.         return 0

This retrieves the object type and name from the argument array. If the object type is not a system, it returns a success code and exits.

  1.     system = api.find_system(name)
  2.     system = utils.blender(api, False, system)
  3.  
  4.     hostname = system[ "hostname" ]

This finds the system in the cobbler API and then flattens it to a dictionary. I'm pretty sure this could be improved upon.

  1.     puppetca_path = settings.puppetca_path
  2.     cmd = [puppetca_path, '--sign', hostname]

This retrieves the path for puppetca and sets up the command to be run to sign the certificate.

  1.     rc = 0
  2.     try:
  3.         rc = utils.subprocess_call(logger, cmd, shell=False)
  4.     except:
  5.         if logger is not None:
  6.             logger.warning("failed to execute %s", puppetca_path)
  7.  
  8.     if rc != 0:
  9.         if logger is not None:
  10.             logger.warning("signing of puppet cert for %s failed", name)
  11.  
  12.     return 0

This runs the command and logs a warning if either the command fails to be executed or does not succeed. Finally, at the end, it returns a success code.

According to the cobbler documentation, the return code of post-install triggers is ignored so there's no reason not to return anything other than value. Pre-install triggers apparently can halt the process if they return a non-zero value.

Note: The above code will not run correctly if logger is set to None. This is because utils.subprocess_call tries to call logger without verifying that it is not None and throws an exception. To use this with cobbler 2.0.3.1, you must either edit change the call to utils.run_triggers in remote.py's run_install_triggers method or you must change utils.subprocess_call to properly check for logger being set to None.

Also note: Since the original code is under the GPL, the code above is also under the GPL.