Skip to content

Commit 8ac76d7

Browse files
committed
First cut of API / caching support
Not 100% functional, but close enough
1 parent 9b821fb commit 8ac76d7

15 files changed

Lines changed: 728 additions & 26 deletions
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Generated by Django 5.2.5 on 2026-01-28 04:23
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('rust', '0001_initial'),
10+
]
11+
12+
operations = [
13+
migrations.AlterField(
14+
model_name='rustcontent',
15+
name='cksum',
16+
field=models.CharField(db_index=True, max_length=64),
17+
),
18+
migrations.AlterField(
19+
model_name='rustcontent',
20+
name='vers',
21+
field=models.CharField(db_index=True, max_length=64),
22+
),
23+
]

pulp_rust/app/models.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,10 @@ class RustContent(Content):
4040
name = models.CharField(max_length=255, blank=False, null=False, db_index=True)
4141

4242
# Semantic version string following SemVer 2.0.0 specification
43-
vers = models.CharField(max_length=64, blank=False, null=False)
43+
vers = models.CharField(max_length=64, blank=False, null=False, db_index=True)
4444

4545
# SHA256 checksum (hex-encoded) of the .crate tarball file for verification
46-
cksum = models.CharField(max_length=64, blank=False, null=False)
46+
cksum = models.CharField(max_length=64, blank=False, null=False, db_index=True)
4747

4848
# Indicates if this version has been yanked (deprecated/removed from use)
4949
# Yanked versions can still be used by existing Cargo.lock files but won't be selected

pulp_rust/app/serializers.py

Lines changed: 51 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,31 @@
1-
from gettext import gettext as _
1+
import logging
22

3+
from gettext import gettext as _
34
from rest_framework import serializers
45

56
from pulpcore.plugin import models as core_models
67
from pulpcore.plugin import serializers as core_serializers
78

89
from . import models
910

11+
log = logging.getLogger(__name__)
12+
13+
14+
class IndexRootSerializer(serializers.Serializer):
15+
"""
16+
A Serializer for summary information of an index.
17+
"""
18+
19+
dl = serializers.CharField(help_text=_("URL of the index root"), read_only=True)
20+
api = serializers.CharField(help_text=_("URL of the API root"), read_only=True)
21+
auth_required = serializers.BooleanField(
22+
help_text=_(
23+
"Indicates whether this is a private registry that requires all operations to "
24+
"be authenticated"
25+
),
26+
read_only=True,
27+
)
28+
1029

1130
class RustDependencySerializer(serializers.ModelSerializer):
1231
"""
@@ -181,28 +200,14 @@ class Meta:
181200
class RustRemoteSerializer(core_serializers.RemoteSerializer):
182201
"""
183202
A Serializer for RustRemote.
184-
185-
Add any new fields if defined on RustRemote.
186-
Similar to the example above, in RustContentSerializer.
187-
Additional validators can be added to the parent validators list
188-
189-
For example::
190-
191-
class Meta:
192-
validators = core_serializers.RemoteSerializer.Meta.validators
193-
+ [myValidator1, myValidator2]
194-
195-
By default the 'policy' field in core_serializers.RemoteSerializer only validates the choice
196-
'immediate'. To add on-demand support for more 'policy' options, e.g. 'streamed' or
197-
'on_demand', re-define the 'policy' option as follows::
203+
"""
198204

199205
policy = serializers.ChoiceField(
200206
help_text="The policy to use when downloading content. The possible values include: "
201-
"'immediate', 'on_demand', and 'streamed'. 'immediate' is the default.",
207+
"'immediate', 'on_demand', and 'streamed'. 'streamed' is the default.",
202208
choices=models.Remote.POLICY_CHOICES,
203-
default=models.Remote.IMMEDIATE
209+
default=models.Remote.STREAMED,
204210
)
205-
"""
206211

207212
class Meta:
208213
fields = core_serializers.RemoteSerializer.Meta.fields
@@ -258,3 +263,31 @@ class Meta:
258263
class Meta:
259264
fields = core_serializers.DistributionSerializer.Meta.fields + ("allow_uploads", "remote")
260265
model = models.RustDistribution
266+
267+
268+
class RepositoryAddCachedContentSerializer(
269+
core_serializers.ValidateFieldsMixin, serializers.Serializer
270+
):
271+
remote = core_serializers.DetailRelatedField(
272+
required=False,
273+
view_name_pattern=r"remotes(-.*/.*)-detail",
274+
queryset=models.Remote.objects.all(),
275+
help_text=_(
276+
"A remote to use to identify content that was cached. This will override a "
277+
"remote set on repository."
278+
),
279+
)
280+
281+
def validate(self, data):
282+
data = super().validate(data)
283+
repository = None
284+
if "repository_pk" in self.context:
285+
repository = models.Repository.objects.get(pk=self.context["repository_pk"])
286+
remote = data.get("remote", None) or getattr(repository, "remote", None)
287+
288+
if not remote:
289+
raise serializers.ValidationError(
290+
{"remote": _("This field is required since a remote is not set on the repository.")}
291+
)
292+
self.check_cross_domains({"repository": repository, "remote": remote})
293+
return data

