1
0
mirror of https://github.com/quay/quay.git synced 2025-07-28 20:22:05 +03:00

api: fully deprecate image api endpoints (PROJQUAY-3418) (#1164)

This commit is contained in:
Kenny Lee Sin Cheong
2022-03-22 11:12:39 -04:00
committed by GitHub
parent ba652e0b98
commit 02dfc63f42
14 changed files with 117 additions and 684 deletions

View File

@ -503,7 +503,6 @@ import endpoints.api.build
import endpoints.api.discovery
import endpoints.api.error
import endpoints.api.globalmessages
import endpoints.api.image
import endpoints.api.logs
import endpoints.api.manifest
import endpoints.api.organization

View File

@ -1,120 +0,0 @@
"""
List and lookup repository images.
"""
import json
from collections import defaultdict
from datetime import datetime
from app import storage
from data.registry_model import registry_model
from endpoints.api import (
resource,
nickname,
require_repo_read,
RepositoryParamResource,
path_param,
disallow_for_app_repositories,
format_date,
deprecated,
)
from endpoints.exception import NotFound
def image_dict(image):
parsed_command = None
if image.command:
try:
parsed_command = json.loads(image.command)
except (ValueError, TypeError):
parsed_command = {"error": "Could not parse command"}
image_data = {
"id": image.docker_image_id,
"created": format_date(image.created),
"comment": image.comment,
"command": parsed_command,
"size": image.image_size,
"uploading": False,
"sort_index": 0,
}
image_data["ancestors"] = "/{0}/".format("/".join(image.ancestor_ids))
return image_data
@resource("/v1/repository/<apirepopath:repository>/image/")
@path_param("repository", "The full path of the repository. e.g. namespace/name")
class RepositoryImageList(RepositoryParamResource):
"""
Resource for listing repository images.
"""
@require_repo_read
@nickname("listRepositoryImages")
@disallow_for_app_repositories
@deprecated()
def get(self, namespace, repository):
"""
List the images for the specified repository.
"""
repo_ref = registry_model.lookup_repository(namespace, repository)
if repo_ref is None:
raise NotFound()
tags = registry_model.list_all_active_repository_tags(repo_ref)
images_with_tags = defaultdict(list)
for tag in tags:
legacy_image_id = tag.manifest.legacy_image_root_id
if legacy_image_id is not None:
images_with_tags[legacy_image_id].append(tag)
# NOTE: This is replicating our older response for this endpoint, but
# returns empty for the metadata fields. This is to ensure back-compat
# for callers still using the deprecated API, while not having to load
# all the manifests from storage.
return {
"images": [
{
"id": image_id,
"created": format_date(
datetime.utcfromtimestamp((min([tag.lifetime_start_ts for tag in tags])))
),
"comment": "",
"command": "",
"size": 0,
"uploading": False,
"sort_index": 0,
"tags": [tag.name for tag in tags],
"ancestors": "",
}
for image_id, tags in images_with_tags.items()
]
}
@resource("/v1/repository/<apirepopath:repository>/image/<image_id>")
@path_param("repository", "The full path of the repository. e.g. namespace/name")
@path_param("image_id", "The Docker image ID")
class RepositoryImage(RepositoryParamResource):
"""
Resource for handling repository images.
"""
@require_repo_read
@nickname("getImage")
@disallow_for_app_repositories
@deprecated()
def get(self, namespace, repository, image_id):
"""
Get the information available for the specified image.
"""
repo_ref = registry_model.lookup_repository(namespace, repository)
if repo_ref is None:
raise NotFound()
image = registry_model.get_legacy_image(repo_ref, image_id, storage)
if image is None:
raise NotFound()
return image_dict(image)

View File

@ -29,7 +29,6 @@ from endpoints.api import (
format_date,
disallow_for_non_normal_repositories,
)
from endpoints.api.image import image_dict
from endpoints.exception import NotFound
from util.validation import VALID_LABEL_KEY_REGEX
@ -83,22 +82,6 @@ def _manifest_dict(manifest):
logger.debug("Missing layers for manifest `%s`", manifest.digest)
abort(404)
image = None
if manifest.legacy_image_root_id:
# NOTE: This is replicating our older response for this endpoint, but
# returns empty for the metadata fields. This is to ensure back-compat
# for callers still using the deprecated API.
image = {
"id": manifest.legacy_image_root_id,
"created": format_date(datetime.utcnow()),
"comment": "",
"command": "",
"size": 0,
"uploading": False,
"sort_index": 0,
"ancestors": "",
}
return {
"digest": manifest.digest,
"is_manifest_list": manifest.is_manifest_list,
@ -107,7 +90,6 @@ def _manifest_dict(manifest):
"layers": (
[_layer_dict(lyr.layer_info, idx) for idx, lyr in enumerate(layers)] if layers else None
),
"image": image,
}

View File

@ -201,7 +201,6 @@ class Tag(
"Tag",
[
"name",
"image_docker_image_id",
"image_aggregate_size",
"lifetime_start_ts",
"tag_manifest_digest",
@ -211,7 +210,6 @@ class Tag(
):
"""
:type name: string
:type image_docker_image_id: string
:type image_aggregate_size: int
:type lifetime_start_ts: int
:type lifetime_end_ts: int|None
@ -222,7 +220,6 @@ class Tag(
def to_dict(self):
tag_info = {
"name": self.name,
"image_id": self.image_docker_image_id,
"size": self.image_aggregate_size,
}

View File

@ -268,7 +268,6 @@ class PreOCIModel(RepositoryDataInterface):
tags = [
Tag(
tag.name,
tag.manifest.legacy_image_root_id,
tag.manifest_layers_size,
tag.lifetime_start_ts,
tag.manifest_digest,

View File

@ -75,42 +75,6 @@ def _security_info(manifest_or_legacy_image, include_vulnerabilities=True):
}
@resource("/v1/repository/<apirepopath:repository>/image/<imageid>/security")
@show_if(features.SECURITY_SCANNER)
@path_param("repository", "The full path of the repository. e.g. namespace/name")
@path_param("imageid", "The image ID")
class RepositoryImageSecurity(RepositoryParamResource):
"""
Operations for managing the vulnerabilities in a repository image.
DEPRECATED: Please retrieve security by manifest .
"""
@process_basic_auth_no_pass
@anon_allowed
@require_repo_read
@nickname("getRepoImageSecurity")
@deprecated()
@disallow_for_app_repositories
@parse_args()
@query_param(
"vulnerabilities", "Include vulnerabilities information", type=truthy_bool, default=False
)
def get(self, namespace, repository, imageid, parsed_args):
"""
Fetches the features and vulnerabilities (if any) for a repository image.
"""
repo_ref = registry_model.lookup_repository(namespace, repository)
if repo_ref is None:
raise NotFound()
legacy_image = registry_model.get_legacy_image(repo_ref, imageid, storage)
if legacy_image is None:
raise NotFound()
return _security_info(legacy_image, parsed_args.vulnerabilities)
@resource(MANIFEST_DIGEST_ROUTE + "/security")
@show_if(features.SECURITY_SCANNER)
@path_param("repository", "The full path of the repository. e.g. namespace/name")

View File

@ -23,7 +23,6 @@ from endpoints.api import (
format_date,
disallow_for_non_normal_repositories,
)
from endpoints.api.image import image_dict
from endpoints.exception import NotFound, InvalidRequest
from util.names import TAG_ERROR, TAG_REGEX
from util.parsing import truthy_bool
@ -44,8 +43,6 @@ def _tag_dict(tag):
tag_info["manifest_digest"] = tag.manifest_digest
tag_info["is_manifest_list"] = tag.manifest.is_manifest_list
tag_info["size"] = tag.manifest_layers_size
tag_info["docker_image_id"] = tag.manifest.legacy_image_root_id
tag_info["image_id"] = tag.manifest.legacy_image_root_id
if tag.lifetime_start_ts and tag.lifetime_start_ts > 0:
last_modified = format_date(datetime.utcfromtimestamp(tag.lifetime_start_ts))
@ -112,10 +109,6 @@ class RepositoryTag(RepositoryParamResource):
"type": "object",
"description": "Makes changes to a specific tag",
"properties": {
"image": {
"type": ["string", "null"],
"description": "(Deprecated: Use `manifest_digest`) Image to which the tag should point.",
},
"manifest_digest": {
"type": ["string", "null"],
"description": "(If specified) The manifest digest to which the tag should point",
@ -181,23 +174,17 @@ class RepositoryTag(RepositoryParamResource):
else:
raise InvalidRequest("Could not update tag expiration; Tag has probably changed")
if "image" in request.get_json() or "manifest_digest" in request.get_json():
if "manifest_digest" in request.get_json():
existing_tag = registry_model.get_repo_tag(repo_ref, tag)
manifest_or_image = None
image_id = None
manifest_digest = None
if "manifest_digest" in request.get_json():
manifest_digest = request.get_json()["manifest_digest"]
manifest_or_image = registry_model.lookup_manifest_by_digest(
manifest = registry_model.lookup_manifest_by_digest(
repo_ref, manifest_digest, require_available=True
)
else:
image_id = request.get_json()["image"]
manifest_or_image = registry_model.get_legacy_image(repo_ref, image_id, storage)
if manifest_or_image is None:
if manifest is None:
raise NotFound()
existing_manifest = (
@ -206,7 +193,7 @@ class RepositoryTag(RepositoryParamResource):
existing_manifest_digest = existing_manifest.digest if existing_manifest else None
if not registry_model.retarget_tag(
repo_ref, tag, manifest_or_image, storage, docker_v2_signing_key
repo_ref, tag, manifest, storage, docker_v2_signing_key
):
raise InvalidRequest("Could not move tag")
@ -220,7 +207,6 @@ class RepositoryTag(RepositoryParamResource):
"repo": repository,
"tag": tag,
"namespace": namespace,
"image": image_id,
"manifest_digest": manifest_digest,
"original_manifest_digest": existing_manifest_digest,
},
@ -254,72 +240,6 @@ class RepositoryTag(RepositoryParamResource):
return "", 204
@resource("/v1/repository/<apirepopath:repository>/tag/<tag>/images")
@path_param("repository", "The full path of the repository. e.g. namespace/name")
@path_param("tag", "The name of the tag")
class RepositoryTagImages(RepositoryParamResource):
"""
Resource for listing the images in a specific repository tag.
"""
@require_repo_read
@nickname("listTagImages")
@disallow_for_app_repositories
@parse_args()
@deprecated()
@query_param(
"owned",
"If specified, only images wholely owned by this tag are returned.",
type=truthy_bool,
default=False,
)
def get(self, namespace, repository, tag, parsed_args):
"""
List the images for the specified repository tag.
"""
repo_ref = registry_model.lookup_repository(namespace, repository)
if repo_ref is None:
raise NotFound()
tag_ref = registry_model.get_repo_tag(repo_ref, tag)
if tag_ref is None:
raise NotFound()
if parsed_args["owned"]:
# NOTE: This is deprecated, so we just return empty now.
return {"images": []}
manifest = registry_model.get_manifest_for_tag(tag_ref)
if manifest is None:
raise NotFound()
legacy_image = registry_model.get_legacy_image(
repo_ref, manifest.legacy_image_root_id, storage
)
if legacy_image is None:
raise NotFound()
# NOTE: This is replicating our older response for this endpoint, but
# returns empty for the metadata fields. This is to ensure back-compat
# for callers still using the deprecated API, while not having to load
# all the manifests from storage.
return {
"images": [
{
"id": image_id,
"created": format_date(datetime.utcfromtimestamp(tag_ref.lifetime_start_ts)),
"comment": "",
"command": "",
"size": 0,
"uploading": False,
"sort_index": 0,
"ancestors": "",
}
for image_id in legacy_image.full_image_id_chain
]
}
@resource("/v1/repository/<apirepopath:repository>/tag/<tag>/restore")
@path_param("repository", "The full path of the repository. e.g. namespace/name")
@path_param("tag", "The name of the tag")
@ -333,10 +253,6 @@ class RestoreTag(RepositoryParamResource):
"type": "object",
"description": "Restores a tag to a specific image",
"properties": {
"image": {
"type": "string",
"description": "(Deprecated: use `manifest_digest`) Image to which the tag should point",
},
"manifest_digest": {
"type": "string",
"description": "If specified, the manifest digest that should be used",
@ -359,10 +275,9 @@ class RestoreTag(RepositoryParamResource):
raise NotFound()
# Restore the tag back to the previous image.
image_id = request.get_json().get("image", None)
manifest_digest = request.get_json().get("manifest_digest", None)
if image_id is None and manifest_digest is None:
if manifest_digest is None:
raise InvalidRequest("Missing manifest_digest")
# Data for logging the reversion/restoration.
@ -371,25 +286,20 @@ class RestoreTag(RepositoryParamResource):
"username": username,
"repo": repository,
"tag": tag,
"image": image_id,
"manifest_digest": manifest_digest,
}
manifest_or_legacy_image = None
if manifest_digest is not None:
manifest_or_legacy_image = registry_model.lookup_manifest_by_digest(
manifest = registry_model.lookup_manifest_by_digest(
repo_ref, manifest_digest, allow_dead=True, require_available=True
)
elif image_id is not None:
manifest_or_legacy_image = registry_model.get_legacy_image(repo_ref, image_id, storage)
if manifest_or_legacy_image is None:
if manifest is None:
raise NotFound()
if not registry_model.retarget_tag(
repo_ref,
tag,
manifest_or_legacy_image,
manifest,
storage,
docker_v2_signing_key,
is_reversion=True,

View File

@ -1,26 +0,0 @@
import pytest
from data import model
from data.model.oci import shared
from data.registry_model import registry_model
from endpoints.api.secscan import RepositoryImageSecurity
from endpoints.api.test.shared import conduct_api_call
from endpoints.test.shared import client_with_identity
from test.fixtures import *
def test_deprecated_route(client):
repository_ref = registry_model.lookup_repository("devtable", "simple")
tag = registry_model.get_repo_tag(repository_ref, "latest")
manifest = registry_model.get_manifest_for_tag(tag)
with client_with_identity("devtable", client) as cl:
resp = conduct_api_call(
cl,
RepositoryImageSecurity,
"get",
{"repository": "devtable/simple", "imageid": manifest.legacy_image_root_id},
expected_code=200,
)
assert resp.headers["Deprecation"] == "true"

View File

@ -8,16 +8,15 @@ from endpoints.api.build import (
RepositoryBuildStatus,
RepositoryBuildLogs,
)
from endpoints.api.image import RepositoryImageList, RepositoryImage
from endpoints.api.manifest import RepositoryManifestLabels, ManageRepositoryManifestLabel
from endpoints.api.repositorynotification import (
RepositoryNotification,
RepositoryNotificationList,
TestRepositoryNotification,
)
from endpoints.api.secscan import RepositoryImageSecurity, RepositoryManifestSecurity
from endpoints.api.secscan import RepositoryManifestSecurity
from endpoints.api.signing import RepositorySignatures
from endpoints.api.tag import ListRepositoryTags, RepositoryTag, RepositoryTagImages, RestoreTag
from endpoints.api.tag import ListRepositoryTags, RepositoryTag, RestoreTag
from endpoints.api.trigger import (
BuildTriggerList,
BuildTrigger,
@ -53,8 +52,6 @@ FIELD_ARGS = {"trigger_uuid": "1234", "field_name": "foobar"}
(RepositoryBuildResource, "delete", BUILD_ARGS),
(RepositoryBuildStatus, "get", BUILD_ARGS),
(RepositoryBuildLogs, "get", BUILD_ARGS),
(RepositoryImageList, "get", None),
(RepositoryImage, "get", IMAGE_ARGS),
(RepositoryManifestLabels, "get", MANIFEST_ARGS),
(RepositoryManifestLabels, "post", MANIFEST_ARGS),
(ManageRepositoryManifestLabel, "get", LABEL_ARGS),
@ -65,13 +62,11 @@ FIELD_ARGS = {"trigger_uuid": "1234", "field_name": "foobar"}
(RepositoryNotification, "delete", NOTIFICATION_ARGS),
(RepositoryNotification, "post", NOTIFICATION_ARGS),
(TestRepositoryNotification, "post", NOTIFICATION_ARGS),
(RepositoryImageSecurity, "get", IMAGE_ARGS),
(RepositoryManifestSecurity, "get", MANIFEST_ARGS),
(RepositorySignatures, "get", None),
(ListRepositoryTags, "get", None),
(RepositoryTag, "put", TAG_ARGS),
(RepositoryTag, "delete", TAG_ARGS),
(RepositoryTagImages, "get", TAG_ARGS),
(RestoreTag, "post", TAG_ARGS),
(BuildTriggerList, "get", None),
(BuildTrigger, "get", TRIGGER_ARGS),

View File

@ -22,4 +22,3 @@ def test_repository_manifest(client):
result = conduct_api_call(cl, RepositoryManifest, "GET", params, None, 200).json
assert result["digest"] == manifest_digest
assert result["manifest_data"]
assert result["image"]

View File

@ -6,7 +6,7 @@ from mock import patch
from data.registry_model import registry_model
from endpoints.test.shared import gen_basic_auth
from endpoints.api.test.shared import conduct_api_call
from endpoints.api.secscan import RepositoryImageSecurity, RepositoryManifestSecurity
from endpoints.api.secscan import RepositoryManifestSecurity
from test.fixtures import *
@ -14,14 +14,10 @@ from test.fixtures import *
@pytest.mark.parametrize(
"endpoint, anonymous_allowed, auth_headers, expected_code",
[
pytest.param(RepositoryImageSecurity, True, gen_basic_auth("devtable", "password"), 200),
pytest.param(RepositoryImageSecurity, False, gen_basic_auth("devtable", "password"), 200),
pytest.param(RepositoryManifestSecurity, True, gen_basic_auth("devtable", "password"), 200),
pytest.param(
RepositoryManifestSecurity, False, gen_basic_auth("devtable", "password"), 200
),
pytest.param(RepositoryImageSecurity, True, None, 401),
pytest.param(RepositoryImageSecurity, False, None, 401),
pytest.param(RepositoryManifestSecurity, True, None, 401),
pytest.param(RepositoryManifestSecurity, False, None, 401),
],
@ -36,7 +32,6 @@ def test_get_security_info_with_pull_secret(
params = {
"repository": "devtable/simple",
"imageid": tag.manifest.legacy_image_root_id,
"manifestref": manifest.digest,
}

View File

@ -14,7 +14,6 @@ from endpoints.api.billing import *
from endpoints.api.build import *
from endpoints.api.discovery import *
from endpoints.api.globalmessages import *
from endpoints.api.image import *
from endpoints.api.logs import *
from endpoints.api.manifest import *
from endpoints.api.organization import *
@ -2648,95 +2647,6 @@ SECURITY_TESTS: List[
"reader",
200,
),
(
RepositoryTagImages,
"GET",
{"tag": "TN96", "repository": "public/publicrepo"},
None,
None,
404,
),
(
RepositoryTagImages,
"GET",
{"tag": "TN96", "repository": "public/publicrepo"},
None,
"devtable",
404,
),
(
RepositoryTagImages,
"GET",
{"tag": "TN96", "repository": "public/publicrepo"},
None,
"freshuser",
404,
),
(
RepositoryTagImages,
"GET",
{"tag": "TN96", "repository": "public/publicrepo"},
None,
"reader",
404,
),
(RepositoryTagImages, "GET", {"tag": "TN96", "repository": "devtable/shared"}, None, None, 401),
(
RepositoryTagImages,
"GET",
{"tag": "TN96", "repository": "devtable/shared"},
None,
"devtable",
404,
),
(
RepositoryTagImages,
"GET",
{"tag": "TN96", "repository": "devtable/shared"},
None,
"freshuser",
403,
),
(
RepositoryTagImages,
"GET",
{"tag": "TN96", "repository": "devtable/shared"},
None,
"reader",
404,
),
(
RepositoryTagImages,
"GET",
{"tag": "TN96", "repository": "buynlarge/orgrepo"},
None,
None,
401,
),
(
RepositoryTagImages,
"GET",
{"tag": "TN96", "repository": "buynlarge/orgrepo"},
None,
"devtable",
404,
),
(
RepositoryTagImages,
"GET",
{"tag": "TN96", "repository": "buynlarge/orgrepo"},
None,
"freshuser",
403,
),
(
RepositoryTagImages,
"GET",
{"tag": "TN96", "repository": "buynlarge/orgrepo"},
None,
"reader",
404,
),
(
PermissionPrototype,
"DELETE",
@ -3743,107 +3653,11 @@ SECURITY_TESTS: List[
"reader",
403,
),
(
RepositoryImage,
"GET",
{"image_id": "5AVQ", "repository": "public/publicrepo"},
None,
None,
404,
),
(
RepositoryImage,
"GET",
{"image_id": "5AVQ", "repository": "public/publicrepo"},
None,
"devtable",
404,
),
(
RepositoryImage,
"GET",
{"image_id": "5AVQ", "repository": "public/publicrepo"},
None,
"freshuser",
404,
),
(
RepositoryImage,
"GET",
{"image_id": "5AVQ", "repository": "public/publicrepo"},
None,
"reader",
404,
),
(
RepositoryImage,
"GET",
{"image_id": "5AVQ", "repository": "devtable/shared"},
None,
None,
401,
),
(
RepositoryImage,
"GET",
{"image_id": "5AVQ", "repository": "devtable/shared"},
None,
"devtable",
404,
),
(
RepositoryImage,
"GET",
{"image_id": "5AVQ", "repository": "devtable/shared"},
None,
"freshuser",
403,
),
(
RepositoryImage,
"GET",
{"image_id": "5AVQ", "repository": "devtable/shared"},
None,
"reader",
404,
),
(
RepositoryImage,
"GET",
{"image_id": "5AVQ", "repository": "buynlarge/orgrepo"},
None,
None,
401,
),
(
RepositoryImage,
"GET",
{"image_id": "5AVQ", "repository": "buynlarge/orgrepo"},
None,
"devtable",
404,
),
(
RepositoryImage,
"GET",
{"image_id": "5AVQ", "repository": "buynlarge/orgrepo"},
None,
"freshuser",
403,
),
(
RepositoryImage,
"GET",
{"image_id": "5AVQ", "repository": "buynlarge/orgrepo"},
None,
"reader",
404,
),
(
RestoreTag,
"POST",
{"tag": "HP8R", "repository": "public/publicrepo"},
{"image": "WXNG"},
{"manifest_digest": "WXNG"},
None,
401,
),
@ -3851,7 +3665,7 @@ SECURITY_TESTS: List[
RestoreTag,
"POST",
{"tag": "HP8R", "repository": "public/publicrepo"},
{"image": "WXNG"},
{"manifest_digest": "WXNG"},
"devtable",
403,
),
@ -3859,7 +3673,7 @@ SECURITY_TESTS: List[
RestoreTag,
"POST",
{"tag": "HP8R", "repository": "public/publicrepo"},
{"image": "WXNG"},
{"manifest_digest": "WXNG"},
"freshuser",
403,
),
@ -3867,7 +3681,7 @@ SECURITY_TESTS: List[
RestoreTag,
"POST",
{"tag": "HP8R", "repository": "public/publicrepo"},
{"image": "WXNG"},
{"manifest_digest": "WXNG"},
"reader",
403,
),
@ -3875,7 +3689,7 @@ SECURITY_TESTS: List[
RestoreTag,
"POST",
{"tag": "HP8R", "repository": "devtable/shared"},
{"image": "WXNG"},
{"manifest_digest": "WXNG"},
None,
401,
),
@ -3883,7 +3697,7 @@ SECURITY_TESTS: List[
RestoreTag,
"POST",
{"tag": "HP8R", "repository": "devtable/shared"},
{"image": "WXNG"},
{"manifest_digest": "WXNG"},
"devtable",
404,
),
@ -3891,7 +3705,7 @@ SECURITY_TESTS: List[
RestoreTag,
"POST",
{"tag": "HP8R", "repository": "devtable/shared"},
{"image": "WXNG"},
{"manifest_digest": "WXNG"},
"freshuser",
403,
),
@ -3899,7 +3713,7 @@ SECURITY_TESTS: List[
RestoreTag,
"POST",
{"tag": "HP8R", "repository": "devtable/shared"},
{"image": "WXNG"},
{"manifest_digest": "WXNG"},
"reader",
403,
),
@ -3907,7 +3721,7 @@ SECURITY_TESTS: List[
RestoreTag,
"POST",
{"tag": "HP8R", "repository": "buynlarge/orgrepo"},
{"image": "WXNG"},
{"manifest_digest": "WXNG"},
None,
401,
),
@ -3915,7 +3729,7 @@ SECURITY_TESTS: List[
RestoreTag,
"POST",
{"tag": "HP8R", "repository": "buynlarge/orgrepo"},
{"image": "WXNG"},
{"manifest_digest": "WXNG"},
"devtable",
404,
),
@ -3923,7 +3737,7 @@ SECURITY_TESTS: List[
RestoreTag,
"POST",
{"tag": "HP8R", "repository": "buynlarge/orgrepo"},
{"image": "WXNG"},
{"manifest_digest": "WXNG"},
"freshuser",
403,
),
@ -3931,7 +3745,7 @@ SECURITY_TESTS: List[
RestoreTag,
"POST",
{"tag": "HP8R", "repository": "buynlarge/orgrepo"},
{"image": "WXNG"},
{"manifest_digest": "WXNG"},
"reader",
403,
),
@ -3964,7 +3778,7 @@ SECURITY_TESTS: List[
RepositoryTag,
"PUT",
{"tag": "HP8R", "repository": "public/publicrepo"},
{"image": "WXNG"},
{"manifest_digest": "WXNG"},
None,
401,
),
@ -3972,7 +3786,7 @@ SECURITY_TESTS: List[
RepositoryTag,
"PUT",
{"tag": "HP8R", "repository": "public/publicrepo"},
{"image": "WXNG"},
{"manifest_digest": "WXNG"},
"devtable",
403,
),
@ -3980,7 +3794,7 @@ SECURITY_TESTS: List[
RepositoryTag,
"PUT",
{"tag": "HP8R", "repository": "public/publicrepo"},
{"image": "WXNG"},
{"manifest_digest": "WXNG"},
"freshuser",
403,
),
@ -3988,7 +3802,7 @@ SECURITY_TESTS: List[
RepositoryTag,
"PUT",
{"tag": "HP8R", "repository": "public/publicrepo"},
{"image": "WXNG"},
{"manifest_digest": "WXNG"},
"reader",
403,
),
@ -4021,7 +3835,7 @@ SECURITY_TESTS: List[
RepositoryTag,
"PUT",
{"tag": "HP8R", "repository": "devtable/shared"},
{"image": "WXNG"},
{"manifest_digest": "WXNG"},
None,
401,
),
@ -4029,7 +3843,7 @@ SECURITY_TESTS: List[
RepositoryTag,
"PUT",
{"tag": "HP8R", "repository": "devtable/shared"},
{"image": "WXNG"},
{"manifest_digest": "WXNG"},
"devtable",
404,
),
@ -4037,7 +3851,7 @@ SECURITY_TESTS: List[
RepositoryTag,
"PUT",
{"tag": "HP8R", "repository": "devtable/shared"},
{"image": "WXNG"},
{"manifest_digest": "WXNG"},
"freshuser",
403,
),
@ -4045,7 +3859,7 @@ SECURITY_TESTS: List[
RepositoryTag,
"PUT",
{"tag": "HP8R", "repository": "devtable/shared"},
{"image": "WXNG"},
{"manifest_digest": "WXNG"},
"reader",
403,
),
@ -4078,7 +3892,7 @@ SECURITY_TESTS: List[
RepositoryTag,
"PUT",
{"tag": "HP8R", "repository": "buynlarge/orgrepo"},
{"image": "WXNG"},
{"manifest_digest": "WXNG"},
None,
401,
),
@ -4086,7 +3900,7 @@ SECURITY_TESTS: List[
RepositoryTag,
"PUT",
{"tag": "HP8R", "repository": "buynlarge/orgrepo"},
{"image": "WXNG"},
{"manifest_digest": "WXNG"},
"devtable",
404,
),
@ -4094,7 +3908,7 @@ SECURITY_TESTS: List[
RepositoryTag,
"PUT",
{"tag": "HP8R", "repository": "buynlarge/orgrepo"},
{"image": "WXNG"},
{"manifest_digest": "WXNG"},
"freshuser",
403,
),
@ -4102,7 +3916,7 @@ SECURITY_TESTS: List[
RepositoryTag,
"PUT",
{"tag": "HP8R", "repository": "buynlarge/orgrepo"},
{"image": "WXNG"},
{"manifest_digest": "WXNG"},
"reader",
403,
),
@ -4744,18 +4558,6 @@ SECURITY_TESTS: List[
"reader",
403,
),
(RepositoryImageList, "GET", {"repository": "public/publicrepo"}, None, None, 200),
(RepositoryImageList, "GET", {"repository": "public/publicrepo"}, None, "devtable", 200),
(RepositoryImageList, "GET", {"repository": "public/publicrepo"}, None, "freshuser", 200),
(RepositoryImageList, "GET", {"repository": "public/publicrepo"}, None, "reader", 200),
(RepositoryImageList, "GET", {"repository": "devtable/shared"}, None, None, 401),
(RepositoryImageList, "GET", {"repository": "devtable/shared"}, None, "devtable", 200),
(RepositoryImageList, "GET", {"repository": "devtable/shared"}, None, "freshuser", 403),
(RepositoryImageList, "GET", {"repository": "devtable/shared"}, None, "reader", 200),
(RepositoryImageList, "GET", {"repository": "buynlarge/orgrepo"}, None, None, 401),
(RepositoryImageList, "GET", {"repository": "buynlarge/orgrepo"}, None, "devtable", 200),
(RepositoryImageList, "GET", {"repository": "buynlarge/orgrepo"}, None, "freshuser", 403),
(RepositoryImageList, "GET", {"repository": "buynlarge/orgrepo"}, None, "reader", 200),
(RepositoryLogs, "GET", {"repository": "public/publicrepo"}, None, None, 401),
(RepositoryLogs, "GET", {"repository": "public/publicrepo"}, None, "devtable", 403),
(RepositoryLogs, "GET", {"repository": "public/publicrepo"}, None, "freshuser", 403),
@ -5393,38 +5195,6 @@ SECURITY_TESTS: List[
"reader",
403,
),
(
RepositoryImageSecurity,
"GET",
{"repository": "devtable/simple", "imageid": "fake"},
None,
None,
401,
),
(
RepositoryImageSecurity,
"GET",
{"repository": "devtable/simple", "imageid": "fake"},
None,
"devtable",
404,
),
(
RepositoryImageSecurity,
"GET",
{"repository": "devtable/simple", "imageid": "fake"},
None,
"freshuser",
403,
),
(
RepositoryImageSecurity,
"GET",
{"repository": "devtable/simple", "imageid": "fake"},
None,
"reader",
403,
),
(
RepositoryManifestSecurity,
"GET",

View File

@ -7,7 +7,7 @@ from data.database import Manifest
from endpoints.api.test.shared import conduct_api_call
from endpoints.test.shared import client_with_identity
from endpoints.api.tag import RepositoryTag, RestoreTag, ListRepositoryTags, RepositoryTagImages
from endpoints.api.tag import RepositoryTag, RestoreTag, ListRepositoryTags
from test.fixtures import *
@ -55,7 +55,7 @@ def test_change_tag_expiration(client, app):
@pytest.mark.parametrize(
"image_exists,test_tag,expected_status",
"manifest_exists,test_tag,expected_status",
[
(True, "-INVALID-TAG-NAME", 400),
(True, ".INVALID-TAG-NAME", 400),
@ -70,18 +70,18 @@ def test_change_tag_expiration(client, app):
(True, "newtag", 201),
],
)
def test_move_tag(image_exists, test_tag, expected_status, client, app):
def test_move_tag(manifest_exists, test_tag, expected_status, client, app):
with client_with_identity("devtable", client) as cl:
test_image = "unknown"
if image_exists:
if manifest_exists:
repo_ref = registry_model.lookup_repository("devtable", "simple")
tag_ref = registry_model.get_repo_tag(repo_ref, "latest")
assert tag_ref
test_image = tag_ref.manifest.legacy_image_root_id
test_image = tag_ref.manifest.digest
params = {"repository": "devtable/simple", "tag": test_tag}
request_body = {"image": test_image}
request_body = {"manifest_digest": test_image}
if expected_status is None:
with pytest.raises(Exception):
conduct_api_call(cl, RepositoryTag, "put", params, request_body, expected_status)
@ -112,18 +112,3 @@ def test_list_repo_tags(repo_namespace, repo_name, client, query_count, app):
repo_ref = registry_model.lookup_repository(repo_namespace, repo_name)
history, _ = registry_model.list_repository_tag_history(repo_ref)
assert len(tags) == len(history)
@pytest.mark.parametrize(
"repository, tag, expect_images",
[
("devtable/simple", "prod", True),
("devtable/simple", "latest", True),
("devtable/complex", "prod", True),
],
)
def test_list_tag_images(repository, tag, expect_images, client, app):
with client_with_identity("devtable", client) as cl:
params = {"repository": repository, "tag": tag}
result = conduct_api_call(cl, RepositoryTagImages, "get", params, None, 200).json
assert bool(result["images"]) == expect_images

View File

@ -48,9 +48,8 @@ from endpoints.api.team import (
TeamPermissions,
InviteTeamMember,
)
from endpoints.api.tag import RepositoryTagImages, RepositoryTag, RestoreTag, ListRepositoryTags
from endpoints.api.tag import RepositoryTag, RestoreTag, ListRepositoryTags
from endpoints.api.search import EntitySearch, ConductSearch
from endpoints.api.image import RepositoryImage, RepositoryImageList
from endpoints.api.build import RepositoryBuildStatus, RepositoryBuildList, RepositoryBuildResource
from endpoints.api.robot import (
UserRobotList,
@ -139,7 +138,7 @@ from endpoints.api.globalmessages import (
GlobalUserMessage,
GlobalUserMessages,
)
from endpoints.api.secscan import RepositoryImageSecurity, RepositoryManifestSecurity
from endpoints.api.secscan import RepositoryManifestSecurity
from endpoints.api.manifest import RepositoryManifestLabels, ManageRepositoryManifestLabel
from util.morecollections import AttrDict
@ -3119,51 +3118,6 @@ class TestRepositoryNotifications(ApiTestCase):
self.assertEqual("Some Notification", json["title"])
class TestListAndGetImage(ApiTestCase):
def test_listandgetimages(self):
self.login(ADMIN_ACCESS_USER)
json = self.getJsonResponse(
RepositoryImageList, params=dict(repository=ADMIN_ACCESS_USER + "/simple")
)
assert len(json["images"]) > 0
for image in json["images"]:
assert "id" in image
assert "tags" in image
assert "created" in image
assert "comment" in image
assert "command" in image
assert "ancestors" in image
assert "size" in image
ijson = self.getJsonResponse(
RepositoryImage,
params=dict(repository=ADMIN_ACCESS_USER + "/simple", image_id=image["id"]),
)
self.assertEqual(image["id"], ijson["id"])
class TestGetImageChanges(ApiTestCase):
def test_getimagechanges(self):
self.login(ADMIN_ACCESS_USER)
# Find an image to check.
json = self.getJsonResponse(
RepositoryImageList, params=dict(repository=ADMIN_ACCESS_USER + "/simple")
)
image_id = json["images"][0]["id"]
# Lookup the image's changes.
# TODO: Fix me once we can get fake changes into the test data
# self.getJsonResponse(RepositoryImageChanges,
# params=dict(repository=ADMIN_ACCESS_USER + '/simple',
# image_id=image_id))
class TestRestoreTag(ApiTestCase):
def test_restoretag_invalidtag(self):
self.login(ADMIN_ACCESS_USER)
@ -3171,7 +3125,7 @@ class TestRestoreTag(ApiTestCase):
self.postResponse(
RestoreTag,
params=dict(repository=ADMIN_ACCESS_USER + "/history", tag="invalidtag"),
data=dict(image="invalid_image"),
data=dict(manifest_digest="invalid_image"),
expected_code=404,
)
@ -3181,7 +3135,7 @@ class TestRestoreTag(ApiTestCase):
self.postResponse(
RestoreTag,
params=dict(repository=ADMIN_ACCESS_USER + "/history", tag="latest"),
data=dict(image="invalid_image"),
data=dict(manifest_digest="invalid_image"),
expected_code=404,
)
@ -3205,12 +3159,12 @@ class TestRestoreTag(ApiTestCase):
self.assertEqual(2, len(json["tags"]))
self.assertFalse("end_ts" in json["tags"][0])
previous_image_id = json["tags"][1]["docker_image_id"]
previous_image_id = json["tags"][1]["manifest_digest"]
self.postJsonResponse(
RestoreTag,
params=dict(repository=ADMIN_ACCESS_USER + "/history", tag="latest"),
data=dict(image=previous_image_id),
data=dict(manifest_digest=previous_image_id),
)
json = self.getJsonResponse(
@ -3218,7 +3172,7 @@ class TestRestoreTag(ApiTestCase):
)
self.assertEqual(3, len(json["tags"]))
self.assertFalse("end_ts" in json["tags"][0])
self.assertEqual(previous_image_id, json["tags"][0]["docker_image_id"])
self.assertEqual(previous_image_id, json["tags"][0]["manifest_digest"])
def test_restoretag_to_digest(self):
self.login(ADMIN_ACCESS_USER)
@ -3235,7 +3189,7 @@ class TestRestoreTag(ApiTestCase):
self.postJsonResponse(
RestoreTag,
params=dict(repository=ADMIN_ACCESS_USER + "/history", tag="latest"),
data=dict(image="foo", manifest_digest=previous_manifest),
data=dict(manifest_digest=previous_manifest),
)
json = self.getJsonResponse(
@ -3252,24 +3206,28 @@ class TestListAndDeleteTag(ApiTestCase):
# List the images for staging.
json = self.getJsonResponse(
RepositoryTagImages,
params=dict(repository=ADMIN_ACCESS_USER + "/complex", tag="staging"),
ListRepositoryTags,
params=dict(
repository=ADMIN_ACCESS_USER + "/complex",
specificTag="staging",
onlyActiveTags=True,
),
)
staging_images = json["images"]
staging_images = json["tags"]
# Try to add some invalid tags.
self.putResponse(
RepositoryTag,
params=dict(repository=ADMIN_ACCESS_USER + "/complex", tag="-fail"),
data=dict(image=staging_images[0]["id"]),
data=dict(manifest_digest=staging_images[0]["manifest_digest"]),
expected_code=400,
)
self.putResponse(
RepositoryTag,
params=dict(repository=ADMIN_ACCESS_USER + "/complex", tag="北京"),
data=dict(image=staging_images[0]["id"]),
data=dict(manifest_digest=staging_images[0]["manifest_digest"]),
expected_code=400,
)
@ -3278,20 +3236,27 @@ class TestListAndDeleteTag(ApiTestCase):
# List the images for prod.
json = self.getJsonResponse(
RepositoryTagImages, params=dict(repository=ADMIN_ACCESS_USER + "/complex", tag="prod")
ListRepositoryTags,
params=dict(
repository=ADMIN_ACCESS_USER + "/complex", specificTag="prod", onlyActiveTags=True
),
)
prod_images = json["images"]
prod_images = json["tags"]
assert len(prod_images) > 0
# List the images for staging.
json = self.getJsonResponse(
RepositoryTagImages,
params=dict(repository=ADMIN_ACCESS_USER + "/complex", tag="staging"),
ListRepositoryTags,
params=dict(
repository=ADMIN_ACCESS_USER + "/complex",
specificTag="staging",
onlyActiveTags=True,
),
)
staging_images = json["images"]
assert len(prod_images) == len(staging_images) + 2
staging_images = json["tags"]
assert len(prod_images) == len(staging_images)
# Delete prod.
self.deleteEmptyResponse(
@ -3301,25 +3266,30 @@ class TestListAndDeleteTag(ApiTestCase):
)
# Make sure the tag is gone.
self.getResponse(
RepositoryTagImages,
params=dict(repository=ADMIN_ACCESS_USER + "/complex", tag="prod"),
expected_code=404,
json = self.getJsonResponse(
ListRepositoryTags,
params=dict(
repository=ADMIN_ACCESS_USER + "/complex", specificTag="prod", onlyActiveTags=True
),
expected_code=200,
)
assert len(json["tags"]) == 0
# Make the sure the staging images are still there.
json = self.getJsonResponse(
RepositoryTagImages,
params=dict(repository=ADMIN_ACCESS_USER + "/complex", tag="staging"),
ListRepositoryTags,
params=dict(
repository=ADMIN_ACCESS_USER + "/complex",
specificTag="staging",
onlyActiveTags=True,
),
)
self.assertEqual(staging_images, json["images"])
# Require a valid tag name.
self.putResponse(
RepositoryTag,
params=dict(repository=ADMIN_ACCESS_USER + "/complex", tag="-fail"),
data=dict(image=staging_images[0]["id"]),
data=dict(manifest_digest=json["tags"][0]["manifest_digest"]),
expected_code=400,
)
@ -3327,44 +3297,55 @@ class TestListAndDeleteTag(ApiTestCase):
self.putResponse(
RepositoryTag,
params=dict(repository=ADMIN_ACCESS_USER + "/complex", tag="sometag"),
data=dict(image=staging_images[0]["id"]),
data=dict(manifest_digest=json["tags"][0]["manifest_digest"]),
expected_code=201,
)
# Make sure the tag is present.
json = self.getJsonResponse(
RepositoryTagImages,
params=dict(repository=ADMIN_ACCESS_USER + "/complex", tag="sometag"),
ListRepositoryTags,
params=dict(
repository=ADMIN_ACCESS_USER + "/complex",
specificTag="sometag",
onlyActiveTags=True,
),
)
assert json["images"]
assert len(json["tags"]) > 0
# Move the tag.
self.putResponse(
RepositoryTag,
params=dict(repository=ADMIN_ACCESS_USER + "/complex", tag="sometag"),
data=dict(image=staging_images[-1]["id"]),
data=dict(manifest_digest=json["tags"][0]["manifest_digest"]),
expected_code=201,
)
# Make sure the tag has moved.
json = self.getJsonResponse(
RepositoryTagImages,
params=dict(repository=ADMIN_ACCESS_USER + "/complex", tag="sometag"),
ListRepositoryTags,
params=dict(
repository=ADMIN_ACCESS_USER + "/complex",
specificTag="sometag",
onlyActiveTags=True,
),
)
sometag_new_images = json["images"]
assert sometag_new_images
sometag_new_images = json["tags"]
assert len(sometag_new_images) > 0
def test_deletesubtag(self):
self.login(ADMIN_ACCESS_USER)
# List the images for prod.
json = self.getJsonResponse(
RepositoryTagImages, params=dict(repository=ADMIN_ACCESS_USER + "/complex", tag="prod")
ListRepositoryTags,
params=dict(
repository=ADMIN_ACCESS_USER + "/complex", specificTag="prod", onlyActiveTags=True
),
)
prod_images = json["images"]
prod_images = json["tags"]
assert len(prod_images) > 0
# Delete staging.
@ -3376,10 +3357,13 @@ class TestListAndDeleteTag(ApiTestCase):
# Make sure the prod images are still around.
json = self.getJsonResponse(
RepositoryTagImages, params=dict(repository=ADMIN_ACCESS_USER + "/complex", tag="prod")
ListRepositoryTags,
params=dict(
repository=ADMIN_ACCESS_USER + "/complex", specificTag="prod", onlyActiveTags=True
),
)
self.assertEqual(prod_images, json["images"])
self.assertEqual(prod_images, json["tags"])
def test_listtag_digest(self):
self.login(ADMIN_ACCESS_USER)