diff options
Diffstat (limited to 'gitlab/cli.py')
| -rw-r--r-- | gitlab/cli.py | 522 |
1 files changed, 31 insertions, 491 deletions
diff --git a/gitlab/cli.py b/gitlab/cli.py index 8cc89c2..f23fff9 100644 --- a/gitlab/cli.py +++ b/gitlab/cli.py @@ -17,447 +17,33 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. from __future__ import print_function -from __future__ import division from __future__ import absolute_import import argparse -import inspect -import operator +import importlib import re import sys -import six - -import gitlab +import gitlab.config camel_re = re.compile('(.)([A-Z])') -EXTRA_ACTIONS = { - gitlab.Group: {'search': {'required': ['query']}}, - gitlab.ProjectBranch: {'protect': {'required': ['id', 'project-id']}, - 'unprotect': {'required': ['id', 'project-id']}}, - gitlab.ProjectBuild: {'cancel': {'required': ['id', 'project-id']}, - 'retry': {'required': ['id', 'project-id']}, - 'artifacts': {'required': ['id', 'project-id']}, - 'trace': {'required': ['id', 'project-id']}}, - gitlab.ProjectCommit: {'diff': {'required': ['id', 'project-id']}, - 'blob': {'required': ['id', 'project-id', - 'filepath']}, - 'builds': {'required': ['id', 'project-id']}, - 'cherrypick': {'required': ['id', 'project-id', - 'branch']}}, - gitlab.ProjectIssue: {'subscribe': {'required': ['id', 'project-id']}, - 'unsubscribe': {'required': ['id', 'project-id']}, - 'move': {'required': ['id', 'project-id', - 'to-project-id']}}, - gitlab.ProjectMergeRequest: { - 'closes-issues': {'required': ['id', 'project-id']}, - 'cancel': {'required': ['id', 'project-id']}, - 'merge': {'required': ['id', 'project-id'], - 'optional': ['merge-commit-message', - 'should-remove-source-branch', - 'merged-when-build-succeeds']} - }, - gitlab.ProjectMilestone: {'issues': {'required': ['id', 'project-id']}}, - gitlab.Project: {'search': {'required': ['query']}, - 'owned': {}, - 'all': {'optional': [('all', bool)]}, - 'starred': {}, - 'star': {'required': ['id']}, - 'unstar': {'required': ['id']}, - 'archive': {'required': ['id']}, - 'unarchive': {'required': ['id']}, - 'share': {'required': ['id', 'group-id', - 'group-access']}}, - gitlab.User: {'block': {'required': ['id']}, - 'unblock': {'required': ['id']}, - 'search': {'required': ['query']}, - 'get-by-username': {'required': ['query']}}, -} - -def _die(msg, e=None): +def die(msg, e=None): if e: msg = "%s (%s)" % (msg, e) sys.stderr.write(msg + "\n") sys.exit(1) -def _what_to_cls(what): +def what_to_cls(what): return "".join([s.capitalize() for s in what.split("-")]) -def _cls_to_what(cls): +def cls_to_what(cls): return camel_re.sub(r'\1-\2', cls.__name__).lower() -def do_auth(gitlab_id, config_files): - try: - gl = gitlab.Gitlab.from_config(gitlab_id, config_files) - gl.auth() - return gl - except Exception as e: - _die(str(e)) - - -class GitlabCLI(object): - def _get_id(self, cls, args): - try: - id = args.pop(cls.idAttr) - except Exception: - _die("Missing --%s argument" % cls.idAttr.replace('_', '-')) - - return id - - def do_create(self, cls, gl, what, args): - if not cls.canCreate: - _die("%s objects can't be created" % what) - - try: - o = cls.create(gl, args) - except Exception as e: - _die("Impossible to create object", e) - - return o - - def do_list(self, cls, gl, what, args): - if not cls.canList: - _die("%s objects can't be listed" % what) - - try: - l = cls.list(gl, **args) - except Exception as e: - _die("Impossible to list objects", e) - - return l - - def do_get(self, cls, gl, what, args): - if cls.canGet is False: - _die("%s objects can't be retrieved" % what) - - id = None - if cls not in [gitlab.CurrentUser] and cls.getRequiresId: - id = self._get_id(cls, args) - - try: - o = cls.get(gl, id, **args) - except Exception as e: - _die("Impossible to get object", e) - - return o - - def do_delete(self, cls, gl, what, args): - if not cls.canDelete: - _die("%s objects can't be deleted" % what) - - id = args.pop(cls.idAttr) - try: - gl.delete(cls, id, **args) - except Exception as e: - _die("Impossible to destroy object", e) - - def do_update(self, cls, gl, what, args): - if not cls.canUpdate: - _die("%s objects can't be updated" % what) - - o = self.do_get(cls, gl, what, args) - try: - for k, v in args.items(): - o.__dict__[k] = v - o.save() - except Exception as e: - _die("Impossible to update object", e) - - return o - - def do_group_search(self, cls, gl, what, args): - try: - return gl.groups.search(args['query']) - except Exception as e: - _die("Impossible to search projects", e) - - def do_project_search(self, cls, gl, what, args): - try: - return gl.projects.search(args['query']) - except Exception as e: - _die("Impossible to search projects", e) - - def do_project_all(self, cls, gl, what, args): - try: - return gl.projects.all(all=args.get('all', False)) - except Exception as e: - _die("Impossible to list all projects", e) - - def do_project_starred(self, cls, gl, what, args): - try: - return gl.projects.starred() - except Exception as e: - _die("Impossible to list starred projects", e) - - def do_project_owned(self, cls, gl, what, args): - try: - return gl.projects.owned() - except Exception as e: - _die("Impossible to list owned projects", e) - - def do_project_star(self, cls, gl, what, args): - try: - o = self.do_get(cls, gl, what, args) - o.star() - except Exception as e: - _die("Impossible to star project", e) - - def do_project_unstar(self, cls, gl, what, args): - try: - o = self.do_get(cls, gl, what, args) - o.unstar() - except Exception as e: - _die("Impossible to unstar project", e) - - def do_project_archive(self, cls, gl, what, args): - try: - o = self.do_get(cls, gl, what, args) - o.archive_() - except Exception as e: - _die("Impossible to archive project", e) - - def do_project_unarchive(self, cls, gl, what, args): - try: - o = self.do_get(cls, gl, what, args) - o.unarchive_() - except Exception as e: - _die("Impossible to unarchive project", e) - - def do_project_share(self, cls, gl, what, args): - try: - o = self.do_get(cls, gl, what, args) - o.share(args['group_id'], args['group_access']) - except Exception as e: - _die("Impossible to share project", e) - - def do_user_block(self, cls, gl, what, args): - try: - o = self.do_get(cls, gl, what, args) - o.block() - except Exception as e: - _die("Impossible to block user", e) - - def do_user_unblock(self, cls, gl, what, args): - try: - o = self.do_get(cls, gl, what, args) - o.unblock() - except Exception as e: - _die("Impossible to block user", e) - - def do_project_commit_diff(self, cls, gl, what, args): - try: - o = self.do_get(cls, gl, what, args) - return [x['diff'] for x in o.diff()] - except Exception as e: - _die("Impossible to get commit diff", e) - - def do_project_commit_blob(self, cls, gl, what, args): - try: - o = self.do_get(cls, gl, what, args) - return o.blob(args['filepath']) - except Exception as e: - _die("Impossible to get commit blob", e) - - def do_project_commit_builds(self, cls, gl, what, args): - try: - o = self.do_get(cls, gl, what, args) - return o.builds() - except Exception as e: - _die("Impossible to get commit builds", e) - - def do_project_commit_cherrypick(self, cls, gl, what, args): - try: - o = self.do_get(cls, gl, what, args) - o.cherry_pick(branch=args['branch']) - except Exception as e: - _die("Impossible to cherry-pick commit", e) - - def do_project_build_cancel(self, cls, gl, what, args): - try: - o = self.do_get(cls, gl, what, args) - return o.cancel() - except Exception as e: - _die("Impossible to cancel project build", e) - - def do_project_build_retry(self, cls, gl, what, args): - try: - o = self.do_get(cls, gl, what, args) - return o.retry() - except Exception as e: - _die("Impossible to retry project build", e) - - def do_project_build_artifacts(self, cls, gl, what, args): - try: - o = self.do_get(cls, gl, what, args) - return o.artifacts() - except Exception as e: - _die("Impossible to get project build artifacts", e) - - def do_project_build_trace(self, cls, gl, what, args): - try: - o = self.do_get(cls, gl, what, args) - return o.trace() - except Exception as e: - _die("Impossible to get project build trace", e) - - def do_project_issue_subscribe(self, cls, gl, what, args): - try: - o = self.do_get(cls, gl, what, args) - o.subscribe() - except Exception as e: - _die("Impossible to subscribe to issue", e) - - def do_project_issue_unsubscribe(self, cls, gl, what, args): - try: - o = self.do_get(cls, gl, what, args) - o.unsubscribe() - except Exception as e: - _die("Impossible to subscribe to issue", e) - - def do_project_issue_move(self, cls, gl, what, args): - try: - o = self.do_get(cls, gl, what, args) - o.move(args['to_project_id']) - except Exception as e: - _die("Impossible to move issue", e) - - def do_project_merge_request_closesissues(self, cls, gl, what, args): - try: - o = self.do_get(cls, gl, what, args) - return o.closes_issues() - except Exception as e: - _die("Impossible to list issues closed by merge request", e) - - def do_project_merge_request_cancel(self, cls, gl, what, args): - try: - o = self.do_get(cls, gl, what, args) - return o.cancel_merge_when_build_succeeds() - except Exception as e: - _die("Impossible to cancel merge request", e) - - def do_project_merge_request_merge(self, cls, gl, what, args): - try: - o = self.do_get(cls, gl, what, args) - should_remove = args.get('should_remove_source_branch', False) - build_succeeds = args.get('merged_when_build_succeeds', False) - return o.merge( - merge_commit_message=args.get('merge_commit_message', ''), - should_remove_source_branch=should_remove, - merged_when_build_succeeds=build_succeeds) - except Exception as e: - _die("Impossible to validate merge request", e) - - def do_project_milestone_issues(self, cls, gl, what, args): - try: - o = self.do_get(cls, gl, what, args) - return o.issues() - except Exception as e: - _die("Impossible to get milestone issues", e) - - def do_user_search(self, cls, gl, what, args): - try: - return gl.users.search(args['query']) - except Exception as e: - _die("Impossible to search users", e) - - def do_user_getbyusername(self, cls, gl, what, args): - try: - return gl.users.search(args['query']) - except Exception as e: - _die("Impossible to get user %s" % args['query'], e) - - -def _populate_sub_parser_by_class(cls, sub_parser): - for action_name in ['list', 'get', 'create', 'update', 'delete']: - attr = 'can' + action_name.capitalize() - if not getattr(cls, attr): - continue - sub_parser_action = sub_parser.add_parser(action_name) - [sub_parser_action.add_argument("--%s" % x.replace('_', '-'), - required=True) - for x in cls.requiredUrlAttrs] - sub_parser_action.add_argument("--sudo", required=False) - - if action_name == "list": - [sub_parser_action.add_argument("--%s" % x.replace('_', '-'), - required=True) - for x in cls.requiredListAttrs] - sub_parser_action.add_argument("--page", required=False) - sub_parser_action.add_argument("--per-page", required=False) - sub_parser_action.add_argument("--all", required=False, - action='store_true') - - if action_name in ["get", "delete"]: - if cls not in [gitlab.CurrentUser]: - if cls.getRequiresId: - id_attr = cls.idAttr.replace('_', '-') - sub_parser_action.add_argument("--%s" % id_attr, - required=True) - [sub_parser_action.add_argument("--%s" % x.replace('_', '-'), - required=True) - for x in cls.requiredGetAttrs if x != cls.idAttr] - - if action_name == "get": - [sub_parser_action.add_argument("--%s" % x.replace('_', '-'), - required=False) - for x in cls.optionalGetAttrs] - - if action_name == "list": - [sub_parser_action.add_argument("--%s" % x.replace('_', '-'), - required=False) - for x in cls.optionalListAttrs] - - if action_name == "create": - [sub_parser_action.add_argument("--%s" % x.replace('_', '-'), - required=True) - for x in cls.requiredCreateAttrs] - [sub_parser_action.add_argument("--%s" % x.replace('_', '-'), - required=False) - for x in cls.optionalCreateAttrs] - - if action_name == "update": - id_attr = cls.idAttr.replace('_', '-') - sub_parser_action.add_argument("--%s" % id_attr, - required=True) - - attrs = (cls.requiredUpdateAttrs - if (cls.requiredUpdateAttrs or cls.optionalUpdateAttrs) - else cls.requiredCreateAttrs) - [sub_parser_action.add_argument("--%s" % x.replace('_', '-'), - required=True) - for x in attrs if x != cls.idAttr] - - attrs = (cls.optionalUpdateAttrs - if (cls.requiredUpdateAttrs or cls.optionalUpdateAttrs) - else cls.optionalCreateAttrs) - [sub_parser_action.add_argument("--%s" % x.replace('_', '-'), - required=False) - for x in attrs] - - if cls in EXTRA_ACTIONS: - def _add_arg(parser, required, data): - extra_args = {} - if isinstance(data, tuple): - if data[1] is bool: - extra_args = {'action': 'store_true'} - data = data[0] - - parser.add_argument("--%s" % data, required=required, **extra_args) - - for action_name in sorted(EXTRA_ACTIONS[cls]): - sub_parser_action = sub_parser.add_parser(action_name) - d = EXTRA_ACTIONS[cls][action_name] - [_add_arg(sub_parser_action, True, arg) - for arg in d.get('required', [])] - [_add_arg(sub_parser_action, False, arg) - for arg in d.get('optional', [])] - - -def _build_parser(args=sys.argv[1:]): +def _get_base_parser(): parser = argparse.ArgumentParser( description="GitLab API Command Line Interface") parser.add_argument("--version", help="Display the version.", @@ -474,35 +60,12 @@ def _build_parser(args=sys.argv[1:]): "will be used."), required=False) - subparsers = parser.add_subparsers(title='object', dest='what', - help="Object to manipulate.") - subparsers.required = True - - # populate argparse for all Gitlab Object - classes = [] - for cls in gitlab.__dict__.values(): - try: - if gitlab.GitlabObject in inspect.getmro(cls): - classes.append(cls) - except AttributeError: - pass - classes.sort(key=operator.attrgetter("__name__")) - - for cls in classes: - arg_name = _cls_to_what(cls) - object_group = subparsers.add_parser(arg_name) - - object_subparsers = object_group.add_subparsers( - dest='action', help="Action to execute.") - _populate_sub_parser_by_class(cls, object_subparsers) - object_subparsers.required = True - return parser -def _parse_args(args=sys.argv[1:]): - parser = _build_parser() - return parser.parse_args(args) +def _get_parser(cli_module): + parser = _get_base_parser() + return cli_module.extend_parser(parser) def main(): @@ -510,56 +73,33 @@ def main(): print(gitlab.__version__) exit(0) - arg = _parse_args() - args = arg.__dict__ - - config_files = arg.config_file - gitlab_id = arg.gitlab - verbose = arg.verbose - action = arg.action - what = arg.what - + parser = _get_base_parser() + (options, args) = parser.parse_known_args(sys.argv) + + config = gitlab.config.GitlabConfigParser(options.gitlab, + options.config_file) + cli_module = importlib.import_module('gitlab.v%s.cli' % config.api_version) + parser = _get_parser(cli_module) + args = parser.parse_args(sys.argv[1:]) + config_files = args.config_file + gitlab_id = args.gitlab + verbose = args.verbose + action = args.action + what = args.what + + args = args.__dict__ # Remove CLI behavior-related args - for item in ("gitlab", "config_file", "verbose", "what", "action", - "version"): + for item in ('gitlab', 'config_file', 'verbose', 'what', 'action', + 'version'): args.pop(item) - args = {k: v for k, v in args.items() if v is not None} - cls = None try: - cls = gitlab.__dict__[_what_to_cls(what)] - except Exception: - _die("Unknown object: %s" % what) - - gl = do_auth(gitlab_id, config_files) - - cli = GitlabCLI() - method = None - what = what.replace('-', '_') - action = action.lower().replace('-', '') - for test in ["do_%s_%s" % (what, action), - "do_%s" % action]: - if hasattr(cli, test): - method = test - break - - if method is None: - sys.stderr.write("Don't know how to deal with this!\n") - sys.exit(1) - - ret_val = getattr(cli, method)(cls, gl, what, args) + gl = gitlab.Gitlab.from_config(gitlab_id, config_files) + gl.auth() + except Exception as e: + die(str(e)) - if isinstance(ret_val, list): - for o in ret_val: - if isinstance(o, gitlab.GitlabObject): - o.display(verbose) - print("") - else: - print(o) - elif isinstance(ret_val, gitlab.GitlabObject): - ret_val.display(verbose) - elif isinstance(ret_val, six.string_types): - print(ret_val) + cli_module.run(gl, what, action, args, verbose) sys.exit(0) |
