Add UI for viewing and changing the expiration of tags

This commit is contained in:
Joseph Schorr 2017-06-21 21:33:26 -04:00
parent 977539bf08
commit 99d7fde8ee
13 changed files with 329 additions and 26 deletions

View file

@ -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'])

View file

@ -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

View file

@ -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),

View file

@ -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,