diff options
Diffstat (limited to 'gitlab/v4/cli.py')
-rw-r--r-- | gitlab/v4/cli.py | 500 |
1 files changed, 0 insertions, 500 deletions
diff --git a/gitlab/v4/cli.py b/gitlab/v4/cli.py deleted file mode 100644 index 6986552..0000000 --- a/gitlab/v4/cli.py +++ /dev/null @@ -1,500 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# Copyright (C) 2013-2017 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/>. - -import argparse -import operator -import sys -from typing import Any, Dict, List, Optional, Type, TYPE_CHECKING, Union - -import gitlab -import gitlab.base -import gitlab.v4.objects -from gitlab import cli - - -class GitlabCLI(object): - def __init__( - self, gl: gitlab.Gitlab, what: str, action: str, args: Dict[str, str] - ) -> None: - self.cls: Type[gitlab.base.RESTObject] = cli.what_to_cls( - what, namespace=gitlab.v4.objects - ) - self.cls_name = self.cls.__name__ - self.what = what.replace("-", "_") - self.action = action.lower() - self.gl = gl - self.args = args - self.mgr_cls: Union[ - Type[gitlab.mixins.CreateMixin], - Type[gitlab.mixins.DeleteMixin], - Type[gitlab.mixins.GetMixin], - Type[gitlab.mixins.GetWithoutIdMixin], - Type[gitlab.mixins.ListMixin], - Type[gitlab.mixins.UpdateMixin], - ] = getattr(gitlab.v4.objects, self.cls.__name__ + "Manager") - # We could do something smart, like splitting the manager name to find - # parents, build the chain of managers to get to the final object. - # Instead we do something ugly and efficient: interpolate variables in - # the class _path attribute, and replace the value with the result. - if TYPE_CHECKING: - assert self.mgr_cls._path is not None - self.mgr_cls._path = self.mgr_cls._path % self.args - self.mgr = self.mgr_cls(gl) - - if self.mgr_cls._types: - for attr_name, type_cls in self.mgr_cls._types.items(): - if attr_name in self.args.keys(): - obj = type_cls() - obj.set_from_cli(self.args[attr_name]) - self.args[attr_name] = obj.get() - - def __call__(self) -> Any: - # Check for a method that matches object + action - method = "do_%s_%s" % (self.what, self.action) - if hasattr(self, method): - return getattr(self, method)() - - # Fallback to standard actions (get, list, create, ...) - method = "do_%s" % self.action - if hasattr(self, method): - return getattr(self, method)() - - # Finally try to find custom methods - return self.do_custom() - - def do_custom(self) -> Any: - in_obj = cli.custom_actions[self.cls_name][self.action][2] - - # Get the object (lazy), then act - if in_obj: - data = {} - if self.mgr._from_parent_attrs: - for k in self.mgr._from_parent_attrs: - data[k] = self.args[k] - if not issubclass(self.cls, gitlab.mixins.GetWithoutIdMixin): - if TYPE_CHECKING: - assert isinstance(self.cls._id_attr, str) - data[self.cls._id_attr] = self.args.pop(self.cls._id_attr) - obj = self.cls(self.mgr, data) - method_name = self.action.replace("-", "_") - return getattr(obj, method_name)(**self.args) - else: - return getattr(self.mgr, self.action)(**self.args) - - def do_project_export_download(self) -> None: - try: - project = self.gl.projects.get(int(self.args["project_id"]), lazy=True) - export_status = project.exports.get() - if TYPE_CHECKING: - assert export_status is not None - data = export_status.download() - sys.stdout.buffer.write(data) - - except Exception as e: - cli.die("Impossible to download the export", e) - - def do_create(self) -> gitlab.base.RESTObject: - if TYPE_CHECKING: - assert isinstance(self.mgr, gitlab.mixins.CreateMixin) - try: - result = self.mgr.create(self.args) - except Exception as e: - cli.die("Impossible to create object", e) - return result - - def do_list( - self, - ) -> Union[gitlab.base.RESTObjectList, List[gitlab.base.RESTObject]]: - if TYPE_CHECKING: - assert isinstance(self.mgr, gitlab.mixins.ListMixin) - try: - result = self.mgr.list(**self.args) - except Exception as e: - cli.die("Impossible to list objects", e) - return result - - def do_get(self) -> Optional[gitlab.base.RESTObject]: - if isinstance(self.mgr, gitlab.mixins.GetWithoutIdMixin): - try: - result = self.mgr.get(id=None, **self.args) - except Exception as e: - cli.die("Impossible to get object", e) - return result - - if TYPE_CHECKING: - assert isinstance(self.mgr, gitlab.mixins.GetMixin) - assert isinstance(self.cls._id_attr, str) - - id = self.args.pop(self.cls._id_attr) - try: - result = self.mgr.get(id, lazy=False, **self.args) - except Exception as e: - cli.die("Impossible to get object", e) - return result - - def do_delete(self) -> None: - if TYPE_CHECKING: - assert isinstance(self.mgr, gitlab.mixins.DeleteMixin) - assert isinstance(self.cls._id_attr, str) - id = self.args.pop(self.cls._id_attr) - try: - self.mgr.delete(id, **self.args) - except Exception as e: - cli.die("Impossible to destroy object", e) - - def do_update(self) -> Dict[str, Any]: - if TYPE_CHECKING: - assert isinstance(self.mgr, gitlab.mixins.UpdateMixin) - if issubclass(self.mgr_cls, gitlab.mixins.GetWithoutIdMixin): - id = None - else: - if TYPE_CHECKING: - assert isinstance(self.cls._id_attr, str) - id = self.args.pop(self.cls._id_attr) - - try: - result = self.mgr.update(id, self.args) - except Exception as e: - cli.die("Impossible to update object", e) - return result - - -def _populate_sub_parser_by_class( - cls: Type[gitlab.base.RESTObject], sub_parser: argparse._SubParsersAction -) -> None: - mgr_cls_name = cls.__name__ + "Manager" - mgr_cls = getattr(gitlab.v4.objects, mgr_cls_name) - - for action_name in ["list", "get", "create", "update", "delete"]: - if not hasattr(mgr_cls, action_name): - continue - - sub_parser_action = sub_parser.add_parser(action_name) - sub_parser_action.add_argument("--sudo", required=False) - if mgr_cls._from_parent_attrs: - for x in mgr_cls._from_parent_attrs: - sub_parser_action.add_argument( - "--%s" % x.replace("_", "-"), required=True - ) - - if action_name == "list": - for x in mgr_cls._list_filters: - sub_parser_action.add_argument( - "--%s" % x.replace("_", "-"), required=False - ) - - 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 == "delete": - if cls._id_attr is not None: - id_attr = cls._id_attr.replace("_", "-") - sub_parser_action.add_argument("--%s" % id_attr, required=True) - - if action_name == "get": - if not issubclass(cls, gitlab.mixins.GetWithoutIdMixin): - if cls._id_attr is not None: - id_attr = cls._id_attr.replace("_", "-") - sub_parser_action.add_argument("--%s" % id_attr, required=True) - - for x in mgr_cls._optional_get_attrs: - sub_parser_action.add_argument( - "--%s" % x.replace("_", "-"), required=False - ) - - if action_name == "create": - for x in mgr_cls._create_attrs.required: - sub_parser_action.add_argument( - "--%s" % x.replace("_", "-"), required=True - ) - for x in mgr_cls._create_attrs.optional: - sub_parser_action.add_argument( - "--%s" % x.replace("_", "-"), required=False - ) - - if action_name == "update": - if cls._id_attr is not None: - id_attr = cls._id_attr.replace("_", "-") - sub_parser_action.add_argument("--%s" % id_attr, required=True) - - for x in mgr_cls._update_attrs.required: - if x != cls._id_attr: - sub_parser_action.add_argument( - "--%s" % x.replace("_", "-"), required=True - ) - - for x in mgr_cls._update_attrs.optional: - if x != cls._id_attr: - sub_parser_action.add_argument( - "--%s" % x.replace("_", "-"), required=False - ) - - if cls.__name__ in cli.custom_actions: - name = cls.__name__ - for action_name in cli.custom_actions[name]: - sub_parser_action = sub_parser.add_parser(action_name) - # Get the attributes for URL/path construction - if mgr_cls._from_parent_attrs: - for x in mgr_cls._from_parent_attrs: - sub_parser_action.add_argument( - "--%s" % x.replace("_", "-"), required=True - ) - sub_parser_action.add_argument("--sudo", required=False) - - # We need to get the object somehow - if not issubclass(cls, gitlab.mixins.GetWithoutIdMixin): - if cls._id_attr is not None: - id_attr = cls._id_attr.replace("_", "-") - sub_parser_action.add_argument("--%s" % id_attr, required=True) - - required, optional, dummy = cli.custom_actions[name][action_name] - [ - sub_parser_action.add_argument( - "--%s" % x.replace("_", "-"), required=True - ) - for x in required - if x != cls._id_attr - ] - [ - sub_parser_action.add_argument( - "--%s" % x.replace("_", "-"), required=False - ) - for x in optional - if x != cls._id_attr - ] - - if mgr_cls.__name__ in cli.custom_actions: - name = mgr_cls.__name__ - for action_name in cli.custom_actions[name]: - sub_parser_action = sub_parser.add_parser(action_name) - if mgr_cls._from_parent_attrs: - for x in mgr_cls._from_parent_attrs: - sub_parser_action.add_argument( - "--%s" % x.replace("_", "-"), required=True - ) - sub_parser_action.add_argument("--sudo", required=False) - - required, optional, dummy = cli.custom_actions[name][action_name] - [ - sub_parser_action.add_argument( - "--%s" % x.replace("_", "-"), required=True - ) - for x in required - if x != cls._id_attr - ] - [ - sub_parser_action.add_argument( - "--%s" % x.replace("_", "-"), required=False - ) - for x in optional - if x != cls._id_attr - ] - - -def extend_parser(parser: argparse.ArgumentParser) -> argparse.ArgumentParser: - 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.v4.objects.__dict__.values(): - if not isinstance(cls, type): - continue - if issubclass(cls, gitlab.base.RESTManager): - if cls._obj_cls is not None: - classes.append(cls._obj_cls) - classes.sort(key=operator.attrgetter("__name__")) - - for cls in classes: - arg_name = cli.cls_to_what(cls) - object_group = subparsers.add_parser(arg_name) - - object_subparsers = object_group.add_subparsers( - title="action", dest="whaction", help="Action to execute." - ) - _populate_sub_parser_by_class(cls, object_subparsers) - object_subparsers.required = True - - return parser - - -def get_dict( - obj: Union[str, gitlab.base.RESTObject], fields: List[str] -) -> Union[str, Dict[str, Any]]: - if isinstance(obj, str): - return obj - - if fields: - return {k: v for k, v in obj.attributes.items() if k in fields} - return obj.attributes - - -class JSONPrinter(object): - def display(self, d: Union[str, Dict[str, Any]], **kwargs: Any) -> None: - import json # noqa - - print(json.dumps(d)) - - def display_list( - self, - data: List[Union[str, gitlab.base.RESTObject]], - fields: List[str], - **kwargs: Any - ) -> None: - import json # noqa - - print(json.dumps([get_dict(obj, fields) for obj in data])) - - -class YAMLPrinter(object): - def display(self, d: Union[str, Dict[str, Any]], **kwargs: Any) -> None: - try: - import yaml # noqa - - print(yaml.safe_dump(d, default_flow_style=False)) - except ImportError: - exit( - "PyYaml is not installed.\n" - "Install it with `pip install PyYaml` " - "to use the yaml output feature" - ) - - def display_list( - self, - data: List[Union[str, gitlab.base.RESTObject]], - fields: List[str], - **kwargs: Any - ) -> None: - try: - import yaml # noqa - - print( - yaml.safe_dump( - [get_dict(obj, fields) for obj in data], default_flow_style=False - ) - ) - except ImportError: - exit( - "PyYaml is not installed.\n" - "Install it with `pip install PyYaml` " - "to use the yaml output feature" - ) - - -class LegacyPrinter(object): - def display(self, d: Union[str, Dict[str, Any]], **kwargs: Any) -> None: - verbose = kwargs.get("verbose", False) - padding = kwargs.get("padding", 0) - obj: Optional[Union[Dict[str, Any], gitlab.base.RESTObject]] = kwargs.get("obj") - if TYPE_CHECKING: - assert obj is not None - - def display_dict(d: Dict[str, Any], padding: int) -> None: - for k in sorted(d.keys()): - v = d[k] - if isinstance(v, dict): - print("%s%s:" % (" " * padding, k.replace("_", "-"))) - new_padding = padding + 2 - self.display(v, verbose=True, padding=new_padding, obj=v) - continue - print("%s%s: %s" % (" " * padding, k.replace("_", "-"), v)) - - if verbose: - if isinstance(obj, dict): - display_dict(obj, padding) - return - - # not a dict, we assume it's a RESTObject - if obj._id_attr: - id = getattr(obj, obj._id_attr, None) - print("%s: %s" % (obj._id_attr, id)) - attrs = obj.attributes - if obj._id_attr: - attrs.pop(obj._id_attr) - display_dict(attrs, padding) - - else: - if TYPE_CHECKING: - assert isinstance(obj, gitlab.base.RESTObject) - if obj._id_attr: - id = getattr(obj, obj._id_attr) - print("%s: %s" % (obj._id_attr.replace("_", "-"), id)) - if obj._short_print_attr: - value = getattr(obj, obj._short_print_attr) or "None" - value = value.replace("\r", "").replace("\n", " ") - # If the attribute is a note (ProjectCommitComment) then we do - # some modifications to fit everything on one line - line = "%s: %s" % (obj._short_print_attr, value) - # ellipsize long lines (comments) - if len(line) > 79: - line = line[:76] + "..." - print(line) - - def display_list( - self, - data: List[Union[str, gitlab.base.RESTObject]], - fields: List[str], - **kwargs: Any - ) -> None: - verbose = kwargs.get("verbose", False) - for obj in data: - if isinstance(obj, gitlab.base.RESTObject): - self.display(get_dict(obj, fields), verbose=verbose, obj=obj) - else: - print(obj) - print("") - - -PRINTERS: Dict[ - str, Union[Type[JSONPrinter], Type[LegacyPrinter], Type[YAMLPrinter]] -] = { - "json": JSONPrinter, - "legacy": LegacyPrinter, - "yaml": YAMLPrinter, -} - - -def run( - gl: gitlab.Gitlab, - what: str, - action: str, - args: Dict[str, Any], - verbose: bool, - output: str, - fields: List[str], -) -> None: - g_cli = GitlabCLI(gl=gl, what=what, action=action, args=args) - data = g_cli() - - printer: Union[JSONPrinter, LegacyPrinter, YAMLPrinter] = PRINTERS[output]() - - if isinstance(data, dict): - printer.display(data, verbose=True, obj=data) - elif isinstance(data, list): - printer.display_list(data, fields, verbose=verbose) - elif isinstance(data, gitlab.base.RESTObject): - printer.display(get_dict(data, fields), verbose=verbose, obj=data) - elif isinstance(data, str): - print(data) - elif isinstance(data, bytes): - sys.stdout.buffer.write(data) - elif hasattr(data, "decode"): - print(data.decode()) |