Skip to content

Commit 5f58544

Browse files
authored
Merge pull request pulp#673 from gerrod3/domains
Add domain support
2 parents 66ada93 + a3068e4 commit 5f58544

25 files changed

+461
-83
lines changed

.github/workflows/scripts/install.sh

+2-2
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ if [ "$TEST" = "s3" ]; then
9797
sed -i -e '$a s3_test: true\
9898
minio_access_key: "'$MINIO_ACCESS_KEY'"\
9999
minio_secret_key: "'$MINIO_SECRET_KEY'"\
100-
pulp_scenario_settings: null\
100+
pulp_scenario_settings: {"domain_enabled": true}\
101101
pulp_scenario_env: {}\
102102
' vars/main.yaml
103103
export PULP_API_ROOT="/rerouted/djnd/"
@@ -111,7 +111,7 @@ if [ "$TEST" = "azure" ]; then
111111
- ./azurite:/etc/pulp\
112112
command: "azurite-blob --blobHost 0.0.0.0"' vars/main.yaml
113113
sed -i -e '$a azure_test: true\
114-
pulp_scenario_settings: null\
114+
pulp_scenario_settings: {"domain_enabled": true}\
115115
pulp_scenario_env: {}\
116116
' vars/main.yaml
117117
fi

CHANGES/668.feature

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Added Domain support.

docs/tech-preview.rst

+1
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ The following features are currently being released as part of a tech preview
99
* Fully mirror Python repositories provided PyPI and Pulp itself.
1010
* ``Twine`` upload packages to indexes at endpoints '/simple` or '/legacy'.
1111
* Create pull-through caches of remote sources.
12+
* Pulp Domain support

pulp_python/app/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ class PulpPythonPluginAppConfig(PulpPluginAppConfig):
1010
label = "python"
1111
version = "3.12.0.dev"
1212
python_package_name = "pulp-python"
13+
domain_compatible = True
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Generated by Django 4.2.10 on 2024-05-30 17:53
2+
3+
from django.db import migrations, models
4+
import django.db.models.deletion
5+
import pulpcore.app.util
6+
7+
8+
class Migration(migrations.Migration):
9+
10+
dependencies = [
11+
("python", "0011_alter_pythondistribution_distribution_ptr_and_more"),
12+
]
13+
14+
operations = [
15+
migrations.AlterUniqueTogether(
16+
name="pythonpackagecontent",
17+
unique_together=set(),
18+
),
19+
migrations.AddField(
20+
model_name="pythonpackagecontent",
21+
name="_pulp_domain",
22+
field=models.ForeignKey(
23+
default=pulpcore.app.util.get_domain_pk,
24+
on_delete=django.db.models.deletion.PROTECT,
25+
to="core.domain",
26+
),
27+
),
28+
migrations.AlterField(
29+
model_name="pythonpackagecontent",
30+
name="sha256",
31+
field=models.CharField(db_index=True, max_length=64),
32+
),
33+
migrations.AlterUniqueTogether(
34+
name="pythonpackagecontent",
35+
unique_together={("sha256", "_pulp_domain")},
36+
),
37+
]

pulp_python/app/modelresource.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from pulpcore.plugin.importexport import BaseContentResource
22
from pulpcore.plugin.modelresources import RepositoryResource
3+
from pulpcore.plugin.util import get_domain
34
from pulp_python.app.models import (
45
PythonPackageContent,
56
PythonRepository,
@@ -15,7 +16,9 @@ def set_up_queryset(self):
1516
"""
1617
:return: PythonPackageContent specific to a specified repo-version.
1718
"""
18-
return PythonPackageContent.objects.filter(pk__in=self.repo_version.content)
19+
return PythonPackageContent.objects.filter(
20+
pk__in=self.repo_version.content, _pulp_domain=get_domain()
21+
)
1922

2023
class Meta:
2124
model = PythonPackageContent

pulp_python/app/models.py

+14-4
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
PYPI_SERIAL_CONSTANT,
2424
)
2525
from pulpcore.plugin.repo_version_utils import remove_duplicates, validate_repo_version
26+
from pulpcore.plugin.util import get_domain_pk, get_domain
2627

2728
log = getLogger(__name__)
2829

