diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml deleted file mode 100644 index 90531a2..0000000 --- a/.gitea/workflows/deploy.yml +++ /dev/null @@ -1,94 +0,0 @@ -name: Validate and deploy Quay - -on: - push: - pull_request: - -env: - KUBECTL_VERSION: v1.36.3 - KUBERNETES_API: https://host.containers.internal:6443 - KUBERNETES_TLS_SERVER_NAME: 192.168.0.2 - NAMESPACE: default - -jobs: - validate: - name: Validate manifest - runs-on: ubuntu-latest - steps: - - name: Check out source - uses: actions/checkout@v4 - - - name: Validate scripts and Kubernetes resources - run: bash test.sh - - deploy: - name: Downtime deployment - if: gitea.ref == 'refs/heads/main' - needs: validate - runs-on: ubuntu-latest - steps: - - name: Check out source - uses: actions/checkout@v4 - - - name: Install kubectl - run: | - curl --fail --silent --show-error --location \ - --output "${RUNNER_TEMP}/kubectl" \ - "https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl" - chmod 0700 "${RUNNER_TEMP}/kubectl" - - - name: Configure Kubernetes access - env: - KUBE_CONFIG_BASE64: ${{ secrets.KUBE_CONFIG_BASE64 }} - run: | - test -n "${KUBE_CONFIG_BASE64}" - printf '%s' "${KUBE_CONFIG_BASE64}" | base64 --decode > "${RUNNER_TEMP}/kubeconfig" - chmod 0600 "${RUNNER_TEMP}/kubeconfig" - export KUBECONFIG="${RUNNER_TEMP}/kubeconfig" - "${RUNNER_TEMP}/kubectl" config set-cluster cluster \ - --server="${KUBERNETES_API}" \ - --tls-server-name="${KUBERNETES_TLS_SERVER_NAME}" - - - name: Validate desired state on the server - run: | - export KUBECONFIG="${RUNNER_TEMP}/kubeconfig" - "${RUNNER_TEMP}/kubectl" apply \ - --dry-run=server \ - --validate=false \ - --filename quay.yaml - - - name: Apply and verify - run: | - export KUBECONFIG="${RUNNER_TEMP}/kubeconfig" - "${RUNNER_TEMP}/kubectl" apply \ - --validate=false \ - --filename quay.yaml - - for deployment in quay quay-repomirror; do - for attempt in {1..180}; do - IFS='|' read -r generation observed desired updated ready available unavailable image <<< "$( - "${RUNNER_TEMP}/kubectl" --namespace "${NAMESPACE}" \ - get "deployment/${deployment}" \ - --output=jsonpath='{.metadata.generation}|{.status.observedGeneration}|{.spec.replicas}|{.status.updatedReplicas}|{.status.readyReplicas}|{.status.availableReplicas}|{.status.unavailableReplicas}|{.spec.template.spec.containers[0].image}' - )" - - echo "${deployment} ${attempt}/180: generation ${observed}/${generation}, replicas ${updated}/${desired} updated, ${ready}/${desired} ready, ${available}/${desired} available, image ${image}" - if [[ "${observed}" == "${generation}" \ - && "${updated}" == "${desired}" \ - && "${ready}" == "${desired}" \ - && "${available}" == "${desired}" \ - && ( -z "${unavailable}" || "${unavailable}" == "0" ) ]]; then - break - fi - - if [[ "${attempt}" == 180 ]]; then - echo "${deployment} rollout did not complete within fifteen minutes" >&2 - exit 1 - fi - sleep 5 - done - done - - curl --fail --silent --show-error \ - --max-time 15 \ - https://registry.brunner.ninja/health/endtoend >/dev/null diff --git a/README.md b/README.md index 7dce919..a07d10b 100644 --- a/README.md +++ b/README.md @@ -1,139 +1,28 @@ -# Quay on Kubernetes +# Project Quay Helm chart -This repository now maintains a [reusable Helm chart](chart/README.md) for -Project Quay. It declares Quay's registry and repository mirror, optional -PostgreSQL and Valkey subcharts, and the Service and Ingress. The chart uses -the official Quay image and is published to `https://brunner.ninja/charts`. - -The homelab migration is staged in -[`homelab-gitops/apps/quay`](https://code.brunner.ninja/feedc0de/homelab-gitops/src/branch/main/apps/quay). -Until Argo CD has adopted and verified the live Deployments, the older plain -manifest and direct-deploy workflow below remain the rollback path. Do not -change `quay.yaml` during the database cutover. - -## Persistent data - -Quay itself has no persistent volume. Durable state remains in existing -external services: - -- metadata, users, permissions, and tags: PostgreSQL database `quay` through - `postgresql.default.svc.cluster.local`; -- image layers and artifacts: Rook Ceph RGW bucket `quay`; -- events and build logs: the existing Valkey service in `default`. - -Applying `quay.yaml` does not recreate or delete the PostgreSQL database or RGW -bucket. The `CephObjectStoreUser` declaration is kept separately in -`ceph-object-user.yaml` and is not managed by CI. - -## Configuration Secret - -The historical manifest stored the complete private Quay configuration in a -ConfigMap. The Deployment now mounts `default/secret/quay-config` instead. - -On its first run, `install.sh` copies the existing ConfigMap directly into that -Secret without printing the configuration. After the new pods are healthy, the -script prints the command that can be used to remove the legacy plaintext -ConfigMap. It does not delete it automatically. - -For future configuration changes, copy `config.yaml.example` to the ignored -`config.yaml`, fill every placeholder, and run `./install.sh`. Preserve the -existing `SECRET_KEY` and `DATABASE_SECRET_KEY`; they are persistent Quay -cryptographic keys and should not be regenerated during normal updates. - -`DEFAULT_UI: angular` keeps the legacy logged-out landing page, including its -public repository browsing experience. Anonymous access remains controlled by -`FEATURE_ANONYMOUS_ACCESS: true`. The Angular UI is deprecated upstream, so a -future Quay release may require returning to the React UI. - -An exported Kubernetes Secret YAML is not a safe public backup because its -values are merely base64 encoded. Keep an encrypted off-cluster copy of the -actual configuration in addition to PostgreSQL and RGW backups. - -## Initial installation - -With an administrator kubeconfig active, run: +This repository maintains a [portable Quay chart](chart/README.md) with the +official Quay image, registry and mirror Deployments, Service, Ingress, probes, +and optional PostgreSQL and Valkey subcharts. It is published to the public +[brunner.ninja Helm repository](https://brunner.ninja/charts): ```sh -./install.sh +helm repo add brunner.ninja https://brunner.ninja/charts +helm repo update brunner.ninja ``` -This creates the configuration Secret when necessary, applies the existing -Rook object user and all Quay resources, and waits for both Deployments. +For a cluster installation, first provision an S3-compatible bucket and a +private Quay `config.yaml` Secret as described in the chart documentation. +Set the chart values for your database, cache, hostname, and storage, then run +`QUAY_VALUES_FILE=values.yaml ./install.sh`. The script performs a waiting Helm +upgrade. The chart does not create or delete S3 buckets or their contents. -The registry Deployment has startup, readiness, and liveness probes. Both -Deployments use `Recreate`, accepting downtime and ensuring old and new Quay -versions do not access PostgreSQL concurrently. +The [homelab Quay Argo CD application](https://code.brunner.ninja/feedc0de/homelab-gitops/src/branch/main/apps/quay) +holds only this cluster's values and the Rook object user. On this cluster, +Argo CD is the normal deployment writer; do not run `install.sh` against it. +The old direct-deploy workflow, kubeconfig, and duplicate manifests were +retired after adoption. PostgreSQL and Ceph data were preserved. -## Automatic tag updates - -To update Quay, change both identical `image:` fields in `quay.yaml`, for -example from a 3.8 image to the selected 3.9 image, then commit and push: - -```sh -git add quay.yaml -git commit -m "Update Quay to 3.9.x" -git push origin main -``` - -The workflow validates every push and pull request. A successful push to -`main` performs a server-side dry run, applies the complete manifest, waits for -both Deployments, and checks the public `/health/endtoend` endpoint. - -Kubernetes handles pod replacement through each Deployment's native `Recreate` -strategy. If the pod template did not change, applying the file does not restart -the pods. When an image reference or another pod-template field changes, -Kubernetes stops that Deployment's old pod before starting its replacement. - -If the new registry cannot migrate or become healthy, CI fails and leaves the -registry stopped or unhealthy for investigation. It does not automatically -start the old image because a completed database migration might not be -backwards compatible. - -Quay requires supported intermediate database migrations. Reach a distant -target using successive supported-version commits rather than skipping from -3.8 directly to 3.18. Kubernetes actions require no manual intervention, but a -Git commit cannot make an unsupported database-version jump safe. - -Applying an unchanged pod template does not restart the registry, so a -README-only commit still verifies the live deployment without causing Quay's -`Recreate` downtime. - -## Gitea Actions bootstrap - -CI requires one repository Actions secret: - -- `KUBE_CONFIG_BASE64`: restricted Quay deployment kubeconfig. - -After `./install.sh` has created all application resources, run once: - -```sh -./create-ci-kubeconfig.sh -``` - -For remote cluster access: - -```sh -KUBECTL_SSH_HOST=arschrock ./create-ci-kubeconfig.sh -``` - -Store its final base64 line as the Gitea Actions secret -`KUBE_CONFIG_BASE64`. After that bootstrap, tag updates are Git-only. - -The deployment identity can update only the existing Deployments `quay` and -`quay-repomirror`, Service `quay`, and Ingress `quay` in `default`. It cannot -read or modify Secrets, PostgreSQL, Valkey, Ceph resources, or the RGW bucket. - -The workflow matches the existing brunner.ninja runner: Kubernetes API URL -`https://host.containers.internal:6443`, with TLS server name `192.168.0.2`. - -## Local validation - -Run: - -```sh -./test.sh -``` - -The test checks shell syntax, requires both Deployments to use one identical -non-`latest` image reference, and validates the Kubernetes manifests with -kubeconform. +Chart releases use Quay's application-version Git tag as the base and append +the commit count and hash for chart-only changes. Main-branch CI tests, +publishes, and promotes the exact chart version to the homelab GitOps repo. +Run `./test.sh` locally to lint and render the chart. diff --git a/ceph-object-user.yaml b/ceph-object-user.yaml deleted file mode 100644 index 6683b26..0000000 --- a/ceph-object-user.yaml +++ /dev/null @@ -1,9 +0,0 @@ -# This is a cluster prerequisite, not part of automated Quay deployments. -# Reapplying it does not recreate or delete the existing RGW bucket. -apiVersion: ceph.rook.io/v1 -kind: CephObjectStoreUser -metadata: - name: quay - namespace: rook-ceph -spec: - store: my-store diff --git a/ci-deployer.yaml b/ci-deployer.yaml deleted file mode 100644 index dd4f829..0000000 --- a/ci-deployer.yaml +++ /dev/null @@ -1,69 +0,0 @@ -# Bootstrap once with an administrator identity. CI can update only the -# existing public Quay resources and cannot access Kubernetes Secrets. -apiVersion: v1 -kind: ServiceAccount -metadata: - name: quay-deployer - namespace: default -automountServiceAccountToken: false ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - name: quay-deployer - namespace: default -rules: - - apiGroups: - - "" - resources: - - services - resourceNames: - - quay - verbs: - - get - - patch - - update - - apiGroups: - - apps - resources: - - deployments - resourceNames: - - quay - - quay-repomirror - verbs: - - get - - patch - - update - - apiGroups: - - networking.k8s.io - resources: - - ingresses - resourceNames: - - quay - verbs: - - get - - patch - - update ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: quay-deployer - namespace: default -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: quay-deployer -subjects: - - kind: ServiceAccount - name: quay-deployer - namespace: default ---- -apiVersion: v1 -kind: Secret -metadata: - name: quay-deployer-token - namespace: default - annotations: - kubernetes.io/service-account.name: quay-deployer -type: kubernetes.io/service-account-token diff --git a/config.yaml.example b/config.yaml.example deleted file mode 100644 index 3243d8e..0000000 --- a/config.yaml.example +++ /dev/null @@ -1,122 +0,0 @@ -# Copy this file to config.yaml and replace every REPLACE_WITH_* value. -# config.yaml is ignored by Git and is installed as the quay/quay-config Secret. -ALLOW_PULLS_WITHOUT_STRICT_LOGGING: true -ALLOWED_OCI_ARTIFACT_TYPES: - application/vnd.oci.image.config.v1+json: - - application/vnd.oci.image.layer.v1.tar+zstd - application/vnd.sylabs.sif.config.v1+json: - - application/vnd.sylabs.sif.layer.v1+tar -AUTHENTICATION_TYPE: Database -AVATAR_KIND: local -BUILDLOGS_REDIS: - host: valkey-primary.default.svc.cluster.local - password: REPLACE_WITH_VALKEY_PASSWORD - port: 6379 -CONTACT_INFO: - - https://brunner.ninja -DATABASE_SECRET_KEY: REPLACE_WITH_PERSISTENT_RANDOM_DATABASE_SECRET_KEY -DB_URI: postgresql://quay:REPLACE_WITH_QUAY_DATABASE_PASSWORD@postgresql.default.svc.cluster.local/quay -DEFAULT_UI: angular -DEFAULT_TAG_EXPIRATION: 2w -DISTRIBUTED_STORAGE_CONFIG: - default: - - RadosGWStorage - - access_key: REPLACE_WITH_ROOK_RGW_ACCESS_KEY - bucket_name: quay - hostname: rook-ceph-rgw-my-store.rook-ceph.svc.cluster.local - is_secure: false - secret_key: REPLACE_WITH_ROOK_RGW_SECRET_KEY - storage_path: / -DISTRIBUTED_STORAGE_DEFAULT_LOCATIONS: [] -DISTRIBUTED_STORAGE_PREFERENCE: - - default -EXTERNAL_TLS_TERMINATION: true -FEATURE_ACI_CONVERSION: false -FEATURE_ACTION_LOG_ROTATION: false -FEATURE_ANONYMOUS_ACCESS: true -FEATURE_APP_REGISTRY: true -FEATURE_APP_SPECIFIC_TOKENS: true -FEATURE_BITBUCKET_BUILD: false -FEATURE_BLACKLISTED_EMAILS: false -FEATURE_BUILD_SUPPORT: true -FEATURE_CHANGE_TAG_EXPIRATION: true -FEATURE_DIRECT_LOGIN: true -FEATURE_EXTENDED_REPOSITORY_NAMES: true -FEATURE_FIPS: false -FEATURE_GITHUB_BUILD: false -FEATURE_GITHUB_LOGIN: false -FEATURE_GITLAB_BUILD: false -FEATURE_GOOGLE_LOGIN: false -FEATURE_INVITE_ONLY_USER_CREATION: false -FEATURE_MAILING: true -FEATURE_NONSUPERUSER_TEAM_SYNCING_SETUP: false -FEATURE_PARTIAL_USER_AUTOCOMPLETE: true -FEATURE_PROXY_STORAGE: true -FEATURE_REPO_MIRROR: true -FEATURE_REQUIRE_TEAM_INVITE: true -FEATURE_RESTRICTED_V1_PUSH: true -FEATURE_SECURITY_NOTIFICATIONS: false -FEATURE_SECURITY_SCANNER: false -FEATURE_STORAGE_REPLICATION: false -FEATURE_TEAM_SYNCING: false -FEATURE_USER_CREATION: true -FEATURE_USER_LAST_ACCESSED: true -FEATURE_USER_LOG_ACCESS: false -FEATURE_USER_METADATA: false -FEATURE_USER_RENAME: false -FEATURE_USERNAME_CONFIRMATION: true -FRESH_LOGIN_TIMEOUT: 10m -GITHUB_LOGIN_CONFIG: {} -GITHUB_TRIGGER_CONFIG: {} -GITLAB_TRIGGER_KIND: {} -LDAP_ALLOW_INSECURE_FALLBACK: false -LDAP_EMAIL_ATTR: mail -LDAP_UID_ATTR: uid -LDAP_URI: ldap://localhost -LOG_ARCHIVE_LOCATION: default -LOGS_MODEL: database -LOGS_MODEL_CONFIG: {} -MAIL_DEFAULT_SENDER: brunner.ninja Registry -MAIL_PORT: 587 -MAIL_SERVER: smtp-relay.gmail.com -MAIL_USE_AUTH: false -MAIL_USE_TLS: false -PREFERRED_URL_SCHEME: https -REGISTRY_LOGIN_CONFIG: - CLIENT_ID: REPLACE_WITH_OIDC_CLIENT_ID - CLIENT_SECRET: REPLACE_WITH_OIDC_CLIENT_SECRET - LOGIN_SCOPES: - - openid - - email - OIDC_SERVER: https://auth.brunner.ninja/application/o/registry/ - SERVICE_ICON: https://s3.brunner.ninja/authentik/media/public/auth-favicon.png - SERVICE_NAME: brunner.ninja Auth - VERIFIED_EMAIL_CLAIM_NAME: email - OIDC_DISABLE_USER_ENDPOINT: true -REGISTRY_TITLE: brunner.ninja Registry -REGISTRY_TITLE_SHORT: brunner.ninja Registry -REPO_MIRROR_INTERVAL: 30 -REPO_MIRROR_TLS_VERIFY: true -SEARCH_MAX_RESULT_PAGE_COUNT: 10 -SEARCH_RESULTS_PER_PAGE: 10 -SECRET_KEY: REPLACE_WITH_PERSISTENT_RANDOM_SECRET_KEY -SECURITY_SCANNER_INDEXING_INTERVAL: 30 -SERVER_HOSTNAME: registry.brunner.ninja -SETUP_COMPLETE: true -SUPER_USERS: - - REPLACE_WITH_QUAY_SUPERUSER -TAG_EXPIRATION_OPTIONS: - - 0s - - 1d - - 1w - - 2w - - 4w -TEAM_RESYNC_STALE_TIME: 30m -TESTING: false -USE_CDN: false -USER_EVENTS_REDIS: - host: valkey-primary.default.svc.cluster.local - password: REPLACE_WITH_VALKEY_PASSWORD - port: 6379 -USER_RECOVERY_TOKEN_LIFETIME: 30m -USERFILES_LOCATION: default diff --git a/create-ci-kubeconfig.sh b/create-ci-kubeconfig.sh deleted file mode 100755 index 82a9afb..0000000 --- a/create-ci-kubeconfig.sh +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -project_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd) -namespace=${NAMESPACE:-default} -service_account=${SERVICE_ACCOUNT:-quay-deployer} -secret=${TOKEN_SECRET:-quay-deployer-token} -ssh_host=${KUBECTL_SSH_HOST:-} -ssh_config=${SSH_CONFIG:-} - -cluster_kubectl() { - if [[ -n "${ssh_host}" ]]; then - if [[ -n "${ssh_config}" ]]; then - ssh -F "${ssh_config}" "${ssh_host}" kubectl "$@" - else - ssh "${ssh_host}" kubectl "$@" - fi - else - kubectl "$@" - fi -} - -if [[ -n "${ssh_host}" ]]; then - cluster_kubectl apply --filename=- < "${project_dir}/ci-deployer.yaml" >&2 -else - cluster_kubectl apply --filename "${project_dir}/ci-deployer.yaml" >&2 -fi - -for attempt in {1..30}; do - if cluster_kubectl --namespace "${namespace}" get secret "${secret}" \ - --output=jsonpath='{.data.token}' 2>/dev/null | grep -q .; then - break - fi - if [[ "${attempt}" == 30 ]]; then - echo "Timed out waiting for the service-account token" >&2 - exit 1 - fi - sleep 1 -done - -workdir=$(mktemp --directory) -trap 'rm -rf "${workdir}"' EXIT - -server=$(cluster_kubectl config view --minify --output=jsonpath='{.clusters[0].cluster.server}') -cluster_kubectl --namespace "${namespace}" get secret "${secret}" \ - --output=jsonpath='{.data.ca\.crt}' | base64 --decode > "${workdir}/ca.crt" -token=$(cluster_kubectl --namespace "${namespace}" get secret "${secret}" \ - --output=jsonpath='{.data.token}' | base64 --decode) - -export KUBECONFIG="${workdir}/config" -kubectl config set-cluster cluster \ - --server="${server}" \ - --certificate-authority="${workdir}/ca.crt" \ - --embed-certs=true >/dev/null -kubectl config set-credentials "${service_account}" --token="${token}" >/dev/null -kubectl config set-context quay \ - --cluster=cluster \ - --user="${service_account}" \ - --namespace="${namespace}" >/dev/null -kubectl config use-context quay >/dev/null - -echo "Store the following single line as the Gitea Actions secret KUBE_CONFIG_BASE64." >&2 -echo "Treat it as a password; do not commit it." >&2 -base64 --wrap=0 "${KUBECONFIG}" -printf '\n' diff --git a/install.sh b/install.sh index 8d5d31d..327b7be 100755 --- a/install.sh +++ b/install.sh @@ -1,75 +1,16 @@ #!/bin/sh - set -eu -namespace=default -script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) -config_file=${QUAY_CONFIG_FILE:-$script_dir/config.yaml} -config_updated=false +namespace=${QUAY_NAMESPACE:-default} +values_file=${QUAY_VALUES_FILE:-values.yaml} -for command in kubectl grep mktemp; do - if ! command -v "$command" >/dev/null 2>&1; then - echo "Required command not found: $command" >&2 - exit 1 - fi -done - -install_config_secret() { - source_file=$1 - - if grep -q 'REPLACE_WITH_' "$source_file"; then - echo "Refusing to install a config containing REPLACE_WITH_ placeholders: $source_file" >&2 - exit 1 - fi - - kubectl create secret generic quay-config \ - --namespace "$namespace" \ - --from-file="config.yaml=$source_file" \ - --dry-run=client \ - --output=yaml | kubectl apply --filename=- - config_updated=true -} - -if [ -f "$config_file" ]; then - install_config_secret "$config_file" - echo "Updated $namespace/secret/quay-config from the private config file." -elif kubectl get secret quay-config --namespace "$namespace" >/dev/null 2>&1; then - echo "Keeping the existing $namespace/secret/quay-config." -elif kubectl get configmap quay-config --namespace "$namespace" >/dev/null 2>&1; then - temp_dir=$(mktemp -d) - trap 'rm -rf "$temp_dir"' EXIT HUP INT TERM - umask 077 - kubectl get configmap quay-config \ - --namespace "$namespace" \ - --output=go-template='{{index .data "config.yaml"}}' > "$temp_dir/config.yaml" - install_config_secret "$temp_dir/config.yaml" - echo "Copied the legacy ConfigMap into $namespace/secret/quay-config without printing it." -else - echo "No private config file, Secret, or legacy ConfigMap was found." >&2 +if [ ! -f "$values_file" ]; then + echo "Values file not found: $values_file" >&2 exit 1 fi -kubectl apply --filename "$script_dir/ceph-object-user.yaml" -kubectl apply --dry-run=server --filename "$script_dir/quay.yaml" -kubectl apply --filename "$script_dir/quay.yaml" - -# The Secret is mounted with subPath, so an explicit Recreate rollout is needed -# after a configuration update. -if [ "$config_updated" = true ]; then - kubectl rollout restart deployment/quay deployment/quay-repomirror \ - --namespace "$namespace" -fi - -kubectl rollout status deployment/quay \ - --namespace "$namespace" \ - --timeout=15m -kubectl rollout status deployment/quay-repomirror \ - --namespace "$namespace" \ - --timeout=15m - -echo -echo "Quay is available at https://registry.brunner.ninja." -if kubectl get configmap quay-config --namespace "$namespace" >/dev/null 2>&1; then - echo "After verifying the new pods, remove the legacy plaintext ConfigMap with:" - echo "kubectl delete configmap quay-config --namespace $namespace" -fi +helm repo add brunner.ninja https://brunner.ninja/charts +helm repo update brunner.ninja +helm upgrade --install quay brunner.ninja/quay \ + --namespace "$namespace" --create-namespace \ + --values "$values_file" --wait --timeout 15m diff --git a/quay.yaml b/quay.yaml deleted file mode 100644 index 38f4281..0000000 --- a/quay.yaml +++ /dev/null @@ -1,146 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: quay - namespace: default - labels: - app: quay - app.kubernetes.io/name: quay - app.kubernetes.io/component: registry -spec: - replicas: 1 - progressDeadlineSeconds: 900 - strategy: - # Version changes deliberately accept downtime so two Quay versions never - # access the database concurrently. - type: Recreate - selector: - matchLabels: - app: quay - template: - metadata: - labels: - app: quay - app.kubernetes.io/name: quay - app.kubernetes.io/component: registry - spec: - containers: - - name: quay - # Quay 3.8.1 is the currently running database-compatible version. - # Upgrade one supported minor version at a time; do not jump to 3.18. - image: quay.io/projectquay/quay:3.18.0 - env: - - name: DEBUGLOG - value: "true" - ports: - - name: http - containerPort: 8080 - protocol: TCP - startupProbe: - httpGet: - path: /health/instance - port: http - failureThreshold: 60 - periodSeconds: 5 - timeoutSeconds: 5 - readinessProbe: - httpGet: - path: /health/endtoend - port: http - failureThreshold: 3 - periodSeconds: 10 - timeoutSeconds: 5 - livenessProbe: - httpGet: - path: /health/instance - port: http - failureThreshold: 3 - periodSeconds: 30 - timeoutSeconds: 5 - volumeMounts: - - name: quay-config - mountPath: /conf/stack/config.yaml - subPath: config.yaml - readOnly: true - volumes: - - name: quay-config - secret: - secretName: quay-config - defaultMode: 0440 ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: quay-repomirror - namespace: default - labels: - app: quay-repomirror - app.kubernetes.io/name: quay - app.kubernetes.io/component: repository-mirror -spec: - replicas: 1 - strategy: - type: Recreate - selector: - matchLabels: - app: quay-repomirror - template: - metadata: - labels: - app: quay-repomirror - app.kubernetes.io/name: quay - app.kubernetes.io/component: repository-mirror - spec: - containers: - - name: quay-repomirror - image: quay.io/projectquay/quay:3.18.0 - args: - - repomirror - env: - - name: DEBUGLOG - value: "true" - volumeMounts: - - name: quay-config - mountPath: /conf/stack/config.yaml - subPath: config.yaml - readOnly: true - volumes: - - name: quay-config - secret: - secretName: quay-config - defaultMode: 0440 ---- -apiVersion: v1 -kind: Service -metadata: - name: quay - namespace: default -spec: - ports: - - name: http - port: 80 - protocol: TCP - targetPort: 8080 - selector: - app: quay ---- -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: quay - namespace: default - annotations: - traefik.ingress.kubernetes.io/router.entrypoints: websecure -spec: - ingressClassName: traefik - rules: - - host: registry.brunner.ninja - http: - paths: - - path: / - pathType: Prefix - backend: - service: - name: quay - port: - name: http diff --git a/test.sh b/test.sh index 6ca08db..5bcaca4 100755 --- a/test.sh +++ b/test.sh @@ -1,44 +1,5 @@ -#!/usr/bin/env bash - -set -euo pipefail - -project_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd) - -sh -n "${project_dir}/install.sh" -bash -n "${project_dir}/create-ci-kubeconfig.sh" -bash -n "${project_dir}/test.sh" - -mapfile -t quay_images < <( - sed -n 's|^[[:space:]]*image: \(quay.io/projectquay/quay[^[:space:]]*\)$|\1|p' \ - "${project_dir}/quay.yaml" | sort --unique -) -if [[ "${#quay_images[@]}" != 1 ]]; then - echo "Both Quay Deployments must use the same single image reference." >&2 - exit 1 -fi -if [[ "${quay_images[0]}" == *:latest ]]; then - echo "The mutable latest tag is not allowed." >&2 - exit 1 -fi - -validate_file() { - local manifest=$1 - shift - - if command -v kubeconform >/dev/null 2>&1; then - kubeconform -strict -summary "$@" "${manifest}" - elif command -v docker >/dev/null 2>&1; then - docker run --rm --interactive \ - ghcr.io/yannh/kubeconform:v0.7.0 \ - -strict -summary "$@" < "${manifest}" - else - echo "Required command not found: install kubeconform or Docker" >&2 - exit 1 - fi -} - -validate_file "${project_dir}/quay.yaml" -validate_file "${project_dir}/ceph-object-user.yaml" -ignore-missing-schemas -validate_file "${project_dir}/ci-deployer.yaml" - -echo "Quay scripts and Kubernetes resources are valid for ${quay_images[0]}." +#!/bin/sh +set -eu +script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +sh -n "$script_dir/install.sh" +bash "$script_dir/chart/test.sh"