Files
step3/.gitea/scripts/deploy-environment.sh
T

120 lines
5.4 KiB
Bash

#!/usr/bin/env bash
set -euo pipefail
: "${DEPLOYMENT_ENVIRONMENT:?DEPLOYMENT_ENVIRONMENT is required}"
: "${KUBE_NAMESPACE:?KUBE_NAMESPACE is required}"
: "${PUBLIC_API_ORIGIN:?PUBLIC_API_ORIGIN is required}"
: "${MONGODB_CONNECTION_STRING:?MONGODB_CONNECTION_STRING is required}"
: "${AUTH_JWT_KEY:?AUTH_JWT_KEY is required}"
: "${INTERNAL_PURGE_SECRET:?INTERNAL_PURGE_SECRET is required}"
: "${STRIPE_SECRET_KEY:?STRIPE_SECRET_KEY is required}"
: "${STRIPE_WEBHOOK_SECRET:?STRIPE_WEBHOOK_SECRET is required}"
: "${STRIPE_ACCESS_WEBHOOK_SECRET:?STRIPE_ACCESS_WEBHOOK_SECRET is required}"
NODES="${NODES:-192.168.86.72 192.168.86.73 192.168.86.74}"
BUILD_TAG="$(git rev-parse HEAD)"
API_IMAGE="step3-questions:${BUILD_TAG}"
WEB_IMAGE="step3-web:${BUILD_TAG}"
TEST_IMAGE="step3-tests:${BUILD_TAG}"
API_TAR="/tmp/step3-questions-${DEPLOYMENT_ENVIRONMENT}.tar"
WEB_TAR="/tmp/step3-web-${DEPLOYMENT_ENVIRONMENT}.tar"
cleanup() {
rm -f "${API_TAR}" "${WEB_TAR}"
docker image rm "${API_IMAGE}" "${WEB_IMAGE}" "${TEST_IMAGE}" 2>/dev/null || true
docker image prune -f >/dev/null || true
}
trap cleanup EXIT
rm -f "${API_TAR}" "${WEB_TAR}"
docker container prune -f
docker image prune -f
docker image prune -af --filter "until=24h"
docker builder prune -af || true
docker system df
df -h
docker build -f api/QuestionsApi.Tests/Dockerfile -t "${TEST_IMAGE}" .
docker build --build-arg BUILD_REVISION="${BUILD_TAG}" -f api/QuestionsApi/Dockerfile -t "${API_IMAGE}" .
docker build \
--build-arg BUILD_REVISION="${BUILD_TAG}" \
--build-arg DEPLOYMENT_ENVIRONMENT="${DEPLOYMENT_ENVIRONMENT}" \
--build-arg PUBLIC_API_ORIGIN="${PUBLIC_API_ORIGIN}" \
-f web/Dockerfile -t "${WEB_IMAGE}" .
docker save "${API_IMAGE}" -o "${API_TAR}"
docker save "${WEB_IMAGE}" -o "${WEB_TAR}"
for node in ${NODES}; do
scp -o StrictHostKeyChecking=no "${API_TAR}" hz@"${node}":/tmp/step3-questions.tar
scp -o StrictHostKeyChecking=no "${WEB_TAR}" hz@"${node}":/tmp/step3-web.tar
ssh -o StrictHostKeyChecking=no hz@"${node}" \
"sudo ctr -n k8s.io images import /tmp/step3-questions.tar && sudo ctr -n k8s.io images import /tmp/step3-web.tar && rm -f /tmp/step3-questions.tar /tmp/step3-web.tar"
done
kubectl apply -f "k8s/overlays/${DEPLOYMENT_ENVIRONMENT}/namespace.yaml"
kubectl create secret generic step3-runtime-secrets \
--from-literal=mongodb-connection-string="${MONGODB_CONNECTION_STRING}" \
--from-literal=jwt-key="${AUTH_JWT_KEY}" \
--from-literal=purge-secret="${INTERNAL_PURGE_SECRET}" \
--from-literal=stripe-secret-key="${STRIPE_SECRET_KEY}" \
--from-literal=stripe-webhook-secret="${STRIPE_WEBHOOK_SECRET}" \
--from-literal=stripe-access-webhook-secret="${STRIPE_ACCESS_WEBHOOK_SECRET}" \
--from-literal=google-client-id="${GOOGLE_CLIENT_ID:-}" \
--from-literal=google-client-secret="${GOOGLE_CLIENT_SECRET:-}" \
--dry-run=client -o yaml | kubectl apply -n "${KUBE_NAMESPACE}" -f -
kubectl kustomize "k8s/overlays/${DEPLOYMENT_ENVIRONMENT}" \
| sed "s|step3-questions:latest|${API_IMAGE}|g; s|step3-web:latest|${WEB_IMAGE}|g" \
| kubectl apply -f -
# Secret and ConfigMap values are consumed as environment variables at pod startup.
# Force a rollout so rerunning an unchanged revision still applies rotated secrets.
kubectl rollout restart deployment/step3-api -n "${KUBE_NAMESPACE}"
kubectl rollout status deployment/step3-api -n "${KUBE_NAMESPACE}" --timeout=240s
kubectl get --raw "/api/v1/namespaces/${KUBE_NAMESPACE}/services/step3-api:http/proxy/readyz" >/dev/null
if [ "${DEPLOYMENT_ENVIRONMENT}" = "uat" ]; then
# A completed old replica can briefly remain visible immediately after a
# rollout. Select the newest pod that is both Running and container-ready
# instead of relying on the first item returned by Kubernetes.
PROMOTION_SUCCEEDED=0
for attempt in 1 2 3; do
API_POD="$(kubectl get pods -n "${KUBE_NAMESPACE}" -l app=step3-api \
--field-selector=status.phase=Running \
--sort-by=.metadata.creationTimestamp \
-o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.containerStatuses[0].ready}{"\n"}{end}' \
| awk '$2 == "true" { name = $1 } END { print name }')"
if [ -n "${API_POD}" ] \
&& kubectl wait -n "${KUBE_NAMESPACE}" --for=condition=Ready "pod/${API_POD}" --timeout=60s \
&& kubectl exec -n "${KUBE_NAMESPACE}" "${API_POD}" -- \
dotnet QuestionsApi.dll promote-dynamic-questions \
--source /app/content/dynamic_questions \
--apply; then
PROMOTION_SUCCEEDED=1
break
fi
echo "Dynamic question promotion attempt ${attempt} did not find a stable ready API pod; retrying." >&2
sleep 2
done
if [ "${PROMOTION_SUCCEEDED}" -ne 1 ]; then
echo "Dynamic question promotion failed after 3 attempts." >&2
exit 1
fi
fi
kubectl rollout status deployment/step3-web -n "${KUBE_NAMESPACE}" --timeout=240s
kubectl get --raw "/api/v1/namespaces/${KUBE_NAMESPACE}/services/step3-web:http/proxy/healthz" >/dev/null
if [ "${DEPLOYMENT_ENVIRONMENT}" = "uat" ]; then
# The images are already imported on every Kubernetes node. Reclaim runner
# space before pulling the Playwright browser image.
docker image rm "${API_IMAGE}" "${WEB_IMAGE}" "${TEST_IMAGE}" 2>/dev/null || true
docker builder prune -af >/dev/null || true
UAT_API_ORIGIN="${PUBLIC_API_ORIGIN}" \
UAT_WEB_ORIGIN="${PUBLIC_WEB_ORIGIN:-https://ustep3.ranaze.com}" \
EXPECTED_REVISION="${BUILD_TAG}" \
bash .gitea/scripts/run-uat-smoke.sh
fi
echo "Deployed ${DEPLOYMENT_ENVIRONMENT} revision ${BUILD_TAG}."