142 lines
6.5 KiB
YAML
142 lines
6.5 KiB
YAML
name: CI
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
schedule:
|
|
- cron: '17 3 * * 1'
|
|
|
|
concurrency:
|
|
group: buzz-sheet-${{ gitea.ref }}
|
|
cancel-in-progress: false
|
|
|
|
env:
|
|
BASE_URL: https://guide.sudloh.com
|
|
NEXT_PUBLIC_GOOGLE_ADSENSE_CLIENT_ID: ca-pub-9687404323559597
|
|
REGISTRY_IMAGE: registry.neko-piranha.ts.net/astral/buzz-sheet
|
|
DEPLOY_NAMESPACE: buzz-sheet
|
|
|
|
jobs:
|
|
verify:
|
|
name: Verify and audit
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 20
|
|
steps:
|
|
- name: Check out repository and history
|
|
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
|
with:
|
|
fetch-depth: 0
|
|
persist-credentials: false
|
|
- name: Set up Bun
|
|
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
|
|
with:
|
|
bun-version: 1.3.14
|
|
- name: Set up kubectl
|
|
uses: azure/setup-kubectl@776406bce94f63e41d621b960d78ee25c8b76ede # v4
|
|
with:
|
|
version: v1.34.1
|
|
- name: Verify application and manifests
|
|
run: |
|
|
bun install --frozen-lockfile
|
|
bun run security:audit
|
|
bun run test
|
|
bun run typecheck
|
|
bun run lint
|
|
kubectl kustomize k8s/ >/dev/null
|
|
- name: Scan Git history for secrets
|
|
run: |
|
|
docker run --rm --cap-drop=ALL --security-opt=no-new-privileges \
|
|
--volume "$PWD:/repo:ro" --workdir /repo \
|
|
ghcr.io/gitleaks/gitleaks@sha256:691af3c7c5a48b16f187ce3446d5f194838f91238f27270ed36eef6359a574d9 \
|
|
git --redact=100 --no-banner --log-opts=--all .
|
|
|
|
build-and-deploy:
|
|
name: Build, scan and deploy immutable images
|
|
needs: verify
|
|
if: >-
|
|
gitea.event_name == 'push' &&
|
|
gitea.ref == 'refs/heads/main' &&
|
|
vars.DEPLOY_ENABLED == 'true'
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 45
|
|
steps:
|
|
- name: Check out repository
|
|
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
|
with:
|
|
persist-credentials: false
|
|
- name: Build and scan application and migration images
|
|
env:
|
|
REVISION: ${{ gitea.sha }}
|
|
run: |
|
|
set -Eeuo pipefail
|
|
docker build --target app \
|
|
--build-arg BASE_URL="$BASE_URL" \
|
|
--build-arg NEXT_PUBLIC_GOOGLE_ADSENSE_CLIENT_ID="$NEXT_PUBLIC_GOOGLE_ADSENSE_CLIENT_ID" \
|
|
--build-arg NEXT_DEPLOYMENT_ID="$REVISION" \
|
|
--build-arg VCS_REF="$REVISION" \
|
|
--tag "$REGISTRY_IMAGE:$REVISION" .
|
|
docker build --target migration --build-arg VCS_REF="$REVISION" \
|
|
--tag "$REGISTRY_IMAGE:migrate-$REVISION" .
|
|
mkdir -p "$RUNNER_TEMP/buzz-trivy-cache"
|
|
for tag in "$REVISION" "migrate-$REVISION"; do
|
|
docker save --output "$RUNNER_TEMP/buzz-image.tar" "$REGISTRY_IMAGE:$tag"
|
|
docker run --rm --user "$(id -u):$(id -g)" --cap-drop=ALL --security-opt=no-new-privileges \
|
|
--volume "$RUNNER_TEMP:/scan:ro" \
|
|
--volume "$RUNNER_TEMP/buzz-trivy-cache:/cache" \
|
|
aquasec/trivy@sha256:bcc376de8d77cfe086a917230e818dc9f8528e3c852f7b1aff648949b6258d1c \
|
|
image --input /scan/buzz-image.tar --cache-dir /cache --scanners vuln --severity HIGH,CRITICAL --exit-code 1
|
|
rm "$RUNNER_TEMP/buzz-image.tar"
|
|
done
|
|
docker push "$REGISTRY_IMAGE:$REVISION" | tee "$RUNNER_TEMP/buzz-app-push.log"
|
|
docker push "$REGISTRY_IMAGE:migrate-$REVISION" | tee "$RUNNER_TEMP/buzz-migration-push.log"
|
|
- name: Set up kubectl
|
|
uses: azure/setup-kubectl@776406bce94f63e41d621b960d78ee25c8b76ede # v4
|
|
with:
|
|
version: v1.34.1
|
|
- name: Migrate, then roll out verified digests
|
|
env:
|
|
REVISION: ${{ gitea.sha }}
|
|
KUBE_CONFIG_B64: ${{ secrets.KUBE_CONFIG_B64 }}
|
|
shell: bash
|
|
run: |
|
|
set -Eeuo pipefail
|
|
kube_dir="$RUNNER_TEMP/buzz-sheet-kube"
|
|
mkdir -p "$kube_dir"
|
|
chmod 700 "$kube_dir"
|
|
export KUBECONFIG="$kube_dir/config"
|
|
trap 'rm -f "$KUBECONFIG"' EXIT
|
|
printf '%s' "$KUBE_CONFIG_B64" | base64 --decode > "$KUBECONFIG"
|
|
chmod 600 "$KUBECONFIG"
|
|
test -s "$KUBECONFIG"
|
|
insecure="$(kubectl config view --minify -o jsonpath='{.clusters[0].cluster.insecure-skip-tls-verify}')"
|
|
if [ "$insecure" = true ]; then
|
|
echo 'The deployment kubeconfig must validate the Kubernetes certificate.' >&2
|
|
exit 1
|
|
fi
|
|
# Preserve the CA and certificate-valid server supplied in the kubeconfig.
|
|
kubectl --namespace "$DEPLOY_NAMESPACE" get deployment buzz-sheet >/dev/null
|
|
app_digest="$(sed -nE 's/.*digest: (sha256:[a-f0-9]{64}).*/\1/p' "$RUNNER_TEMP/buzz-app-push.log" | tail -n 1)"
|
|
migration_digest="$(sed -nE 's/.*digest: (sha256:[a-f0-9]{64}).*/\1/p' "$RUNNER_TEMP/buzz-migration-push.log" | tail -n 1)"
|
|
app_image="$REGISTRY_IMAGE@$app_digest"
|
|
migration_image="$REGISTRY_IMAGE@$migration_digest"
|
|
[[ "$app_image" =~ @sha256:[a-f0-9]{64}$ ]]
|
|
[[ "$migration_image" =~ @sha256:[a-f0-9]{64}$ ]]
|
|
migration_manifest="$RUNNER_TEMP/buzz-sheet-migration.yaml"
|
|
kubectl kustomize k8s/migration > "$migration_manifest"
|
|
sed -i "s#image: $REGISTRY_IMAGE:.*#image: $migration_image#" "$migration_manifest"
|
|
grep -Fq "image: $migration_image" "$migration_manifest"
|
|
kubectl --namespace "$DEPLOY_NAMESPACE" delete job buzz-sheet-migrate --ignore-not-found
|
|
migration_resource="$(kubectl --namespace "$DEPLOY_NAMESPACE" create -f "$migration_manifest" -o name)"
|
|
if ! kubectl --namespace "$DEPLOY_NAMESPACE" wait --for=condition=complete --timeout=10m "$migration_resource"; then
|
|
kubectl --namespace "$DEPLOY_NAMESPACE" logs "$migration_resource" --all-containers=true || true
|
|
exit 1
|
|
fi
|
|
kubectl --namespace "$DEPLOY_NAMESPACE" patch configmap buzz-sheet-config --type=merge \
|
|
--patch "{\"data\":{\"NEXT_DEPLOYMENT_ID\":\"$REVISION\",\"BASE_URL\":\"$BASE_URL\"}}"
|
|
kubectl --namespace "$DEPLOY_NAMESPACE" set image deployment/buzz-sheet app="$app_image"
|
|
kubectl --namespace "$DEPLOY_NAMESPACE" set image deployment/buzz-sheet-worker worker="$app_image"
|
|
kubectl --namespace "$DEPLOY_NAMESPACE" set image deployment/buzz-sheet-discord-worker discord-worker="$app_image"
|
|
for deployment in buzz-sheet buzz-sheet-worker buzz-sheet-discord-worker; do
|
|
kubectl --namespace "$DEPLOY_NAMESPACE" rollout status "deployment/$deployment" --timeout=10m
|
|
done
|