diff --git a/data/model/tag.py b/data/model/tag.py index 57faea80c..d62ee2bcb 100644 --- a/data/model/tag.py +++ b/data/model/tag.py @@ -530,6 +530,15 @@ def get_active_tag(namespace, repo_name, tag_name): Namespace.username == namespace)).get() +def get_possibly_expired_tag(namespace, repo_name, tag_name): + return (RepositoryTag + .select() + .join(Repository) + .join(Namespace, on=(Repository.namespace_user == Namespace.id)) + .where(RepositoryTag.name == tag_name, Repository.name == repo_name, + Namespace.username == namespace)).get() + + def associate_generated_tag_manifest(namespace, repo_name, tag_name, manifest_digest, manifest_data): tag = get_active_tag(namespace, repo_name, tag_name) diff --git a/endpoints/v2/errors.py b/endpoints/v2/errors.py index 0ae998106..40d7b9529 100644 --- a/endpoints/v2/errors.py +++ b/endpoints/v2/errors.py @@ -57,6 +57,13 @@ class ManifestUnknown(V2RegistryException): def __init__(self, detail=None): super(ManifestUnknown, self).__init__('MANIFEST_UNKNOWN', 'manifest unknown', detail, 404) +class TagExpired(V2RegistryException): + def __init__(self, message=None, detail=None): + super(TagExpired, self).__init__('TAG_EXPIRED', + message or 'Tag has expired', + detail, + 404) + class ManifestUnverified(V2RegistryException): def __init__(self, detail=None): diff --git a/endpoints/v2/manifest.py b/endpoints/v2/manifest.py index d35c46556..6a1fc801d 100644 --- a/endpoints/v2/manifest.py +++ b/endpoints/v2/manifest.py @@ -13,10 +13,11 @@ from endpoints.common import parse_repository_name from endpoints.decorators import anon_protect from endpoints.notificationhelper import spawn_notification from endpoints.v2 import v2_bp, require_repo_read, require_repo_write -from endpoints.v2.errors import ( - BlobUnknown, ManifestInvalid, ManifestUnknown, TagInvalid, NameInvalid) from endpoints.v2.models_interface import Label from endpoints.v2.models_pre_oci import data_model as model +from endpoints.v2.errors import (BlobUnknown, ManifestInvalid, ManifestUnknown, TagInvalid, + NameInvalid, TagExpired) +>>>>>>> Change error message when trying to pull a deleted or expired tag from endpoints.v2.labelhandlers import handle_label from image.docker import ManifestException from image.docker.schema1 import DockerSchema1Manifest, DockerSchema1ManifestBuilder @@ -43,7 +44,14 @@ def fetch_manifest_by_tagname(namespace_name, repo_name, manifest_ref): if manifest is None: has_tag = model.has_active_tag(namespace_name, repo_name, manifest_ref) if not has_tag: - raise ManifestUnknown() + has_expired_tag = model.has_tag(namespace_name, repo_name, manifest_ref) + if has_expired_tag: + logger.debug('Found expired tag %s for repository %s/%s', manifest_ref, namespace_name, + repo_name) + msg = 'Tag %s was deleted or has expired. To pull, revive via time machine' % manifest_ref + raise TagExpired(msg) + else: + raise ManifestUnknown() manifest = _generate_and_store_manifest(namespace_name, repo_name, manifest_ref) if manifest is None: diff --git a/endpoints/v2/models_pre_oci.py b/endpoints/v2/models_pre_oci.py index 264bf149d..fa2e7dc49 100644 --- a/endpoints/v2/models_pre_oci.py +++ b/endpoints/v2/models_pre_oci.py @@ -37,6 +37,13 @@ class PreOCIModel(DockerRegistryV2DataInterface): except database.RepositoryTag.DoesNotExist: return False + def has_tag(self, namespace_name, repo_name, tag_name): + try: + model.tag.get_possibly_expired_tag(namespace_name, repo_name, tag_name) + return True + except database.RepositoryTag.DoesNotExist: + return False + def get_manifest_by_tag(self, namespace_name, repo_name, tag_name): try: manifest = model.tag.load_tag_manifest(namespace_name, repo_name, tag_name) diff --git a/static/css/directives/ui/expiration-status-view.css b/static/js/directives/ui/expiration-status-view/expiration-status-view.component.css similarity index 100% rename from static/css/directives/ui/expiration-status-view.css rename to static/js/directives/ui/expiration-status-view/expiration-status-view.component.css diff --git a/static/js/directives/ui/expiration-status-view/expiration-status-view.component.ts b/static/js/directives/ui/expiration-status-view/expiration-status-view.component.ts index b5f4859a2..e3a5bd4a6 100644 --- a/static/js/directives/ui/expiration-status-view/expiration-status-view.component.ts +++ b/static/js/directives/ui/expiration-status-view/expiration-status-view.component.ts @@ -1,5 +1,6 @@ import { Input, Component, Inject } from 'ng-metadata/core'; import * as moment from "moment"; +import './expiration-status-view.component.css'; /** * A component that displays expiration status. @@ -16,16 +17,16 @@ export class ExpirationStatusViewComponent { return ''; } - var expiration_date = moment(expirationDate); - if (moment().isAfter(expiration_date)) { + var expiration = moment(expirationDate); + if (moment().isAfter(expiration)) { return {'className': 'expired', 'icon': 'fa-warning'}; } - if (moment().add(1, 'week').isAfter(expiration_date)) { + if (moment().add(1, 'week').isAfter(expiration)) { return {'className': 'critical', 'icon': 'fa-warning'}; } - if (moment().add(1, 'month').isAfter(expiration_date)) { + if (moment().add(1, 'month').isAfter(expiration)) { return {'className': 'warning', 'icon': 'fa-warning'}; } diff --git a/test/data/test.db b/test/data/test.db index 2dde19399..e109172d6 100644 Binary files a/test/data/test.db and b/test/data/test.db differ