Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create and return empty blob on the fly. #331

Merged
merged 1 commit into from
May 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES/8819.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Create and return empty_blob on the fly. (Backported from https://pulp.plan.io/issues/8654).
20 changes: 19 additions & 1 deletion pulp_container/app/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from pulp_container.app.models import ContainerDistribution, Tag
from pulp_container.app.schema_convert import Schema2toSchema1ConverterWrapper
from pulp_container.app.utils import get_accepted_media_types
from pulp_container.constants import MEDIA_TYPE
from pulp_container.constants import EMPTY_BLOB, MEDIA_TYPE

log = logging.getLogger(__name__)

Expand Down Expand Up @@ -212,6 +212,8 @@ async def get_by_digest(self, request):
distribution = self._match_distribution(path)
self._permit(request, distribution)
repository_version = distribution.get_repository_version()
if digest == EMPTY_BLOB:
return await Registry._empty_blob()
try:
ca = ContentArtifact.objects.get(
content__in=repository_version.content, relative_path=digest
Expand All @@ -228,3 +230,19 @@ async def get_by_digest(self, request):
return await Registry._dispatch(artifact.file, headers)
else:
return await self._stream_content_artifact(request, web.StreamResponse(), ca)

@staticmethod
async def _empty_blob():
# fmt: off
empty_tar = [
31, 139, 8, 0, 0, 9, 110, 136, 0, 255, 98, 24, 5, 163, 96, 20, 140, 88, 0, 8, 0, 0, 255,
255, 46, 175, 181, 239, 0, 4, 0, 0,
]
# fmt: on
body = bytearray(empty_tar)
response_headers = {
"Docker-Content-Digest": EMPTY_BLOB,
"Content-Type": MEDIA_TYPE.REGULAR_BLOB,
"Docker-Distribution-API-Version": "registry/2.0",
}
return web.Response(body=body, headers=response_headers)
3 changes: 3 additions & 0 deletions pulp_container/app/registry_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
from pulp_container.app.authorization import AuthorizationService
from pulp_container.app.redirects import FileStorageRedirects, S3StorageRedirects
from pulp_container.app.token_verification import TokenAuthentication, TokenPermission
from pulp_container.constants import EMPTY_BLOB


log = logging.getLogger(__name__)
Expand Down Expand Up @@ -516,6 +517,8 @@ def get(self, request, path, pk=None):
try:
blob = models.Blob.objects.get(digest=pk, pk__in=repository_version.content)
except models.Blob.DoesNotExist:
if pk == EMPTY_BLOB:
return redirects.redirect_to_content_app("blobs", pk)
raise BlobNotFound(digest=pk)
return redirects.issue_blob_redirect(blob)

Expand Down
1 change: 1 addition & 0 deletions pulp_container/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@
REGULAR_BLOB_OCI="application/vnd.oci.image.layer.v1.tar+gzip",
FOREIGN_BLOB_OCI="application/vnd.oci.image.layer.nondistributable.v1.tar+gzip",
)
EMPTY_BLOB = "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
34 changes: 33 additions & 1 deletion pulp_container/tests/functional/api/test_pull_content.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
)

from pulp_container.tests.functional.utils import (
TOKEN_AUTH_DISABLED,
core_client,
gen_container_client,
gen_container_remote,
Expand All @@ -29,7 +30,7 @@
REPO_UPSTREAM_NAME,
REPO_UPSTREAM_TAG,
)
from pulp_container.constants import MEDIA_TYPE
from pulp_container.constants import EMPTY_BLOB, MEDIA_TYPE

from pulpcore.client.pulp_container import (
ContainerContainerDistribution,
Expand Down Expand Up @@ -180,6 +181,37 @@ def test_api_performes_schema_conversion(self):
computed_digest, header_digest.split(":")[1], "The manifest digests are not equal"
)

def test_create_empty_blob_on_the_fly(self):
"""
Test if empty blob getscreated and served on the fly.
"""
blob_path = "/v2/{}/blobs/{}".format(self.distribution_with_repo.base_path, EMPTY_BLOB)
empty_blob_url = urljoin(self.cfg.get_base_url(), blob_path)

if TOKEN_AUTH_DISABLED:
auth = ()
else:
with self.assertRaises(requests.HTTPError) as cm:
requests.get(empty_blob_url).raise_for_status()

content_response = cm.exception.response
self.assertEqual(content_response.status_code, 401)

authenticate_header = content_response.headers["Www-Authenticate"]
queries = AuthenticationHeaderQueries(authenticate_header)
content_response = requests.get(
queries.realm, params={"service": queries.service, "scope": queries.scope}
)
content_response.raise_for_status()
auth = BearerTokenAuth(content_response.json()["token"])
content_response = requests.get(empty_blob_url, auth=auth)
content_response.raise_for_status()
# calculate digest of the payload
digest = hashlib.sha256(content_response.content).hexdigest()
# compare with the digest returned in the response header
header_digest = content_response.headers["docker-content-digest"].split(":")[1]
self.assertEqual(digest, header_digest)

def test_pull_image_from_repository(self):
"""Verify that a client can pull the image from Pulp.

Expand Down
5 changes: 4 additions & 1 deletion pulp_container/tests/functional/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from unittest import SkipTest
from tempfile import NamedTemporaryFile

from pulp_smash import selectors, config
from pulp_smash import cli, config, selectors, utils
from pulp_smash.pulp3.bindings import monitor_task
from pulp_smash.pulp3.utils import (
gen_remote,
Expand Down Expand Up @@ -35,8 +35,11 @@
)

cfg = config.get_config()
cli_client = cli.Client(cfg)
configuration = cfg.get_bindings_config()

TOKEN_AUTH_DISABLED = utils.get_pulp_setting(cli_client, "TOKEN_AUTH_DISABLED")


def gen_container_client():
"""Return an OBJECT for container client."""
Expand Down