name: CI and deploy on: push: branches: [main] pull_request: branches: [main] workflow_dispatch: jobs: verify: runs-on: docker steps: - name: Check out repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - name: Scan secrets run: | docker run --rm -v "$PWD:/repo" \ ghcr.io/gitleaks/gitleaks:v8.30.1 \ detect --source /repo --redact -v - name: Test API run: | docker run --rm -v "$PWD/apps/api:/src" -w /src golang:1.26 \ bash -ec 'go vet ./... && go test -race ./...' - name: Test load generator run: | docker run --rm -v "$PWD/apps/loadgen:/src" -w /src golang:1.26 \ bash -ec 'go vet ./... && go test -race ./... && test "$(wc -l < main.go)" -lt 200' - name: Lint Go run: | docker run --rm -v "$PWD:/repo" -w /repo/apps/api \ golangci/golangci-lint:v2.12.2 golangci-lint run docker run --rm -v "$PWD:/repo" -w /repo/apps/loadgen \ golangci/golangci-lint:v2.12.2 golangci-lint run - name: Check reachable Go vulnerabilities run: | docker run --rm -v "$PWD/apps/api:/src" -w /src golang:1.26 \ bash -ec 'go run golang.org/x/vuln/cmd/govulncheck@v1.7.0 ./...' docker run --rm -v "$PWD/apps/loadgen:/src" -w /src golang:1.26 \ bash -ec 'go run golang.org/x/vuln/cmd/govulncheck@v1.7.0 ./...' - name: Check frontend and shell syntax run: | docker run --rm -v "$PWD:/repo" -w /repo node:24-alpine \ node --check apps/web/app.js docker run --rm -v "$PWD:/repo" -w /repo bash:5.3 \ bash -ec 'for file in scripts/k3d/lab.sh scripts/provision/bootstrap.sh scripts/provision/lab.sh; do bash -n "$file"; done' - name: Validate Compose and Kubernetes configuration env: CLUSTER_API_UPSTREAM: 192.0.2.1:8080 NEREUS_HOST: nereus.example.test run: | docker compose -f compose.yaml config -q docker compose -f build/compose.mini.yaml config -q for path in \ deploy/base \ deploy/overlays/dev \ deploy/overlays/prod \ deploy/rollouts \ deploy/observability do docker run --rm -v "$PWD:/work" -w /work \ registry.k8s.io/kubectl:v1.33.4 kustomize "$path" >/dev/null done - name: Scan configuration run: | docker run --rm -v "$PWD:/repo" -w /repo aquasec/trivy:0.74.0 \ config --severity HIGH,CRITICAL --exit-code 1 \ --skip-dirs .git --skip-dirs terraform . publish: if: github.event_name == 'push' && github.ref == 'refs/heads/main' needs: verify runs-on: docker env: API_IMAGE: git.fiwlabs.dev/fiwdev/nereus-api:${{ github.sha }} LOADGEN_IMAGE: git.fiwlabs.dev/fiwdev/nereus-loadgen:${{ github.sha }} WEB_IMAGE: git.fiwlabs.dev/fiwdev/nereus-web:${{ github.sha }} DOCKER_CONFIG: ${{ github.workspace }}/.ci/docker steps: - name: Check out repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - name: Log in to registry env: REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }} REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }} run: | install -d -m 0700 "$DOCKER_CONFIG" printf '%s' "$REGISTRY_PASSWORD" | \ docker login git.fiwlabs.dev --username "$REGISTRY_USERNAME" --password-stdin - name: Build and push images run: | docker build -f build/api.Dockerfile \ --build-arg "APP_VERSION=${{ github.sha }}" \ -t "$API_IMAGE" . docker build -f build/loadgen.Dockerfile -t "$LOADGEN_IMAGE" . docker build -f build/web.Dockerfile -t "$WEB_IMAGE" . for image in "$API_IMAGE" "$LOADGEN_IMAGE" "$WEB_IMAGE" do docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \ aquasec/trivy:0.74.0 image --severity HIGH,CRITICAL \ --ignore-unfixed --exit-code 1 "$image" done docker push "$API_IMAGE" docker push "$LOADGEN_IMAGE" docker push "$WEB_IMAGE" - name: Remove registry credentials if: always() run: rm -rf .ci deploy: if: github.event_name == 'push' && github.ref == 'refs/heads/main' needs: publish runs-on: docker env: API_IMAGE: git.fiwlabs.dev/fiwdev/nereus-api:${{ github.sha }} LOADGEN_IMAGE: git.fiwlabs.dev/fiwdev/nereus-loadgen:${{ github.sha }} steps: - name: Check out repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - name: Prepare cluster access env: REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }} REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }} KUBECONFIG_B64: ${{ secrets.KUBECONFIG_B64 }} run: | install -d -m 0700 .ci printf '%s' "$KUBECONFIG_B64" | base64 -d >.ci/kubeconfig chmod 0600 .ci/kubeconfig printf '%s' "$REGISTRY_PASSWORD" | \ docker --config "$PWD/.ci/docker" login git.fiwlabs.dev \ --username "$REGISTRY_USERNAME" --password-stdin - name: Apply and verify rollout run: | kube() { docker run --rm -i --network host \ -v "$PWD:/work" -w /work \ registry.k8s.io/kubectl:v1.33.4 \ --kubeconfig=/work/.ci/kubeconfig "$@" } kube create namespace nereus --dry-run=client -o yaml | kube apply -f - kube create secret generic nereus-registry \ --namespace nereus \ --type kubernetes.io/dockerconfigjson \ --from-file=.dockerconfigjson=/work/.ci/docker/config.json \ --dry-run=client -o yaml | kube apply -f - kube get secret nereus-db --namespace nereus >/dev/null kube apply -k deploy/observability kube apply -k deploy/overlays/prod kube patch rollout.argoproj.io nereus-api --namespace nereus --type json \ --patch "[{\"op\":\"replace\",\"path\":\"/spec/template/spec/containers/0/image\",\"value\":\"$API_IMAGE\"}]" kube set image deployment/nereus-loadgen --namespace nereus \ "loadgen=$LOADGEN_IMAGE" kube set image deployment/nereus-loadgen-preview --namespace nereus \ "loadgen=$LOADGEN_IMAGE" kube rollout status deployment/nereus-loadgen --namespace nereus --timeout=5m kube rollout status deployment/nereus-loadgen-preview --namespace nereus --timeout=5m kube wait rollout.argoproj.io/nereus-api --namespace nereus \ --for=jsonpath='{.status.phase}'=Healthy --timeout=10m kube get rollout.argoproj.io/nereus-api --namespace nereus - name: Remove cluster credentials if: always() run: rm -rf .ci