2017-07-21 18:04:59 +00:00
|
|
|
from abc import ABCMeta, abstractmethod
|
|
|
|
from collections import namedtuple, defaultdict
|
|
|
|
|
|
|
|
from datetime import datetime
|
|
|
|
from six import add_metaclass
|
|
|
|
|
|
|
|
import features
|
|
|
|
from endpoints.api import format_date
|
|
|
|
|
|
|
|
|
2017-07-24 15:05:15 +00:00
|
|
|
class RepositoryBaseElement(
|
|
|
|
namedtuple('RepositoryBaseElement', [
|
|
|
|
'namespace_name', 'repository_name', 'is_starred', 'is_public', 'kind_name', 'description',
|
|
|
|
'namespace_user_organization', 'namespace_user_removed_tag_expiration_s', 'last_modified',
|
|
|
|
'action_count', 'should_last_modified', 'should_popularity', 'should_is_starred'
|
|
|
|
])):
|
2017-07-21 18:04:59 +00:00
|
|
|
"""
|
|
|
|
Repository a single quay repository
|
|
|
|
:type namespace_name: string
|
|
|
|
:type repository_name: string
|
|
|
|
:type is_starred: boolean
|
|
|
|
:type is_public: boolean
|
|
|
|
:type kind_name: string
|
|
|
|
:type description: string
|
|
|
|
:type namespace_user_organization: boolean
|
|
|
|
:type should_last_modified: boolean
|
|
|
|
:type should_popularity: boolean
|
|
|
|
:type should_is_starred: boolean
|
|
|
|
"""
|
|
|
|
|
|
|
|
def to_dict(self):
|
|
|
|
repo = {
|
|
|
|
'namespace': self.namespace_name,
|
|
|
|
'name': self.repository_name,
|
|
|
|
'description': self.description,
|
|
|
|
'is_public': self.is_public,
|
|
|
|
'kind': self.kind_name,
|
|
|
|
}
|
|
|
|
|
|
|
|
if self.should_last_modified:
|
|
|
|
repo['last_modified'] = self.last_modified
|
|
|
|
|
|
|
|
if self.should_popularity:
|
|
|
|
repo['popularity'] = float(self.action_count if self.action_count else 0)
|
|
|
|
|
|
|
|
if self.should_is_starred:
|
|
|
|
repo['is_starred'] = self.is_starred
|
|
|
|
|
|
|
|
return repo
|
|
|
|
|
|
|
|
|
|
|
|
class ApplicationRepository(
|
|
|
|
namedtuple('ApplicationRepository', ['repository_base_elements', 'channels', 'releases'])):
|
|
|
|
"""
|
|
|
|
Repository a single quay repository
|
|
|
|
:type repository_base_elements: RepositoryBaseElement
|
|
|
|
:type channels: [Channel]
|
|
|
|
:type releases: [Release]
|
|
|
|
"""
|
|
|
|
|
2017-07-24 15:05:15 +00:00
|
|
|
def to_dict(self):
|
2017-07-21 18:04:59 +00:00
|
|
|
repo_data = {
|
|
|
|
'namespace': self.repository_base_elements.namespace_name,
|
|
|
|
'name': self.repository_base_elements.repository_name,
|
|
|
|
'kind': self.repository_base_elements.kind_name,
|
|
|
|
'description': self.repository_base_elements.description,
|
|
|
|
'is_public': self.repository_base_elements.is_public,
|
|
|
|
'is_organization': self.repository_base_elements.namespace_user_organization,
|
|
|
|
'is_starred': self.repository_base_elements.is_starred,
|
|
|
|
'channels': [chan.to_dict() for chan in self.channels],
|
2017-07-24 15:05:15 +00:00
|
|
|
'releases': [release.to_dict() for release in self.releases],
|
2017-07-21 18:04:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return repo_data
|
|
|
|
|
|
|
|
|
2017-07-24 15:05:15 +00:00
|
|
|
class ImageRepositoryRepository(
|
|
|
|
namedtuple('NonApplicationRepository',
|
|
|
|
['repository_base_elements', 'tags', 'counts', 'badge_token', 'trust_enabled'])):
|
2017-07-21 18:04:59 +00:00
|
|
|
"""
|
|
|
|
Repository a single quay repository
|
|
|
|
:type repository_base_elements: RepositoryBaseElement
|
|
|
|
:type tags: [Tag]
|
|
|
|
:type counts: [count]
|
|
|
|
:type badge_token: string
|
|
|
|
:type trust_enabled: boolean
|
|
|
|
"""
|
|
|
|
|
2017-07-24 15:05:15 +00:00
|
|
|
def to_dict(self):
|
2018-09-14 19:30:54 +00:00
|
|
|
img_repo = {
|
2017-07-21 18:04:59 +00:00
|
|
|
'namespace': self.repository_base_elements.namespace_name,
|
|
|
|
'name': self.repository_base_elements.repository_name,
|
|
|
|
'kind': self.repository_base_elements.kind_name,
|
|
|
|
'description': self.repository_base_elements.description,
|
|
|
|
'is_public': self.repository_base_elements.is_public,
|
|
|
|
'is_organization': self.repository_base_elements.namespace_user_organization,
|
|
|
|
'is_starred': self.repository_base_elements.is_starred,
|
|
|
|
'status_token': self.badge_token if not self.repository_base_elements.is_public else '',
|
|
|
|
'trust_enabled': bool(features.SIGNING) and self.trust_enabled,
|
|
|
|
'tag_expiration_s': self.repository_base_elements.namespace_user_removed_tag_expiration_s,
|
|
|
|
}
|
2018-09-14 19:30:54 +00:00
|
|
|
if self.tags is not None:
|
|
|
|
img_repo['tags'] = {tag.name: tag.to_dict() for tag in self.tags}
|
|
|
|
return img_repo
|
2017-07-21 18:04:59 +00:00
|
|
|
|
|
|
|
|
2017-07-24 15:05:15 +00:00
|
|
|
class Repository(namedtuple('Repository', [
|
|
|
|
'namespace_name',
|
|
|
|
'repository_name',
|
|
|
|
])):
|
2017-07-21 18:04:59 +00:00
|
|
|
"""
|
|
|
|
Repository a single quay repository
|
|
|
|
:type namespace_name: string
|
|
|
|
:type repository_name: string
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
class Channel(namedtuple('Channel', ['name', 'linked_tag_name', 'linked_tag_lifetime_start'])):
|
|
|
|
"""
|
|
|
|
Repository a single quay repository
|
|
|
|
:type name: string
|
|
|
|
:type linked_tag_name: string
|
|
|
|
:type linked_tag_lifetime_start: string
|
|
|
|
"""
|
|
|
|
|
|
|
|
def to_dict(self):
|
|
|
|
return {
|
|
|
|
'name': self.name,
|
|
|
|
'release': self.linked_tag_name,
|
|
|
|
'last_modified': format_date(datetime.fromtimestamp(self.linked_tag_lifetime_start / 1000)),
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-07-24 15:05:15 +00:00
|
|
|
class Release(
|
2017-07-27 14:18:40 +00:00
|
|
|
namedtuple('Channel', ['name', 'lifetime_start', 'releases_channels_map'])):
|
2017-07-21 18:04:59 +00:00
|
|
|
"""
|
|
|
|
Repository a single quay repository
|
|
|
|
:type name: string
|
|
|
|
:type last_modified: string
|
2017-07-24 15:05:15 +00:00
|
|
|
:type releases_channels_map: {string -> string}
|
2017-07-21 18:04:59 +00:00
|
|
|
"""
|
|
|
|
|
2017-07-24 15:05:15 +00:00
|
|
|
def to_dict(self):
|
2017-07-21 18:04:59 +00:00
|
|
|
return {
|
|
|
|
'name': self.name,
|
|
|
|
'last_modified': format_date(datetime.fromtimestamp(self.lifetime_start / 1000)),
|
2017-07-24 15:05:15 +00:00
|
|
|
'channels': self.releases_channels_map[self.name],
|
2017-07-21 18:04:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-07-24 15:05:15 +00:00
|
|
|
class Tag(
|
|
|
|
namedtuple('Tag', [
|
|
|
|
'name', 'image_docker_image_id', 'image_aggregate_size', 'lifetime_start_ts',
|
2017-07-26 15:29:21 +00:00
|
|
|
'tag_manifest_digest', 'lifetime_end_ts',
|
2017-07-24 15:05:15 +00:00
|
|
|
])):
|
2017-07-21 18:04:59 +00:00
|
|
|
"""
|
|
|
|
:type name: string
|
|
|
|
:type image_docker_image_id: string
|
|
|
|
:type image_aggregate_size: int
|
|
|
|
:type lifetime_start_ts: int
|
2017-07-26 15:29:21 +00:00
|
|
|
:type lifetime_end_ts: int|None
|
2017-07-21 18:04:59 +00:00
|
|
|
:type tag_manifest_digest: string
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
def to_dict(self):
|
|
|
|
tag_info = {
|
|
|
|
'name': self.name,
|
|
|
|
'image_id': self.image_docker_image_id,
|
|
|
|
'size': self.image_aggregate_size
|
|
|
|
}
|
|
|
|
|
|
|
|
if self.lifetime_start_ts > 0:
|
|
|
|
last_modified = format_date(datetime.fromtimestamp(self.lifetime_start_ts))
|
|
|
|
tag_info['last_modified'] = last_modified
|
|
|
|
|
2017-07-26 15:29:21 +00:00
|
|
|
if self.lifetime_end_ts:
|
|
|
|
expiration = format_date(datetime.fromtimestamp(self.lifetime_end_ts))
|
|
|
|
tag_info['expiration'] = expiration
|
|
|
|
|
2017-07-21 18:04:59 +00:00
|
|
|
if self.tag_manifest_digest is not None:
|
|
|
|
tag_info['manifest_digest'] = self.tag_manifest_digest
|
|
|
|
|
|
|
|
return tag_info
|
|
|
|
|
|
|
|
|
|
|
|
class Count(namedtuple('Count', ['date', 'count'])):
|
|
|
|
"""
|
|
|
|
date: DateTime
|
|
|
|
count: int
|
|
|
|
"""
|
|
|
|
|
|
|
|
def to_dict(self):
|
|
|
|
return {
|
|
|
|
'date': self.date.isoformat(),
|
|
|
|
'count': self.count,
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@add_metaclass(ABCMeta)
|
|
|
|
class RepositoryDataInterface(object):
|
|
|
|
"""
|
|
|
|
Interface that represents all data store interactions required by a Repository.
|
|
|
|
"""
|
|
|
|
|
|
|
|
@abstractmethod
|
2018-09-14 19:30:54 +00:00
|
|
|
def get_repo(self, namespace_name, repository_name, user, include_tags=True, max_tags=500):
|
2017-07-21 18:04:59 +00:00
|
|
|
"""
|
|
|
|
Returns a repository
|
|
|
|
"""
|
|
|
|
|
|
|
|
@abstractmethod
|
|
|
|
def repo_exists(self, namespace_name, repository_name):
|
|
|
|
"""
|
|
|
|
Returns true if a repo exists and false if not
|
|
|
|
"""
|
|
|
|
|
|
|
|
@abstractmethod
|
|
|
|
def create_repo(self, namespace, name, creating_user, description, visibility='private',
|
|
|
|
repo_kind='image'):
|
|
|
|
"""
|
|
|
|
Returns creates a new repo
|
|
|
|
"""
|
|
|
|
|
|
|
|
@abstractmethod
|
|
|
|
def get_repo_list(self, starred, user, repo_kind, namespace, username, public, page_token,
|
|
|
|
last_modified, popularity):
|
|
|
|
"""
|
|
|
|
Returns a RepositoryBaseElement
|
|
|
|
"""
|
|
|
|
|
|
|
|
@abstractmethod
|
|
|
|
def set_repository_visibility(self, namespace_name, repository_name, visibility):
|
|
|
|
"""
|
|
|
|
Sets a repository's visibility if it is found
|
|
|
|
"""
|
|
|
|
|
|
|
|
@abstractmethod
|
|
|
|
def set_trust(self, namespace_name, repository_name, trust):
|
|
|
|
"""
|
|
|
|
Sets a repository's trust_enabled field if it is found
|
|
|
|
"""
|
|
|
|
|
|
|
|
@abstractmethod
|
|
|
|
def set_description(self, namespace_name, repository_name, description):
|
|
|
|
"""
|
|
|
|
Sets a repository's description if it is found.
|
|
|
|
"""
|
|
|
|
|
|
|
|
@abstractmethod
|
|
|
|
def purge_repository(self, namespace_name, repository_name):
|
|
|
|
"""
|
|
|
|
Removes a repository
|
|
|
|
"""
|
|
|
|
|
|
|
|
@abstractmethod
|
|
|
|
def check_repository_usage(self, user_name, plan_found):
|
|
|
|
"""
|
|
|
|
Creates a notification for a user if they are over or under on their repository usage
|
|
|
|
"""
|