195 lines
7.8 KiB
YAML
195 lines
7.8 KiB
YAML
name: CI
|
|
|
|
on:
|
|
push:
|
|
branches:
|
|
- main
|
|
|
|
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
|
|
KUBE_API_SERVER: https://100.112.189.33:6443/
|
|
|
|
jobs:
|
|
verify:
|
|
name: Verify
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 15
|
|
steps:
|
|
- name: Check out repository
|
|
uses: actions/checkout@v4
|
|
- name: Set up Bun
|
|
uses: oven-sh/setup-bun@v2
|
|
with:
|
|
bun-version: 1.3.14
|
|
- name: Set up kubectl
|
|
uses: azure/setup-kubectl@v4
|
|
- name: Verify application and manifests
|
|
run: |
|
|
bun install --frozen-lockfile
|
|
bun run test
|
|
bun run typecheck
|
|
bun run lint
|
|
kubectl kustomize k8s/ >/dev/null
|
|
|
|
build-and-deploy:
|
|
name: Build immutable images and deploy
|
|
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@v4
|
|
|
|
- name: Announce incoming deployment
|
|
continue-on-error: true
|
|
env:
|
|
DEPLOYMENT_WEBHOOK_SECRET: ${{ secrets.DEPLOYMENT_WEBHOOK_SECRET }}
|
|
REVISION: ${{ gitea.sha }}
|
|
run: |
|
|
if [ -z "$DEPLOYMENT_WEBHOOK_SECRET" ]; then
|
|
echo "DEPLOYMENT_WEBHOOK_SECRET is not configured; skipping notification"
|
|
exit 0
|
|
fi
|
|
curl --fail-with-body --silent --show-error --retry 3 \
|
|
--header "Authorization: Bearer $DEPLOYMENT_WEBHOOK_SECRET" \
|
|
--header "Content-Type: application/json" \
|
|
--data "{\"deploymentId\":\"$REVISION\",\"status\":\"deploying\"}" \
|
|
"$BASE_URL/api/deployments/status"
|
|
|
|
- name: Build and push application image
|
|
run: |
|
|
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=${{ gitea.sha }} \
|
|
--build-arg VCS_REF=${{ gitea.sha }} \
|
|
--tag ${{ env.REGISTRY_IMAGE }}:${{ gitea.sha }} \
|
|
.
|
|
docker push ${{ env.REGISTRY_IMAGE }}:${{ gitea.sha }}
|
|
|
|
- name: Build and push migration image
|
|
run: |
|
|
docker build \
|
|
--target migration \
|
|
--build-arg VCS_REF=${{ gitea.sha }} \
|
|
--tag ${{ env.REGISTRY_IMAGE }}:migrate-${{ gitea.sha }} \
|
|
.
|
|
docker push ${{ env.REGISTRY_IMAGE }}:migrate-${{ gitea.sha }}
|
|
|
|
- name: Set up kubectl
|
|
uses: azure/setup-kubectl@v4
|
|
|
|
- name: Configure kubectl
|
|
env:
|
|
KUBE_CONFIG_B64: ${{ secrets.KUBE_CONFIG_B64 }}
|
|
run: |
|
|
if [ -z "$KUBE_CONFIG_B64" ]; then
|
|
echo "Missing KUBE_CONFIG_B64 secret for the CI deploy identity" >&2
|
|
exit 1
|
|
fi
|
|
|
|
kube_dir="$RUNNER_TEMP/buzz-sheet-kube"
|
|
mkdir -p "$kube_dir"
|
|
chmod 700 "$kube_dir"
|
|
export KUBECONFIG="$kube_dir/config"
|
|
printf '%s' "$KUBE_CONFIG_B64" | base64 --decode >"$KUBECONFIG"
|
|
chmod 600 "$KUBECONFIG"
|
|
cluster_name="$(kubectl config view --kubeconfig "$KUBECONFIG" --minify -o jsonpath='{.clusters[0].name}')"
|
|
kubectl config set-cluster "$cluster_name" \
|
|
--kubeconfig "$KUBECONFIG" \
|
|
--server "$KUBE_API_SERVER" \
|
|
--insecure-skip-tls-verify=true >/dev/null
|
|
|
|
env_file="${GITEA_ENV_FILE:-${GITHUB_ENV:-}}"
|
|
if [ -n "$env_file" ]; then
|
|
printf '%s\n' "KUBECONFIG=$KUBECONFIG" >> "$env_file"
|
|
fi
|
|
|
|
- name: Migrate, then roll out the immutable revision
|
|
env:
|
|
REVISION: ${{ gitea.sha }}
|
|
KUBE_CONFIG_B64: ${{ secrets.KUBE_CONFIG_B64 }}
|
|
shell: bash
|
|
run: |
|
|
set -Eeuo pipefail
|
|
|
|
if [ -z "$KUBE_CONFIG_B64" ]; then
|
|
echo "Missing KUBE_CONFIG_B64 secret for the CI deploy identity" >&2
|
|
exit 1
|
|
fi
|
|
|
|
kube_dir="$RUNNER_TEMP/buzz-sheet-kube"
|
|
mkdir -p "$kube_dir"
|
|
chmod 700 "$kube_dir"
|
|
export KUBECONFIG="$kube_dir/config"
|
|
if ! printf '%s' "$KUBE_CONFIG_B64" | base64 --decode >"$KUBECONFIG"; then
|
|
echo "KUBE_CONFIG_B64 is not valid base64" >&2
|
|
exit 1
|
|
fi
|
|
chmod 600 "$KUBECONFIG"
|
|
if [ ! -s "$KUBECONFIG" ]; then
|
|
echo "KUBE_CONFIG_B64 decoded to an empty kubeconfig" >&2
|
|
exit 1
|
|
fi
|
|
cluster_name="$(kubectl config view --kubeconfig "$KUBECONFIG" --minify -o jsonpath='{.clusters[0].name}')"
|
|
if [ -z "$cluster_name" ]; then
|
|
echo "Decoded kubeconfig has no active cluster" >&2
|
|
exit 1
|
|
fi
|
|
kubectl config set-cluster "$cluster_name" \
|
|
--kubeconfig "$KUBECONFIG" \
|
|
--server "$KUBE_API_SERVER" \
|
|
--insecure-skip-tls-verify=true >/dev/null
|
|
|
|
revision_short="${REVISION:0:12}"
|
|
migration_dir="$RUNNER_TEMP/buzz-sheet-migration-$revision_short"
|
|
migration_manifest="$RUNNER_TEMP/buzz-sheet-migration-$revision_short.yaml"
|
|
cp -R k8s/migration "$migration_dir"
|
|
sed -i "s#newName: .*#newName: $REGISTRY_IMAGE#" "$migration_dir/kustomization.yaml"
|
|
sed -i "s/newTag: .*/newTag: migrate-$REVISION/" "$migration_dir/kustomization.yaml"
|
|
kubectl kustomize "$migration_dir" >"$migration_manifest"
|
|
|
|
kubectl --kubeconfig "$KUBECONFIG" --namespace "$DEPLOY_NAMESPACE" delete job buzz-sheet-migrate --ignore-not-found
|
|
migration_resource="$(kubectl --kubeconfig "$KUBECONFIG" --namespace "$DEPLOY_NAMESPACE" create --validate=false -f "$migration_manifest" -o name)"
|
|
if ! kubectl --kubeconfig "$KUBECONFIG" --namespace "$DEPLOY_NAMESPACE" wait \
|
|
--for=condition=complete \
|
|
--timeout=10m \
|
|
"$migration_resource"; then
|
|
kubectl --kubeconfig "$KUBECONFIG" --namespace "$DEPLOY_NAMESPACE" logs "$migration_resource" --all-containers=true || true
|
|
exit 1
|
|
fi
|
|
|
|
kubectl --kubeconfig "$KUBECONFIG" --namespace "$DEPLOY_NAMESPACE" patch configmap buzz-sheet-config \
|
|
--type=merge \
|
|
--patch "{\"data\":{\"NEXT_DEPLOYMENT_ID\":\"$REVISION\",\"BASE_URL\":\"$BASE_URL\"}}"
|
|
# Replace one web replica at a time without reserving CPU for a surge pod.
|
|
kubectl --kubeconfig "$KUBECONFIG" --namespace "$DEPLOY_NAMESPACE" patch deployment buzz-sheet \
|
|
--type=merge \
|
|
--patch '{"spec":{"strategy":{"type":"RollingUpdate","rollingUpdate":{"maxSurge":0,"maxUnavailable":1}}}}'
|
|
kubectl --kubeconfig "$KUBECONFIG" --namespace "$DEPLOY_NAMESPACE" set image \
|
|
deployment/buzz-sheet \
|
|
app="$REGISTRY_IMAGE:$REVISION"
|
|
kubectl --kubeconfig "$KUBECONFIG" --namespace "$DEPLOY_NAMESPACE" set image \
|
|
deployment/buzz-sheet-worker \
|
|
worker="$REGISTRY_IMAGE:$REVISION"
|
|
kubectl --kubeconfig "$KUBECONFIG" --namespace "$DEPLOY_NAMESPACE" set image \
|
|
deployment/buzz-sheet-discord-worker \
|
|
discord-worker="$REGISTRY_IMAGE:$REVISION"
|
|
kubectl --kubeconfig "$KUBECONFIG" --namespace "$DEPLOY_NAMESPACE" rollout status \
|
|
deployment/buzz-sheet \
|
|
--timeout=10m
|
|
kubectl --kubeconfig "$KUBECONFIG" --namespace "$DEPLOY_NAMESPACE" rollout status \
|
|
deployment/buzz-sheet-worker \
|
|
--timeout=10m
|
|
kubectl --kubeconfig "$KUBECONFIG" --namespace "$DEPLOY_NAMESPACE" rollout status \
|
|
deployment/buzz-sheet-discord-worker \
|
|
--timeout=10m
|