Skip to content

Commit 8363489

Browse files
authored
Merge pull request #623 from gerrod3/export
Add import export support of python content
2 parents 07b55e8 + 71f0df9 commit 8363489

File tree

6 files changed

+173
-4
lines changed

6 files changed

+173
-4
lines changed

.github/workflows/scripts/install.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ VARSYAML
7070

7171
cat >> vars/main.yaml << VARSYAML
7272
pulp_env: {}
73-
pulp_settings: {"orphan_protection_time": 0, "pypi_api_hostname": "https://pulp:443"}
73+
pulp_settings: {"allowed_export_paths": "/tmp", "allowed_import_paths": "/tmp", "orphan_protection_time": 0, "pypi_api_hostname": "https://pulp:443"}
7474
pulp_scheme: https
7575
7676
pulp_container_tag: "latest"

CHANGES/579.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Added import export support of python content.

pulp_python/app/modelresource.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
from pulpcore.plugin.importexport import BaseContentResource
2+
from pulpcore.plugin.modelresources import RepositoryResource
3+
from pulp_python.app.models import (
4+
PythonPackageContent,
5+
PythonRepository,
6+
)
7+
8+
9+
class PythonPackageContentResource(BaseContentResource):
10+
"""
11+
Resource for import/export of python_pythonpackagecontent entities.
12+
"""
13+
14+
def set_up_queryset(self):
15+
"""
16+
:return: PythonPackageContent specific to a specified repo-version.
17+
"""
18+
return PythonPackageContent.objects.filter(pk__in=self.repo_version.content)
19+
20+
class Meta:
21+
model = PythonPackageContent
22+
import_id_fields = model.natural_key_fields()
23+
24+
25+
class PythonRepositoryResource(RepositoryResource):
26+
"""
27+
A resource for importing/exporting python repository entities
28+
"""
29+
30+
def set_up_queryset(self):
31+
"""
32+
:return: A queryset containing one repository that will be exported.
33+
"""
34+
return PythonRepository.objects.filter(pk=self.repo_version.repository)
35+
36+
class Meta:
37+
model = PythonRepository
38+
39+
40+
IMPORT_ORDER = [PythonPackageContentResource, PythonRepositoryResource]
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
"""
2+
Tests PulpExporter and PulpExport functionality.
3+
4+
NOTE: assumes ALLOWED_EXPORT_PATHS setting contains "/tmp" - all tests will fail if this is not
5+
the case.
6+
"""
7+
import pytest
8+
import uuid
9+
10+
from pulp_python.tests.functional.constants import (
11+
PYTHON_XS_PROJECT_SPECIFIER, PYTHON_SM_PROJECT_SPECIFIER
12+
)
13+
14+
15+
@pytest.mark.parallel
16+
def test_export_then_import(
17+
python_repo_factory,
18+
python_remote_factory,
19+
python_repo_api_client,
20+
python_repo_version_api_client,
21+
exporters_pulp_api_client,
22+
exporters_pulp_exports_api_client,
23+
importers_pulp_api_client,
24+
importers_pulp_imports_api_client,
25+
monitor_task,
26+
monitor_task_group,
27+
gen_object_with_cleanup,
28+
):
29+
"""Issue and evaluate a PulpExport (tests both Create and Read)."""
30+
# Prepare content
31+
remote_a = python_remote_factory(includes=PYTHON_XS_PROJECT_SPECIFIER, policy="immediate")
32+
remote_b = python_remote_factory(includes=PYTHON_SM_PROJECT_SPECIFIER, policy="immediate")
33+
repo_a = python_repo_factory()
34+
repo_b = python_repo_factory()
35+
sync_response_a = python_repo_api_client.sync(
36+
repo_a.pulp_href, {"remote": remote_a.pulp_href}
37+
)
38+
sync_response_b = python_repo_api_client.sync(
39+
repo_b.pulp_href, {"remote": remote_b.pulp_href}
40+
)
41+
monitor_task(sync_response_a.task)
42+
monitor_task(sync_response_b.task)
43+
44+
repo_ver_a = python_repo_version_api_client.read(f"{repo_a.pulp_href}versions/1/")
45+
repo_ver_b = python_repo_version_api_client.read(f"{repo_b.pulp_href}versions/1/")
46+
47+
# Prepare export
48+
exporter = gen_object_with_cleanup(
49+
exporters_pulp_api_client,
50+
{
51+
"name": str(uuid.uuid4()),
52+
"path": f"/tmp/{uuid.uuid4()}/",
53+
"repositories": [repo.pulp_href for repo in [repo_a, repo_b]],
54+
},
55+
)
56+
57+
# Export
58+
task = exporters_pulp_exports_api_client.create(exporter.pulp_href, {}).task
59+
task = monitor_task(task)
60+
assert len(task.created_resources) == 1
61+
export = exporters_pulp_exports_api_client.read(task.created_resources[0])
62+
assert export is not None
63+
assert len(exporter.repositories) == len(export.exported_resources)
64+
assert export.output_file_info is not None
65+
for an_export_filename in export.output_file_info.keys():
66+
assert "//" not in an_export_filename
67+
export_filename = next(
68+
f for f in export.output_file_info.keys() if f.endswith("tar.gz") or f.endswith("tar")
69+
)
70+
71+
# Prepare import
72+
repo_c = python_repo_factory()
73+
repo_d = python_repo_factory()
74+
repo_mapping = {repo_a.name: repo_c.name, repo_b.name: repo_d.name}
75+
importer = gen_object_with_cleanup(
76+
importers_pulp_api_client, {"name": str(uuid.uuid4()), "repo_mapping": repo_mapping}
77+
)
78+
79+
# Import
80+
import_response = importers_pulp_imports_api_client.create(
81+
importer.pulp_href, {"path": export_filename}
82+
)
83+
monitor_task_group(import_response.task_group)
84+
repo_c = python_repo_api_client.read(repo_c.pulp_href)
85+
repo_d = python_repo_api_client.read(repo_d.pulp_href)
86+
assert repo_c.latest_version_href == f"{repo_c.pulp_href}versions/1/"
87+
assert repo_d.latest_version_href == f"{repo_d.pulp_href}versions/1/"
88+
repo_ver_c = python_repo_version_api_client.read(f"{repo_c.pulp_href}versions/1/")
89+
repo_ver_d = python_repo_version_api_client.read(f"{repo_d.pulp_href}versions/1/")
90+
assert (
91+
repo_ver_c.content_summary.added["python.python"]["count"]
92+
== repo_ver_a.content_summary.present["python.python"]["count"]
93+
)
94+
assert (
95+
repo_ver_d.content_summary.added["python.python"]["count"]
96+
== repo_ver_b.content_summary.present["python.python"]["count"]
97+
)
98+
99+
# Import a second time
100+
import_response = importers_pulp_imports_api_client.create(
101+
importer.pulp_href, {"path": export_filename}
102+
)
103+
monitor_task_group(import_response.task_group)
104+
assert len(importers_pulp_imports_api_client.list(importer.pulp_href).results) == 2
105+
for repo in [repo_c, repo_d]:
106+
repo = python_repo_api_client.read(repo.pulp_href)
107+
# still only one version as pulp won't create a new version if nothing changed
108+
assert repo.latest_version_href == f"{repo.pulp_href}versions/1/"

