Skip to content

Commit 4b779bd

Browse files
fix: cli: url-encode path components of the URL
In the CLI we need to make sure the components put into the path portion of the URL are url-encoded. Otherwise they will be interpreted as part of the path. For example can specify the project ID as a path, but in the URL it must be url-encoded or it doesn't work. Also stop adding the components of the path as query parameters in the URL. Closes: #783 Closes: #1498
1 parent c4d074f commit 4b779bd

File tree

2 files changed

+82
-2
lines changed

2 files changed

+82
-2
lines changed

gitlab/v4/cli.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ def __init__(
3939
self.action = action.lower()
4040
self.gl = gl
4141
self.args = args
42+
self.parent_args: Dict[str, Any] = {}
4243
self.mgr_cls: Union[
4344
Type[gitlab.mixins.CreateMixin],
4445
Type[gitlab.mixins.DeleteMixin],
@@ -53,7 +54,10 @@ def __init__(
5354
# the class _path attribute, and replace the value with the result.
5455
if TYPE_CHECKING:
5556
assert self.mgr_cls._path is not None
56-
self.mgr_cls._path = self.mgr_cls._path.format(**self.args)
57+
58+
self._process_from_parent_attributes()
59+
60+
self.mgr_cls._path = self.mgr_cls._path.format(**self.parent_args)
5761
self.mgr = self.mgr_cls(gl)
5862

5963
if self.mgr_cls._types:
@@ -63,6 +67,17 @@ def __init__(
6367
obj.set_from_cli(self.args[attr_name])
6468
self.args[attr_name] = obj.get()
6569

70+
def _process_from_parent_attributes(self) -> None:
71+
"""Items in the path need to be url-encoded. There is a 1:1 mapping from
72+
mgr_cls._from_parent_attrs <--> mgr_cls._path. Those values must be url-encoded
73+
as they may contain a slash '/'."""
74+
for key in self.mgr_cls._from_parent_attrs:
75+
if key in self.args:
76+
self.parent_args[key] = gitlab.utils.clean_str_id(self.args[key])
77+
# If we don't delete it then it will be added to the URL as a
78+
# query-string
79+
del self.args[key]
80+
6681
def __call__(self) -> Any:
6782
# Check for a method that matches object + action
6883
method = f"do_{self.what}_{self.action}"
@@ -85,7 +100,7 @@ def do_custom(self) -> Any:
85100
data = {}
86101
if self.mgr._from_parent_attrs:
87102
for k in self.mgr._from_parent_attrs:
88-
data[k] = self.args[k]
103+
data[k] = self.parent_args[k]
89104
if not issubclass(self.cls, gitlab.mixins.GetWithoutIdMixin):
90105
if TYPE_CHECKING:
91106
assert isinstance(self.cls._id_attr, str)

tests/functional/cli/test_cli_variables.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
import copy
2+
3+
import pytest
4+
import responses
5+
6+
from gitlab import config
7+
from gitlab.const import DEFAULT_URL
8+
9+
110
def test_list_instance_variables(gitlab_cli, gl):
211
cmd = ["variable", "list"]
312
ret = gitlab_cli(cmd)
@@ -17,3 +26,59 @@ def test_list_project_variables(gitlab_cli, project):
1726
ret = gitlab_cli(cmd)
1827

1928
assert ret.success
29+
30+
31+
def test_list_project_variables_with_path(gitlab_cli, project):
32+
cmd = ["project-variable", "list", "--project-id", project.path_with_namespace]
33+
ret = gitlab_cli(cmd)
34+
35+
assert ret.success
36+
37+
38+
@pytest.fixture
39+
def resp_get_project():
40+
return {
41+
"method": responses.GET,
42+
"url": f"{DEFAULT_URL}/api/v4/projects/1",
43+
"json": {"name": "name", "path": "test-path", "id": 1},
44+
"content_type": "application/json",
45+
"status": 200,
46+
}
47+
48+
49+
@pytest.mark.script_launch_mode("inprocess")
50+
@responses.activate
51+
def test_list_project_variables_with_path_url_check(
52+
monkeypatch, script_runner, resp_get_project
53+
):
54+
monkeypatch.setattr(config, "_DEFAULT_FILES", [])
55+
resp_get_project_in_ci = copy.deepcopy(resp_get_project)
56+
resp_get_project_in_ci.update(
57+
url=f"{DEFAULT_URL}/api/v4/projects/project%2Fwith%2Fa%2Fnamespace/variables"
58+
)
59+
resp_get_project_in_ci.update(
60+
json=[
61+
{
62+
"variable_type": "env_var",
63+
"key": "TEST_VARIABLE_1",
64+
"value": "TEST_1",
65+
"protected": False,
66+
"masked": True,
67+
"environment_scope": "*",
68+
},
69+
{
70+
"variable_type": "env_var",
71+
"key": "TEST_VARIABLE_2",
72+
"value": "TEST_2",
73+
"protected": False,
74+
"masked": False,
75+
"environment_scope": "*",
76+
},
77+
]
78+
)
79+
80+
responses.add(**resp_get_project_in_ci)
81+
ret = script_runner.run(
82+
"gitlab", "project-variable", "list", "--project-id", "project/with/a/namespace"
83+
)
84+
assert ret.success

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy