From a839a78eb52e612a3f99c573ce1a552c5bb5e7a0 Mon Sep 17 00:00:00 2001 From: Kenny Lee Sin Cheong <2530351+kleesc@users.noreply.github.com> Date: Wed, 7 Jul 2021 12:45:24 -0400 Subject: [PATCH] chore: allows Quay to run for account recoveries (PROJQUAY-970) (#793) Adds ACCOUNT_RECOVERY_MODE to allow Quay to run with some core features disabled. When this is set, the instance should only be used in order by existing users who hasn't linked their account to an external login service, after database authentication has been disabled. --- buildman/builder.py | 7 +++++-- config.py | 3 +++ endpoints/common.py | 1 + endpoints/decorators.py | 15 +++++++++++++++ endpoints/v2/blob.py | 9 +++++++++ endpoints/v2/catalog.py | 3 ++- endpoints/v2/manifest.py | 12 +++++++++++- endpoints/v2/tag.py | 10 ++++++++-- secscan.py | 4 ++-- static/directives/global-message-tab.html | 2 +- static/directives/quay-message-bar.html | 9 ++++++++- static/js/directives/quay-message-bar.js | 1 + static/js/quay-run.ts | 4 ++++ static/js/services/state-service.js | 12 +++++++++++- templates/base.html | 1 + util/config/schema.py | 1 + .../blobuploadcleanupworker.py | 6 ++++++ workers/buildlogsarchiver/buildlogsarchiver.py | 6 ++++++ workers/chunkcleanupworker.py | 5 +++++ workers/expiredappspecifictokenworker.py | 5 +++++ workers/gc/gcworker.py | 5 +++++ workers/globalpromstats/globalpromstats.py | 5 +++++ workers/logrotateworker.py | 5 +++++ workers/manifestbackfillworker.py | 6 ++++++ workers/namespacegcworker.py | 5 +++++ workers/notificationworker/notificationworker.py | 6 ++++++ workers/queuecleanupworker.py | 6 ++++++ workers/repomirrorworker/repomirrorworker.py | 5 +++++ workers/repositoryactioncounter.py | 5 +++++ workers/repositorygcworker.py | 5 +++++ workers/securityscanningnotificationworker.py | 5 +++++ workers/securityworker/securityworker.py | 5 +++++ workers/storagereplication.py | 5 +++++ workers/teamsyncworker/teamsyncworker.py | 5 +++++ 34 files changed, 178 insertions(+), 11 deletions(-) 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: