Skip to content

Commit

Permalink
Add a field to limit the size of uploading content
Browse files Browse the repository at this point in the history
closes: pulp#532
  • Loading branch information
git-hyagi committed Jul 12, 2024
1 parent 5908f47 commit d43eaeb
Show file tree
Hide file tree
Showing 7 changed files with 71 additions and 5 deletions.
1 change: 1 addition & 0 deletions CHANGES/532.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added a field to distributions to set a limit on layer sizes.
9 changes: 9 additions & 0 deletions pulp_container/app/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from gettext import gettext as _
from rest_framework.exceptions import APIException, NotFound, ParseError

from rest_framework.status import HTTP_413_REQUEST_ENTITY_TOO_LARGE


class BadGateway(APIException):
status_code = 502
Expand Down Expand Up @@ -151,3 +154,9 @@ def __init__(self, message):
]
}
)


class PayloadTooLarge(APIException):
status_code = HTTP_413_REQUEST_ENTITY_TOO_LARGE
default_detail = _("The playload is larger than the defined limits.")
default_code = "request_entity_too_large"
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.2.13 on 2024-07-12 10:42

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('container', '0040_add_remote_repo_filter'),
]

operations = [
migrations.AddField(
model_name='containerdistribution',
name='max_payload_size',
field=models.IntegerField(null=True),
),
]
2 changes: 2 additions & 0 deletions pulp_container/app/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -751,6 +751,8 @@ class ContainerDistribution(Distribution, AutoAddObjPermsMixin):
null=True,
)

max_payload_size = models.IntegerField(null=True)

def get_repository_version(self):
"""
Returns the repository version that is supposed to be served by this ContainerDistribution.
Expand Down
18 changes: 13 additions & 5 deletions pulp_container/app/registry_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
ManifestNotFound,
ManifestInvalid,
ManifestSignatureInvalid,
PayloadTooLarge,
)
from pulp_container.app.redirects import (
FileStorageRedirects,
Expand Down Expand Up @@ -757,15 +758,15 @@ def create(self, request, path):
Create a new upload.
"""
_, repository = self.get_dr_push(request, path, create=True)
distribution, repository = self.get_dr_push(request, path, create=True)

if self.tries_to_mount_blob(request):
response = self.mount_blob(request, path, repository)
elif digest := request.query_params.get("digest"):
# if the digest parameter is present, the request body will be
# used to complete the upload in a single request.
# this is monolithic upload
response = self.single_request_upload(request, path, repository, digest)
response = self.single_request_upload(request, path, repository, digest, distribution)
else:
upload = models.Upload(repository=repository, size=0)
upload.save()
Expand Down Expand Up @@ -811,9 +812,10 @@ def create_blob(self, artifact, digest):
ca.save(update_fields=["artifact"])
return blob

def single_request_upload(self, request, path, repository, digest):
def single_request_upload(self, request, path, repository, digest, distribution):
"""Monolithic upload."""
chunk = request.META["wsgi.input"]
self._verify_payload_size(distribution, chunk)
artifact = self.create_single_chunk_artifact(chunk)
blob = self.create_blob(artifact, digest)
repository.pending_blobs.add(blob)
Expand Down Expand Up @@ -851,9 +853,10 @@ def partial_update(self, request, path, pk=None):
"""
Process a chunk that will be appended to an existing upload.
"""
_, repository = self.get_dr_push(request, path)
distribution, repository = self.get_dr_push(request, path)
upload = get_object_or_404(models.Upload, repository=repository, pk=pk)
chunk = request.META["wsgi.input"]
self._verify_payload_size(distribution, chunk)
if range_header := request.headers.get("Content-Range"):
found = self.content_range_pattern.match(range_header)
if not found:
Expand Down Expand Up @@ -895,13 +898,14 @@ def put(self, request, path, pk=None):
body or last chunk can be uploaded.
"""
_, repository = self.get_dr_push(request, path)
distribution, repository = self.get_dr_push(request, path)

digest = request.query_params["digest"]
chunk = request.META["wsgi.input"]
# last chunk (and the only one) from monolitic upload
# or last chunk from chunked upload
last_chunk = ContentFile(chunk.read())
self._verify_payload_size(distribution, chunk)
upload = get_object_or_404(models.Upload, pk=pk, repository=repository)

if artifact := upload.artifact:
Expand Down Expand Up @@ -938,6 +942,10 @@ def put(self, request, path, pk=None):
repository.pending_blobs.add(blob)
return BlobResponse(blob, path, 201, request)

def _verify_payload_size(self, distribution, chunk):
if distribution.max_payload_size and chunk.reader.length > distribution.max_payload_size:
raise PayloadTooLarge()


class RedirectsMixin:
"""
Expand Down
4 changes: 4 additions & 0 deletions pulp_container/app/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,9 @@ class ContainerDistributionSerializer(DistributionSerializer, GetOrCreateSeriali
view_name_pattern=r"remotes(-.*/.*)?-detail",
read_only=True,
)
max_payload_size = serializers.IntegerField(
required=False, min_value=0, help_text=_("Maximum size of image blob layers.")
)

def validate(self, data):
"""
Expand Down Expand Up @@ -430,6 +433,7 @@ class Meta:
"namespace",
"private",
"description",
"max_payload_size",
)


Expand Down
24 changes: 24 additions & 0 deletions pulp_container/tests/functional/api/test_push_content.py
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,30 @@ def test_push_matching_username(
add_to_cleanup(container_namespace_api, distribution.namespace)


def test_push_with_layer_size_limit(
add_to_cleanup,
container_distribution_api,
registry_client,
local_registry,
):
"""
Test that push fails when a layer is greater than max_upload_size
"""
repo_name = "test/layer_size_limit"
local_url = f"{repo_name}:2.0"
image_path = f"{REGISTRY_V2_REPO_PULP}:manifest_a"
registry_client.pull(image_path)

distribution = {"name": "foo", "base_path": repo_name, "max_payload_size": 100}
distribution_response = container_distribution_api.create(distribution)
created_resources = monitor_task(distribution_response.task).created_resources
distribution = container_distribution_api.read(created_resources[0])
add_to_cleanup(container_distribution_api, distribution.pulp_href)

with pytest.raises(CalledProcessError):
local_registry.tag_and_push(image_path, local_url)


class PushManifestListTestCase(PulpTestCase, rbac_base.BaseRegistryTest):
"""A test case that verifies if a container client can push manifest lists to the registry."""

Expand Down

0 comments on commit d43eaeb

Please sign in to comment.