Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
hubboss / boss / app / commands / base.py
Size: Mime:
from ... import utils


class BaseCommand(object):
    '''
    Base class for boss subcommands
    '''

    help = None
    arguments = ()

    def run(self):
        raise NotImplementedError

    @classmethod
    def get_arg_spec(cls, thing):
        if isinstance(thing, tuple):
            args, kwargs = thing
        else:
            args, kwargs = (), thing

        return args, kwargs

    @classmethod
    def configure_parser(cls, app, parser_factory):
        names = getattr(cls, 'names', [])
        if not names:
            names = [getattr(cls, 'name', cls.__name__.lower())]

        parsers = [
            parser_factory(name, help=cls.help)
            for name in utils.remove_dupes(names)
        ]

        parser_func = lambda **kw: cls(app, **kw).run()

        for parser in parsers:
            for args, kwargs in map(cls.get_arg_spec, cls.arguments):
                action = parser.add_argument(*args, **kwargs)
                kwargs.setdefault('dest', action.dest)

            parser.set_defaults(func=parser_func)

    def __init__(self, app, **kwargs):
        object.__init__(self)
        self.app = app
        self.kwargs = kwargs
        self.kwargs.pop('func')
        self.validate()

        self.__dict__.update(self.kwargs)

    def validate(self):
        '''
        Determine if arguments provided are valid; else throw an Exception.

        relies on validate_{FIELD} methods to do the actual validation.
        '''
        failed = []
        for key in self.kwargs.keys():
            validator = getattr(self, 'validate_{}'.format(key), None)
            if validator is None:
                continue

            try:
                setattr(self, '_orig_' + key, self.kwargs.get(key))
                self.kwargs[key] = validator(self.kwargs.get(key))
            except ValueError as exc:
                failed.append((key, exc.message))

        if failed:
            raise Exception("Some invalid values were found:\n{}".format(
                '\n'.join('{}: {}'.format(key, msg) for key, msg in failed)
            ))

    def validate_hosts(self, hosts):
        '''
        A "host" can be provided as a named host entry from the config,
        a substring that matches the hostname, or the name of a group of
        hosts. This function resolves substrings and groups into named host
        entries and makes sure they are all present in the config (also includes
        dynamic targets e.g. aws_prod).

        Raises a ValueError if an unknown host is found.
        Returns a list of hostnames and also assigns a 'host_objs'
        attribute which contains boss.Host objects (TODO: only use
        boss.Host objects).
        '''
        hosts_or_groups = hosts[:]
        hosts = []
        missing_hosts = []

        all_real_hosts = sum(self.app.config.hosts.values(), [])

        while hosts_or_groups:
            host_or_group = hosts_or_groups.pop(0)

            group = self.app.config.default_get('host_groups', host_or_group, None)
            if group is None:
                host = utils.get_host(host_or_group, all_real_hosts)
                if host is None:
                    missing_hosts.append(host_or_group)
                else:
                    hosts.append(host)
            else:
                hosts.extend(
                    self.app.config.get_hosts_for_group(host_or_group)
                )

        if missing_hosts:
            raise ValueError("Hosts or groups not found: {}".format(
                ', '.join(sorted(missing_hosts)))
            )

        # TODO: all commands should switch to just using host objects
        self.host_objs = hosts
        return [x.name for x in hosts]

    def validate_image(self, image):
        '''
        An 'image' is a name of a docker image, specified as [namespace/]name:tag.

        Note that this is different from an image-ish accepted by docker proper in
        the following ways:
          - Hashes are not accepted
          - Registry (host) is not accepted
          - Tags are required

        Note that there is currently a failure in registry parsing as the following images are
        equivalent:
         - docker.io/debian:latest
         - docker.io/library/debian:latest

        'docker.io/debian:latest' will be validated as a proper image for hub-boss as it is
        indistingushable from a namespace of 'docker.io' (with canonical name:
        'docker.io/docker.io/debian:latest'). All images deployed with hub-boss at this time
        are from the official Docker registry, so this shouldn't be a problem.

        If a hash or registry is detected, or a tag is not provided, a value error is raised.
        This returns the validated image name as well as sets the "image_repo" and "image_tag"
        attributes.
        '''

        parts = image.split(':')
        has_hash = '@' in image
        name_parts = parts[0].split('/')

        if has_hash or len(parts) != 2 or len(name_parts) > 2:
            raise ValueError("Invalid image name: {}. Must match [namespace/]name:tag".format(image))

        self.image_repo = parts[0]
        self.image_tag = parts[1]

        return image

    def __repr__(self):
        arg_names = [self.get_arg_spec(arg)[1]['dest'] for arg in self.arguments]

        return '{cls}({props})'.format(
            cls=type(self).__name__,
            props=', '.join('{k}={v!r}'.format(k=k, v=getattr(self, k, None)) for k in arg_names)
        )

    def _spread(self, function, *args, **kwargs):
        operation = utils.ParallelHostOperation(self.host_objs)
        operation.start(function, args, kwargs)
        return operation.finish()