Skip to content

Commit

Permalink
Fixed blob media_type during image push.
Browse files Browse the repository at this point in the history
closes #9229
closes #8303
  • Loading branch information
ipanova committed Oct 19, 2021
1 parent fa6a339 commit c4b0902
Show file tree
Hide file tree
Showing 7 changed files with 85 additions and 11 deletions.
1 change: 1 addition & 0 deletions CHANGES/8303.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added validation for the supported manifests and blobs media_types.
1 change: 1 addition & 0 deletions CHANGES/9229.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed blob media_type during image push.
2 changes: 1 addition & 1 deletion pulp_container/app/redirects.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ def issue_blob_redirect(self, blob):
except ObjectDoesNotExist:
return self.redirect_to_content_app("blobs", blob.digest)

return self.redirect_to_object_storage(artifact, blob.media_type)
return self.redirect_to_object_storage(artifact, "application/octet-stream")

def redirect_to_object_storage(self, artifact, return_media_type):
"""
Expand Down
10 changes: 7 additions & 3 deletions pulp_container/app/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

from pulpcore.plugin.content import Handler, PathNotResolved
from pulpcore.plugin.models import ContentArtifact
from pulp_container.app.models import ContainerDistribution, Tag
from pulp_container.app.models import ContainerDistribution, Tag, Blob
from pulp_container.app.schema_convert import Schema2toSchema1ConverterWrapper
from pulp_container.app.utils import get_accepted_media_types
from pulp_container.constants import EMPTY_BLOB, MEDIA_TYPE
Expand Down Expand Up @@ -220,8 +220,12 @@ async def get_by_digest(self, request):
relative_path=digest,
)
ca_content = await sync_to_async(ca.content.cast)()
if isinstance(ca_content, Blob):
media_type = "application/octet-stream"
else:
media_type = ca_content.media_type
headers = {
"Content-Type": ca_content.media_type,
"Content-Type": media_type,
"Docker-Content-Digest": ca_content.digest,
}
except ObjectDoesNotExist:
Expand All @@ -244,7 +248,7 @@ async def _empty_blob():
body = bytearray(empty_tar)
response_headers = {
"Docker-Content-Digest": EMPTY_BLOB,
"Content-Type": MEDIA_TYPE.REGULAR_BLOB,
"Content-Type": "application/octet-stream",
"Docker-Distribution-API-Version": "registry/2.0",
}
return web.Response(body=body, headers=response_headers)
78 changes: 73 additions & 5 deletions pulp_container/app/registry_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,24 @@ def __init__(self, digest):
)


class BlobInvalid(ParseError):
"""Exception to render a 400 with the code 'BLOB_UNKNOWN'"""

def __init__(self, digest):
"""Initialize the exception with the blob digest."""
super().__init__(
detail={
"errors": [
{
"code": "BLOB_UNKNOWN",
"message": "blob unknown to registry",
"detail": {"digest": digest},
}
]
}
)


class ManifestNotFound(NotFound):
"""Exception to render a 404 with the code 'MANIFEST_UNKNOWN'"""

Expand All @@ -133,6 +151,24 @@ def __init__(self, reference):
)


class ManifestInvalid(ParseError):
"""Exception to render a 400 with the code 'MANIFEST_INVALID'"""

def __init__(self, digest):
"""Initialize the exception with the manifest digest."""
super().__init__(
detail={
"errors": [
{
"code": "MANIFEST_INVALID",
"message": "manifest invalid",
"detail": {"digest": digest},
}
]
}
)


class ContentRenderer(BaseRenderer):
"""
Rendered class for rendering Manifest and Blob responses.
Expand Down Expand Up @@ -817,14 +853,36 @@ def put(self, request, path, pk=None):
# iterate over all the layers and create
chunk = request.META["wsgi.input"]
artifact = self.receive_artifact(chunk)
manifest_digest = "sha256:{id}".format(id=artifact.sha256)
with storage.open(artifact.file.name) as artifact_file:
raw_data = artifact_file.read()
content_data = json.loads(raw_data)
# need to check request content type
# oci format content_data might not contain that field, docker should
if request.content_type not in (
models.MEDIA_TYPE.MANIFEST_V2,
models.MEDIA_TYPE.MANIFEST_OCI,
):
# we suport only v2 docker/oci schema upload
raise ManifestInvalid(digest=manifest_digest)
# both docker/oci format should contain config, digest, media_type, size
config_layer = content_data.get("config")
config_blob = models.Blob.objects.get(digest=config_layer.get("digest"))
try:
config_digest = config_layer.get("digest")
config_blob = models.Blob.objects.get(digest=config_digest)
except models.Blob.DoesNotExist:
raise BlobNotFound(digest=config_digest)
config_media_type = config_layer.get("mediaType")
if config_media_type not in (
models.MEDIA_TYPE.CONFIG_BLOB,
models.MEDIA_TYPE.CONFIG_BLOB_OCI,
):
raise BlobInvalid(digest=config_blob.digest)
# config_blob.media_type = config_media_type
# config_blob.save()

manifest = models.Manifest(
digest="sha256:{id}".format(id=artifact.sha256),
digest=manifest_digest,
schema_version=2,
media_type=request.content_type,
config_blob=config_blob,
Expand All @@ -839,15 +897,25 @@ def put(self, request, path, pk=None):
ca.save()
except IntegrityError:
pass
# both docker/oci format should contain layers, digest, media_type, size
layers = content_data.get("layers")
blobs = []
blobs = {}
for layer in layers:
blobs.append(layer.get("digest"))
blobs_qs = models.Blob.objects.filter(digest__in=blobs)
blobs[layer.get("digest")] = layer.get("mediaType")
blobs_qs = models.Blob.objects.filter(digest__in=blobs.keys())
thru = []
for blob in blobs_qs:
# ensure there are no foreign layers
blob_media_type = blobs[blob.digest]
if blob_media_type not in (
models.MEDIA_TYPE.REGULAR_BLOB,
models.MEDIA_TYPE.REGULAR_BLOB_OCI,
):
raise BlobInvalid(digest=blob.digest)
# blob.media_type = blob_media_type
thru.append(models.BlobManifest(manifest=manifest, manifest_blob=blob))
models.BlobManifest.objects.bulk_create(objs=thru, ignore_conflicts=True, batch_size=1000)
# models.Blob.objects.bulk_update(objs=blobs_qs, fields=["media_type"])
tag = models.Tag(name=pk, tagged_manifest=manifest)
try:
tag.save()
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# remember to also update unittest_requirements.txt when updating this file
pulpcore>=3.15.0
pulpcore>=3.17.0.dev
ecdsa~=0.14
pyjwkest~=1.4.0
pyjwt[crypto]~=1.7.1
Expand Down
2 changes: 1 addition & 1 deletion unittest_requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
mock
# include the top-level requirements.txt contents below
pulpcore>=3.15.0
pulpcore>=3.17.0.dev
ecdsa~=0.14
pyjwkest~=1.4.0
pyjwt[crypto]~=1.7.1
Expand Down

0 comments on commit c4b0902

Please sign in to comment.