Skip to content
Snippets Groups Projects
plugins.py 2.88 KiB
Newer Older
import imp
import logging
from collections import defaultdict

from cloudcontrol.common.client.tags import Tag
from cloudcontrol.common.jobs import Job
from cloudcontrol.common.tql.db.helpers import taggify


logger = logging.getLogger(__name__)


def _tag_direct_use_error(*args, **kwargs):
    raise RuntimeError('Plugins tags can\'t be called directly')


class Plugin(object):

    """ Represent a loaded cloudcontrol plugin.
    """

    def __init__(self, logger, tag_db, name, sha1, plugin_body):
        self.logger = logger
        self.name = name
        self.sha1 = sha1
        self.tag_db = tag_db
        self.methods = {}
        self.events = defaultdict(lambda: [])
        self._load_module(plugin_body)
        self.trigger('install', self)

    def _load_module(self, plugin_body):
        # load the code into a module:
        module = imp.new_module(self.name)
        module.logger = self.logger
        module.tag = self._decorator_tag
        module.method = self._decorator_method
        module.on = self._decorator_event
        try:
            exec plugin_body in module.__dict__
        except Exception:
            err_msg = 'Error during plugin installation (%s)' % self.name
            logger.exception(err_msg)
            raise RuntimeError(err_msg)
        else:
            # prevents module from being garbage collected
            self.module = module

    def _decorator_tag(self, ttl=-1, refresh=None, name=None):
        def decorator(func):
            tag = Tag(func.__name__ if name is None else name,
                      lambda: taggify(func()), ttl=ttl, refresh=refresh)
            self.tag_db.add_tag(tag)
            return _tag_direct_use_error
        return decorator

    def _decorator_method(self, name=None):
        def decorator(func):
            self.methods[func.__name__ if name is None else name] = func
            return func
        return decorator

    def _decorator_event(self, event):
        def decorator(func):
            self.events[event].append(func)
            return func
        return decorator

    def trigger(self, event, *args, **kwargs):
        """Trigger an event."""
        for func in self.events[event]:
            func(*args, **kwargs)

    def uninstall(self):
        self.trigger('uninstall')
        # shutdown the tags database:
        self.tag_db.set_parent(None)


class PluginMethodJob(Job):

    """Job that run a plugin method."""

    def job(self, plugin_name, method_name, method, method_kwargs):
        kwargs_str = ', '.join(('%s=%s' % (k, v) for k, v in method_kwargs.iteritems()))
        self.title = 'Plugin: %s.%s(%s)' % (plugin_name, method_name, kwargs_str)
        try:
            returned = method(self, **method_kwargs)
        except Exception:
            self.logger.exception('Error while executing method')
            raise
        if returned is not None:
            self.attachment('output').write(returned)