python-gitlab 4.5.0__py3-none-any.whl → 4.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gitlab/_version.py +1 -1
- gitlab/cli.py +44 -45
- gitlab/client.py +2 -2
- gitlab/mixins.py +22 -14
- gitlab/v4/cli.py +25 -12
- gitlab/v4/objects/__init__.py +1 -0
- gitlab/v4/objects/artifacts.py +5 -2
- gitlab/v4/objects/ci_lint.py +4 -4
- gitlab/v4/objects/commits.py +6 -6
- gitlab/v4/objects/container_registry.py +2 -2
- gitlab/v4/objects/deploy_keys.py +6 -1
- gitlab/v4/objects/deployments.py +2 -2
- gitlab/v4/objects/environments.py +1 -1
- gitlab/v4/objects/files.py +15 -7
- gitlab/v4/objects/geo_nodes.py +4 -4
- gitlab/v4/objects/groups.py +13 -7
- gitlab/v4/objects/integrations.py +3 -1
- gitlab/v4/objects/issues.py +6 -4
- gitlab/v4/objects/iterations.py +29 -2
- gitlab/v4/objects/jobs.py +11 -14
- gitlab/v4/objects/merge_request_approvals.py +8 -3
- gitlab/v4/objects/merge_requests.py +13 -12
- gitlab/v4/objects/milestones.py +4 -4
- gitlab/v4/objects/namespaces.py +3 -1
- gitlab/v4/objects/packages.py +4 -4
- gitlab/v4/objects/pipelines.py +25 -4
- gitlab/v4/objects/projects.py +21 -18
- gitlab/v4/objects/repositories.py +13 -9
- gitlab/v4/objects/runners.py +2 -2
- gitlab/v4/objects/secure_files.py +1 -1
- gitlab/v4/objects/service_accounts.py +18 -0
- gitlab/v4/objects/sidekiq.py +4 -4
- gitlab/v4/objects/snippets.py +3 -3
- gitlab/v4/objects/todos.py +2 -2
- gitlab/v4/objects/topics.py +2 -2
- gitlab/v4/objects/users.py +10 -10
- {python_gitlab-4.5.0.dist-info → python_gitlab-4.7.0.dist-info}/METADATA +3 -3
- {python_gitlab-4.5.0.dist-info → python_gitlab-4.7.0.dist-info}/RECORD +43 -42
- {python_gitlab-4.5.0.dist-info → python_gitlab-4.7.0.dist-info}/WHEEL +1 -1
- {python_gitlab-4.5.0.dist-info → python_gitlab-4.7.0.dist-info}/AUTHORS +0 -0
- {python_gitlab-4.5.0.dist-info → python_gitlab-4.7.0.dist-info}/COPYING +0 -0
- {python_gitlab-4.5.0.dist-info → python_gitlab-4.7.0.dist-info}/entry_points.txt +0 -0
- {python_gitlab-4.5.0.dist-info → python_gitlab-4.7.0.dist-info}/top_level.txt +0 -0
gitlab/_version.py
CHANGED
gitlab/cli.py
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
import argparse
|
2
|
+
import dataclasses
|
2
3
|
import functools
|
3
4
|
import os
|
4
5
|
import pathlib
|
5
6
|
import re
|
6
7
|
import sys
|
7
|
-
import textwrap
|
8
8
|
from types import ModuleType
|
9
9
|
from typing import (
|
10
10
|
Any,
|
@@ -29,12 +29,22 @@ from gitlab.base import RESTObject
|
|
29
29
|
camel_upperlower_regex = re.compile(r"([A-Z]+)([A-Z][a-z])")
|
30
30
|
camel_lowerupper_regex = re.compile(r"([a-z\d])([A-Z])")
|
31
31
|
|
32
|
+
|
33
|
+
@dataclasses.dataclass
|
34
|
+
class CustomAction:
|
35
|
+
required: Tuple[str, ...]
|
36
|
+
optional: Tuple[str, ...]
|
37
|
+
in_object: bool
|
38
|
+
requires_id: bool # if the `_id_attr` value should be a required argument
|
39
|
+
help: Optional[str] # help text for the custom action
|
40
|
+
|
41
|
+
|
32
42
|
# custom_actions = {
|
33
43
|
# cls: {
|
34
|
-
# action:
|
44
|
+
# action: CustomAction,
|
35
45
|
# },
|
36
46
|
# }
|
37
|
-
custom_actions: Dict[str, Dict[str,
|
47
|
+
custom_actions: Dict[str, Dict[str, CustomAction]] = {}
|
38
48
|
|
39
49
|
|
40
50
|
# For an explanation of how these type-hints work see:
|
@@ -44,38 +54,14 @@ custom_actions: Dict[str, Dict[str, Tuple[Tuple[str, ...], Tuple[str, ...], bool
|
|
44
54
|
__F = TypeVar("__F", bound=Callable[..., Any])
|
45
55
|
|
46
56
|
|
47
|
-
class VerticalHelpFormatter(argparse.HelpFormatter):
|
48
|
-
def format_help(self) -> str:
|
49
|
-
result = super().format_help()
|
50
|
-
output = ""
|
51
|
-
indent = self._indent_increment * " "
|
52
|
-
for line in result.splitlines(keepends=True):
|
53
|
-
# All of our resources are on one line and wrapped inside braces.
|
54
|
-
# For example: {application,resource1,resource2}
|
55
|
-
# except if there are fewer resources - then the line and help text
|
56
|
-
# are collapsed on the same line.
|
57
|
-
# For example: {list} Action to execute on the GitLab resource.
|
58
|
-
# We then put each resource on its own line to make it easier to read.
|
59
|
-
if line.strip().startswith("{"):
|
60
|
-
choice_string, help_string = line.split("}", 1)
|
61
|
-
choice_list = choice_string.strip(" {").split(",")
|
62
|
-
help_string = help_string.strip()
|
63
|
-
|
64
|
-
if help_string:
|
65
|
-
help_indent = len(max(choice_list, key=len)) * " "
|
66
|
-
choice_list.append(f"{help_indent} {help_string}")
|
67
|
-
|
68
|
-
choices = "\n".join(choice_list)
|
69
|
-
line = f"{textwrap.indent(choices, indent)}\n"
|
70
|
-
output += line
|
71
|
-
return output
|
72
|
-
|
73
|
-
|
74
57
|
def register_custom_action(
|
58
|
+
*,
|
75
59
|
cls_names: Union[str, Tuple[str, ...]],
|
76
|
-
|
60
|
+
required: Tuple[str, ...] = (),
|
77
61
|
optional: Tuple[str, ...] = (),
|
78
62
|
custom_action: Optional[str] = None,
|
63
|
+
requires_id: bool = True, # if the `_id_attr` value should be a required argument
|
64
|
+
help: Optional[str] = None, # help text for the action
|
79
65
|
) -> Callable[[__F], __F]:
|
80
66
|
def wrap(f: __F) -> __F:
|
81
67
|
@functools.wraps(f)
|
@@ -98,7 +84,13 @@ def register_custom_action(
|
|
98
84
|
custom_actions[final_name] = {}
|
99
85
|
|
100
86
|
action = custom_action or f.__name__.replace("_", "-")
|
101
|
-
custom_actions[final_name][action] = (
|
87
|
+
custom_actions[final_name][action] = CustomAction(
|
88
|
+
required=required,
|
89
|
+
optional=optional,
|
90
|
+
in_object=in_obj,
|
91
|
+
requires_id=requires_id,
|
92
|
+
help=help,
|
93
|
+
)
|
102
94
|
|
103
95
|
return cast(__F, wrapped_f)
|
104
96
|
|
@@ -134,7 +126,6 @@ def _get_base_parser(add_help: bool = True) -> argparse.ArgumentParser:
|
|
134
126
|
parser = argparse.ArgumentParser(
|
135
127
|
add_help=add_help,
|
136
128
|
description="GitLab API Command Line Interface",
|
137
|
-
formatter_class=VerticalHelpFormatter,
|
138
129
|
allow_abbrev=False,
|
139
130
|
)
|
140
131
|
parser.add_argument("--version", help="Display the version.", action="store_true")
|
@@ -292,6 +283,12 @@ def _get_base_parser(add_help: bool = True) -> argparse.ArgumentParser:
|
|
292
283
|
action="store_true",
|
293
284
|
default=os.getenv("GITLAB_SKIP_LOGIN"),
|
294
285
|
)
|
286
|
+
parser.add_argument(
|
287
|
+
"--no-mask-credentials",
|
288
|
+
help="Don't mask credentials in debug mode",
|
289
|
+
dest="mask_credentials",
|
290
|
+
action="store_false",
|
291
|
+
)
|
295
292
|
return parser
|
296
293
|
|
297
294
|
|
@@ -379,29 +376,31 @@ def main() -> None:
|
|
379
376
|
gitlab_resource = args.gitlab_resource
|
380
377
|
resource_action = args.resource_action
|
381
378
|
skip_login = args.skip_login
|
379
|
+
mask_credentials = args.mask_credentials
|
382
380
|
|
383
381
|
args_dict = vars(args)
|
384
382
|
# Remove CLI behavior-related args
|
385
383
|
for item in (
|
386
|
-
"
|
384
|
+
"api_version",
|
387
385
|
"config_file",
|
388
|
-
"verbose",
|
389
386
|
"debug",
|
387
|
+
"fields",
|
388
|
+
"gitlab",
|
390
389
|
"gitlab_resource",
|
391
|
-
"
|
392
|
-
"
|
390
|
+
"job_token",
|
391
|
+
"mask_credentials",
|
392
|
+
"oauth_token",
|
393
393
|
"output",
|
394
|
-
"
|
394
|
+
"pagination",
|
395
|
+
"private_token",
|
396
|
+
"resource_action",
|
395
397
|
"server_url",
|
398
|
+
"skip_login",
|
396
399
|
"ssl_verify",
|
397
400
|
"timeout",
|
398
|
-
"api_version",
|
399
|
-
"pagination",
|
400
401
|
"user_agent",
|
401
|
-
"
|
402
|
-
"
|
403
|
-
"job_token",
|
404
|
-
"skip_login",
|
402
|
+
"verbose",
|
403
|
+
"version",
|
405
404
|
):
|
406
405
|
args_dict.pop(item)
|
407
406
|
args_dict = {k: _parse_value(v) for k, v in args_dict.items() if v is not None}
|
@@ -409,7 +408,7 @@ def main() -> None:
|
|
409
408
|
try:
|
410
409
|
gl = gitlab.Gitlab.merge_config(vars(options), gitlab_id, config_files)
|
411
410
|
if debug:
|
412
|
-
gl.enable_debug()
|
411
|
+
gl.enable_debug(mask_credentials=mask_credentials)
|
413
412
|
if not skip_login and (gl.private_token or gl.oauth_token):
|
414
413
|
gl.auth()
|
415
414
|
except Exception as e:
|
gitlab/client.py
CHANGED
@@ -625,8 +625,8 @@ class Gitlab:
|
|
625
625
|
for item in result.history:
|
626
626
|
if item.status_code not in (301, 302):
|
627
627
|
continue
|
628
|
-
# GET methods can be redirected without issue
|
629
|
-
if item.request.method
|
628
|
+
# GET and HEAD methods can be redirected without issue
|
629
|
+
if item.request.method in ("GET", "HEAD"):
|
630
630
|
continue
|
631
631
|
target = item.headers.get("location")
|
632
632
|
raise gitlab.exceptions.RedirectError(
|
gitlab/mixins.py
CHANGED
@@ -550,7 +550,7 @@ class UserAgentDetailMixin(_RestObjectBase):
|
|
550
550
|
_updated_attrs: Dict[str, Any]
|
551
551
|
manager: base.RESTManager
|
552
552
|
|
553
|
-
@cli.register_custom_action(("Snippet", "ProjectSnippet", "ProjectIssue"))
|
553
|
+
@cli.register_custom_action(cls_names=("Snippet", "ProjectSnippet", "ProjectIssue"))
|
554
554
|
@exc.on_http_error(exc.GitlabGetError)
|
555
555
|
def user_agent_detail(self, **kwargs: Any) -> Dict[str, Any]:
|
556
556
|
"""Get the user agent detail.
|
@@ -578,7 +578,8 @@ class AccessRequestMixin(_RestObjectBase):
|
|
578
578
|
manager: base.RESTManager
|
579
579
|
|
580
580
|
@cli.register_custom_action(
|
581
|
-
("ProjectAccessRequest", "GroupAccessRequest"),
|
581
|
+
cls_names=("ProjectAccessRequest", "GroupAccessRequest"),
|
582
|
+
optional=("access_level",),
|
582
583
|
)
|
583
584
|
@exc.on_http_error(exc.GitlabUpdateError)
|
584
585
|
def approve(
|
@@ -611,7 +612,7 @@ class DownloadMixin(_RestObjectBase):
|
|
611
612
|
_updated_attrs: Dict[str, Any]
|
612
613
|
manager: base.RESTManager
|
613
614
|
|
614
|
-
@cli.register_custom_action(("GroupExport", "ProjectExport"))
|
615
|
+
@cli.register_custom_action(cls_names=("GroupExport", "ProjectExport"))
|
615
616
|
@exc.on_http_error(exc.GitlabGetError)
|
616
617
|
def download(
|
617
618
|
self,
|
@@ -721,7 +722,7 @@ class SubscribableMixin(_RestObjectBase):
|
|
721
722
|
manager: base.RESTManager
|
722
723
|
|
723
724
|
@cli.register_custom_action(
|
724
|
-
("ProjectIssue", "ProjectMergeRequest", "ProjectLabel", "GroupLabel")
|
725
|
+
cls_names=("ProjectIssue", "ProjectMergeRequest", "ProjectLabel", "GroupLabel")
|
725
726
|
)
|
726
727
|
@exc.on_http_error(exc.GitlabSubscribeError)
|
727
728
|
def subscribe(self, **kwargs: Any) -> None:
|
@@ -741,7 +742,7 @@ class SubscribableMixin(_RestObjectBase):
|
|
741
742
|
self._update_attrs(server_data)
|
742
743
|
|
743
744
|
@cli.register_custom_action(
|
744
|
-
("ProjectIssue", "ProjectMergeRequest", "ProjectLabel", "GroupLabel")
|
745
|
+
cls_names=("ProjectIssue", "ProjectMergeRequest", "ProjectLabel", "GroupLabel")
|
745
746
|
)
|
746
747
|
@exc.on_http_error(exc.GitlabUnsubscribeError)
|
747
748
|
def unsubscribe(self, **kwargs: Any) -> None:
|
@@ -769,7 +770,7 @@ class TodoMixin(_RestObjectBase):
|
|
769
770
|
_updated_attrs: Dict[str, Any]
|
770
771
|
manager: base.RESTManager
|
771
772
|
|
772
|
-
@cli.register_custom_action(("ProjectIssue", "ProjectMergeRequest"))
|
773
|
+
@cli.register_custom_action(cls_names=("ProjectIssue", "ProjectMergeRequest"))
|
773
774
|
@exc.on_http_error(exc.GitlabTodoError)
|
774
775
|
def todo(self, **kwargs: Any) -> None:
|
775
776
|
"""Create a todo associated to the object.
|
@@ -793,7 +794,7 @@ class TimeTrackingMixin(_RestObjectBase):
|
|
793
794
|
_updated_attrs: Dict[str, Any]
|
794
795
|
manager: base.RESTManager
|
795
796
|
|
796
|
-
@cli.register_custom_action(("ProjectIssue", "ProjectMergeRequest"))
|
797
|
+
@cli.register_custom_action(cls_names=("ProjectIssue", "ProjectMergeRequest"))
|
797
798
|
@exc.on_http_error(exc.GitlabTimeTrackingError)
|
798
799
|
def time_stats(self, **kwargs: Any) -> Dict[str, Any]:
|
799
800
|
"""Get time stats for the object.
|
@@ -819,7 +820,9 @@ class TimeTrackingMixin(_RestObjectBase):
|
|
819
820
|
assert not isinstance(result, requests.Response)
|
820
821
|
return result
|
821
822
|
|
822
|
-
@cli.register_custom_action(
|
823
|
+
@cli.register_custom_action(
|
824
|
+
cls_names=("ProjectIssue", "ProjectMergeRequest"), required=("duration",)
|
825
|
+
)
|
823
826
|
@exc.on_http_error(exc.GitlabTimeTrackingError)
|
824
827
|
def time_estimate(self, duration: str, **kwargs: Any) -> Dict[str, Any]:
|
825
828
|
"""Set an estimated time of work for the object.
|
@@ -839,7 +842,7 @@ class TimeTrackingMixin(_RestObjectBase):
|
|
839
842
|
assert not isinstance(result, requests.Response)
|
840
843
|
return result
|
841
844
|
|
842
|
-
@cli.register_custom_action(("ProjectIssue", "ProjectMergeRequest"))
|
845
|
+
@cli.register_custom_action(cls_names=("ProjectIssue", "ProjectMergeRequest"))
|
843
846
|
@exc.on_http_error(exc.GitlabTimeTrackingError)
|
844
847
|
def reset_time_estimate(self, **kwargs: Any) -> Dict[str, Any]:
|
845
848
|
"""Resets estimated time for the object to 0 seconds.
|
@@ -857,7 +860,9 @@ class TimeTrackingMixin(_RestObjectBase):
|
|
857
860
|
assert not isinstance(result, requests.Response)
|
858
861
|
return result
|
859
862
|
|
860
|
-
@cli.register_custom_action(
|
863
|
+
@cli.register_custom_action(
|
864
|
+
cls_names=("ProjectIssue", "ProjectMergeRequest"), required=("duration",)
|
865
|
+
)
|
861
866
|
@exc.on_http_error(exc.GitlabTimeTrackingError)
|
862
867
|
def add_spent_time(self, duration: str, **kwargs: Any) -> Dict[str, Any]:
|
863
868
|
"""Add time spent working on the object.
|
@@ -877,7 +882,7 @@ class TimeTrackingMixin(_RestObjectBase):
|
|
877
882
|
assert not isinstance(result, requests.Response)
|
878
883
|
return result
|
879
884
|
|
880
|
-
@cli.register_custom_action(("ProjectIssue", "ProjectMergeRequest"))
|
885
|
+
@cli.register_custom_action(cls_names=("ProjectIssue", "ProjectMergeRequest"))
|
881
886
|
@exc.on_http_error(exc.GitlabTimeTrackingError)
|
882
887
|
def reset_spent_time(self, **kwargs: Any) -> Dict[str, Any]:
|
883
888
|
"""Resets the time spent working on the object.
|
@@ -904,7 +909,7 @@ class ParticipantsMixin(_RestObjectBase):
|
|
904
909
|
_updated_attrs: Dict[str, Any]
|
905
910
|
manager: base.RESTManager
|
906
911
|
|
907
|
-
@cli.register_custom_action(("ProjectMergeRequest", "ProjectIssue"))
|
912
|
+
@cli.register_custom_action(cls_names=("ProjectMergeRequest", "ProjectIssue"))
|
908
913
|
@exc.on_http_error(exc.GitlabListError)
|
909
914
|
def participants(self, **kwargs: Any) -> Dict[str, Any]:
|
910
915
|
"""List the participants.
|
@@ -932,7 +937,8 @@ class ParticipantsMixin(_RestObjectBase):
|
|
932
937
|
|
933
938
|
class BadgeRenderMixin(_RestManagerBase):
|
934
939
|
@cli.register_custom_action(
|
935
|
-
("GroupBadgeManager", "ProjectBadgeManager"),
|
940
|
+
cls_names=("GroupBadgeManager", "ProjectBadgeManager"),
|
941
|
+
required=("link_url", "image_url"),
|
936
942
|
)
|
937
943
|
@exc.on_http_error(exc.GitlabRenderError)
|
938
944
|
def render(self, link_url: str, image_url: str, **kwargs: Any) -> Dict[str, Any]:
|
@@ -1025,7 +1031,9 @@ class UploadMixin(_RestObjectBase):
|
|
1025
1031
|
data = self.attributes
|
1026
1032
|
return self._upload_path.format(**data)
|
1027
1033
|
|
1028
|
-
@cli.register_custom_action(
|
1034
|
+
@cli.register_custom_action(
|
1035
|
+
cls_names=("Project", "ProjectWiki"), required=("filename", "filepath")
|
1036
|
+
)
|
1029
1037
|
@exc.on_http_error(exc.GitlabUploadError)
|
1030
1038
|
def upload(
|
1031
1039
|
self,
|
gitlab/v4/cli.py
CHANGED
@@ -82,7 +82,7 @@ class GitlabCLI:
|
|
82
82
|
|
83
83
|
def do_custom(self) -> Any:
|
84
84
|
class_instance: Union[gitlab.base.RESTManager, gitlab.base.RESTObject]
|
85
|
-
in_obj = cli.custom_actions[self.cls_name][self.resource_action]
|
85
|
+
in_obj = cli.custom_actions[self.cls_name][self.resource_action].in_object
|
86
86
|
|
87
87
|
# Get the object (lazy), then act
|
88
88
|
if in_obj:
|
@@ -207,12 +207,20 @@ def _populate_sub_parser_by_class(
|
|
207
207
|
mgr_cls = getattr(gitlab.v4.objects, mgr_cls_name)
|
208
208
|
|
209
209
|
action_parsers: Dict[str, argparse.ArgumentParser] = {}
|
210
|
-
for action_name in [
|
210
|
+
for action_name, help_text in [
|
211
|
+
("list", "List the GitLab resources"),
|
212
|
+
("get", "Get a GitLab resource"),
|
213
|
+
("create", "Create a GitLab resource"),
|
214
|
+
("update", "Update a GitLab resource"),
|
215
|
+
("delete", "Delete a GitLab resource"),
|
216
|
+
]:
|
211
217
|
if not hasattr(mgr_cls, action_name):
|
212
218
|
continue
|
213
219
|
|
214
220
|
sub_parser_action = sub_parser.add_parser(
|
215
|
-
action_name,
|
221
|
+
action_name,
|
222
|
+
conflict_handler="resolve",
|
223
|
+
help=help_text,
|
216
224
|
)
|
217
225
|
action_parsers[action_name] = sub_parser_action
|
218
226
|
sub_parser_action.add_argument("--sudo", required=False)
|
@@ -292,11 +300,14 @@ def _populate_sub_parser_by_class(
|
|
292
300
|
if cls.__name__ in cli.custom_actions:
|
293
301
|
name = cls.__name__
|
294
302
|
for action_name in cli.custom_actions[name]:
|
303
|
+
custom_action = cli.custom_actions[name][action_name]
|
295
304
|
# NOTE(jlvillal): If we put a function for the `default` value of
|
296
305
|
# the `get` it will always get called, which will break things.
|
297
306
|
action_parser = action_parsers.get(action_name)
|
298
307
|
if action_parser is None:
|
299
|
-
sub_parser_action = sub_parser.add_parser(
|
308
|
+
sub_parser_action = sub_parser.add_parser(
|
309
|
+
action_name, help=custom_action.help
|
310
|
+
)
|
300
311
|
else:
|
301
312
|
sub_parser_action = action_parser
|
302
313
|
# Get the attributes for URL/path construction
|
@@ -309,17 +320,16 @@ def _populate_sub_parser_by_class(
|
|
309
320
|
|
310
321
|
# We need to get the object somehow
|
311
322
|
if not issubclass(cls, gitlab.mixins.GetWithoutIdMixin):
|
312
|
-
if cls._id_attr is not None:
|
323
|
+
if cls._id_attr is not None and custom_action.requires_id:
|
313
324
|
id_attr = cls._id_attr.replace("_", "-")
|
314
325
|
sub_parser_action.add_argument(f"--{id_attr}", required=True)
|
315
326
|
|
316
|
-
|
317
|
-
for x in required:
|
327
|
+
for x in custom_action.required:
|
318
328
|
if x != cls._id_attr:
|
319
329
|
sub_parser_action.add_argument(
|
320
330
|
f"--{x.replace('_', '-')}", required=True
|
321
331
|
)
|
322
|
-
for x in optional:
|
332
|
+
for x in custom_action.optional:
|
323
333
|
if x != cls._id_attr:
|
324
334
|
sub_parser_action.add_argument(
|
325
335
|
f"--{x.replace('_', '-')}", required=False
|
@@ -342,13 +352,13 @@ def _populate_sub_parser_by_class(
|
|
342
352
|
)
|
343
353
|
sub_parser_action.add_argument("--sudo", required=False)
|
344
354
|
|
345
|
-
|
346
|
-
for x in required:
|
355
|
+
custom_action = cli.custom_actions[name][action_name]
|
356
|
+
for x in custom_action.required:
|
347
357
|
if x != cls._id_attr:
|
348
358
|
sub_parser_action.add_argument(
|
349
359
|
f"--{x.replace('_', '-')}", required=True
|
350
360
|
)
|
351
|
-
for x in optional:
|
361
|
+
for x in custom_action.optional:
|
352
362
|
if x != cls._id_attr:
|
353
363
|
sub_parser_action.add_argument(
|
354
364
|
f"--{x.replace('_', '-')}", required=False
|
@@ -374,8 +384,11 @@ def extend_parser(parser: argparse.ArgumentParser) -> argparse.ArgumentParser:
|
|
374
384
|
|
375
385
|
for cls in sorted(classes, key=operator.attrgetter("__name__")):
|
376
386
|
arg_name = cli.cls_to_gitlab_resource(cls)
|
387
|
+
mgr_cls_name = f"{cls.__name__}Manager"
|
388
|
+
mgr_cls = getattr(gitlab.v4.objects, mgr_cls_name)
|
377
389
|
object_group = subparsers.add_parser(
|
378
|
-
arg_name,
|
390
|
+
arg_name,
|
391
|
+
help=f"API endpoint: {mgr_cls._path}",
|
379
392
|
)
|
380
393
|
|
381
394
|
object_subparsers = object_group.add_subparsers(
|
gitlab/v4/objects/__init__.py
CHANGED
gitlab/v4/objects/artifacts.py
CHANGED
@@ -44,7 +44,9 @@ class ProjectArtifactManager(RESTManager):
|
|
44
44
|
self.gitlab.http_delete(path, **kwargs)
|
45
45
|
|
46
46
|
@cli.register_custom_action(
|
47
|
-
"ProjectArtifactManager",
|
47
|
+
cls_names="ProjectArtifactManager",
|
48
|
+
required=("ref_name", "job"),
|
49
|
+
optional=("job_token",),
|
48
50
|
)
|
49
51
|
@exc.on_http_error(exc.GitlabGetError)
|
50
52
|
def download(
|
@@ -93,7 +95,8 @@ class ProjectArtifactManager(RESTManager):
|
|
93
95
|
)
|
94
96
|
|
95
97
|
@cli.register_custom_action(
|
96
|
-
"ProjectArtifactManager",
|
98
|
+
cls_names="ProjectArtifactManager",
|
99
|
+
required=("ref_name", "artifact_path", "job"),
|
97
100
|
)
|
98
101
|
@exc.on_http_error(exc.GitlabGetError)
|
99
102
|
def raw(
|
gitlab/v4/objects/ci_lint.py
CHANGED
@@ -31,8 +31,8 @@ class CiLintManager(CreateMixin, RESTManager):
|
|
31
31
|
)
|
32
32
|
|
33
33
|
@register_custom_action(
|
34
|
-
"CiLintManager",
|
35
|
-
("content",),
|
34
|
+
cls_names="CiLintManager",
|
35
|
+
required=("content",),
|
36
36
|
optional=("include_merged_yaml", "include_jobs"),
|
37
37
|
)
|
38
38
|
def validate(self, *args: Any, **kwargs: Any) -> None:
|
@@ -63,8 +63,8 @@ class ProjectCiLintManager(GetWithoutIdMixin, CreateMixin, RESTManager):
|
|
63
63
|
return cast(ProjectCiLint, super().get(**kwargs))
|
64
64
|
|
65
65
|
@register_custom_action(
|
66
|
-
"ProjectCiLintManager",
|
67
|
-
("content",),
|
66
|
+
cls_names="ProjectCiLintManager",
|
67
|
+
required=("content",),
|
68
68
|
optional=("dry_run", "include_jobs", "ref"),
|
69
69
|
)
|
70
70
|
def validate(self, *args: Any, **kwargs: Any) -> None:
|
gitlab/v4/objects/commits.py
CHANGED
@@ -28,7 +28,7 @@ class ProjectCommit(RESTObject):
|
|
28
28
|
discussions: ProjectCommitDiscussionManager
|
29
29
|
statuses: "ProjectCommitStatusManager"
|
30
30
|
|
31
|
-
@cli.register_custom_action("ProjectCommit")
|
31
|
+
@cli.register_custom_action(cls_names="ProjectCommit")
|
32
32
|
@exc.on_http_error(exc.GitlabGetError)
|
33
33
|
def diff(self, **kwargs: Any) -> Union[gitlab.GitlabList, List[Dict[str, Any]]]:
|
34
34
|
"""Generate the commit diff.
|
@@ -46,7 +46,7 @@ class ProjectCommit(RESTObject):
|
|
46
46
|
path = f"{self.manager.path}/{self.encoded_id}/diff"
|
47
47
|
return self.manager.gitlab.http_list(path, **kwargs)
|
48
48
|
|
49
|
-
@cli.register_custom_action("ProjectCommit", ("branch",))
|
49
|
+
@cli.register_custom_action(cls_names="ProjectCommit", required=("branch",))
|
50
50
|
@exc.on_http_error(exc.GitlabCherryPickError)
|
51
51
|
def cherry_pick(self, branch: str, **kwargs: Any) -> None:
|
52
52
|
"""Cherry-pick a commit into a branch.
|
@@ -63,7 +63,7 @@ class ProjectCommit(RESTObject):
|
|
63
63
|
post_data = {"branch": branch}
|
64
64
|
self.manager.gitlab.http_post(path, post_data=post_data, **kwargs)
|
65
65
|
|
66
|
-
@cli.register_custom_action("ProjectCommit", optional=("type",))
|
66
|
+
@cli.register_custom_action(cls_names="ProjectCommit", optional=("type",))
|
67
67
|
@exc.on_http_error(exc.GitlabGetError)
|
68
68
|
def refs(
|
69
69
|
self, type: str = "all", **kwargs: Any
|
@@ -85,7 +85,7 @@ class ProjectCommit(RESTObject):
|
|
85
85
|
query_data = {"type": type}
|
86
86
|
return self.manager.gitlab.http_list(path, query_data=query_data, **kwargs)
|
87
87
|
|
88
|
-
@cli.register_custom_action("ProjectCommit")
|
88
|
+
@cli.register_custom_action(cls_names="ProjectCommit")
|
89
89
|
@exc.on_http_error(exc.GitlabGetError)
|
90
90
|
def merge_requests(
|
91
91
|
self, **kwargs: Any
|
@@ -105,7 +105,7 @@ class ProjectCommit(RESTObject):
|
|
105
105
|
path = f"{self.manager.path}/{self.encoded_id}/merge_requests"
|
106
106
|
return self.manager.gitlab.http_list(path, **kwargs)
|
107
107
|
|
108
|
-
@cli.register_custom_action("ProjectCommit", ("branch",))
|
108
|
+
@cli.register_custom_action(cls_names="ProjectCommit", required=("branch",))
|
109
109
|
@exc.on_http_error(exc.GitlabRevertError)
|
110
110
|
def revert(
|
111
111
|
self, branch: str, **kwargs: Any
|
@@ -127,7 +127,7 @@ class ProjectCommit(RESTObject):
|
|
127
127
|
post_data = {"branch": branch}
|
128
128
|
return self.manager.gitlab.http_post(path, post_data=post_data, **kwargs)
|
129
129
|
|
130
|
-
@cli.register_custom_action("ProjectCommit")
|
130
|
+
@cli.register_custom_action(cls_names="ProjectCommit")
|
131
131
|
@exc.on_http_error(exc.GitlabGetError)
|
132
132
|
def signature(self, **kwargs: Any) -> Union[Dict[str, Any], requests.Response]:
|
133
133
|
"""Get the signature of the commit.
|
@@ -42,8 +42,8 @@ class ProjectRegistryTagManager(DeleteMixin, RetrieveMixin, RESTManager):
|
|
42
42
|
_path = "/projects/{project_id}/registry/repositories/{repository_id}/tags"
|
43
43
|
|
44
44
|
@cli.register_custom_action(
|
45
|
-
"ProjectRegistryTagManager",
|
46
|
-
("name_regex_delete",),
|
45
|
+
cls_names="ProjectRegistryTagManager",
|
46
|
+
required=("name_regex_delete",),
|
47
47
|
optional=("keep_n", "name_regex_keep", "older_than"),
|
48
48
|
)
|
49
49
|
@exc.on_http_error(exc.GitlabDeleteError)
|
gitlab/v4/objects/deploy_keys.py
CHANGED
@@ -36,7 +36,12 @@ class ProjectKeyManager(CRUDMixin, RESTManager):
|
|
36
36
|
_create_attrs = RequiredOptional(required=("title", "key"), optional=("can_push",))
|
37
37
|
_update_attrs = RequiredOptional(optional=("title", "can_push"))
|
38
38
|
|
39
|
-
@cli.register_custom_action(
|
39
|
+
@cli.register_custom_action(
|
40
|
+
cls_names="ProjectKeyManager",
|
41
|
+
required=("key_id",),
|
42
|
+
requires_id=False,
|
43
|
+
help="Enable a deploy key for the project",
|
44
|
+
)
|
40
45
|
@exc.on_http_error(exc.GitlabProjectDeployKeyError)
|
41
46
|
def enable(
|
42
47
|
self, key_id: int, **kwargs: Any
|
gitlab/v4/objects/deployments.py
CHANGED
@@ -23,8 +23,8 @@ class ProjectDeployment(SaveMixin, RESTObject):
|
|
23
23
|
mergerequests: ProjectDeploymentMergeRequestManager
|
24
24
|
|
25
25
|
@cli.register_custom_action(
|
26
|
-
"ProjectDeployment",
|
27
|
-
|
26
|
+
cls_names="ProjectDeployment",
|
27
|
+
required=("status",),
|
28
28
|
optional=("comment", "represented_as"),
|
29
29
|
)
|
30
30
|
@exc.on_http_error(exc.GitlabDeploymentApprovalError)
|
@@ -24,7 +24,7 @@ __all__ = [
|
|
24
24
|
|
25
25
|
|
26
26
|
class ProjectEnvironment(SaveMixin, ObjectDeleteMixin, RESTObject):
|
27
|
-
@cli.register_custom_action("ProjectEnvironment")
|
27
|
+
@cli.register_custom_action(cls_names="ProjectEnvironment")
|
28
28
|
@exc.on_http_error(exc.GitlabStopError)
|
29
29
|
def stop(self, **kwargs: Any) -> Union[Dict[str, Any], requests.Response]:
|
30
30
|
"""Stop the environment.
|
gitlab/v4/objects/files.py
CHANGED
@@ -40,6 +40,7 @@ class ProjectFile(SaveMixin, ObjectDeleteMixin, RESTObject):
|
|
40
40
|
commit_message: str
|
41
41
|
file_path: str
|
42
42
|
manager: "ProjectFileManager"
|
43
|
+
content: str # since the `decode()` method uses `self.content`
|
43
44
|
|
44
45
|
def decode(self) -> bytes:
|
45
46
|
"""Returns the decoded content of the file.
|
@@ -108,7 +109,9 @@ class ProjectFileManager(GetMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTMa
|
|
108
109
|
optional=("encoding", "author_email", "author_name"),
|
109
110
|
)
|
110
111
|
|
111
|
-
@cli.register_custom_action(
|
112
|
+
@cli.register_custom_action(
|
113
|
+
cls_names="ProjectFileManager", required=("file_path", "ref")
|
114
|
+
)
|
112
115
|
# NOTE(jlvillal): Signature doesn't match UpdateMixin.update() so ignore
|
113
116
|
# type error
|
114
117
|
def get( # type: ignore
|
@@ -131,9 +134,9 @@ class ProjectFileManager(GetMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTMa
|
|
131
134
|
return cast(ProjectFile, GetMixin.get(self, file_path, ref=ref, **kwargs))
|
132
135
|
|
133
136
|
@cli.register_custom_action(
|
134
|
-
"ProjectFileManager",
|
135
|
-
("file_path", "branch", "content", "commit_message"),
|
136
|
-
("encoding", "author_email", "author_name"),
|
137
|
+
cls_names="ProjectFileManager",
|
138
|
+
required=("file_path", "branch", "content", "commit_message"),
|
139
|
+
optional=("encoding", "author_email", "author_name"),
|
137
140
|
)
|
138
141
|
@exc.on_http_error(exc.GitlabCreateError)
|
139
142
|
def create(
|
@@ -198,7 +201,8 @@ class ProjectFileManager(GetMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTMa
|
|
198
201
|
return result
|
199
202
|
|
200
203
|
@cli.register_custom_action(
|
201
|
-
"ProjectFileManager",
|
204
|
+
cls_names="ProjectFileManager",
|
205
|
+
required=("file_path", "branch", "commit_message"),
|
202
206
|
)
|
203
207
|
@exc.on_http_error(exc.GitlabDeleteError)
|
204
208
|
# NOTE(jlvillal): Signature doesn't match DeleteMixin.delete() so ignore
|
@@ -223,7 +227,9 @@ class ProjectFileManager(GetMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTMa
|
|
223
227
|
data = {"branch": branch, "commit_message": commit_message}
|
224
228
|
self.gitlab.http_delete(path, query_data=data, **kwargs)
|
225
229
|
|
226
|
-
@cli.register_custom_action(
|
230
|
+
@cli.register_custom_action(
|
231
|
+
cls_names="ProjectFileManager", required=("file_path", "ref")
|
232
|
+
)
|
227
233
|
@exc.on_http_error(exc.GitlabGetError)
|
228
234
|
def raw(
|
229
235
|
self,
|
@@ -270,7 +276,9 @@ class ProjectFileManager(GetMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTMa
|
|
270
276
|
result, streamed, action, chunk_size, iterator=iterator
|
271
277
|
)
|
272
278
|
|
273
|
-
@cli.register_custom_action(
|
279
|
+
@cli.register_custom_action(
|
280
|
+
cls_names="ProjectFileManager", required=("file_path", "ref")
|
281
|
+
)
|
274
282
|
@exc.on_http_error(exc.GitlabListError)
|
275
283
|
def blame(self, file_path: str, ref: str, **kwargs: Any) -> List[Dict[str, Any]]:
|
276
284
|
"""Return the content of a file for a commit.
|
gitlab/v4/objects/geo_nodes.py
CHANGED
@@ -19,7 +19,7 @@ __all__ = [
|
|
19
19
|
|
20
20
|
|
21
21
|
class GeoNode(SaveMixin, ObjectDeleteMixin, RESTObject):
|
22
|
-
@cli.register_custom_action("GeoNode")
|
22
|
+
@cli.register_custom_action(cls_names="GeoNode")
|
23
23
|
@exc.on_http_error(exc.GitlabRepairError)
|
24
24
|
def repair(self, **kwargs: Any) -> None:
|
25
25
|
"""Repair the OAuth authentication of the geo node.
|
@@ -37,7 +37,7 @@ class GeoNode(SaveMixin, ObjectDeleteMixin, RESTObject):
|
|
37
37
|
assert isinstance(server_data, dict)
|
38
38
|
self._update_attrs(server_data)
|
39
39
|
|
40
|
-
@cli.register_custom_action("GeoNode")
|
40
|
+
@cli.register_custom_action(cls_names="GeoNode")
|
41
41
|
@exc.on_http_error(exc.GitlabGetError)
|
42
42
|
def status(self, **kwargs: Any) -> Dict[str, Any]:
|
43
43
|
"""Get the status of the geo node.
|
@@ -69,7 +69,7 @@ class GeoNodeManager(RetrieveMixin, UpdateMixin, DeleteMixin, RESTManager):
|
|
69
69
|
def get(self, id: Union[str, int], lazy: bool = False, **kwargs: Any) -> GeoNode:
|
70
70
|
return cast(GeoNode, super().get(id=id, lazy=lazy, **kwargs))
|
71
71
|
|
72
|
-
@cli.register_custom_action("GeoNodeManager")
|
72
|
+
@cli.register_custom_action(cls_names="GeoNodeManager")
|
73
73
|
@exc.on_http_error(exc.GitlabGetError)
|
74
74
|
def status(self, **kwargs: Any) -> List[Dict[str, Any]]:
|
75
75
|
"""Get the status of all the geo nodes.
|
@@ -89,7 +89,7 @@ class GeoNodeManager(RetrieveMixin, UpdateMixin, DeleteMixin, RESTManager):
|
|
89
89
|
assert isinstance(result, list)
|
90
90
|
return result
|
91
91
|
|
92
|
-
@cli.register_custom_action("GeoNodeManager")
|
92
|
+
@cli.register_custom_action(cls_names="GeoNodeManager")
|
93
93
|
@exc.on_http_error(exc.GitlabGetError)
|
94
94
|
def current_failures(self, **kwargs: Any) -> List[Dict[str, Any]]:
|
95
95
|
"""Get the list of failures on the current geo node.
|