mirror of
https://github.com/quay/quay.git
synced 2026-01-26 06:21:37 +03:00
* storage: Disable pushes on registry (PROJQUAY-6870)
The current read-only option for Quay is not sometimes feasible, since it requires an insert of the service key and other manual config changes. For instance, if you want to just recalculate quota on the registry, but would like to allow all registry operations (including UI) without the possibility of pushes until recalculation is done, setting the whole registry `read-only` cannot be done since it makes the database read only as well.
This PR introduces a new flag called `DISABLE_PUSHES` which allows all registry operations to continue (changing tags, repo editing, robot account creation/deletion, user creation etc.) but will disable pushes of new images to the registry (i.e. backend storage will not change). If a registry already contains the image and a new tag is simply being added, that operation should succeed.
The following message would appear in the logs:
~~~
gunicorn-registry stdout | 2024-03-13 20:19:49,414 [369] [DEBUG] [endpoints.v2] sending response: b'{"errors":[{"code":"METHOD NOT ALLOWED","detail":{},"message":"Pushes to the registry are currently disabled. Please contact the administrator for more information."}]}\n'
gunicorn-registry stdout | 2024-03-13 20:19:49,414 [369] [INFO] [gunicorn.access] 172.17.0.1 - - [13/Mar/2024:20:19:49 +0000] "PUT /v2/ibazulic/mariadb/manifests/sha256:c4694ba424e0259694a5117bbb510d67340051f0bdb7f9fa8033941a2d66e53e HTTP/1.1" 405 169 "-" "skopeo/1.9.3"
nginx stdout | 172.17.0.1 (-) - - [13/Mar/2024:20:19:49 +0000] "PUT /v2/ibazulic/mariadb/manifests/sha256:c4694ba424e0259694a5117bbb510d67340051f0bdb7f9fa8033941a2d66e53e HTTP/1.1" 405 169 "-" "skopeo/1.9.3" (0.002 3813 0.002)
~~~
The flag defaults to `False` (pushes enabled), unless set otherwise.
* Removed constraint on storage replication when pushes are disabled
* Rebase
* Fix isort sorting
* Fix isort sorting #2
* Removed constraint on storage replication when pushes are disabled
* Rebase
* Remove constraint on storage replication worker
* Fix linting on config.py
255 lines
4.4 KiB
Python
255 lines
4.4 KiB
Python
from data.database import db, db_transaction
|
|
|
|
|
|
class DataModelException(Exception):
|
|
pass
|
|
|
|
|
|
class InvalidLabelKeyException(DataModelException):
|
|
pass
|
|
|
|
|
|
class InvalidMediaTypeException(DataModelException):
|
|
pass
|
|
|
|
|
|
class BlobDoesNotExist(DataModelException):
|
|
pass
|
|
|
|
|
|
class InvalidBlobUpload(DataModelException):
|
|
pass
|
|
|
|
|
|
class InvalidEmailAddressException(DataModelException):
|
|
pass
|
|
|
|
|
|
class InvalidOrganizationException(DataModelException):
|
|
pass
|
|
|
|
|
|
class InvalidProxyCacheConfigException(DataModelException):
|
|
pass
|
|
|
|
|
|
class InvalidPasswordException(DataModelException):
|
|
pass
|
|
|
|
|
|
class InvalidRobotException(DataModelException):
|
|
pass
|
|
|
|
|
|
class DeactivatedRobotOwnerException(InvalidRobotException):
|
|
pass
|
|
|
|
|
|
class InvalidRobotCredentialException(InvalidRobotException):
|
|
pass
|
|
|
|
|
|
class InvalidRobotOwnerException(InvalidRobotException):
|
|
pass
|
|
|
|
|
|
class InvalidUsernameException(DataModelException):
|
|
pass
|
|
|
|
|
|
class RepositoryDoesNotExist(DataModelException):
|
|
pass
|
|
|
|
|
|
class InvalidRepositoryBuildException(DataModelException):
|
|
pass
|
|
|
|
|
|
class InvalidBuildTriggerException(DataModelException):
|
|
pass
|
|
|
|
|
|
class InvalidTokenException(DataModelException):
|
|
pass
|
|
|
|
|
|
class InvalidNotificationException(DataModelException):
|
|
pass
|
|
|
|
|
|
class InvalidImageException(DataModelException):
|
|
pass
|
|
|
|
|
|
class UserAlreadyInTeam(DataModelException):
|
|
pass
|
|
|
|
|
|
class InvalidTeamException(DataModelException):
|
|
pass
|
|
|
|
|
|
class InvalidTeamMemberException(DataModelException):
|
|
pass
|
|
|
|
|
|
class InvalidManifestException(DataModelException):
|
|
pass
|
|
|
|
|
|
class ManifestDoesNotExist(DataModelException):
|
|
pass
|
|
|
|
|
|
class ServiceKeyDoesNotExist(DataModelException):
|
|
pass
|
|
|
|
|
|
class ServiceKeyAlreadyApproved(DataModelException):
|
|
pass
|
|
|
|
|
|
class ServiceNameInvalid(DataModelException):
|
|
pass
|
|
|
|
|
|
class TagDoesNotExist(DataModelException):
|
|
pass
|
|
|
|
|
|
class TagAlreadyCreatedException(DataModelException):
|
|
pass
|
|
|
|
|
|
class StaleTagException(DataModelException):
|
|
pass
|
|
|
|
|
|
class InvalidSystemQuotaConfig(Exception):
|
|
pass
|
|
|
|
|
|
class QuotaExceededException(DataModelException):
|
|
pass
|
|
|
|
|
|
class InvalidNamespaceQuota(DataModelException):
|
|
pass
|
|
|
|
|
|
class InvalidNamespaceQuotaLimit(DataModelException):
|
|
pass
|
|
|
|
|
|
class InvalidNamespaceQuotaType(DataModelException):
|
|
pass
|
|
|
|
|
|
class UnsupportedQuotaSize(DataModelException):
|
|
pass
|
|
|
|
|
|
class OrgSubscriptionBindingAlreadyExists(DataModelException):
|
|
pass
|
|
|
|
|
|
class NamespaceAutoPrunePolicyAlreadyExists(DataModelException):
|
|
pass
|
|
|
|
|
|
class NamespaceAutoPrunePolicyDoesNotExist(DataModelException):
|
|
pass
|
|
|
|
|
|
class InvalidNamespaceAutoPrunePolicy(DataModelException):
|
|
pass
|
|
|
|
|
|
class InvalidNamespaceAutoPruneMethod(DataModelException):
|
|
pass
|
|
|
|
|
|
class InvalidNamespaceException(DataModelException):
|
|
pass
|
|
|
|
|
|
class RepositoryAutoPrunePolicyAlreadyExists(DataModelException):
|
|
pass
|
|
|
|
|
|
class RepositoryAutoPrunePolicyDoesNotExist(DataModelException):
|
|
pass
|
|
|
|
|
|
class InvalidRepositoryAutoPrunePolicy(DataModelException):
|
|
pass
|
|
|
|
|
|
class InvalidRepositoryAutoPruneMethod(DataModelException):
|
|
pass
|
|
|
|
|
|
class InvalidRepositoryException(DataModelException):
|
|
pass
|
|
|
|
|
|
class PushesDisabledException(Exception):
|
|
pass
|
|
|
|
|
|
class TooManyLoginAttemptsException(Exception):
|
|
def __init__(self, message, retry_after):
|
|
super(TooManyLoginAttemptsException, self).__init__(message)
|
|
self.retry_after = retry_after
|
|
|
|
|
|
class Config(object):
|
|
def __init__(self):
|
|
self.app_config = None
|
|
self.store = None
|
|
self.image_cleanup_callbacks = []
|
|
self.repo_cleanup_callbacks = []
|
|
|
|
def register_image_cleanup_callback(self, callback):
|
|
self.image_cleanup_callbacks.append(callback)
|
|
return lambda: self.image_cleanup_callbacks.remove(callback)
|
|
|
|
def register_repo_cleanup_callback(self, callback):
|
|
self.repo_cleanup_callbacks.append(callback)
|
|
return lambda: self.repo_cleanup_callbacks.remove(callback)
|
|
|
|
|
|
config = Config()
|
|
|
|
|
|
# There MUST NOT be any circular dependencies between these subsections. If there are fix it by
|
|
# moving the minimal number of things to _basequery
|
|
from data.model import (
|
|
appspecifictoken,
|
|
autoprune,
|
|
blob,
|
|
build,
|
|
entitlements,
|
|
gc,
|
|
label,
|
|
log,
|
|
message,
|
|
modelutil,
|
|
namespacequota,
|
|
notification,
|
|
oauth,
|
|
organization,
|
|
organization_skus,
|
|
permission,
|
|
proxy_cache,
|
|
release,
|
|
repo_mirror,
|
|
repository,
|
|
repositoryactioncount,
|
|
service_keys,
|
|
storage,
|
|
team,
|
|
token,
|
|
user,
|
|
)
|