예제 #1
0
    def collect_dependencies(self):

        if not self.args.do_auto_collect and not self.args.explicit_collect:
            self.logger.debug('Not collecting dependencies.')

        auto_collect = (DEPENDENCY_COLLECTORS.copy()
                        if self.args.do_auto_collect else {})

        if self.args.explicit_collect:
            # This is how
            # "--collect-dependencies python:py_custom ruby:rb_custom python"
            # => {python: [py_custom, <py default cmds>],
            #     ruby: [rb_custom] }

            get_type = attrgetter('key')
            get_command = attrgetter('value')
            # Group by type so that we can instantiate one collector per type:
            groups = groupby(sorted(self.args.explicit_collect, key=get_type),
                             key=get_type)
            for dep_type, group in groups:
                self.logger.debug('Explicit %r dependency collection',
                                  dep_type)
                if dep_type in auto_collect:
                    del auto_collect[dep_type]

                try:
                    collector_class = DEPENDENCY_COLLECTORS[dep_type]
                except KeyError:
                    raise InvalidArgumentError(
                        'Unknown dependency type to collect: %r' % dep_type)
                custom_commands = list(map(get_command, group))
                if len(custom_commands) != len(set(custom_commands)):
                    raise InvalidArgumentError(
                        'Duplicate dependency type: %s' % dep_type)
                commands = []
                for custom_command in custom_commands:
                    if custom_command is None:
                        # eg. 'python' => add all default commands for type
                        commands.extend(collector_class.default_commands)
                    else:
                        # eg. 'python:custom_command'
                        commands.append(custom_command)
                collector = collector_class(custom_commands=commands)
                for dep in collector.collect():
                    yield dep

        if auto_collect:
            self.logger.debug('Auto-collecting dependencies: %s',
                              ', '.join(sorted(auto_collect.keys())))
            for collector_class in auto_collect.values():
                collector = collector_class()
                try:
                    for dep in collector.collect():
                        yield dep
                except ExternalCommandNotFoundError:
                    # Completely ignore missing auto collection commands.
                    pass
                except ExternalCommandError as e:
                    # Warn about auto collection command failures.
                    self.logger.warn(str(e))
예제 #2
0
    def add_command_args(cls, subparser):
        """
        :type subparser: argparse.ArgumentParser

        """
        subparser.add_argument(
            '--hostname',
            action='store',
            dest='hostname',
            default=os.environ.get('OPBEAT_HOSTNAME', settings.HOSTNAME),
            help="""
            Override hostname of current machine. Can be set with environment
            variable OPBEAT_HOSTNAME.
            """,
        )

        subparser.add_argument(
            '--component',
            nargs=argparse.ONE_OR_MORE,
            dest='components',
            metavar='attribute:value',
            action='append',
            type=KeyValue.from_string,
            help=r"""
                A description of a component of the app being deployed.
                Multiple components can be specified by using this option
                multiple times.

                Attributes:

                    path:<local-path>          (required)
                    name:<name>                (optional)
                    version:<version-string>   (optional if VCS info specified)

                    Path is used to find out local VCS information for a
                    component, and also to match error logs with components on
                    opbeat.com, therefore it is required even if it is not a
                    VCS repository.

                VCS attributes:

                    None of these attributes should be specified if the
                    provided path is a VCS repository, because then they are
                    filled in automatically:

                    vcs:<{vcs_types}>
                    rev:<vcs-revision>
                    branch:<vcs-branch>
                    remote_url:<vcs-remote-url>

                A component has to have "path", and at least a "version" or
                "rev".

                Examples:

                    --component path:.

                    --component \
                        path:frontends/web \
                        name:web-frontend \
                        version:0.2.1

                    --component \
                        path:tools/scheduler \
                        name:scheduler \
                        vcs:git \
                        rev:383dba \
                        branch:dev \
                        remote_url:[email protected]:opbeat/scheduler.git

            """
            .format(vcs_types='|'.join(sorted(VCS_NAME_MAP.values()))),
        )

        subparser.add_argument(
            '--dependency',
            nargs=argparse.ONE_OR_MORE,
            dest='dependencies',
            metavar='attribute:value',
            action='append',
            type=KeyValue.from_string,
            help=r"""
                A description of an installed third-party package that the app
                being deployed depends on. Multiple dependencies can be
                specified by using this option multiple times.

                Attributes are the same as with --component. There is no path,
                however. In addition to the common attributes, the type
                of the dependency has to be specified as well:

                    type:<{dependency_types}>

                A dependency has to have "type", "name", and at least "version"
                or "rev".

                Examples:

                    --dependency type:other name:nginx version:1.5.3
                    --dependency type:python name:django version:1.5.0
                    --dependency type:ruby name:app2 vcs:git \
                                    rev:383dba branch:prod \
                                    remote_url:[email protected]:opbeat/app2.git

            """
            .format(
                dependency_types='|'.join(sorted(DEPENDENCIES_BY_TYPE.keys()))
            ),
        )
        subparser.add_argument(
            '--auto-collect-dependencies',
            default=True,
            dest='do_auto_collect',
            action='store_true',
            help="""
            (Re-)enable automatic collection of installed dependencies (on by
            default). These types of dependencies are attempted to be
            collected:

                {dependency_types}

            For each type, there is one or more default shell commands which
            are run to collect information about the dependencies:

{default_commands_table}

            """
            .format(
                dependency_types=', '.join(
                    sorted(DEPENDENCY_COLLECTORS.keys())),
                default_commands_table=''.join(sorted(
                    "{type: >23}: {commands}\n"
                    .format(
                        type=dep_type,
                        commands=('\n' + ' '*25).join(
                            collector.default_commands
                        )
                    ).replace('%', '%%')  #
                    for dep_type, collector in DEPENDENCY_COLLECTORS.items()
                ))
            )
        )
        subparser.add_argument(
            '--no-auto-collect-dependencies',
            dest='do_auto_collect',
            action='store_false',
            help="""
            Disable automatic collection of dependencies (which is enabled by
            default).

            """
        )

        subparser.add_argument(
            '--collect-dependencies',
            nargs=argparse.ONE_OR_MORE,
            metavar='type[:command]',
            dest='explicit_collect',
            type=KeyOptionalValue.from_string,
            help=r"""

            Specify dependency collection that should be used in addition to,
            or instead of the automatic one.

            (See --auto-collect-dependencies for supported dependency
            collection types and their default commands.)

            You can supply one or more custom commands for each type
            ("type:command"). The output of the custom commands needs to use
            the same format as the corresponding default commands do. If only
            a type is specified ("type"), default commands for the type will
            be used.

            Examples:

            Overwrite automatic node.js dependency collection with a custom
            command:

                --collect-dependencies \
                    nodejs:'cd /webapp1 && npm --local --json list' \

            Add a custom Python dependency collection command but also call
            the default one:

                --collect-dependencies \
                    python:'venv/bin/pip freeze' \
                    python

            Disable automatic collection and use only the listed types and
            commands:

                --no-auto-collect-dependencies --collect-dependencies \
                    deb \
                    nodejs \
                    python \
                    python:'virtualenv/bin/pip freeze' \
                    ruby:bin/script

            """
        )

        # Hidden aliases for --component to preserve
        # backward-compatibility with opbeatcli==1.1.5.
        subparser.add_argument(
            '-d', '--directory',
            dest='legacy_directory',
            help=argparse.SUPPRESS,
        )
        subparser.add_argument(
            '-m', '--module-name',
            dest='legacy_module',
            help=argparse.SUPPRESS,
        )