diff options
author | Gauvain Pocentek <gauvain.pocentek@objectif-libre.com> | 2015-05-13 18:28:38 +0200 |
---|---|---|
committer | Gauvain Pocentek <gauvain.pocentek@objectif-libre.com> | 2015-05-13 18:41:23 +0200 |
commit | d7bea076257febf36cc6de50d170bc61f3c6a49a (patch) | |
tree | f9d6d9b73e655a03c9aec7ae9966c0370f6892be /gitlab/cli.py | |
parent | 4c6c7785ba17d053548181fc2eafbe3356ea33f5 (diff) | |
download | gitlab-d7bea076257febf36cc6de50d170bc61f3c6a49a.tar.gz |
Move the CLI in the gitlab package
Setup an console_script entry point to create the executable script.
Diffstat (limited to 'gitlab/cli.py')
-rwxr-xr-x | gitlab/cli.py | 357 |
1 files changed, 357 insertions, 0 deletions
diff --git a/gitlab/cli.py b/gitlab/cli.py new file mode 100755 index 0000000..1805cde --- /dev/null +++ b/gitlab/cli.py @@ -0,0 +1,357 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright (C) 2013-2014 Gauvain Pocentek <gauvain@pocentek.net> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# 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 os +import re +import sys +try: + import ConfigParser as configparser +except ImportError: + import configparser + +import gitlab + +camel_re = re.compile('(.)([A-Z])') +LIST = 'list' +GET = 'get' +CREATE = 'create' +UPDATE = 'update' +DELETE = 'delete' +PROTECT = 'protect' +UNPROTECT = 'unprotect' +SEARCH = 'search' +OWNED = 'owned' +ALL = 'all' +ACTIONS = [LIST, GET, CREATE, UPDATE, DELETE] +EXTRA_ACTION = [PROTECT, UNPROTECT, SEARCH, OWNED, ALL] + +extra_actions = { + gitlab.ProjectBranch: {PROTECT: {'requiredAttrs': ['id', 'project-id']}, + UNPROTECT: {'requiredAttrs': ['id', 'project-id']}}, + gitlab.Project: {SEARCH: {'requiredAttrs': ['query']}, + OWNED: {'requiredAttrs': []}, + ALL: {'requiredAttrs': []}}, +} + + +def die(msg): + sys.stderr.write(msg + "\n") + sys.exit(1) + + +def whatToCls(what): + return "".join([s.capitalize() for s in what.split("-")]) + + +def clsToWhat(cls): + return camel_re.sub(r'\1-\2', cls.__name__).lower() + + +def populate_sub_parser_by_class(cls, sub_parser): + for action_name in ACTIONS: + attr = 'can' + action_name.capitalize() + y = getattr(cls, attr) or getattr(gitlab.GitlabObject, attr) + if not y: + 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] + + 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) + + elif action_name in [GET, DELETE]: + if cls not in [gitlab.CurrentUser]: + 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] + + elif 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] + + elif action_name == UPDATE: + [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 cls in extra_actions: + for action_name in sorted(extra_actions[cls]): + sub_parser_action = sub_parser.add_parser(action_name) + d = extra_actions[cls][action_name] + [sub_parser_action.add_argument("--%s" % arg, required=True) + for arg in d['requiredAttrs']] + + +def do_auth(gitlab_url, gitlab_token, ssl_verify, timeout): + try: + gl = gitlab.Gitlab(gitlab_url, private_token=gitlab_token, + ssl_verify=ssl_verify, timeout=timeout) + gl.auth() + return gl + except Exception as e: + die("Could not connect to GitLab %s (%s)" % (gitlab_url, str(e))) + + +def get_id(cls, args): + try: + id = args.pop(cls.idAttr) + except Exception: + die("Missing --%s argument" % cls.idAttr.replace('_', '-')) + + return id + + +def do_create(cls, gl, what, args): + if not cls.canCreate: + die("%s objects can't be created" % what) + + try: + o = cls(gl, args) + o.save() + except Exception as e: + die("Impossible to create object (%s)" % str(e)) + + return o + + +def do_list(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 (%s)" % str(e)) + + return l + + +def do_get(cls, gl, what, args): + if not cls.canGet: + die("%s objects can't be retrieved" % what) + + id = None + if cls not in [gitlab.CurrentUser]: + id = get_id(cls, args) + + try: + o = cls(gl, id, **args) + except Exception as e: + die("Impossible to get object (%s)" % str(e)) + + return o + + +def do_delete(cls, gl, what, args): + if not cls.canDelete: + die("%s objects can't be deleted" % what) + + o = do_get(cls, args) + try: + o.delete() + except Exception as e: + die("Impossible to destroy object (%s)" % str(e)) + + +def do_update(cls, gl, what, args): + if not cls.canUpdate: + die("%s objects can't be updated" % what) + + o = do_get(cls, args) + try: + for k, v in args.items(): + o.__dict__[k] = v + o.save() + except Exception as e: + die("Impossible to update object (%s)" % str(e)) + + return o + + +def do_project_search(gl, what, args): + try: + return gl.search_projects(args['query']) + except Exception as e: + die("Impossible to search projects (%s)" % str(e)) + + +def do_project_all(gl, what, args): + try: + return gl.all_projects() + except Exception as e: + die("Impossible to list all projects (%s)" % str(e)) + + +def do_project_owned(gl, what, args): + try: + return gl.owned_projects() + except Exception as e: + die("Impossible to list owned projects (%s)" % str(e)) + + +def main(): + ssl_verify = True + timeout = 60 + + parser = argparse.ArgumentParser( + description="GitLab API Command Line Interface") + parser.add_argument("-v", "--verbose", "--fancy", + help="increase output verbosity", + action="store_true") + parser.add_argument("--gitlab", + help=("Specifies which python-gitlab.cfg " + "configuration section should be used. " + "If not defined, the default selection " + "will be used."), + required=False) + + subparsers = parser.add_subparsers(dest='what') + + # 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() + + for cls in classes: + arg_name = clsToWhat(cls) + object_group = subparsers.add_parser(arg_name) + + object_subparsers = object_group.add_subparsers(dest='action') + populate_sub_parser_by_class(cls, object_subparsers) + + arg = parser.parse_args() + args = arg.__dict__ + + # read the config + config = configparser.ConfigParser() + config.read(['/etc/python-gitlab.cfg', + os.path.expanduser('~/.python-gitlab.cfg')]) + gitlab_id = arg.gitlab + # conflicts with "gitlab" attribute from GitlabObject class + args.pop("gitlab") + verbose = arg.verbose + action = arg.action + what = arg.what + + if gitlab_id is None: + try: + gitlab_id = config.get('global', 'default') + except Exception: + die("Impossible to get the gitlab id " + "(not specified in config file)") + + try: + gitlab_url = config.get(gitlab_id, 'url') + gitlab_token = config.get(gitlab_id, 'private_token') + except Exception: + die("Impossible to get gitlab informations from configuration " + "(%s)" % gitlab_id) + + try: + ssl_verify = config.getboolean('global', 'ssl_verify') + except Exception: + pass + try: + ssl_verify = config.getboolean(gitlab_id, 'ssl_verify') + except Exception: + pass + + try: + timeout = config.getint('global', 'timeout') + except Exception: + pass + try: + timeout = config.getint(gitlab_id, 'timeout') + except Exception: + pass + + cls = None + try: + cls = gitlab.__dict__[whatToCls(what)] + except Exception: + die("Unknown object: %s" % what) + + gl = do_auth(gitlab_url, gitlab_token, ssl_verify, timeout) + + if action == CREATE or action == GET: + o = globals()['do_%s' % action.lower()](cls, gl, what, args) + o.display(verbose) + + elif action == LIST: + for o in do_list(cls, gl, what, args): + o.display(verbose) + print("") + + elif action == DELETE or action == UPDATE: + o = globals()['do_%s' % action.lower()](cls, gl, what, args) + + elif action == PROTECT or action == UNPROTECT: + if cls != gitlab.ProjectBranch: + die("%s objects can't be protected" % what) + + o = do_get(cls, gl, what, args) + getattr(o, action)() + + elif action == SEARCH: + if cls != gitlab.Project: + die("%s objects don't support this request" % what) + + for o in do_project_search(gl, what, args): + o.display(verbose) + + elif action == OWNED: + if cls != gitlab.Project: + die("%s objects don't support this request" % what) + + for o in do_project_owned(gl, what, args): + o.display(verbose) + + elif action == ALL: + if cls != gitlab.Project: + die("%s objects don't support this request" % what) + + for o in do_project_all(gl, what, args): + o.display(verbose) + + sys.exit(0) |