Add isolated UAT and production environments
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
#!/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}"
|
||||
|
||||
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}"
|
||||
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}" 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
|
||||
|
||||
dotnet test api/QuestionsApi.Tests/QuestionsApi.Tests.csproj -c Release
|
||||
docker build --build-arg BUILD_REVISION="${BUILD_TAG}" -t "${API_IMAGE}" api/QuestionsApi
|
||||
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}" \
|
||||
--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 -
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
echo "Deployed ${DEPLOYMENT_ENVIRONMENT} revision ${BUILD_TAG}."
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
name: Deploy STEP3 PRD
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- production
|
||||
workflow_dispatch: {}
|
||||
|
||||
concurrency:
|
||||
group: step3-prd
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: self-hosted
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Configure kubeconfig
|
||||
env:
|
||||
KUBECONFIG_CONTENT: ${{ secrets.KUBECONFIG }}
|
||||
run: |
|
||||
if [ -n "$KUBECONFIG_CONTENT" ]; then
|
||||
mkdir -p /tmp/kube
|
||||
printf '%s\n' "$KUBECONFIG_CONTENT" > /tmp/kube/config
|
||||
echo "KUBECONFIG=/tmp/kube/config" >> "$GITHUB_ENV"
|
||||
fi
|
||||
|
||||
- name: Deploy PRD
|
||||
env:
|
||||
DEPLOYMENT_ENVIRONMENT: prd
|
||||
KUBE_NAMESPACE: step3-prd
|
||||
PUBLIC_API_ORIGIN: https://s3q.ranaze.com
|
||||
MONGODB_CONNECTION_STRING: ${{ secrets.PRD_MONGODB_CONNECTION_STRING }}
|
||||
AUTH_JWT_KEY: ${{ secrets.PRD_AUTH_JWT_KEY }}
|
||||
INTERNAL_PURGE_SECRET: ${{ secrets.PRD_INTERNAL_PURGE_SECRET }}
|
||||
STRIPE_SECRET_KEY: ${{ secrets.PRD_STRIPE_SECRET_KEY }}
|
||||
STRIPE_WEBHOOK_SECRET: ${{ secrets.PRD_STRIPE_WEBHOOK_SECRET }}
|
||||
run: bash .gitea/scripts/deploy-environment.sh
|
||||
|
||||
@@ -1,111 +0,0 @@
|
||||
name: Deploy STEP3 Questions API
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch: {}
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: self-hosted
|
||||
|
||||
env:
|
||||
IMAGE_NAME: step3-questions:latest
|
||||
IMAGE_TAR: /tmp/step3-questions.tar
|
||||
NODES: "192.168.86.72 192.168.86.73 192.168.86.74"
|
||||
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Build Docker image
|
||||
run: |
|
||||
cd api/QuestionsApi
|
||||
docker build -t "${IMAGE_NAME}" .
|
||||
|
||||
- name: Save Docker image to TAR
|
||||
run: |
|
||||
docker save "${IMAGE_NAME}" -o "${IMAGE_TAR}"
|
||||
|
||||
- name: Copy TAR to nodes
|
||||
run: |
|
||||
for node in ${NODES}; do
|
||||
echo "Copying image tar to $node ..."
|
||||
scp -o StrictHostKeyChecking=no "${IMAGE_TAR}" hz@"$node":/tmp/step3-questions.tar
|
||||
done
|
||||
|
||||
- name: Import image on nodes
|
||||
run: |
|
||||
for node in ${NODES}; do
|
||||
echo "Importing image on $node ..."
|
||||
ssh -o StrictHostKeyChecking=no hz@"$node" "sudo ctr -n k8s.io images import /tmp/step3-questions.tar"
|
||||
done
|
||||
|
||||
- name: Clean TAR from nodes
|
||||
run: |
|
||||
for node in ${NODES}; do
|
||||
echo "Removing image tar on $node ..."
|
||||
ssh -o StrictHostKeyChecking=no hz@"$node" "rm -f /tmp/step3-questions.tar"
|
||||
done
|
||||
|
||||
- name: Clean TAR on runner
|
||||
run: |
|
||||
rm -f "${IMAGE_TAR}"
|
||||
|
||||
- name: Configure kubeconfig
|
||||
env:
|
||||
KUBECONFIG_CONTENT: ${{ secrets.KUBECONFIG }}
|
||||
run: |
|
||||
if [ -n "$KUBECONFIG_CONTENT" ]; then
|
||||
mkdir -p /tmp/kube
|
||||
printf '%s\n' "$KUBECONFIG_CONTENT" > /tmp/kube/config
|
||||
echo "KUBECONFIG=/tmp/kube/config" >> "$GITHUB_ENV"
|
||||
echo "Using kubeconfig from KUBECONFIG secret."
|
||||
else
|
||||
echo "KUBECONFIG secret is not set for this repo; using runner default kubeconfig."
|
||||
fi
|
||||
|
||||
- name: Create namespace if missing
|
||||
run: |
|
||||
kubectl create namespace step3-questions --dry-run=client -o yaml | kubectl apply -f -
|
||||
|
||||
- name: Configure internal API secret
|
||||
env:
|
||||
INTERNAL_PURGE_SECRET: ${{ secrets.INTERNAL_PURGE_SECRET }}
|
||||
run: |
|
||||
test -n "$INTERNAL_PURGE_SECRET"
|
||||
kubectl create secret generic medmind-internal-api \
|
||||
--from-literal=purge-secret="$INTERNAL_PURGE_SECRET" \
|
||||
--dry-run=client -o yaml | kubectl apply -n step3-questions -f -
|
||||
|
||||
- name: Configure Stripe secrets
|
||||
env:
|
||||
STRIPE_SECRET_KEY: ${{ secrets.STRIPE_SECRET_KEY }}
|
||||
STRIPE_WEBHOOK_SECRET: ${{ secrets.STRIPE_WEBHOOK_SECRET }}
|
||||
run: |
|
||||
test -n "$STRIPE_SECRET_KEY"
|
||||
test -n "$STRIPE_WEBHOOK_SECRET"
|
||||
kubectl create secret generic medmind-stripe \
|
||||
--from-literal=secret-key="$STRIPE_SECRET_KEY" \
|
||||
--from-literal=webhook-secret="$STRIPE_WEBHOOK_SECRET" \
|
||||
--dry-run=client -o yaml | kubectl apply -n step3-questions -f -
|
||||
|
||||
- name: Configure authentication secrets
|
||||
env:
|
||||
AUTH_JWT_KEY: ${{ secrets.AUTH_JWT_KEY }}
|
||||
run: |
|
||||
test -n "$AUTH_JWT_KEY"
|
||||
kubectl create secret generic ranaze-auth \
|
||||
--from-literal=jwt-key="$AUTH_JWT_KEY" \
|
||||
--dry-run=client -o yaml | kubectl apply -n step3-questions -f -
|
||||
|
||||
- name: Apply Questions deployment & service
|
||||
run: |
|
||||
kubectl apply -f api/QuestionsApi/k8s/deployment.yaml -n step3-questions
|
||||
kubectl apply -f api/QuestionsApi/k8s/service.yaml -n step3-questions
|
||||
|
||||
- name: Restart Questions deployment
|
||||
run: |
|
||||
kubectl rollout restart deployment/step3-questions -n step3-questions
|
||||
kubectl rollout status deployment/step3-questions -n step3-questions
|
||||
@@ -0,0 +1,41 @@
|
||||
name: Deploy STEP3 UAT
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch: {}
|
||||
|
||||
concurrency:
|
||||
group: step3-uat
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: self-hosted
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Configure kubeconfig
|
||||
env:
|
||||
KUBECONFIG_CONTENT: ${{ secrets.KUBECONFIG }}
|
||||
run: |
|
||||
if [ -n "$KUBECONFIG_CONTENT" ]; then
|
||||
mkdir -p /tmp/kube
|
||||
printf '%s\n' "$KUBECONFIG_CONTENT" > /tmp/kube/config
|
||||
echo "KUBECONFIG=/tmp/kube/config" >> "$GITHUB_ENV"
|
||||
fi
|
||||
|
||||
- name: Deploy UAT
|
||||
env:
|
||||
DEPLOYMENT_ENVIRONMENT: uat
|
||||
KUBE_NAMESPACE: step3-uat
|
||||
PUBLIC_API_ORIGIN: https://us3q.ranaze.com
|
||||
MONGODB_CONNECTION_STRING: ${{ secrets.UAT_MONGODB_CONNECTION_STRING }}
|
||||
AUTH_JWT_KEY: ${{ secrets.UAT_AUTH_JWT_KEY }}
|
||||
INTERNAL_PURGE_SECRET: ${{ secrets.UAT_INTERNAL_PURGE_SECRET }}
|
||||
STRIPE_SECRET_KEY: ${{ secrets.UAT_STRIPE_SECRET_KEY }}
|
||||
STRIPE_WEBHOOK_SECRET: ${{ secrets.UAT_STRIPE_WEBHOOK_SECRET }}
|
||||
run: bash .gitea/scripts/deploy-environment.sh
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
name: Deploy STEP3 Web Game
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch: {}
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: self-hosted
|
||||
|
||||
env:
|
||||
IMAGE_TAR: /tmp/step3-web.tar
|
||||
NODES: "192.168.86.72 192.168.86.73 192.168.86.74"
|
||||
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set build identity
|
||||
run: |
|
||||
echo "BUILD_TAG=$(git rev-parse HEAD)" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Reclaim failed build layers
|
||||
run: |
|
||||
rm -f "${IMAGE_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
|
||||
|
||||
- name: Build Web game image
|
||||
run: |
|
||||
docker build --build-arg BUILD_REVISION="${BUILD_TAG}" -f web/Dockerfile -t "step3-web:${BUILD_TAG}" .
|
||||
|
||||
- name: Save image
|
||||
run: |
|
||||
docker save "step3-web:${BUILD_TAG}" -o "${IMAGE_TAR}"
|
||||
|
||||
- name: Import image on Kubernetes nodes
|
||||
run: |
|
||||
for node in ${NODES}; do
|
||||
scp -o StrictHostKeyChecking=no "${IMAGE_TAR}" hz@"$node":/tmp/step3-web.tar
|
||||
ssh -o StrictHostKeyChecking=no hz@"$node" "sudo ctr -n k8s.io images import /tmp/step3-web.tar && rm -f /tmp/step3-web.tar"
|
||||
done
|
||||
|
||||
- name: Clean runner build artifacts
|
||||
if: always()
|
||||
run: |
|
||||
rm -f "${IMAGE_TAR}"
|
||||
if [ -n "${BUILD_TAG}" ]; then
|
||||
docker image rm "step3-web:${BUILD_TAG}" 2>/dev/null || true
|
||||
fi
|
||||
docker image prune -f
|
||||
|
||||
- name: Configure kubeconfig
|
||||
env:
|
||||
KUBECONFIG_CONTENT: ${{ secrets.KUBECONFIG }}
|
||||
run: |
|
||||
if [ -n "$KUBECONFIG_CONTENT" ]; then
|
||||
mkdir -p /tmp/kube
|
||||
printf '%s\n' "$KUBECONFIG_CONTENT" > /tmp/kube/config
|
||||
echo "KUBECONFIG=/tmp/kube/config" >> "$GITHUB_ENV"
|
||||
fi
|
||||
|
||||
- name: Deploy Web game
|
||||
run: |
|
||||
for attempt in $(seq 1 30); do
|
||||
if kubectl get --raw "/api/v1/namespaces/step3-questions/services/step3-questions:http/proxy/api/Auth/legal" >/dev/null; then
|
||||
echo "Merged authentication API is ready."
|
||||
break
|
||||
fi
|
||||
if [ "$attempt" -eq 30 ]; then
|
||||
echo "Merged authentication API did not become ready; preserving the current web deployment."
|
||||
exit 1
|
||||
fi
|
||||
sleep 10
|
||||
done
|
||||
kubectl create namespace step3-web --dry-run=client -o yaml | kubectl apply -f -
|
||||
kubectl apply -f web/k8s/service.yaml -n step3-web
|
||||
sed "s|step3-web:latest|step3-web:${BUILD_TAG}|" web/k8s/deployment.yaml | kubectl apply -n step3-web -f -
|
||||
kubectl rollout status deployment/step3-web -n step3-web --timeout=180s
|
||||
@@ -0,0 +1,120 @@
|
||||
name: Promote UAT content to PRD
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
mode:
|
||||
description: "dry-run or apply"
|
||||
required: true
|
||||
default: dry-run
|
||||
confirmation:
|
||||
description: "Enter PROMOTE_TO_PRD when mode is apply"
|
||||
required: false
|
||||
default: ""
|
||||
|
||||
concurrency:
|
||||
group: step3-content-promotion
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
promote:
|
||||
runs-on: self-hosted
|
||||
steps:
|
||||
- name: Checkout production configuration
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: production
|
||||
|
||||
- name: Configure kubeconfig
|
||||
env:
|
||||
KUBECONFIG_CONTENT: ${{ secrets.KUBECONFIG }}
|
||||
run: |
|
||||
if [ -n "$KUBECONFIG_CONTENT" ]; then
|
||||
mkdir -p /tmp/kube
|
||||
printf '%s\n' "$KUBECONFIG_CONTENT" > /tmp/kube/config
|
||||
echo "KUBECONFIG=/tmp/kube/config" >> "$GITHUB_ENV"
|
||||
fi
|
||||
|
||||
- name: Validate promotion request
|
||||
env:
|
||||
MODE: ${{ inputs.mode }}
|
||||
CONFIRMATION: ${{ inputs.confirmation }}
|
||||
run: |
|
||||
test "$MODE" = "dry-run" || test "$MODE" = "apply"
|
||||
if [ "$MODE" = "apply" ]; then
|
||||
test "$CONFIRMATION" = "PROMOTE_TO_PRD"
|
||||
fi
|
||||
|
||||
- name: Configure promotion credentials
|
||||
env:
|
||||
SOURCE_CONNECTION: ${{ secrets.UAT_MONGODB_CONNECTION_STRING }}
|
||||
TARGET_CONNECTION: ${{ secrets.PRD_MONGODB_CONNECTION_STRING }}
|
||||
run: |
|
||||
test -n "$SOURCE_CONNECTION"
|
||||
test -n "$TARGET_CONNECTION"
|
||||
kubectl create secret generic step3-content-promotion \
|
||||
--from-literal=source-connection="$SOURCE_CONNECTION" \
|
||||
--from-literal=target-connection="$TARGET_CONNECTION" \
|
||||
--dry-run=client -o yaml | kubectl apply -n step3-prd -f -
|
||||
|
||||
- name: Run promotion
|
||||
env:
|
||||
MODE: ${{ inputs.mode }}
|
||||
CONFIRMATION: ${{ inputs.confirmation }}
|
||||
run: |
|
||||
IMAGE="$(kubectl get deployment step3-api -n step3-prd -o jsonpath='{.spec.template.spec.containers[0].image}')"
|
||||
REVISION="${IMAGE##*:}"
|
||||
POD="content-promotion-${GITHUB_RUN_NUMBER}"
|
||||
APPLY_ARGS=""
|
||||
if [ "$MODE" = "apply" ]; then
|
||||
APPLY_ARGS="--apply --confirm $CONFIRMATION"
|
||||
fi
|
||||
cat <<EOF | kubectl apply -f -
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: ${POD}
|
||||
namespace: step3-prd
|
||||
labels:
|
||||
app: step3-content-promotion
|
||||
spec:
|
||||
restartPolicy: Never
|
||||
containers:
|
||||
- name: promotion
|
||||
image: ${IMAGE}
|
||||
imagePullPolicy: IfNotPresent
|
||||
command: ["/bin/sh", "-c"]
|
||||
args:
|
||||
- >-
|
||||
dotnet QuestionsApi.dll promote-content
|
||||
--source-database ranaze_step3_uat
|
||||
--target-database ranaze_step3_prd
|
||||
--operator "${GITHUB_ACTOR:-gitea-operator}"
|
||||
--build-revision "${REVISION}"
|
||||
${APPLY_ARGS}
|
||||
env:
|
||||
- name: SOURCE_MONGODB_CONNECTION_STRING
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: step3-content-promotion
|
||||
key: source-connection
|
||||
- name: TARGET_MONGODB_CONNECTION_STRING
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: step3-content-promotion
|
||||
key: target-connection
|
||||
EOF
|
||||
for attempt in $(seq 1 120); do
|
||||
PHASE="$(kubectl get pod "$POD" -n step3-prd -o jsonpath='{.status.phase}')"
|
||||
if [ "$PHASE" = "Succeeded" ] || [ "$PHASE" = "Failed" ]; then break; fi
|
||||
sleep 2
|
||||
done
|
||||
kubectl logs "$POD" -n step3-prd
|
||||
test "$(kubectl get pod "$POD" -n step3-prd -o jsonpath='{.status.phase}')" = "Succeeded"
|
||||
|
||||
- name: Remove promotion credentials and pod
|
||||
if: always()
|
||||
run: |
|
||||
kubectl delete pod "content-promotion-${GITHUB_RUN_NUMBER}" -n step3-prd --ignore-not-found
|
||||
kubectl delete secret step3-content-promotion -n step3-prd --ignore-not-found
|
||||
|
||||
Reference in New Issue
Block a user