Skip to content

Repository files navigation

Quay Service Tool

Description

The purpose of this repository is to handle tasks like configuring or updating quay. This is an internal tool and only Red Hat associates can access this. The tool is implemented using React(Patternfly) on the frontend and Python(flask) on the backend.

Prerequisites

The project expects the following environment variables in config.yaml:

  • authentication:
    • url: Keycloak authentication url
    • clientid: Keycloak client id
    • realm: Keycloak realm, the client id belongs to
  • DB_URI: databaseEngine://databaseUser:databasePassword@databaseHost:databasePort/databaseName
  • is_local: A boolean value. When set to true, forces app to connect with postgres and if set to false, connects with mysql.
  • ENV: Development/Production environment.

Run the Quay app and its dependencies. Make sure that the quay-db is running. Pass the database credentials for authenticating into the quay db to the config.yaml.

You can find the reference for config.yaml at backend\config.yaml

Remove the node_modules folder under frontend (if exists)

Testing

Backend Tests

Backend tests require PostgreSQL and MinIO. From the repository root, start the Podman services and run the complete suite with:

make spam-storage-test

Frontend Tests

Frontend tests are configured with the following pnpm script: cd frontend && pnpm test

Spam detection end-to-end demo

The spam detection demo starts the Quay ingress implementation and service tool together, then opens both UIs in separate system Chrome tabs. The visible workflow:

  1. Signs in to Quay and attempts to create a repository with a spam description, showing the ingress rejection in the Quay UI.
  2. Creates an empty test repository that represents spam content predating ingress enforcement.
  3. Attempts to update that repository with a spam description, showing the update rejection and unchanged description in Quay.
  4. Imports and activates the exact classifier artifact configured by the local Quay checkout, previews the legacy repository, and runs a review scan.
  5. Quarantines the flagged repository and verifies the owner-facing notice in Quay.
  6. Restores the repository and verifies the original description returns in Quay.
  7. Reopens the mistaken restore with a required reason and quarantines the repository again without changing the classifier.
  8. Verifies the final Quay quarantine notice and the complete service-tool audit history.

Spam matching requires an HTTP or HTTPS hyperlink in addition to the classifier threshold. In the service-tool UI, operators can label review matches as spam or ham for future training, download an exported artifact, reopen mistaken restores or dismissals, and select Scan all repositories for an unbounded scan.

Meaningful clicks and input focus are marked with an animated yellow ring. The browser remains open until you press Ctrl+C after the workflow completes.

Prerequisites:

  • Check out quay/quay#6154 and this service-tool PR.
  • Install Node.js, Corepack, make, curl, and system Chrome.
  • Start Podman or Docker with Compose support. The demo does not start or modify a Podman machine.
  • Keep ports 8080, 5001, and 9000 available.
  • Install the browser dependencies once:
corepack pnpm --dir /absolute/path/to/quay/web install --frozen-lockfile
corepack pnpm@10.28.2 --dir /absolute/path/to/quay-service-tool/frontend install --frozen-lockfile

The Quay PR checkout must contain the classifier artifact referenced by its local spam detection configuration. The artifact is read at runtime and is never copied into this repository. Validate all prerequisites without starting anything:

QUAY_DIR=/absolute/path/to/quay \
make -C /absolute/path/to/quay-service-tool spam-demo-check

From the service-tool repository, run:

make spam-demo

From any directory, or when the Quay checkout is not the default sibling named quay, run:

QUAY_DIR=/absolute/path/to/quay \
make -C /absolute/path/to/quay-service-tool spam-demo

To start both applications with the configured classifier and a flagged review record, without running the visible Playwright walkthrough:

QUAY_DIR=/absolute/path/to/quay \
make -C /absolute/path/to/quay-service-tool spam-demo-explore

The command opens Quay and Spam Detection in separate Chrome tabs, signs in to Quay, and performs no further browser actions. Drive either application manually, then press Ctrl+C when finished. The services remain running until make spam-demo-down is run.

To use a different local artifact without changing Quay configuration:

SPAM_CLASSIFIER_ARTIFACT=/absolute/path/to/classifier.json \
QUAY_DIR=/absolute/path/to/quay \
make -C /absolute/path/to/quay-service-tool spam-demo

Use PLAYWRIGHT_SLOW_MO to change individual browser action timing, DEMO_STEP_DELAY to change the pause between visible stages, and DEMO_CLICK_DELAY to change how long click highlighting remains visible. After the workflow finishes, the browser remains open until you press Ctrl+C. Set HOLD_SECONDS to a positive number to close it automatically. For a differently located Quay checkout or a slower presentation:

QUAY_DIR=/path/to/quay PLAYWRIGHT_SLOW_MO=1500 DEMO_STEP_DELAY=10000 DEMO_CLICK_DELAY=2000 HOLD_SECONDS=900 make spam-demo

Production classifier storage

For a multi-replica deployment, use PostgreSQL for service-tool state and S3 for classifier artifacts. PostgreSQL stores classifier identity, S3 URI, checksum, thresholds, policy, training feedback, scan results, reviews, and audit history. Classifier bodies are not duplicated into PostgreSQL. Each S3 object is loaded once at the start of a scan or request, rather than once per repository.