@@ -68,6 +69,7 @@ def content_handler(self, path):
6869
path = PurePath(path)
6970
name = None
7071
version = None
72+
domain = get_domain()
7173
if path.match("pypi/*/*/json"):
7274
version = path.parts[2]
7375
name = path.parts[1]
@@ -76,7 +78,7 @@ def content_handler(self, path):
7678
elif len(path.parts) and path.parts[0] == "simple":
7779
# Temporary fix for PublishedMetadata not being properly served from remote storage
7880
# https://github.com/pulp/pulp_python/issues/413
79-
if settings.DEFAULT_FILE_STORAGE != "pulpcore.app.models.storage.FileSystem":
81+
if domain.storage_class != "pulpcore.app.models.storage.FileSystem":
8082
if self.publication or self.repository:
8183
try:
8284
publication = self.publication or Publication.objects.filter(
@@ -105,7 +107,11 @@ def content_handler(self, path):
105107
)
106108
# TODO Change this value to the Repo's serial value when implemented
107109
headers = {PYPI_LAST_SERIAL: str(PYPI_SERIAL_CONSTANT)}
108-
json_body = python_content_to_json(self.base_path, package_content, version=version)
110+
if not settings.DOMAIN_ENABLED:
111+
domain = None
112+
json_body = python_content_to_json(
113+
self.base_path, package_content, version=version, domain=domain
114+
)
109115
if json_body:
110116
return json_response(json_body, headers=headers)
111117

@@ -143,7 +149,7 @@ class PythonPackageContent(Content):
143149
name = models.TextField()
144150
name.register_lookup(NormalizeName)
145151
version = models.TextField()
146-
sha256 = models.CharField(unique=True, db_index=True, max_length=64)
152+
sha256 = models.CharField(db_index=True, max_length=64)
147153
# Optional metadata
148154
python_version = models.TextField()
149155
metadata_version = models.TextField()
@@ -168,6 +174,8 @@ class PythonPackageContent(Content):
168174
classifiers = models.JSONField(default=list)
169175
project_urls = models.JSONField(default=dict)
170176
description_content_type = models.TextField()
177+
# Pulp Domains
178+
_pulp_domain = models.ForeignKey("core.Domain", default=get_domain_pk, on_delete=models.PROTECT)
171179

172180
@staticmethod
173181
def init_from_artifact_and_relative_path(artifact, relative_path):
@@ -179,6 +187,8 @@ def init_from_artifact_and_relative_path(artifact, relative_path):
179187
data["version"] = metadata.version
180188
data["filename"] = path.name
181189
data["sha256"] = artifact.sha256
190+
data["pulp_domain_id"] = artifact.pulp_domain_id
191+
data["_pulp_domain_id"] = artifact.pulp_domain_id
182192
return PythonPackageContent(**data)
183193

184194
def __str__(self):
@@ -200,7 +210,7 @@ def __str__(self):
200210

201211
class Meta:
202212
default_related_name = "%(app_label)s_%(model_name)s"
203-
unique_together = ("sha256",)
213+
unique_together = ("sha256", "_pulp_domain")
204214

205215

206216
class PythonPublication(Publication):

pulp_python/app/pypi/serializers.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from rest_framework import serializers
55
from pulp_python.app.utils import DIST_EXTENSIONS
66
from pulpcore.plugin.models import Artifact
7+
from pulpcore.plugin.util import get_domain
78
from django.db.utils import IntegrityError
89

910
log = logging.getLogger(__name__)
@@ -76,7 +77,7 @@ def validate(self, data):
7677
try:
7778
artifact.save()
7879
except IntegrityError:
79-
artifact = Artifact.objects.get(sha256=artifact.sha256)
80+
artifact = Artifact.objects.get(sha256=artifact.sha256, pulp_domain=get_domain())
8081
artifact.touch()
8182
log.info(f"Artifact for {file.name} already existed in database")
8283
data["content"] = (artifact, file.name)

pulp_python/app/pypi/views.py

+26-11
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@
2626
from pathlib import PurePath
2727
from pypi_simple.parse_stream import parse_links_stream_response
2828

29+
from pulpcore.plugin.viewsets import OperationPostponedResponse
2930
from pulpcore.plugin.tasking import dispatch
31+
from pulpcore.plugin.util import get_domain
3032
from pulp_python.app.models import (
3133
PythonDistribution,
3234
PythonPackageContent,
@@ -75,7 +77,7 @@ def get_distribution(path):
7577
"repository", "publication", "publication__repository_version", "remote"
7678
)
7779
try:
78-
return distro_qs.get(base_path=path)
80+
return distro_qs.get(base_path=path, pulp_domain=get_domain())
7981
except ObjectDoesNotExist:
8082
raise Http404(f"No PythonDistribution found for base_path {path}")
8183

