Skip to content

Commit ac1c619

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 c9ed3dd commit ac1c619

File tree

4 files changed

+67
-13
lines changed

4 files changed

+67
-13
lines changed

gitlab/v4/cli.py

Lines changed: 18 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_attrs()
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,18 @@ def __init__(
6367
obj.set_from_cli(self.args[attr_name])
6468
self.args[attr_name] = obj.get()
6569

70+
def _process_from_parent_attrs(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 not in self.args:
76+
continue
77+
78+
self.parent_args[key] = gitlab.utils.clean_str_id(self.args[key])
79+
# If we don't delete it then it will be added to the URL as a query-string
80+
del self.args[key]
81+
6682
def __call__(self) -> Any:
6783
# Check for a method that matches object + action
6884
method = f"do_{self.what}_{self.action}"
@@ -85,7 +101,7 @@ def do_custom(self) -> Any:
85101
data = {}
86102
if self.mgr._from_parent_attrs:
87103
for k in self.mgr._from_parent_attrs:
88-
data[k] = self.args[k]
104+
data[k] = self.parent_args[k]
89105
if not issubclass(self.cls, gitlab.mixins.GetWithoutIdMixin):
90106
if TYPE_CHECKING:
91107
assert isinstance(self.cls._id_attr, str)

tests/functional/cli/conftest.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
import pytest
2+
import responses
3+
4+
from gitlab.const import DEFAULT_URL
25

36

47
@pytest.fixture
@@ -19,3 +22,14 @@ def _gitlab_cli(subcommands):
1922
return script_runner.run(*command)
2023

2124
return _gitlab_cli
25+
26+
27+
@pytest.fixture
28+
def resp_get_project():
29+
return {
30+
"method": responses.GET,
31+
"url": f"{DEFAULT_URL}/api/v4/projects/1",
32+
"json": {"name": "name", "path": "test-path", "id": 1},
33+
"content_type": "application/json",
34+
"status": 200,
35+
}

tests/functional/cli/test_cli.py

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,6 @@
1717
CI_SERVER_URL = "https://gitlab.example.com"
1818

1919

20-
@pytest.fixture
21-
def resp_get_project():
22-
return {
23-
"method": responses.GET,
24-
"url": f"{DEFAULT_URL}/api/v4/projects/1",
25-
"json": {"name": "name", "path": "test-path", "id": 1},
26-
"content_type": "application/json",
27-
"status": 200,
28-
}
29-
30-
3120
def test_main_entrypoint(script_runner, gitlab_config):
3221
ret = script_runner.run("python", "-m", "gitlab", "--config-file", gitlab_config)
3322
assert ret.returncode == 2

tests/functional/cli/test_cli_variables.py

Lines changed: 35 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,29 @@ 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.mark.script_launch_mode("inprocess")
39+
@responses.activate
40+
def test_list_project_variables_with_path_url_check(
41+
monkeypatch, script_runner, resp_get_project
42+
):
43+
monkeypatch.setattr(config, "_DEFAULT_FILES", [])
44+
resp_get_project_variables = copy.deepcopy(resp_get_project)
45+
resp_get_project_variables.update(
46+
url=f"{DEFAULT_URL}/api/v4/projects/project%2Fwith%2Fa%2Fnamespace/variables"
47+
)
48+
resp_get_project_variables.update(json=[])
49+
50+
responses.add(**resp_get_project_variables)
51+
ret = script_runner.run(
52+
"gitlab", "project-variable", "list", "--project-id", "project/with/a/namespace"
53+
)
54+
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