diff --git a/buildman/builder.py b/buildman/builder.py index d01f47024..6ac1c24df 100644 --- a/buildman/builder.py +++ b/buildman/builder.py @@ -34,17 +34,20 @@ DEFAULT_CONTROLLER_PORT = 8686 def run_build_manager(): + if app.config.get("ACCOUNT_RECOVERY_MODE", False): + logger.debug("Quay running in account recovery mode") + while True: + time.sleep(100000) + if not features.BUILD_SUPPORT: logger.debug("Building is disabled. Please enable the feature flag") while True: time.sleep(1000) - return if app.config.get("REGISTRY_STATE", "normal") == "readonly": logger.debug("Building is disabled while in read-only mode.") while True: time.sleep(1000) - return build_manager_config = app.config.get("BUILD_MANAGER") if build_manager_config is None: diff --git a/config.py b/config.py index a2e1df1f4..997ee8af5 100644 --- a/config.py +++ b/config.py @@ -773,3 +773,6 @@ class DefaultConfig(ImmutableConfig): # Create organization on push if it does not exist CREATE_NAMESPACE_ON_PUSH = False + + # Account recovery mode + ACCOUNT_RECOVERY_MODE = False diff --git a/endpoints/common.py b/endpoints/common.py index dd6c5cd86..e1af1b8af 100644 --- a/endpoints/common.py +++ b/endpoints/common.py @@ -158,6 +158,7 @@ def render_page_template(name, route_data=None, **kwargs): version_number=version_number, current_year=datetime.datetime.now().year, kubernetes_namespace=IS_KUBERNETES and QE_NAMESPACE, + account_recovery_mode=app.config.get("ACCOUNT_RECOVERY_MODE", False), **kwargs, ) diff --git a/endpoints/decorators.py b/endpoints/decorators.py index 21dd84941..388434cf5 100644 --- a/endpoints/decorators.py +++ b/endpoints/decorators.py @@ -171,6 +171,21 @@ def route_show_if(value): return decorator +def disallow_for_account_recovery_mode(func): + """ + Disable route if ACCOUNT_RECOVERY_MODE is set. + """ + + @wraps(func) + def wrapper(*args, **kwargs): + if app.config.get("ACCOUNT_RECOVERY_MODE", False): + abort(405, "Quay running for account recoveries only.") + + return func(*args, **kwargs) + + return wrapper + + def require_xhr_from_browser(func): """ Requires that API GET calls made from browsers are made via XHR, in order to prevent reflected diff --git a/endpoints/v2/blob.py b/endpoints/v2/blob.py index a70398bed..29dfae712 100644 --- a/endpoints/v2/blob.py +++ b/endpoints/v2/blob.py @@ -21,6 +21,7 @@ from digest import digest_tools from endpoints.decorators import ( anon_protect, anon_allowed, + disallow_for_account_recovery_mode, parse_repository_name, check_region_blacklisted, check_readonly, @@ -51,6 +52,7 @@ BLOB_CONTENT_TYPE = "application/octet-stream" @v2_bp.route(BLOB_DIGEST_ROUTE, methods=["HEAD"]) +@disallow_for_account_recovery_mode @parse_repository_name() @process_registry_jwt_auth(scopes=["pull"]) @require_repo_read @@ -78,6 +80,7 @@ def check_blob_exists(namespace_name, repo_name, digest): @v2_bp.route(BLOB_DIGEST_ROUTE, methods=["GET"]) +@disallow_for_account_recovery_mode @parse_repository_name() @process_registry_jwt_auth(scopes=["pull"]) @require_repo_read @@ -217,6 +220,7 @@ def _try_to_mount_blob(repository_ref, mount_blob_digest): @v2_bp.route("//blobs/uploads/", methods=["POST"]) +@disallow_for_account_recovery_mode @parse_repository_name() @process_registry_jwt_auth(scopes=["pull", "push"]) @require_repo_write @@ -278,6 +282,7 @@ def start_blob_upload(namespace_name, repo_name): @v2_bp.route("//blobs/uploads/", methods=["GET"]) +@disallow_for_account_recovery_mode @parse_repository_name() @process_registry_jwt_auth(scopes=["pull"]) @require_repo_write @@ -305,6 +310,7 @@ def fetch_existing_upload(namespace_name, repo_name, upload_uuid): @v2_bp.route("//blobs/uploads/", methods=["PATCH"]) +@disallow_for_account_recovery_mode @parse_repository_name() @process_registry_jwt_auth(scopes=["pull", "push"]) @require_repo_write @@ -336,6 +342,7 @@ def upload_chunk(namespace_name, repo_name, upload_uuid): @v2_bp.route("//blobs/uploads/", methods=["PUT"]) +@disallow_for_account_recovery_mode @parse_repository_name() @process_registry_jwt_auth(scopes=["pull", "push"]) @require_repo_write @@ -376,6 +383,7 @@ def monolithic_upload_or_last_chunk(namespace_name, repo_name, upload_uuid): @v2_bp.route("//blobs/uploads/", methods=["DELETE"]) +@disallow_for_account_recovery_mode @parse_repository_name() @process_registry_jwt_auth(scopes=["pull", "push"]) @require_repo_write @@ -397,6 +405,7 @@ def cancel_upload(namespace_name, repo_name, upload_uuid): @v2_bp.route("//blobs/", methods=["DELETE"]) +@disallow_for_account_recovery_mode @parse_repository_name() @process_registry_jwt_auth(scopes=["pull", "push"]) @require_repo_write diff --git a/endpoints/v2/catalog.py b/endpoints/v2/catalog.py index 0dc3c2d7e..020504fee 100644 --- a/endpoints/v2/catalog.py +++ b/endpoints/v2/catalog.py @@ -9,7 +9,7 @@ from auth.auth_context import get_authenticated_user, get_authenticated_context from auth.registry_jwt_auth import process_registry_jwt_auth from data import model from data.cache import cache_key -from endpoints.decorators import anon_protect +from endpoints.decorators import anon_protect, disallow_for_account_recovery_mode, route_show_if from endpoints.v2 import v2_bp, paginate @@ -18,6 +18,7 @@ class Repository(namedtuple("Repository", ["id", "namespace_name", "name"])): @v2_bp.route("/_catalog", methods=["GET"]) +@disallow_for_account_recovery_mode @process_registry_jwt_auth() @anon_protect @paginate() diff --git a/endpoints/v2/manifest.py b/endpoints/v2/manifest.py index 3c2f61eac..5b12c5e38 100644 --- a/endpoints/v2/manifest.py +++ b/endpoints/v2/manifest.py @@ -13,7 +13,12 @@ from data.database import db_disallow_replica_use from data.registry_model import registry_model from data.model.oci.manifest import CreateManifestException from data.model.oci.tag import RetargetTagException -from endpoints.decorators import anon_protect, parse_repository_name, check_readonly +from endpoints.decorators import ( + anon_protect, + disallow_for_account_recovery_mode, + parse_repository_name, + check_readonly, +) from endpoints.metrics import image_pulls, image_pushes from endpoints.v2 import v2_bp, require_repo_read, require_repo_write from endpoints.v2.errors import ( @@ -43,6 +48,7 @@ MANIFEST_TAGNAME_ROUTE = BASE_MANIFEST_ROUTE.format(VALID_TAG_PATTERN) @v2_bp.route(MANIFEST_TAGNAME_ROUTE, methods=["GET"]) +@disallow_for_account_recovery_mode @parse_repository_name() @process_registry_jwt_auth(scopes=["pull"]) @require_repo_read @@ -101,6 +107,7 @@ def fetch_manifest_by_tagname(namespace_name, repo_name, manifest_ref): @v2_bp.route(MANIFEST_DIGEST_ROUTE, methods=["GET"]) +@disallow_for_account_recovery_mode @parse_repository_name() @process_registry_jwt_auth(scopes=["pull"]) @require_repo_read @@ -213,6 +220,7 @@ def _doesnt_accept_schema_v1(): @v2_bp.route(MANIFEST_TAGNAME_ROUTE, methods=["PUT"]) +@disallow_for_account_recovery_mode @parse_repository_name() @_reject_manifest2_schema2 @process_registry_jwt_auth(scopes=["pull", "push"]) @@ -225,6 +233,7 @@ def write_manifest_by_tagname(namespace_name, repo_name, manifest_ref): @v2_bp.route(MANIFEST_DIGEST_ROUTE, methods=["PUT"]) +@disallow_for_account_recovery_mode @parse_repository_name() @_reject_manifest2_schema2 @process_registry_jwt_auth(scopes=["pull", "push"]) @@ -285,6 +294,7 @@ def _parse_manifest(): @v2_bp.route(MANIFEST_DIGEST_ROUTE, methods=["DELETE"]) +@disallow_for_account_recovery_mode @parse_repository_name() @process_registry_jwt_auth(scopes=["pull", "push"]) @require_repo_write diff --git a/endpoints/v2/tag.py b/endpoints/v2/tag.py index 1904ae7c5..feffd89db 100644 --- a/endpoints/v2/tag.py +++ b/endpoints/v2/tag.py @@ -1,14 +1,20 @@ from flask import jsonify -from app import model_cache +from app import app, model_cache from auth.registry_jwt_auth import process_registry_jwt_auth from data.registry_model import registry_model -from endpoints.decorators import anon_protect, parse_repository_name +from endpoints.decorators import ( + anon_protect, + disallow_for_account_recovery_mode, + parse_repository_name, + route_show_if, +) from endpoints.v2 import v2_bp, require_repo_read, paginate from endpoints.v2.errors import NameUnknown @v2_bp.route("//tags/list", methods=["GET"]) +@disallow_for_account_recovery_mode @parse_repository_name() @process_registry_jwt_auth(scopes=["pull"]) @require_repo_read diff --git a/secscan.py b/secscan.py index 8c8c5a0d6..66a1c62f2 100644 --- a/secscan.py +++ b/secscan.py @@ -1,5 +1,5 @@ from app import app as application from endpoints.secscan import secscan - -application.register_blueprint(secscan, url_prefix="/secscan") +if not application.config.get("ACCOUNT_RECOVERY_MODE", False): + application.register_blueprint(secscan, url_prefix="/secscan") diff --git a/static/directives/global-message-tab.html b/static/directives/global-message-tab.html index d25592751..ab293b4a4 100644 --- a/static/directives/global-message-tab.html +++ b/static/directives/global-message-tab.html @@ -101,4 +101,4 @@ - \ No newline at end of file + diff --git a/static/directives/quay-message-bar.html b/static/directives/quay-message-bar.html index 2e2ad19f6..59b1fda8b 100644 --- a/static/directives/quay-message-bar.html +++ b/static/directives/quay-message-bar.html @@ -1,10 +1,17 @@ -
+
 is currently in read-only mode. Pulls and other read-only operations will succeed but all other operations are currently suspended.
