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))
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, )