Add UI for viewing and changing the expiration of tags
This commit is contained in:
parent
977539bf08
commit
99d7fde8ee
13 changed files with 329 additions and 26 deletions
|
@ -334,6 +334,10 @@ class Repository(RepositoryParamResource):
|
|||
last_modified = format_date(datetime.fromtimestamp(tag.lifetime_start_ts))
|
||||
tag_info['last_modified'] = last_modified
|
||||
|
||||
if tag.lifetime_end_ts:
|
||||
expiration = format_date(datetime.fromtimestamp(tag.lifetime_end_ts))
|
||||
tag_info['expiration'] = expiration
|
||||
|
||||
if tag.tagmanifest is not None:
|
||||
tag_info['manifest_digest'] = tag.tagmanifest.digest
|
||||
|
||||
|
@ -498,11 +502,11 @@ class RepositoryTrust(RepositoryParamResource):
|
|||
repo = model.repository.get_repository(namespace, repository)
|
||||
if not repo:
|
||||
raise NotFound()
|
||||
|
||||
|
||||
tags, _ = tuf_metadata_api.get_default_tags_with_expiration(namespace, repository)
|
||||
if tags and not tuf_metadata_api.delete_metadata(namespace, repository):
|
||||
raise DownstreamIssue({'message': 'Unable to delete downstream trust metadata'})
|
||||
|
||||
|
||||
values = request.get_json()
|
||||
model.repository.set_trust(repo, values['trust_enabled'])
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
""" Manage the tags of a repository. """
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from flask import request, abort
|
||||
|
||||
from auth.auth_context import get_authenticated_user
|
||||
|
@ -54,12 +55,15 @@ class RepositoryTag(RepositoryParamResource):
|
|||
schemas = {
|
||||
'MoveTag': {
|
||||
'type': 'object',
|
||||
'description': 'Description of to which image a new or existing tag should point',
|
||||
'required': ['image',],
|
||||
'description': 'Makes changes to a specific tag',
|
||||
'properties': {
|
||||
'image': {
|
||||
'type': 'string',
|
||||
'description': 'Image identifier to which the tag should point',
|
||||
'type': ['string', 'null'],
|
||||
'description': '(If specified) Image identifier to which the tag should point',
|
||||
},
|
||||
'image': {
|
||||
'type': ['number', 'null'],
|
||||
'description': '(If specified) The expiration for the image',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -75,25 +79,52 @@ class RepositoryTag(RepositoryParamResource):
|
|||
if not TAG_REGEX.match(tag):
|
||||
abort(400, TAG_ERROR)
|
||||
|
||||
image_id = request.get_json()['image']
|
||||
repo = model.get_repo(namespace, repository, image_id)
|
||||
repo = model.get_repo(namespace, repository)
|
||||
if not repo:
|
||||
raise NotFound()
|
||||
|
||||
original_image_id = model.get_repo_tag_image(repo, tag)
|
||||
model.create_or_update_tag(namespace, repository, tag, image_id)
|
||||
if 'expiration' in request.get_json():
|
||||
expiration = request.get_json().get('expiration')
|
||||
expiration_date = None
|
||||
if expiration is not None:
|
||||
try:
|
||||
expiration_date = datetime.utcfromtimestamp(float(expiration))
|
||||
except ValueError:
|
||||
abort(400)
|
||||
|
||||
username = get_authenticated_user().username
|
||||
log_action('move_tag' if original_image_id else 'create_tag', namespace, {
|
||||
'username': username,
|
||||
'repo': repository,
|
||||
'tag': tag,
|
||||
'namespace': namespace,
|
||||
'image': image_id,
|
||||
'original_image': original_image_id
|
||||
}, repo_name=repository)
|
||||
if expiration_date <= datetime.now():
|
||||
abort(400)
|
||||
|
||||
_generate_and_store_manifest(namespace, repository, tag)
|
||||
existing_end_ts, ok = model.change_repository_tag_expiration(namespace, repository, tag,
|
||||
expiration_date)
|
||||
if ok:
|
||||
if not (existing_end_ts is None and expiration_date is None):
|
||||
log_action('change_tag_expiration', namespace, {
|
||||
'username': get_authenticated_user().username,
|
||||
'repo': repository,
|
||||
'tag': tag,
|
||||
'namespace': namespace,
|
||||
'expiration_date': expiration_date,
|
||||
'old_expiration_date': existing_end_ts
|
||||
}, repo=repo)
|
||||
else:
|
||||
abort(400, 'Could not update tag expiration; Tag has probably changed')
|
||||
|
||||
if 'image' in request.get_json():
|
||||
image_id = request.get_json()['image']
|
||||
original_image_id = model.get_repo_tag_image(repo, tag)
|
||||
model.create_or_update_tag(namespace, repository, tag, image_id)
|
||||
|
||||
username = get_authenticated_user().username
|
||||
log_action('move_tag' if original_image_id else 'create_tag', namespace, {
|
||||
'username': username,
|
||||
'repo': repository,
|
||||
'tag': tag,
|
||||
'namespace': namespace,
|
||||
'image': image_id,
|
||||
'original_image': original_image_id
|
||||
}, repo_name=repository)
|
||||
_generate_and_store_manifest(namespace, repository, tag)
|
||||
|
||||
return 'Updated', 201
|
||||
|
||||
|
|
|
@ -124,6 +124,43 @@ def find_no_repo_tag_history():
|
|||
yield
|
||||
|
||||
|
||||
@pytest.mark.parametrize('expiration_time, expected_status', [
|
||||
(None, 201),
|
||||
('aksdjhasd', 400),
|
||||
])
|
||||
def test_change_tag_expiration_default(expiration_time, expected_status, client, app):
|
||||
with client_with_identity('devtable', client) as cl:
|
||||
params = {
|
||||
'repository': 'devtable/simple',
|
||||
'tag': 'latest',
|
||||
}
|
||||
|
||||
request_body = {
|
||||
'expiration': expiration_time,
|
||||
}
|
||||
|
||||
conduct_api_call(cl, RepositoryTag, 'put', params, request_body, expected_status)
|
||||
|
||||
|
||||
def test_change_tag_expiration(client, app):
|
||||
with client_with_identity('devtable', client) as cl:
|
||||
params = {
|
||||
'repository': 'devtable/simple',
|
||||
'tag': 'latest',
|
||||
}
|
||||
|
||||
tag = model.tag.get_active_tag('devtable', 'simple', 'latest')
|
||||
updated_expiration = tag.lifetime_start_ts + 60*60*24
|
||||
|
||||
request_body = {
|
||||
'expiration': updated_expiration,
|
||||
}
|
||||
|
||||
conduct_api_call(cl, RepositoryTag, 'put', params, request_body, 201)
|
||||
tag = model.tag.get_active_tag('devtable', 'simple', 'latest')
|
||||
assert tag.lifetime_end_ts == updated_expiration
|
||||
|
||||
|
||||
@pytest.mark.parametrize('test_image,test_tag,expected_status', [
|
||||
('image1', '-INVALID-TAG-NAME', 400),
|
||||
('image1', '.INVALID-TAG-NAME', 400),
|
||||
|
|
|
@ -20,7 +20,7 @@ from auth.permissions import (AdministerOrganizationPermission, CreateRepository
|
|||
UserAdminPermission, UserReadPermission, SuperUserPermission)
|
||||
from data import model
|
||||
from data.billing import get_plan
|
||||
from data.database import Repository as RepositoryTable, UserPromptTypes
|
||||
from data.database import Repository as RepositoryTable
|
||||
from endpoints.api import (ApiResource, nickname, resource, validate_json_request, request_error,
|
||||
log_action, internal_only, require_user_admin, parse_args,
|
||||
query_param, require_scope, format_date, show_if,
|
||||
|
|
Reference in a new issue