mirror of
https://github.com/quay/quay.git
synced 2026-01-27 18:42:52 +03:00
Reducing the number of DB calls in the repo list endpoint with quota enabled by: - Adding the id to RepositoryBaseElement when the repositories are initially fetched, removing the need to fetch the repository ID's again - Fetching the repository sizes with a single DB call using the IN operator
337 lines
9.4 KiB
Python
337 lines
9.4 KiB
Python
from abc import ABCMeta, abstractmethod
|
|
from collections import defaultdict, namedtuple
|
|
from datetime import datetime
|
|
|
|
from six import add_metaclass
|
|
|
|
import features
|
|
from data import model
|
|
from data.database import RepositoryState
|
|
from endpoints.api import format_date
|
|
|
|
|
|
class RepositoryBaseElement(
|
|
namedtuple(
|
|
"RepositoryBaseElement",
|
|
[
|
|
"id",
|
|
"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",
|
|
"is_free_account",
|
|
"state",
|
|
],
|
|
)
|
|
):
|
|
"""
|
|
Repository a single quay repository.
|
|
|
|
:type id: int
|
|
: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,
|
|
"state": self.state.name if self.state is not None else None,
|
|
}
|
|
|
|
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", "state"]
|
|
)
|
|
):
|
|
"""
|
|
Repository a single quay repository.
|
|
|
|
:type repository_base_elements: RepositoryBaseElement
|
|
:type channels: [Channel]
|
|
:type releases: [Release]
|
|
"""
|
|
|
|
def to_dict(self):
|
|
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],
|
|
"releases": [release.to_dict() for release in self.releases],
|
|
"state": self.state.name if self.state is not None else None,
|
|
"is_free_account": self.repository_base_elements.is_free_account,
|
|
}
|
|
|
|
return repo_data
|
|
|
|
|
|
class ImageRepositoryRepository(
|
|
namedtuple(
|
|
"NonApplicationRepository",
|
|
["repository_base_elements", "tags", "counts", "badge_token", "trust_enabled", "state"],
|
|
)
|
|
):
|
|
"""
|
|
Repository a single quay repository.
|
|
|
|
:type repository_base_elements: RepositoryBaseElement
|
|
:type tags: [Tag]
|
|
:type counts: [count]
|
|
:type badge_token: string
|
|
:type trust_enabled: boolean
|
|
"""
|
|
|
|
def to_dict(self):
|
|
img_repo = {
|
|
"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,
|
|
"is_free_account": self.repository_base_elements.is_free_account,
|
|
"state": self.state.name if self.state is not None else None,
|
|
}
|
|
|
|
if self.tags is not None:
|
|
img_repo["tags"] = {tag.name: tag.to_dict() for tag in self.tags}
|
|
|
|
if self.repository_base_elements.state:
|
|
img_repo["state"] = self.repository_base_elements.state.name
|
|
|
|
return img_repo
|
|
|
|
|
|
class Repository(
|
|
namedtuple(
|
|
"Repository",
|
|
[
|
|
"namespace_name",
|
|
"repository_name",
|
|
],
|
|
)
|
|
):
|
|
"""
|
|
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)
|
|
),
|
|
}
|
|
|
|
|
|
class Release(namedtuple("Channel", ["name", "lifetime_start", "releases_channels_map"])):
|
|
"""
|
|
Repository a single quay repository.
|
|
|
|
:type name: string
|
|
:type last_modified: string
|
|
:type releases_channels_map: {string -> string}
|
|
"""
|
|
|
|
def to_dict(self):
|
|
return {
|
|
"name": self.name,
|
|
"last_modified": format_date(datetime.fromtimestamp(self.lifetime_start // 1000)),
|
|
"channels": self.releases_channels_map[self.name],
|
|
}
|
|
|
|
|
|
class Tag(
|
|
namedtuple(
|
|
"Tag",
|
|
[
|
|
"name",
|
|
"image_aggregate_size",
|
|
"lifetime_start_ts",
|
|
"tag_manifest_digest",
|
|
"lifetime_end_ts",
|
|
],
|
|
)
|
|
):
|
|
"""
|
|
:type name: string
|
|
:type image_aggregate_size: int
|
|
:type lifetime_start_ts: int
|
|
:type lifetime_end_ts: int|None
|
|
:type tag_manifest_digest: string
|
|
|
|
"""
|
|
|
|
def to_dict(self):
|
|
tag_info = {
|
|
"name": self.name,
|
|
"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
|
|
|
|
if self.lifetime_end_ts:
|
|
expiration = format_date(datetime.fromtimestamp(self.lifetime_end_ts))
|
|
tag_info["expiration"] = expiration
|
|
|
|
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
|
|
def get_repo(self, namespace_name, repository_name, user, include_tags=True, max_tags=500):
|
|
"""
|
|
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 mark_repository_for_deletion(self, namespace_name, repository_name, repository_gc_queue):
|
|
"""
|
|
Marks a repository for deletion.
|
|
"""
|
|
|
|
@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.
|
|
"""
|
|
|
|
@abstractmethod
|
|
def set_repository_state(self, namespace_name, repository_name, state):
|
|
"""
|
|
Set the State of the Repository.
|
|
"""
|
|
|
|
@abstractmethod
|
|
def add_quota_view(self, repos):
|
|
"""
|
|
Adds quota size and limit to the repository list.
|
|
"""
|