Add VITE_APP_VERSION build arg to UI Dockerfile, pass short SHA from CI docker build step. vite.config.ts truncates to 7 chars so both CI build and Docker build produce consistent short hashes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
376 lines
16 KiB
YAML
376 lines
16 KiB
YAML
name: CI
|
|
|
|
on:
|
|
push:
|
|
branches: [main, 'feature/**', 'fix/**', 'feat/**']
|
|
tags-ignore:
|
|
- 'v*'
|
|
pull_request:
|
|
branches: [main]
|
|
delete:
|
|
|
|
jobs:
|
|
build:
|
|
runs-on: ubuntu-latest
|
|
if: github.event_name != 'delete'
|
|
container:
|
|
image: gitea.siegeln.net/cameleer/cameleer-build:1
|
|
credentials:
|
|
username: cameleer
|
|
password: ${{ secrets.REGISTRY_TOKEN }}
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- name: Configure Gitea Maven Registry
|
|
run: |
|
|
mkdir -p ~/.m2
|
|
cat > ~/.m2/settings.xml << 'SETTINGS'
|
|
<settings>
|
|
<servers>
|
|
<server>
|
|
<id>gitea</id>
|
|
<username>cameleer</username>
|
|
<password>${env.REGISTRY_TOKEN}</password>
|
|
</server>
|
|
</servers>
|
|
</settings>
|
|
SETTINGS
|
|
env:
|
|
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
|
|
|
- name: Cache Maven dependencies
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: ~/.m2/repository
|
|
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
|
|
restore-keys: ${{ runner.os }}-maven-
|
|
|
|
- name: Build UI
|
|
working-directory: ui
|
|
run: |
|
|
echo '//gitea.siegeln.net/api/packages/cameleer/npm/:_authToken=${REGISTRY_TOKEN}' >> .npmrc
|
|
npm ci
|
|
npm run build
|
|
env:
|
|
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
|
VITE_APP_VERSION: ${{ github.sha }}
|
|
|
|
- name: Build and Test
|
|
run: mvn clean verify -DskipITs -U --batch-mode
|
|
|
|
docker:
|
|
needs: build
|
|
runs-on: ubuntu-latest
|
|
if: github.event_name == 'push'
|
|
container:
|
|
image: gitea.siegeln.net/cameleer/cameleer-docker-builder:1
|
|
credentials:
|
|
username: cameleer
|
|
password: ${{ secrets.REGISTRY_TOKEN }}
|
|
steps:
|
|
- name: Checkout
|
|
run: |
|
|
git clone --depth=1 --branch=${GITHUB_REF_NAME} https://cameleer:${REGISTRY_TOKEN}@gitea.siegeln.net/${GITHUB_REPOSITORY}.git .
|
|
env:
|
|
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
|
- name: Login to registry
|
|
run: echo "$REGISTRY_TOKEN" | docker login gitea.siegeln.net -u cameleer --password-stdin
|
|
env:
|
|
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
|
- name: Compute branch slug
|
|
run: |
|
|
. .gitea/sanitize-branch.sh
|
|
if [ "$GITHUB_REF_NAME" = "main" ]; then
|
|
echo "BRANCH_SLUG=main" >> "$GITHUB_ENV"
|
|
echo "IMAGE_TAGS=latest" >> "$GITHUB_ENV"
|
|
else
|
|
SLUG=$(sanitize_branch "$GITHUB_REF_NAME")
|
|
echo "BRANCH_SLUG=$SLUG" >> "$GITHUB_ENV"
|
|
echo "IMAGE_TAGS=branch-$SLUG" >> "$GITHUB_ENV"
|
|
fi
|
|
- name: Set up QEMU for cross-platform builds
|
|
run: docker run --rm --privileged gitea.siegeln.net/cameleer/binfmt:1 --install all
|
|
- name: Build and push server
|
|
run: |
|
|
docker buildx create --use --name cibuilder
|
|
TAGS="-t gitea.siegeln.net/cameleer/cameleer3-server:${{ github.sha }}"
|
|
for TAG in $IMAGE_TAGS; do
|
|
TAGS="$TAGS -t gitea.siegeln.net/cameleer/cameleer3-server:$TAG"
|
|
done
|
|
docker buildx build --platform linux/amd64 \
|
|
--build-arg REGISTRY_TOKEN="$REGISTRY_TOKEN" \
|
|
$TAGS \
|
|
--cache-from type=registry,ref=gitea.siegeln.net/cameleer/cameleer3-server:buildcache \
|
|
--cache-to type=registry,ref=gitea.siegeln.net/cameleer/cameleer3-server:buildcache,mode=max \
|
|
--provenance=false \
|
|
--push .
|
|
env:
|
|
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
|
- name: Build and push UI
|
|
run: |
|
|
TAGS="-t gitea.siegeln.net/cameleer/cameleer3-server-ui:${{ github.sha }}"
|
|
for TAG in $IMAGE_TAGS; do
|
|
TAGS="$TAGS -t gitea.siegeln.net/cameleer/cameleer3-server-ui:$TAG"
|
|
done
|
|
SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7)
|
|
docker buildx build --platform linux/amd64 \
|
|
-f ui/Dockerfile \
|
|
--build-arg REGISTRY_TOKEN="$REGISTRY_TOKEN" \
|
|
--build-arg VITE_APP_VERSION="$SHORT_SHA" \
|
|
$TAGS \
|
|
--cache-from type=registry,ref=gitea.siegeln.net/cameleer/cameleer3-server-ui:buildcache \
|
|
--cache-to type=registry,ref=gitea.siegeln.net/cameleer/cameleer3-server-ui:buildcache,mode=max \
|
|
--provenance=false \
|
|
--push ui/
|
|
env:
|
|
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
|
- name: Cleanup local Docker
|
|
run: docker system prune -af --filter "until=24h"
|
|
if: always()
|
|
- name: Cleanup old container images
|
|
run: |
|
|
API="https://gitea.siegeln.net/api/v1"
|
|
AUTH="Authorization: token ${REGISTRY_TOKEN}"
|
|
CURRENT_SHA="${{ github.sha }}"
|
|
# Build list of tags to keep
|
|
KEEP_TAGS="latest buildcache $CURRENT_SHA"
|
|
if [ "$BRANCH_SLUG" != "main" ]; then
|
|
KEEP_TAGS="$KEEP_TAGS branch-$BRANCH_SLUG"
|
|
fi
|
|
for PKG in cameleer3-server cameleer3-server-ui; do
|
|
curl -sf -H "$AUTH" "$API/packages/cameleer/container/$PKG" | \
|
|
jq -r '.[] | "\(.id) \(.version)"' | \
|
|
while read id version; do
|
|
SHOULD_KEEP=false
|
|
for KEEP in $KEEP_TAGS; do
|
|
if [ "$version" = "$KEEP" ]; then
|
|
SHOULD_KEEP=true
|
|
break
|
|
fi
|
|
done
|
|
if [ "$SHOULD_KEEP" = "false" ]; then
|
|
# Only clean up images for this branch
|
|
if [ "$BRANCH_SLUG" = "main" ] || echo "$version" | grep -q "branch-$BRANCH_SLUG"; then
|
|
echo "Deleting old image tag: $PKG:$version"
|
|
curl -sf -X DELETE -H "$AUTH" "$API/packages/cameleer/container/$PKG/$version"
|
|
fi
|
|
fi
|
|
done
|
|
done
|
|
env:
|
|
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
|
if: always()
|
|
|
|
deploy:
|
|
needs: docker
|
|
runs-on: ubuntu-latest
|
|
if: github.ref == 'refs/heads/main'
|
|
container:
|
|
image: alpine/k8s:1.32.3
|
|
steps:
|
|
- name: Checkout
|
|
run: |
|
|
git clone --depth=1 --branch=${GITHUB_REF_NAME} https://cameleer:${REGISTRY_TOKEN}@gitea.siegeln.net/${GITHUB_REPOSITORY}.git .
|
|
env:
|
|
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
|
- name: Configure kubectl
|
|
run: |
|
|
mkdir -p ~/.kube
|
|
echo "$KUBECONFIG_B64" | base64 -d > ~/.kube/config
|
|
env:
|
|
KUBECONFIG_B64: ${{ secrets.KUBECONFIG_BASE64 }}
|
|
- name: Deploy
|
|
run: |
|
|
kubectl create namespace cameleer --dry-run=client -o yaml | kubectl apply -f -
|
|
|
|
kubectl create secret docker-registry gitea-registry \
|
|
--namespace=cameleer \
|
|
--docker-server=gitea.siegeln.net \
|
|
--docker-username=cameleer \
|
|
--docker-password="$REGISTRY_TOKEN" \
|
|
--dry-run=client -o yaml | kubectl apply -f -
|
|
|
|
kubectl create secret generic cameleer-auth \
|
|
--namespace=cameleer \
|
|
--from-literal=CAMELEER_AUTH_TOKEN="$CAMELEER_AUTH_TOKEN" \
|
|
--from-literal=CAMELEER_UI_USER="${CAMELEER_UI_USER:-admin}" \
|
|
--from-literal=CAMELEER_UI_PASSWORD="${CAMELEER_UI_PASSWORD:-admin}" \
|
|
--from-literal=CAMELEER_JWT_SECRET="${CAMELEER_JWT_SECRET}" \
|
|
--dry-run=client -o yaml | kubectl apply -f -
|
|
|
|
kubectl create secret generic postgres-credentials \
|
|
--namespace=cameleer \
|
|
--from-literal=POSTGRES_USER="$POSTGRES_USER" \
|
|
--from-literal=POSTGRES_PASSWORD="$POSTGRES_PASSWORD" \
|
|
--from-literal=POSTGRES_DB="${POSTGRES_DB:-cameleer}" \
|
|
--dry-run=client -o yaml | kubectl apply -f -
|
|
|
|
kubectl create secret generic logto-credentials \
|
|
--namespace=cameleer \
|
|
--from-literal=PG_USER="${LOGTO_PG_USER:-logto}" \
|
|
--from-literal=PG_PASSWORD="${LOGTO_PG_PASSWORD}" \
|
|
--from-literal=ENDPOINT="${LOGTO_ENDPOINT}" \
|
|
--from-literal=ADMIN_ENDPOINT="${LOGTO_ADMIN_ENDPOINT}" \
|
|
--dry-run=client -o yaml | kubectl apply -f -
|
|
|
|
kubectl create secret generic clickhouse-credentials \
|
|
--namespace=cameleer \
|
|
--from-literal=CLICKHOUSE_USER="${CLICKHOUSE_USER:-default}" \
|
|
--from-literal=CLICKHOUSE_PASSWORD="$CLICKHOUSE_PASSWORD" \
|
|
--dry-run=client -o yaml | kubectl apply -f -
|
|
|
|
kubectl apply -f deploy/postgres.yaml
|
|
kubectl -n cameleer rollout status statefulset/postgres --timeout=120s
|
|
|
|
kubectl apply -f deploy/clickhouse.yaml
|
|
kubectl -n cameleer rollout status statefulset/clickhouse --timeout=180s
|
|
|
|
kubectl apply -f deploy/logto.yaml
|
|
kubectl -n cameleer rollout status deployment/logto --timeout=180s
|
|
|
|
kubectl apply -k deploy/overlays/main
|
|
kubectl -n cameleer set image deployment/cameleer3-server \
|
|
server=gitea.siegeln.net/cameleer/cameleer3-server:${{ github.sha }}
|
|
kubectl -n cameleer rollout status deployment/cameleer3-server --timeout=120s
|
|
|
|
kubectl -n cameleer set image deployment/cameleer3-ui \
|
|
ui=gitea.siegeln.net/cameleer/cameleer3-server-ui:${{ github.sha }}
|
|
kubectl -n cameleer rollout status deployment/cameleer3-ui --timeout=120s
|
|
env:
|
|
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
|
CAMELEER_AUTH_TOKEN: ${{ secrets.CAMELEER_AUTH_TOKEN }}
|
|
CAMELEER_JWT_SECRET: ${{ secrets.CAMELEER_JWT_SECRET }}
|
|
CAMELEER_UI_USER: ${{ secrets.CAMELEER_UI_USER }}
|
|
CAMELEER_UI_PASSWORD: ${{ secrets.CAMELEER_UI_PASSWORD }}
|
|
POSTGRES_USER: ${{ secrets.POSTGRES_USER }}
|
|
POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }}
|
|
POSTGRES_DB: ${{ secrets.POSTGRES_DB }}
|
|
LOGTO_PG_USER: ${{ secrets.LOGTO_PG_USER }}
|
|
LOGTO_PG_PASSWORD: ${{ secrets.LOGTO_PG_PASSWORD }}
|
|
LOGTO_ENDPOINT: ${{ secrets.LOGTO_ENDPOINT }}
|
|
LOGTO_ADMIN_ENDPOINT: ${{ secrets.LOGTO_ADMIN_ENDPOINT }}
|
|
CLICKHOUSE_USER: ${{ secrets.CLICKHOUSE_USER }}
|
|
CLICKHOUSE_PASSWORD: ${{ secrets.CLICKHOUSE_PASSWORD }}
|
|
|
|
deploy-feature:
|
|
needs: docker
|
|
runs-on: ubuntu-latest
|
|
if: github.ref != 'refs/heads/main' && github.event_name == 'push'
|
|
container:
|
|
image: alpine/k8s:1.32.3
|
|
steps:
|
|
- name: Checkout
|
|
run: |
|
|
git clone --depth=1 --branch=${GITHUB_REF_NAME} https://cameleer:${REGISTRY_TOKEN}@gitea.siegeln.net/${GITHUB_REPOSITORY}.git .
|
|
env:
|
|
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
|
- name: Configure kubectl
|
|
run: |
|
|
mkdir -p ~/.kube
|
|
echo "$KUBECONFIG_B64" | base64 -d > ~/.kube/config
|
|
env:
|
|
KUBECONFIG_B64: ${{ secrets.KUBECONFIG_BASE64 }}
|
|
- name: Compute branch variables
|
|
run: |
|
|
. .gitea/sanitize-branch.sh
|
|
SLUG=$(sanitize_branch "$GITHUB_REF_NAME")
|
|
NS="cam-${SLUG}"
|
|
SCHEMA="cam_$(echo $SLUG | tr '-' '_')"
|
|
echo "BRANCH_SLUG=$SLUG" >> "$GITHUB_ENV"
|
|
echo "BRANCH_NS=$NS" >> "$GITHUB_ENV"
|
|
echo "BRANCH_SCHEMA=$SCHEMA" >> "$GITHUB_ENV"
|
|
- name: Create namespace
|
|
run: kubectl create namespace "$BRANCH_NS" --dry-run=client -o yaml | kubectl apply -f -
|
|
- name: Copy secrets from cameleer namespace
|
|
run: |
|
|
for SECRET in gitea-registry postgres-credentials clickhouse-credentials cameleer-auth; do
|
|
kubectl get secret "$SECRET" -n cameleer -o json \
|
|
| jq 'del(.metadata.namespace, .metadata.resourceVersion, .metadata.uid, .metadata.creationTimestamp, .metadata.managedFields)' \
|
|
| kubectl apply -n "$BRANCH_NS" -f -
|
|
done
|
|
- name: Substitute placeholders and deploy
|
|
run: |
|
|
# Work on a copy preserving the directory structure so ../../base resolves
|
|
mkdir -p /tmp/feature-deploy/deploy/overlays
|
|
cp -r deploy/base /tmp/feature-deploy/deploy/base
|
|
cp -r deploy/overlays/feature /tmp/feature-deploy/deploy/overlays/feature
|
|
# Substitute all BRANCH_* placeholders
|
|
for f in /tmp/feature-deploy/deploy/overlays/feature/*.yaml; do
|
|
sed -i \
|
|
-e "s|BRANCH_NAMESPACE|${BRANCH_NS}|g" \
|
|
-e "s|BRANCH_SCHEMA|${BRANCH_SCHEMA}|g" \
|
|
-e "s|BRANCH_SLUG|${BRANCH_SLUG}|g" \
|
|
-e "s|BRANCH_SHA|${{ github.sha }}|g" \
|
|
"$f"
|
|
done
|
|
kubectl apply -k /tmp/feature-deploy/deploy/overlays/feature
|
|
- name: Wait for init-job
|
|
run: |
|
|
kubectl -n "$BRANCH_NS" wait --for=condition=complete job/init-schema --timeout=60s || \
|
|
echo "Warning: init-schema job did not complete in time"
|
|
- name: Wait for server rollout
|
|
run: kubectl -n "$BRANCH_NS" rollout status deployment/cameleer3-server --timeout=120s
|
|
- name: Wait for UI rollout
|
|
run: kubectl -n "$BRANCH_NS" rollout status deployment/cameleer3-ui --timeout=60s
|
|
- name: Print deployment URLs
|
|
run: |
|
|
echo "===================================="
|
|
echo "Feature branch deployed!"
|
|
echo "API: http://${BRANCH_SLUG}-api.cameleer.siegeln.net"
|
|
echo "UI: http://${BRANCH_SLUG}.cameleer.siegeln.net"
|
|
echo "===================================="
|
|
env:
|
|
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
|
|
|
cleanup-branch:
|
|
runs-on: ubuntu-latest
|
|
if: github.event_name == 'delete' && github.event.ref_type == 'branch'
|
|
container:
|
|
image: alpine/k8s:1.32.3
|
|
steps:
|
|
- name: Configure kubectl
|
|
run: |
|
|
mkdir -p ~/.kube
|
|
echo "$KUBECONFIG_B64" | base64 -d > ~/.kube/config
|
|
env:
|
|
KUBECONFIG_B64: ${{ secrets.KUBECONFIG_BASE64 }}
|
|
- name: Compute branch variables
|
|
run: |
|
|
sanitize_branch() {
|
|
echo "$1" | sed -E 's#^(feature|fix|feat|hotfix)/##' \
|
|
| tr '[:upper:]' '[:lower:]' \
|
|
| sed 's/[^a-z0-9-]/-/g' \
|
|
| sed 's/--*/-/g; s/^-//; s/-$//' \
|
|
| cut -c1-20 \
|
|
| sed 's/-$//'
|
|
}
|
|
SLUG=$(sanitize_branch "${{ github.event.ref }}")
|
|
NS="cam-${SLUG}"
|
|
SCHEMA="cam_$(echo $SLUG | tr '-' '_')"
|
|
echo "BRANCH_SLUG=$SLUG" >> "$GITHUB_ENV"
|
|
echo "BRANCH_NS=$NS" >> "$GITHUB_ENV"
|
|
echo "BRANCH_SCHEMA=$SCHEMA" >> "$GITHUB_ENV"
|
|
- name: Delete namespace
|
|
run: kubectl delete namespace "$BRANCH_NS" --ignore-not-found
|
|
- name: Drop PostgreSQL schema
|
|
run: |
|
|
kubectl run cleanup-schema-${BRANCH_SLUG} \
|
|
--namespace=cameleer \
|
|
--image=postgres:16 \
|
|
--restart=Never \
|
|
--env="PGPASSWORD=$(kubectl get secret postgres-credentials -n cameleer -o jsonpath='{.data.POSTGRES_PASSWORD}' | base64 -d)" \
|
|
--command -- sh -c "psql -h postgres -U $(kubectl get secret postgres-credentials -n cameleer -o jsonpath='{.data.POSTGRES_USER}' | base64 -d) -d cameleer3 -c 'DROP SCHEMA IF EXISTS ${BRANCH_SCHEMA} CASCADE'"
|
|
kubectl wait --for=condition=Ready pod/cleanup-schema-${BRANCH_SLUG} -n cameleer --timeout=30s || true
|
|
kubectl wait --for=jsonpath='{.status.phase}'=Succeeded pod/cleanup-schema-${BRANCH_SLUG} -n cameleer --timeout=60s || true
|
|
kubectl delete pod cleanup-schema-${BRANCH_SLUG} -n cameleer --ignore-not-found
|
|
- name: Cleanup Docker images
|
|
run: |
|
|
API="https://gitea.siegeln.net/api/v1"
|
|
AUTH="Authorization: token ${REGISTRY_TOKEN}"
|
|
for PKG in cameleer3-server cameleer3-server-ui; do
|
|
# Delete branch-specific tag
|
|
curl -sf -X DELETE -H "$AUTH" "$API/packages/cameleer/container/$PKG/branch-${BRANCH_SLUG}" || true
|
|
done
|
|
env:
|
|
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|