4. Writing Your Own Actions

Writing your own actions is simple - each action class should extend elsware.base.BaseAction.

4.1. The BaseAction class

Every action requires the constructor to take two parameters. A deployment instance, and the action parameters dictionary. The default __init__ method in BaseAction, accepts those parameters.

4.2. The __init__ method

The __init__ method accepts the two parameters you should always accept (deployment, and action_info).

def __init__(self,deployment,action_info):
        self.deployment=deployment
        self.action_info=action_info

4.2.1. The deployment parameter

The deployment parameter is an instance of elsware.base.Deployment. It act’s much like the “request” parameter passed around to views in Django, or similarly Rails. It gives you information about what is happening in your action chain, and has helper methods to get information about what deployment instruction is running, and getting common information from the configuration dictionary.

4.2.2. The action_info parameter

This was described in chapter 3

4.3. The setup() method

Use the setup method as a hook to initialize variables instead of overriding the __init__ method and calling super.

4.4. The validate() method

The validate method is a hook to make sure that the required information is available for this action. When you raise an exception from validate, it halts the entire deployment before any actions have actually run.

As an example, consider this snippet from the built-in ssh login action:

def validate(self):
        self.servername=self.get_server_name(True)
        self.serverinfo=self.get_server_info(True)
        self.ip,self.user=self.get_ip_and_user(True)
        self.password=self.get_password(self.serverinfo,self.action_info,self.deployment.options)
        if not self.password: self.password=self.get_password_in_opt(self.serverinfo,self.action_info)
        if not self.password: raise exceptions.ActionRequirementsError(actions.ActionErrors.missing_password % self.meta.action_name)

You can see, all this method is used for is ensuring the right information is available.

4.5. The run() method

The run method performs the action logic. For example, the ssh login action uses pxssh to login to a remote ssh session, and saves that session object into the self.deployment.clients object. The “clients” objects is covered in another chapter.

4.6. The revert() method

The revert method is used when transaction rollbacks are triggered. Your action instance should keep track of any information it needs from the run method, so it can revert.

Revert is called on each action - in reverse order, starting with the action that raised the exception.

4.7. The finalize() method

The finalize method is always called. It’s called after either all of the actions have successfully run, or after transactions have been reverted.

4.8. The teardown() method

The teardown method is much like the finalize method, in that it’s always called. But this is the last method called for each action, which can be used to do some sort of teardown.

4.9. Registering your action with elsware

After you’ve written your own actions, you need to register it as an action class. Consider a couple of the default registrations:

from elsware import actions,ssh
actions.register_action_class('ssh_login',ssh.SSHLoginAction)
actions.register_action_class('ssh_logout',ssh.SSHLogoutAction)

After you’ve registered the action, elsware knows which class to initialize.

For example, let’s say we wanted to write a custom action, that restarts a mail server. You would register the action like this:

register_action_class('restart_mail',myactions.RestartMailAction)

Now you can use that action class in configurations:

DEPLOYMENTS={
        #...
        'slicehost':{
                'actions':('login','restart_mail','logout')
        }
        #...
}

4.10. Keeping Actions Re-usable

In order to keep your actions re-usable, you should abstract actions into operations that require parameters. You wouldn’t want to write actions that have hard coded values. For example, a bad action might look like this:

class PushToDreamhost(base.BaseActions):
        ...
        def run(self):
                ...
                ftp="ftp.mydomainatdreamhost.com"
                password="wordup"
                ...
        ...

When you write actions like this, they’re not re-usable. Instead you should write a generic FTP push action, that requires parameters from configuration.

Writing the correct action would become something like this:

class FTPPushAction(base.BaseAction):
        ...
        def validate(self):
                ...
                self.servername=self.get_server_name(True)
                localdir=self.action_info.get("localdir",False)
                if not localdir: raise exceptions.ActionRequirementsError("FTPPushAction requires the 'localdir' action parameter key.")
                ...
        ...

The idea is to parameterize the required information for an action - which will make sure you can re-use your actions more than once.

4.11. Advanced topics

4.11.1. Helpers on base action class

There are a number of helpers on the BaseAction class, which simplify some common operations. Read the source code for that class, or read some of the default actions available for examples of those helpers.