pulp_python/tests/functional/conftest.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import pytest
2+
import uuid
23

3-
from pulp_smash.pulp3.utils import gen_distribution, gen_repo
4+
from pulp_smash.pulp3.utils import gen_distribution
45
from pulp_python.tests.functional.utils import gen_python_remote
56

67
from pulpcore.client.pulp_python import (
@@ -9,6 +10,7 @@
910
DistributionsPypiApi,
1011
PublicationsPypiApi,
1112
RepositoriesPythonApi,
13+
RepositoriesPythonVersionsApi,
1214
RemotesPythonApi,
1315
)
1416

@@ -29,6 +31,12 @@ def python_repo_api_client(python_bindings_client):
2931
return RepositoriesPythonApi(python_bindings_client)
3032

3133

34+
@pytest.fixture
35+
def python_repo_version_api_client(python_bindings_client):
36+
"""Provides the Python Repository Version API client object."""
37+
return RepositoriesPythonVersionsApi(python_bindings_client)
38+
39+
3240
@pytest.fixture
3341
def python_distro_api_client(python_bindings_client):
3442
"""Provides the Python Distribution API client object."""
@@ -56,9 +64,19 @@ def python_publication_api_client(python_bindings_client):
5664
# Object Generation Fixtures
5765

5866
@pytest.fixture
59-
def python_repo(python_repo_api_client, gen_object_with_cleanup):
67+
def python_repo_factory(python_repo_api_client, gen_object_with_cleanup):
68+
"""A factory to generate a Python Repository with auto-cleanup."""
69+
def _gen_python_repo(**kwargs):
70+
kwargs.setdefault("name", str(uuid.uuid4()))
71+
return gen_object_with_cleanup(python_repo_api_client, kwargs)
72+
73+
return _gen_python_repo
74+
75+
76+
@pytest.fixture
77+
def python_repo(python_repo_factory):
6078
"""Creates a Python Repository and deletes it at test cleanup time."""
61-
return gen_object_with_cleanup(python_repo_api_client, gen_repo())
79+
return python_repo_factory()
6280

6381

6482
@pytest.fixture

template_config.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ pulp_scheme: https
5555
pulp_settings:
5656
orphan_protection_time: 0
5757
pypi_api_hostname: https://pulp:443
58+
allowed_export_paths: /tmp
59+
allowed_import_paths: /tmp
5860
pulp_settings_azure: null
5961
pulp_settings_gcp: null
6062
pulp_settings_s3: null

0 commit comments

Comments
 (0)