165 lines
6.1 KiB
Python
165 lines
6.1 KiB
Python
|
from uuid import uuid4
|
||
|
|
||
|
from data.model import image, db_transaction, DataModelException, _basequery
|
||
|
from data.database import (RepositoryTag, Repository, Image, ImageStorage, Namespace,
|
||
|
get_epoch_timestamp, db_for_update)
|
||
|
|
||
|
|
||
|
def _tag_alive(query, now_ts=None):
|
||
|
if now_ts is None:
|
||
|
now_ts = get_epoch_timestamp()
|
||
|
return query.where((RepositoryTag.lifetime_end_ts >> None) |
|
||
|
(RepositoryTag.lifetime_end_ts > now_ts))
|
||
|
|
||
|
|
||
|
def list_repository_tags(namespace_name, repository_name, include_hidden=False,
|
||
|
include_storage=False):
|
||
|
to_select = (RepositoryTag, Image)
|
||
|
if include_storage:
|
||
|
to_select = (RepositoryTag, Image, ImageStorage)
|
||
|
|
||
|
query = _tag_alive(RepositoryTag
|
||
|
.select(*to_select)
|
||
|
.join(Repository)
|
||
|
.join(Namespace, on=(Repository.namespace_user == Namespace.id))
|
||
|
.switch(RepositoryTag)
|
||
|
.join(Image)
|
||
|
.where(Repository.name == repository_name,
|
||
|
Namespace.username == namespace_name))
|
||
|
|
||
|
if not include_hidden:
|
||
|
query = query.where(RepositoryTag.hidden == False)
|
||
|
|
||
|
if include_storage:
|
||
|
query = query.switch(Image).join(ImageStorage)
|
||
|
|
||
|
return query
|
||
|
|
||
|
|
||
|
def create_or_update_tag(namespace_name, repository_name, tag_name,
|
||
|
tag_docker_image_id, reversion=False):
|
||
|
try:
|
||
|
repo = _basequery.get_existing_repository(namespace_name, repository_name)
|
||
|
except Repository.DoesNotExist:
|
||
|
raise DataModelException('Invalid repository %s/%s' % (namespace_name, repository_name))
|
||
|
|
||
|
now_ts = get_epoch_timestamp()
|
||
|
|
||
|
with db_transaction():
|
||
|
try:
|
||
|
tag = db_for_update(_tag_alive(RepositoryTag
|
||
|
.select()
|
||
|
.where(RepositoryTag.repository == repo,
|
||
|
RepositoryTag.name == tag_name), now_ts)).get()
|
||
|
tag.lifetime_end_ts = now_ts
|
||
|
tag.save()
|
||
|
except RepositoryTag.DoesNotExist:
|
||
|
pass
|
||
|
|
||
|
try:
|
||
|
image_obj = Image.get(Image.docker_image_id == tag_docker_image_id, Image.repository == repo)
|
||
|
except Image.DoesNotExist:
|
||
|
raise DataModelException('Invalid image with id: %s' % tag_docker_image_id)
|
||
|
|
||
|
return RepositoryTag.create(repository=repo, image=image_obj, name=tag_name,
|
||
|
lifetime_start_ts=now_ts, reversion=reversion)
|
||
|
|
||
|
|
||
|
def create_temporary_hidden_tag(repo, image_obj, expiration_s):
|
||
|
""" Create a tag with a defined timeline, that will not appear in the UI or CLI. Returns the name
|
||
|
of the temporary tag. """
|
||
|
now_ts = get_epoch_timestamp()
|
||
|
expire_ts = now_ts + expiration_s
|
||
|
tag_name = str(uuid4())
|
||
|
RepositoryTag.create(repository=repo, image=image_obj, name=tag_name, lifetime_start_ts=now_ts,
|
||
|
lifetime_end_ts=expire_ts, hidden=True)
|
||
|
return tag_name
|
||
|
|
||
|
|
||
|
def delete_tag(namespace_name, repository_name, tag_name):
|
||
|
now_ts = get_epoch_timestamp()
|
||
|
with db_transaction():
|
||
|
try:
|
||
|
query = _tag_alive(RepositoryTag
|
||
|
.select(RepositoryTag, Repository)
|
||
|
.join(Repository)
|
||
|
.join(Namespace, on=(Repository.namespace_user == Namespace.id))
|
||
|
.where(Repository.name == repository_name,
|
||
|
Namespace.username == namespace_name,
|
||
|
RepositoryTag.name == tag_name), now_ts)
|
||
|
found = db_for_update(query).get()
|
||
|
except RepositoryTag.DoesNotExist:
|
||
|
msg = ('Invalid repository tag \'%s\' on repository \'%s/%s\'' %
|
||
|
(tag_name, namespace_name, repository_name))
|
||
|
raise DataModelException(msg)
|
||
|
|
||
|
found.lifetime_end_ts = now_ts
|
||
|
found.save()
|
||
|
|
||
|
|
||
|
def garbage_collect_tags(namespace_name, repository_name):
|
||
|
# We do this without using a join to prevent holding read locks on the repository table
|
||
|
repo = _basequery.get_existing_repository(namespace_name, repository_name)
|
||
|
expired_time = get_epoch_timestamp() - repo.namespace_user.removed_tag_expiration_s
|
||
|
|
||
|
tags_to_delete = list(RepositoryTag
|
||
|
.select(RepositoryTag.id)
|
||
|
.where(RepositoryTag.repository == repo,
|
||
|
~(RepositoryTag.lifetime_end_ts >> None),
|
||
|
(RepositoryTag.lifetime_end_ts <= expired_time))
|
||
|
.order_by(RepositoryTag.id))
|
||
|
if len(tags_to_delete) > 0:
|
||
|
(RepositoryTag
|
||
|
.delete()
|
||
|
.where(RepositoryTag.id << tags_to_delete)
|
||
|
.execute())
|
||
|
|
||
|
|
||
|
def get_tag_image(namespace_name, repository_name, tag_name):
|
||
|
def limit_to_tag(query):
|
||
|
return _tag_alive(query
|
||
|
.switch(Image)
|
||
|
.join(RepositoryTag)
|
||
|
.where(RepositoryTag.name == tag_name))
|
||
|
|
||
|
images = image.get_repository_images_base(namespace_name, repository_name, limit_to_tag)
|
||
|
if not images:
|
||
|
raise DataModelException('Unable to find image for tag.')
|
||
|
else:
|
||
|
return images[0]
|
||
|
|
||
|
|
||
|
def list_repository_tag_history(repo_obj, page=1, size=100, specific_tag=None):
|
||
|
query = (RepositoryTag
|
||
|
.select(RepositoryTag, Image)
|
||
|
.join(Image)
|
||
|
.where(RepositoryTag.repository == repo_obj)
|
||
|
.where(RepositoryTag.hidden == False)
|
||
|
.order_by(RepositoryTag.lifetime_start_ts.desc())
|
||
|
.paginate(page, size))
|
||
|
|
||
|
if specific_tag:
|
||
|
query = query.where(RepositoryTag.name == specific_tag)
|
||
|
|
||
|
return query
|
||
|
|
||
|
|
||
|
def revert_tag(repo_obj, tag_name, docker_image_id):
|
||
|
""" Reverts a tag to a specific image ID. """
|
||
|
# Verify that the image ID already existed under this repository under the
|
||
|
# tag.
|
||
|
try:
|
||
|
(RepositoryTag
|
||
|
.select()
|
||
|
.join(Image)
|
||
|
.where(RepositoryTag.repository == repo_obj)
|
||
|
.where(RepositoryTag.name == tag_name)
|
||
|
.where(Image.docker_image_id == docker_image_id)
|
||
|
.get())
|
||
|
except RepositoryTag.DoesNotExist:
|
||
|
raise DataModelException('Cannot revert to unknown or invalid image')
|
||
|
|
||
|
return create_or_update_tag(repo_obj.namespace_user.username, repo_obj.name, tag_name,
|
||
|
docker_image_id, reversion=True)
|
||
|
|