+
+
+  is currently in account recovery mode. This instance should only be + used to link accounts to an external login service. e.g RedHat. Registry operations such as pushes/pulls + will not work. +
+
Your external application token {{ token.title }} diff --git a/static/js/directives/quay-message-bar.js b/static/js/directives/quay-message-bar.js index 98ed5c008..6d2bec5bf 100644 --- a/static/js/directives/quay-message-bar.js +++ b/static/js/directives/quay-message-bar.js @@ -16,6 +16,7 @@ angular.module('quay').directive('quayMessageBar', function () { StateService.updateStateIn($scope, function(state) { $scope.inReadOnlyMode = state.inReadOnlyMode; + $scope.inAccountRecoveryMode = state.inAccountRecoveryMode; }); ApiService.getGlobalMessages().then(function (data) { diff --git a/static/js/quay-run.ts b/static/js/quay-run.ts index ce242f753..ad19eede6 100644 --- a/static/js/quay-run.ts +++ b/static/js/quay-run.ts @@ -33,6 +33,10 @@ export function provideRun($rootScope: QuayRunScope, stateService.setInReadOnlyMode(); } + if ((window).__account_recovery_mode) { + stateService.setInAccountRecoveryMode(); + } + // Handle session security. restangular.setDefaultHeaders({ 'X-Requested-With': 'XMLHttpRequest', diff --git a/static/js/services/state-service.js b/static/js/services/state-service.js index e60e9ec95..d2f8f9830 100644 --- a/static/js/services/state-service.js +++ b/static/js/services/state-service.js @@ -6,7 +6,8 @@ angular.module('quay') var stateService = {}; var currentState = { - 'inReadOnlyMode': false + 'inReadOnlyMode': false, + 'inAccountRecoveryMode': false }; stateService.inReadOnlyMode = function() { @@ -17,6 +18,15 @@ angular.module('quay') currentState.inReadOnlyMode = true; }; + + stateService.inAccountRecoveryMode = function() { + return currentState.inAccountRecoveryMode; + }; + + stateService.setInAccountRecoveryMode = function() { + currentState.inAccountRecoveryMode = true; + }; + stateService.updateStateIn = function(scope, opt_callback) { scope.$watch(function () { return stateService.currentState(); }, function (currentState) { $timeout(function(){ diff --git a/templates/base.html b/templates/base.html index 58196c895..7ab4b6385 100644 --- a/templates/base.html +++ b/templates/base.html @@ -47,6 +47,7 @@ window.__token = '{{ csrf_token() }}'; window.__kubernetes_namespace = {{ kubernetes_namespace|tojson|safe }}; window.__registry_state = '{{ registry_state }}'; + window.__account_recovery_mode = '{{ account_recovery_mode }}'; {% if error_code %} window.__error_code = {{ error_code }}; diff --git a/util/config/schema.py b/util/config/schema.py index 9d2a68c61..4d8c51cc8 100644 --- a/util/config/schema.py +++ b/util/config/schema.py @@ -99,6 +99,7 @@ INTERNAL_ONLY_PROPERTIES = { "LOGS_MODEL_CONFIG", "APP_REGISTRY_RESULTS_LIMIT", "V3_UPGRADE_MODE", # Deprecated old flag + "ACCOUNT_RECOVERY_MODE", } CONFIG_SCHEMA = { diff --git a/workers/blobuploadcleanupworker/blobuploadcleanupworker.py b/workers/blobuploadcleanupworker/blobuploadcleanupworker.py index ec9bc7569..a44857086 100644 --- a/workers/blobuploadcleanupworker/blobuploadcleanupworker.py +++ b/workers/blobuploadcleanupworker/blobuploadcleanupworker.py @@ -1,5 +1,6 @@ import logging import logging.config +import time from datetime import timedelta, datetime @@ -85,6 +86,11 @@ def create_gunicorn_worker(): if __name__ == "__main__": + if app.config.get("ACCOUNT_RECOVERY_MODE", False): + logger.debug("Quay running in account recovery mode") + while True: + time.sleep(100000) + logging.config.fileConfig(logfile_path(debug=False), disable_existing_loggers=False) GlobalLock.configure(app.config) worker = BlobUploadCleanupWorker() diff --git a/workers/buildlogsarchiver/buildlogsarchiver.py b/workers/buildlogsarchiver/buildlogsarchiver.py index 8005fc441..eeb98bb11 100644 --- a/workers/buildlogsarchiver/buildlogsarchiver.py +++ b/workers/buildlogsarchiver/buildlogsarchiver.py @@ -1,4 +1,5 @@ import logging +import time from gzip import GzipFile from tempfile import SpooledTemporaryFile @@ -79,5 +80,10 @@ def create_gunicorn_worker(): if __name__ == "__main__": + if app.config.get("ACCOUNT_RECOVERY_MODE", False): + logger.debug("Quay running in account recovery mode") + while True: + time.sleep(100000) + worker = ArchiveBuildLogsWorker() worker.start() diff --git a/workers/chunkcleanupworker.py b/workers/chunkcleanupworker.py index 3f49d3361..2626f557d 100644 --- a/workers/chunkcleanupworker.py +++ b/workers/chunkcleanupworker.py @@ -59,6 +59,11 @@ def create_gunicorn_worker(): if __name__ == "__main__": logging.config.fileConfig(logfile_path(debug=False), disable_existing_loggers=False) + if app.config.get("ACCOUNT_RECOVERY_MODE", False): + logger.debug("Quay running in account recovery mode") + while True: + time.sleep(100000) + engines = set( [config[0] for config in list(app.config.get("DISTRIBUTED_STORAGE_CONFIG", {}).values())] ) diff --git a/workers/expiredappspecifictokenworker.py b/workers/expiredappspecifictokenworker.py index c011eb129..651e72806 100644 --- a/workers/expiredappspecifictokenworker.py +++ b/workers/expiredappspecifictokenworker.py @@ -56,6 +56,11 @@ def create_gunicorn_worker(): if __name__ == "__main__": logging.config.fileConfig(logfile_path(debug=False), disable_existing_loggers=False) + if app.config.get("ACCOUNT_RECOVERY_MODE", False): + logger.debug("Quay running in account recovery mode") + while True: + time.sleep(100000) + if not features.APP_SPECIFIC_TOKENS: logger.debug("App specific tokens disabled; skipping") while True: diff --git a/workers/gc/gcworker.py b/workers/gc/gcworker.py index 4ec0e053b..6fde4a38b 100644 --- a/workers/gc/gcworker.py +++ b/workers/gc/gcworker.py @@ -86,6 +86,11 @@ def create_gunicorn_worker(): if __name__ == "__main__": + if app.config.get("ACCOUNT_RECOVERY_MODE", False): + logger.debug("Quay running in account recovery mode") + while True: + time.sleep(100000) + if not features.GARBAGE_COLLECTION: logger.debug("Garbage collection is disabled; skipping") while True: diff --git a/workers/globalpromstats/globalpromstats.py b/workers/globalpromstats/globalpromstats.py index 6d6c233b1..9fb6b5d8e 100644 --- a/workers/globalpromstats/globalpromstats.py +++ b/workers/globalpromstats/globalpromstats.py @@ -82,6 +82,11 @@ def create_gunicorn_worker(): def main(): logging.config.fileConfig(logfile_path(debug=False), disable_existing_loggers=False) + if app.config.get("ACCOUNT_RECOVERY_MODE", False): + logger.debug("Quay running in account recovery mode") + while True: + time.sleep(100000) + if not app.config.get("PROMETHEUS_PUSHGATEWAY_URL"): logger.debug("Prometheus not enabled; skipping global stats reporting") while True: diff --git a/workers/logrotateworker.py b/workers/logrotateworker.py index 84571efb3..28fc64e74 100644 --- a/workers/logrotateworker.py +++ b/workers/logrotateworker.py @@ -148,6 +148,11 @@ def create_gunicorn_worker(): def main(): logging.config.fileConfig(logfile_path(debug=False), disable_existing_loggers=False) + if app.config.get("ACCOUNT_RECOVERY_MODE", False): + logger.debug("Quay running in account recovery mode") + while True: + time.sleep(100000) + if not features.ACTION_LOG_ROTATION or None in [SAVE_PATH, SAVE_LOCATION]: logger.debug("Action log rotation worker not enabled; skipping") while True: diff --git a/workers/manifestbackfillworker.py b/workers/manifestbackfillworker.py index 904e186de..847246824 100644 --- a/workers/manifestbackfillworker.py +++ b/workers/manifestbackfillworker.py @@ -1,4 +1,5 @@ import logging +import time from peewee import fn @@ -103,6 +104,11 @@ def create_gunicorn_worker(): def main(): logging.config.fileConfig(logfile_path(debug=False), disable_existing_loggers=False) + if app.config.get("ACCOUNT_RECOVERY_MODE", False): + logger.debug("Quay running in account recovery mode") + while True: + time.sleep(100000) + if not features.MANIFEST_SIZE_BACKFILL: logger.debug("Manifest backfill worker not enabled; skipping") while True: diff --git a/workers/namespacegcworker.py b/workers/namespacegcworker.py index 0ddc5da4c..0dfba8506 100644 --- a/workers/namespacegcworker.py +++ b/workers/namespacegcworker.py @@ -64,6 +64,11 @@ def create_gunicorn_worker(): if __name__ == "__main__": logging.config.fileConfig(logfile_path(debug=False), disable_existing_loggers=False) + if app.config.get("ACCOUNT_RECOVERY_MODE", False): + logger.debug("Quay running in account recovery mode") + while True: + time.sleep(100000) + if not features.NAMESPACE_GARBAGE_COLLECTION: logger.debug("Namespace garbage collection is disabled; skipping") while True: diff --git a/workers/notificationworker/notificationworker.py b/workers/notificationworker/notificationworker.py index 291bb326f..0d5961a63 100644 --- a/workers/notificationworker/notificationworker.py +++ b/workers/notificationworker/notificationworker.py @@ -1,4 +1,5 @@ import logging +import time from app import app, notification_queue from notifications.notificationmethod import NotificationMethod, InvalidNotificationMethodException @@ -55,6 +56,11 @@ def create_gunicorn_worker(): if __name__ == "__main__": + if app.config.get("ACCOUNT_RECOVERY_MODE", False): + logger.debug("Quay running in account recovery mode") + while True: + time.sleep(100000) + worker = NotificationWorker( notification_queue, poll_period_seconds=10, reservation_seconds=30, retry_after_seconds=30 ) diff --git a/workers/queuecleanupworker.py b/workers/queuecleanupworker.py index 401520d32..da6493336 100644 --- a/workers/queuecleanupworker.py +++ b/workers/queuecleanupworker.py @@ -1,4 +1,5 @@ import logging +import time from datetime import timedelta, datetime @@ -51,5 +52,10 @@ def create_gunicorn_worker(): if __name__ == "__main__": + if app.config.get("ACCOUNT_RECOVERY_MODE", False): + logger.debug("Quay running in account recovery mode") + while True: + time.sleep(100000) + worker = QueueCleanupWorker() worker.start() diff --git a/workers/repomirrorworker/repomirrorworker.py b/workers/repomirrorworker/repomirrorworker.py index b00bfbd7d..3ae3c46e5 100644 --- a/workers/repomirrorworker/repomirrorworker.py +++ b/workers/repomirrorworker/repomirrorworker.py @@ -68,6 +68,11 @@ if __name__ == "__main__": ) args = parser.parse_args() + if app.config.get("ACCOUNT_RECOVERY_MODE", False): + logger.debug("Quay running in account recovery mode") + while True: + time.sleep(100000) + if not features.REPO_MIRROR: logger.debug("Repository mirror disabled; skipping RepoMirrorWorker") while True: diff --git a/workers/repositoryactioncounter.py b/workers/repositoryactioncounter.py index 1aa0cb167..f815f12ba 100644 --- a/workers/repositoryactioncounter.py +++ b/workers/repositoryactioncounter.py @@ -121,6 +121,11 @@ def create_gunicorn_worker(): if __name__ == "__main__": + if app.config.get("ACCOUNT_RECOVERY_MODE", False): + logger.debug("Quay running in account recovery mode") + while True: + time.sleep(100000) + if not features.REPOSITORY_ACTION_COUNTER: logger.info("Repository action count is disabled; skipping") while True: diff --git a/workers/repositorygcworker.py b/workers/repositorygcworker.py index 498951cc6..ce495b81d 100644 --- a/workers/repositorygcworker.py +++ b/workers/repositorygcworker.py @@ -70,6 +70,11 @@ def create_gunicorn_worker(): if __name__ == "__main__": logging.config.fileConfig(logfile_path(debug=False), disable_existing_loggers=False) + if app.config.get("ACCOUNT_RECOVERY_MODE", False): + logger.debug("Quay running in account recovery mode") + while True: + time.sleep(100000) + if not features.REPOSITORY_GARBAGE_COLLECTION: logger.info("Repository garbage collection is disabled; skipping") while True: diff --git a/workers/securityscanningnotificationworker.py b/workers/securityscanningnotificationworker.py index 18276e0f9..f87cdfc5f 100644 --- a/workers/securityscanningnotificationworker.py +++ b/workers/securityscanningnotificationworker.py @@ -152,6 +152,11 @@ def create_gunicorn_worker(): if __name__ == "__main__": logging.config.fileConfig(logfile_path(debug=False), disable_existing_loggers=False) + if app.config.get("ACCOUNT_RECOVERY_MODE", False): + logger.debug("Quay running in account recovery mode") + while True: + time.sleep(100000) + if not features.SECURITY_SCANNER: logger.debug("Security scanner disabled; sleeping") while True: diff --git a/workers/securityworker/securityworker.py b/workers/securityworker/securityworker.py index f50f1c1c7..39870f690 100644 --- a/workers/securityworker/securityworker.py +++ b/workers/securityworker/securityworker.py @@ -57,6 +57,11 @@ if __name__ == "__main__": app.register_blueprint(v2_bp, url_prefix="/v2") + if app.config.get("ACCOUNT_RECOVERY_MODE", False): + logger.debug("Quay running in account recovery mode") + while True: + time.sleep(100000) + if not features.SECURITY_SCANNER: logger.debug("Security scanner disabled; skipping SecurityWorker") while True: diff --git a/workers/storagereplication.py b/workers/storagereplication.py index a2deae190..8fec70dae 100644 --- a/workers/storagereplication.py +++ b/workers/storagereplication.py @@ -202,6 +202,11 @@ if __name__ == "__main__": has_local_storage = False + if app.config.get("ACCOUNT_RECOVERY_MODE", False): + logger.debug("Quay running in account recovery mode") + while True: + time.sleep(100000) + if features.STORAGE_REPLICATION: for storage_type, _ in list(app.config.get("DISTRIBUTED_STORAGE_CONFIG", {}).values()): if storage_type == "LocalStorage": diff --git a/workers/teamsyncworker/teamsyncworker.py b/workers/teamsyncworker/teamsyncworker.py index 4f9982f86..193af9fbc 100644 --- a/workers/teamsyncworker/teamsyncworker.py +++ b/workers/teamsyncworker/teamsyncworker.py @@ -47,6 +47,11 @@ def create_gunicorn_worker(): def main(): logging.config.fileConfig(logfile_path(debug=False), disable_existing_loggers=False) + if app.config.get("ACCOUNT_RECOVERY_MODE", False): + logger.debug("Quay running in account recovery mode") + while True: + time.sleep(100000) + if not features.TEAM_SYNCING or not authentication.federated_service: logger.debug("Team syncing is disabled; sleeping") while True: