OIDC configuration should be managed by the server itself (database-backed), not injected via K8s secrets. Remove all CAMELEER_OIDC_* env vars from deployment manifests and the cameleer-oidc secret from CI. The server defaults to OIDC disabled via application.yml. This also fixes the Kustomize strategic merge conflict where the feature overlay tried to set value on an env var that had valueFrom in the base. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
394 lines
17 KiB
YAML
394 lines
17 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: maven:3.9-eclipse-temurin-17
|
|
steps:
|
|
- name: Install Node.js 22
|
|
run: |
|
|
apt-get update && apt-get install -y ca-certificates curl gnupg
|
|
mkdir -p /etc/apt/keyrings
|
|
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
|
|
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_22.x nodistro main" > /etc/apt/sources.list.d/nodesource.list
|
|
apt-get update && apt-get install -y nodejs
|
|
|
|
- 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: |
|
|
npm ci
|
|
npm run build
|
|
|
|
- name: Build and Test
|
|
run: mvn clean verify -DskipITs --batch-mode
|
|
|
|
docker:
|
|
needs: build
|
|
runs-on: ubuntu-latest
|
|
if: github.event_name == 'push'
|
|
container:
|
|
image: docker:27
|
|
steps:
|
|
- name: Checkout
|
|
run: |
|
|
apk add --no-cache git
|
|
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: |
|
|
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/-$//'
|
|
}
|
|
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 tonistiigi/binfmt --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
|
|
docker buildx build --platform linux/amd64 \
|
|
-f ui/Dockerfile \
|
|
$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: |
|
|
apk add --no-cache curl jq
|
|
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 opensearch-credentials \
|
|
--namespace=cameleer \
|
|
--from-literal=OPENSEARCH_USER="${OPENSEARCH_USER:-admin}" \
|
|
--from-literal=OPENSEARCH_PASSWORD="$OPENSEARCH_PASSWORD" \
|
|
--dry-run=client -o yaml | kubectl apply -f -
|
|
|
|
kubectl create secret generic authentik-credentials \
|
|
--namespace=cameleer \
|
|
--from-literal=PG_USER="${AUTHENTIK_PG_USER:-authentik}" \
|
|
--from-literal=PG_PASSWORD="${AUTHENTIK_PG_PASSWORD}" \
|
|
--from-literal=AUTHENTIK_SECRET_KEY="${AUTHENTIK_SECRET_KEY}" \
|
|
--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/opensearch.yaml
|
|
kubectl -n cameleer rollout status statefulset/opensearch --timeout=180s
|
|
|
|
kubectl apply -f deploy/authentik.yaml
|
|
kubectl -n cameleer rollout status deployment/authentik-server --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 }}
|
|
OPENSEARCH_USER: ${{ secrets.OPENSEARCH_USER }}
|
|
OPENSEARCH_PASSWORD: ${{ secrets.OPENSEARCH_PASSWORD }}
|
|
AUTHENTIK_PG_USER: ${{ secrets.AUTHENTIK_PG_USER }}
|
|
AUTHENTIK_PG_PASSWORD: ${{ secrets.AUTHENTIK_PG_PASSWORD }}
|
|
AUTHENTIK_SECRET_KEY: ${{ secrets.AUTHENTIK_SECRET_KEY }}
|
|
|
|
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: |
|
|
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_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 opensearch-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: Delete OpenSearch indices
|
|
run: |
|
|
kubectl run cleanup-indices-${BRANCH_SLUG} \
|
|
--namespace=cameleer \
|
|
--image=curlimages/curl:latest \
|
|
--restart=Never \
|
|
--command -- curl -sf -X DELETE "http://opensearch:9200/cam-${BRANCH_SLUG}-*"
|
|
kubectl wait --for=jsonpath='{.status.phase}'=Succeeded pod/cleanup-indices-${BRANCH_SLUG} -n cameleer --timeout=60s || true
|
|
kubectl delete pod cleanup-indices-${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 }}
|