pulp_rust/app/settings.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
"""
2-
Check `Plugin Writer's Guide`_ for more details.
1+
import socket
32

4-
.. _Plugin Writer's Guide:
5-
https://pulpproject.org/pulpcore/docs/dev/
6-
"""
3+
CRATES_IO_API_HOSTNAME = "https://" + socket.getfqdn()

pulp_rust/app/tasks/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
from .synchronizing import synchronize # noqa
2+
from .streaming import add_cached_content_to_repository # noqa

pulp_rust/app/tasks/streaming.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import datetime
2+
3+
from asgiref.sync import sync_to_async
4+
5+
from pulpcore.plugin.models import Content, ContentArtifact, RemoteArtifact
6+
from pulpcore.plugin.tasking import add_and_remove
7+
8+
from pulp_rust.app.models import RustRemote, RustRepository
9+
10+
11+
async def aadd_and_remove(*args, **kwargs):
12+
return await sync_to_async(add_and_remove)(*args, **kwargs)
13+
14+
15+
def add_cached_content_to_repository(repository_pk=None, remote_pk=None):
16+
"""
17+
Create a new repository version by adding content that was cached by pulpcore-content when
18+
streaming it from a remote.
19+
20+
Args:
21+
repository_pk (uuid): The primary key for a Repository for which a new Repository Version
22+
should be created.
23+
remote_pk (uuid): The primary key for a Remote which will be used to identify Content
24+
created by pulpcore-content when it streamed it to clients.
25+
"""
26+
repository = RustRepository.objects.get(pk=repository_pk)
27+
remote = RustRemote.objects.get(pk=remote_pk)
28+
29+
latest_version = repository.latest_version()
30+
31+
if latest_version.number == 0:
32+
date_min = datetime.datetime(1970, 1, 1, tzinfo=datetime.timezone.utc)
33+
else:
34+
date_min = latest_version.pulp_created
35+
with repository.new_version(base_version=None) as new_version:
36+
ca_id_list = RemoteArtifact.objects.filter(
37+
remote=remote, pulp_created__gte=date_min
38+
).values_list("content_artifact")
39+
content_list = ContentArtifact.objects.filter(pk__in=ca_id_list).values_list("content")
40+
new_version.add_content(Content.objects.filter(pk__in=content_list))

pulp_rust/app/tasks/synchronizing.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ async def run(self):
7373
downloader = self.remote.get_downloader(url=self.remote.url)
7474
result = await downloader.run()
7575
# Use ProgressReport to report progress
76+
raise NotImplementedError("Not implemented")
77+
7678
for entry in self.read_my_metadata_file_somehow(result.path):
7779
unit = RustContent(entry) # make the content unit in memory-only
7880
artifact = Artifact(entry) # make Artifact in memory-only

pulp_rust/app/urls.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from django.conf import settings
2+
from django.urls import path
3+
4+
from pulp_rust.app.views import IndexRoot, CargoIndexApiViewSet, CargoDownloadApiView
5+
6+
if settings.DOMAIN_ENABLED:
7+
CRATES_IO_URL = "pulp/cargo/<slug:pulp_domain>/<slug:repo>/"
8+
else:
9+
CRATES_IO_URL = "pulp/cargo/<slug:repo>/"
10+
11+
12+
urlpatterns = [
13+
path(
14+
CRATES_IO_URL + "api/v1/crates/<str:package>/<str:version>/<path:rest>",
15+
CargoDownloadApiView.as_view(),
16+
name="cargo-download-api",
17+
),
18+
path(CRATES_IO_URL + "config.json", IndexRoot.as_view({"get": "retrieve"}), name="index-root"),
19+
path(
20+
CRATES_IO_URL + "<path:path>",
21+
CargoIndexApiViewSet.as_view({"get": "retrieve"}),
22+
name="cargo-index-api",
23+
),
24+
]

0 commit comments

Comments
 (0)