@@ -100,6 +102,14 @@ def get_drvc(self, path):
100102
content = self.get_content(repo_ver)
101103
return distro, repo_ver, content
102104

105+
def initial(self, request, *args, **kwargs):
106+
"""Perform common initialization tasks for PyPI endpoints."""
107+
super().initial(request, *args, **kwargs)
108+
if settings.DOMAIN_ENABLED:
109+
self.base_content_url = urljoin(BASE_CONTENT_URL, f"{get_domain().name}/")
110+
else:
111+
self.base_content_url = BASE_CONTENT_URL
112+
103113

104114
class PackageUploadMixin(PyPIMixin):
105115
"""A Mixin to provide package upload support."""
@@ -137,7 +147,7 @@ def upload(self, request, path):
137147
kwargs={"artifact_sha256": artifact.sha256,
138148
"filename": filename,
139149
"repository_pk": str(repo.pk)})
140-
return Response(data={"task": reverse('tasks-detail', args=[result.pk], request=None)})
150+
return OperationPostponedResponse(result, request)
141151

142152
def upload_package_group(self, repo, artifact, filename, session):
143153
"""Steps 4 & 5, spawns tasks to add packages to index."""
@@ -176,7 +186,7 @@ def create_group_upload_task(self, cur_session, repository, artifact, filename,
176186
return reverse('tasks-detail', args=[result.pk], request=None)
177187

178188

179-
class SimpleView(ViewSet, PackageUploadMixin):
189+
class SimpleView(PackageUploadMixin, ViewSet):
180190
"""View for the PyPI simple API."""
181191

182192
permission_classes = [IsAuthenticatedOrReadOnly]
@@ -186,7 +196,7 @@ def list(self, request, path):
186196
"""Gets the simple api html page for the index."""
187197
distro, repo_version, content = self.get_drvc(path)
188198
if self.should_redirect(distro, repo_version=repo_version):
189-
return redirect(urljoin(BASE_CONTENT_URL, f'{path}/simple/'))
199+
return redirect(urljoin(self.base_content_url, f'{path}/simple/'))
190200
names = content.order_by('name').values_list('name', flat=True).distinct().iterator()
191201
return StreamingHttpResponse(write_simple_index(names, streamed=True))
192202

@@ -197,7 +207,7 @@ def parse_url(link):
197207
digest, _, value = parsed.fragment.partition('=')
198208
stripped_url = urlunsplit(chain(parsed[:3], ("", "")))
199209
redirect = f'{path}/{link.text}?redirect={stripped_url}'
200-
d_url = urljoin(BASE_CONTENT_URL, redirect)
210+
d_url = urljoin(self.base_content_url, redirect)
201211
return link.text, d_url, value if digest == 'sha256' else ''
202212

203213
url = remote.get_remote_artifact_url(f'simple/{package}/')
@@ -224,7 +234,7 @@ def retrieve(self, request, path, package):
224234
if not repo_ver or not content.filter(name__normalize=normalized).exists():
225235
return self.pull_through_package_simple(normalized, path, distro.remote)
226236
if self.should_redirect(distro, repo_version=repo_ver):
227-
return redirect(urljoin(BASE_CONTENT_URL, f'{path}/simple/{normalized}/'))
237+
return redirect(urljoin(self.base_content_url, f'{path}/simple/{normalized}/'))
228238
packages = (
229239
content.filter(name__normalize=normalized)
230240
.values_list('filename', 'sha256', 'name')
@@ -237,7 +247,7 @@ def retrieve(self, request, path, package):
237247
else:
238248
packages = chain([present], packages)
239249
name = present[2]
240-
releases = ((f, urljoin(BASE_CONTENT_URL, f'{path}/{f}'), d) for f, d, _ in packages)
250+
releases = ((f, urljoin(self.base_content_url, f'{path}/{f}'), d) for f, d, _ in packages)
241251
return StreamingHttpResponse(write_simple_detail(name, releases, streamed=True))
242252

243253
@extend_schema(request=PackageUploadSerializer,
@@ -253,7 +263,7 @@ def create(self, request, path):
253263
return self.upload(request, path)
254264

255265

256-
class MetadataView(ViewSet, PyPIMixin):
266+
class MetadataView(PyPIMixin, ViewSet):
257267
"""View for the PyPI JSON metadata endpoint."""
258268

259269
authentication_classes = []
@@ -272,6 +282,7 @@ def retrieve(self, request, path, meta):
272282
meta_path = PurePath(meta)
273283
name = None
274284
version = None
285+
domain = None
275286
if meta_path.match("*/*/json"):
276287
version = meta_path.parts[1]
277288
name = meta_path.parts[0]
@@ -281,13 +292,17 @@ def retrieve(self, request, path, meta):
281292
package_content = content.filter(name__iexact=name)
282293
# TODO Change this value to the Repo's serial value when implemented
283294
headers = {PYPI_LAST_SERIAL: str(PYPI_SERIAL_CONSTANT)}
284-
json_body = python_content_to_json(path, package_content, version=version)
295+
if settings.DOMAIN_ENABLED:
296+
domain = get_domain()
297+
json_body = python_content_to_json(
298+
path, package_content, version=version, domain=domain
299+
)
285300
if json_body:
286301
return Response(data=json_body, headers=headers)
287302
return Response(status="404")
288303

289304

290-
class PyPIView(ViewSet, PyPIMixin):
305+
class PyPIView(PyPIMixin, ViewSet):
291306
"""View for base_url of distribution."""
292307

293308
authentication_classes = []
@@ -305,7 +320,7 @@ def retrieve(self, request, path):
305320
return Response(data=data)
306321

307322

308-
class UploadView(ViewSet, PackageUploadMixin):
323+
class UploadView(PackageUploadMixin, ViewSet):
309324
"""View for the `/legacy` upload endpoint."""
310325

311326
@extend_schema(request=PackageUploadSerializer,

pulp_python/app/serializers.py

+8-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
from pulpcore.plugin import models as core_models
77
from pulpcore.plugin import serializers as core_serializers
8+
from pulpcore.plugin.util import get_domain
89

910
from pulp_python.app import models as python_models
1011
from pulp_python.app.utils import get_project_metadata_from_artifact, parse_project_metadata
@@ -56,6 +57,8 @@ class PythonDistributionSerializer(core_serializers.DistributionSerializer):
5657

5758
def get_base_url(self, obj):
5859
"""Gets the base url."""
60+
if settings.DOMAIN_ENABLED:
61+
return f"{settings.PYPI_API_HOSTNAME}/pypi/{get_domain().name}/{obj.base_path}/"
5962
return f"{settings.PYPI_API_HOSTNAME}/pypi/{obj.base_path}/"
6063

6164
class Meta:
@@ -232,13 +235,17 @@ def deferred_validate(self, data):
232235
_data['version'] = metadata.version
233236
_data['filename'] = filename
234237
_data['sha256'] = artifact.sha256
238+
data["pulp_domain_id"] = artifact.pulp_domain_id
239+
data["_pulp_domain_id"] = artifact.pulp_domain_id
235240

236241
data.update(_data)
237242

238243
return data
239244

240245
def retrieve(self, validated_data):
241-
content = python_models.PythonPackageContent.objects.filter(sha256=validated_data["sha256"])
246+
content = python_models.PythonPackageContent.objects.filter(
247+
sha256=validated_data["sha256"], _pulp_domain=get_domain()
248+
)
242249
return content.first()
243250

244251
class Meta:

pulp_python/app/tasks/publish.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from packaging.utils import canonicalize_name
77

88
from pulpcore.plugin import models
9+
from pulpcore.plugin.util import get_domain
910

1011
from pulp_python.app import models as python_models
1112
from pulp_python.app.utils import write_simple_index, write_simple_detail
@@ -49,11 +50,12 @@ def write_simple_api(publication):
4950
publication (pulpcore.plugin.models.Publication): A publication to generate metadata for
5051
5152
"""
53+
domain = get_domain()
5254
simple_dir = 'simple/'
5355
os.mkdir(simple_dir)
5456
project_names = (
5557
python_models.PythonPackageContent.objects.filter(
56-
pk__in=publication.repository_version.content
58+
pk__in=publication.repository_version.content, _pulp_domain=domain
5759
)
5860
.order_by('name')
5961
.values_list('name', flat=True)
@@ -76,7 +78,7 @@ def write_simple_api(publication):
7678
return
7779

7880
packages = python_models.PythonPackageContent.objects.filter(
79-
pk__in=publication.repository_version.content
81+
pk__in=publication.repository_version.content, _pulp_domain=domain
8082
)
8183
releases = packages.order_by("name").values("name", "filename", "sha256")
8284

0 commit comments

Comments
 (0)