Writing your own actions is simple - each action class should extend elsware.base.BaseAction.
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.
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
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.
This was described in chapter 3
Use the setup method as a hook to initialize variables instead of overriding the __init__ method and calling super.
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.
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.
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.
The finalize method is always called. It’s called after either all of the actions have successfully run, or after transactions have been reverted.
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.
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')
}
#...
}
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.