Retire direct Quay deployment after Argo adoption
Validate, publish and promote Quay chart / chart (push) Successful in 45s

This commit is contained in:
feedc0de committed 2026-10-20 09:06:04 +02:00
1 parent 53d2639080
commit fd919cdb54
9 files changed
+35 -750

No files matched your search

-94
View File
@@ -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
+21 -132
View File
@@ -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.
-9
View File
@@ -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
-69
View File
@@ -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
-122
View File
@@ -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 <registry@brunner.ninja>
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
-66
View File
@@ -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'
+9 -68
View File
@@ -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
-146
View File
@@ -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
+5 -44
View File
@@ -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"