Import the initial JSON artifact from the Classifier tab and leave Activate after import selected. Manual scans then use that artifact from the persistent service-tool state. Spam and ham labels are retained as training feedback; Train new version combines that feedback with the imported base model and immediately updates subsequent manual scans. Download retrieves the selected S3 object without changing it. Stage for Quay build copies it to the configured promoted S3 key, writes a .sha256 sidecar, and records an audit event.

The Quay image build then materializes the promoted object into its build workspace. This command verifies the S3 checksum before writing both files:

cd backend
CONFIG_PATH=/path/to/config \
uv run python spam_detection_cli.py materialize-promoted-artifact \
  --output-path /build/quay/conf/spam-detection/classifier.json

Copy classifier.json and classifier.json.sha256 into the image at /conf/spam-detection/. Quay continues to load the baked artifact at startup; it does not call service-tool or S3 on the request path.

Configure the production secret with settings equivalent to:

SPAM_DETECTION_STATE_DB_URI: postgresql://service_tool:<password>@<rds-host>:5432/service_tool
SPAM_DETECTION_STATE_DB_SCHEMA: spam_detection
SPAM_DETECTION_STATE_DB_CREATE_SCHEMA: false
SPAM_DETECTION_STATE_DB_POOL_MIN: 1
SPAM_DETECTION_STATE_DB_POOL_MAX: 10
SPAM_DETECTION_STATE_DB_LOCK_CONNECTIONS: 1
SPAM_DETECTION_S3_BUCKET: quay-service-tool
SPAM_DETECTION_S3_PREFIX: spam-detection
SPAM_DETECTION_S3_REGION: us-east-1
# Set only for an S3-compatible service with a custom endpoint:
# SPAM_DETECTION_S3_ENDPOINT_URL: https://s3.example.com
# SPAM_DETECTION_S3_ADDRESSING_STYLE: path
SPAM_DETECTION_S3_CREATE_BUCKET: false
SPAM_DETECTION_STALE_SCAN_TIMEOUT_SECONDS: 3600

Use the normal AWS SDK credential chain, preferably workload identity in the cluster. Do not put access keys in config.yaml. The database pool maximum is the total per-process limit: LOCK_CONNECTIONS is reserved for advisory locks and the remainder is used by the request pool. Size it together with the RDS connection limit and the number of service-tool processes and pods. S3 lifecycle rules must retain every object referenced by PostgreSQL. Create the configured database schema and grant the service user access ahead of deployment; the CREATE_SCHEMA option is intended only for controlled bootstrap and local testing.

Initialize the final schema before starting the service:

cd backend
CONFIG_PATH=/path/to/config uv run python spam_detection_cli.py init-state-db

Local PostgreSQL and S3 testing

The Compose stack runs PostgreSQL and MinIO, so local development and every spam-detection backend test exercise the same database and S3 APIs as production without using RDS or AWS. Run the complete backend suite with:

make spam-storage-test

The command starts both containers and runs all backend tests, including the three-worker import, S3 promotion, checksum materialization, and cross-worker scan-lease coverage. The containers and their volumes remain available for inspection. MinIO is exposed at http://localhost:9002, and its console is at http://localhost:9003 with the local-only credentials minioadmin / minioadmin.

Use make spam-storage-up to start the services without running the test, make spam-storage-down to stop them while preserving data, or make spam-storage-clean to remove the local volumes. The full make spam-demo workflow uses these same PostgreSQL and MinIO services.

Stop the demo while preserving volumes, or remove its volumes completely:

QUAY_DIR=/absolute/path/to/quay \
make -C /absolute/path/to/quay-service-tool spam-demo-down

QUAY_DIR=/absolute/path/to/quay \
make -C /absolute/path/to/quay-service-tool spam-demo-clean

make spam-demo-check validates paths, configuration, required commands, and container-runtime availability without starting either application.

Quick Start

Starting application

Start the application using docker-compose.yml.

docker-compose up -d

Starting servers individually

Backend

The application uses: Python 3.13. So, please create a Python Environment - 3.13 and install the requirements using uv.

  uv sync

Export the path of the config.yaml file in the CONFIG_PATH environment variable as:

  export CONFIG_PATH=/home/parallels/Documents/quay-service-tool/backend/config

Run the server using gunicorn in the backend directory as:

  uv run gunicorn -k gevent -b 0.0.0.0:5000 app:app

This allows flask to serve requests on http://0.0.0.0:5000.

Frontend

Run pnpm install to install the node modules.

After successful execution of the command, run pnpm build to build the application bundles.

You can run the below command to start the front-end server.

  pnpm start:dev

You can now access the application at: http://0.0.0.0:9000.

Keycloak - Auth server

You can get a keycloak server running using the command below. This starts the keycloak server on the port 8081. You can skip this if you are running locally and don't require user authentication to the application.

docker run --name keycloak -p 8081:8080 -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=password jboss/keycloak

You can refer to this document to set up a Keycloak realm, client and user - https://scalac.io/blog/user-authentication-keycloak-1/

The details of the keycloak configuration needs to be updated in backend/config/config.yaml and frontend/.env.

About

Service tool for running quay admin tasks

Resources

Stars

1 star

Watchers

11 watching

Forks

Releases

Packages

Used by

Contributors

Languages