Skip to content

Commit e5dc72d

Browse files
feat(api): add support for remote project import (#2348)
1 parent 31ec146 commit e5dc72d

File tree

4 files changed

+99
-0
lines changed

4 files changed

+99
-0
lines changed

docs/gl_objects/projects.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,15 @@ Import the project into a namespace and override parameters::
342342
override_params={'visibility': 'private'},
343343
)
344344

345+
Import the project using file stored on a remote URL::
346+
347+
output = gl.projects.remote_import(
348+
url="https://whatever.com/url/file.tar.gz",
349+
path="my_new_remote_project",
350+
name="My New Remote Project",
351+
namespace="my-group",
352+
override_params={'visibility': 'private'},
353+
)
345354

346355
Project custom attributes
347356
=========================

gitlab/v4/objects/projects.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -822,6 +822,8 @@ def import_project(
822822
Args:
823823
file: Data or file object containing the project
824824
path: Name and path for the new project
825+
name: The name of the project to import. If not provided,
826+
defaults to the path of the project.
825827
namespace: The ID or path of the namespace that the project
826828
will be imported to
827829
overwrite: If True overwrite an existing project with the
@@ -849,6 +851,49 @@ def import_project(
849851
"/projects/import", post_data=data, files=files, **kwargs
850852
)
851853

854+
def remote_import(
855+
self,
856+
url: str,
857+
path: str,
858+
name: Optional[str] = None,
859+
namespace: Optional[str] = None,
860+
overwrite: bool = False,
861+
override_params: Optional[Dict[str, Any]] = None,
862+
**kwargs: Any,
863+
) -> Union[Dict[str, Any], requests.Response]:
864+
"""Import a project from an archive file stored on a remote URL.
865+
866+
Args:
867+
url: URL for the file containing the project data to import
868+
path: Name and path for the new project
869+
name: The name of the project to import. If not provided,
870+
defaults to the path of the project.
871+
namespace: The ID or path of the namespace that the project
872+
will be imported to
873+
overwrite: If True overwrite an existing project with the
874+
same path
875+
override_params: Set the specific settings for the project
876+
**kwargs: Extra options to send to the server (e.g. sudo)
877+
878+
Raises:
879+
GitlabAuthenticationError: If authentication is not correct
880+
GitlabListError: If the server failed to perform the request
881+
882+
Returns:
883+
A representation of the import status.
884+
"""
885+
data = {"path": path, "overwrite": str(overwrite), "url": url}
886+
if override_params:
887+
for k, v in override_params.items():
888+
data[f"override_params[{k}]"] = v
889+
if name is not None:
890+
data["name"] = name
891+
if namespace:
892+
data["namespace"] = namespace
893+
return self.gitlab.http_post(
894+
"/projects/remote-import", post_data=data, **kwargs
895+
)
896+
852897
def import_bitbucket_server(
853898
self,
854899
bitbucket_server_url: str,

tests/functional/api/test_import_export.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import time
22

3+
import pytest
4+
35
import gitlab
46

57

@@ -64,3 +66,15 @@ def test_project_import_export(gl, project, temp_dir):
6466
count += 1
6567
if count == 15:
6668
raise Exception("Project import taking too much time")
69+
70+
71+
def test_project_remote_import(gl):
72+
with pytest.raises(gitlab.exceptions.GitlabHttpError) as err_info:
73+
gl.projects.remote_import(
74+
"ftp://whatever.com/url", "remote-project", "remote-project", "root"
75+
)
76+
assert err_info.value.response_code == 400
77+
assert (
78+
"File url is blocked: Only allowed schemes are https"
79+
in err_info.value.error_message
80+
)

tests/unit/objects/test_project_import_export.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,30 @@ def resp_import_project():
2929
yield rsps
3030

3131

32+
@pytest.fixture
33+
def resp_remote_import():
34+
content = {
35+
"id": 1,
36+
"description": None,
37+
"name": "remote-project",
38+
"name_with_namespace": "Administrator / remote-project",
39+
"path": "remote-project",
40+
"path_with_namespace": "root/remote-project",
41+
"created_at": "2018-02-13T09:05:58.023Z",
42+
"import_status": "scheduled",
43+
}
44+
45+
with responses.RequestsMock() as rsps:
46+
rsps.add(
47+
method=responses.POST,
48+
url="http://localhost/api/v4/projects/remote-import",
49+
json=content,
50+
content_type="application/json",
51+
status=200,
52+
)
53+
yield rsps
54+
55+
3256
@pytest.fixture
3357
def resp_import_status():
3458
content = {
@@ -99,6 +123,13 @@ def test_import_project(gl, resp_import_project):
99123
assert project_import["import_status"] == "scheduled"
100124

101125

126+
def test_remote_import(gl, resp_remote_import):
127+
project_import = gl.projects.remote_import(
128+
"https://whatever.com/url", "remote-project", "remote-project", "root"
129+
)
130+
assert project_import["import_status"] == "scheduled"
131+
132+
102133
def test_import_project_with_override_params(gl, resp_import_project):
103134
project_import = gl.projects.import_project(
104135
"file", "api-project", override_params={"visibility": "private"}

0 commit comments

Comments
 (0)