Skip to content
Snippets Groups Projects
plugins.py 3 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 as exc:
            err_msg = 'Error during plugin installation (%s): %s' % (
                self.name, exc)
            logger.exception(err_msg)
            # make sure all tags are stopped
            self.tag_db.set_parent(None)
            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)