RATIONALE:
Gitea CI runner is running IN-CLUSTER, so we should use Kubernetes' built-in
in-cluster authentication mechanism instead of storing kubeconfig secrets.
IN-CLUSTER AUTHENTICATION:
- Kubernetes automatically mounts service account token
- Location: /var/run/secrets/kubernetes.io/serviceaccount/token
- Location: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
- kubectl automatically detects and uses these
- No need to pass credentials via secrets
CHANGES:
1. Remove KUBECONFIG_B64 secret requirement
2. Add in-cluster auth detection step
3. Update Job to use actual built image (not golang base)
4. Job uses imagePullSecrets for registry auth (can be encrypted with SOPS)
5. Add regcred image pull secret reference
CI FLOW:
1. Detect in-cluster authentication is available
2. kubectl commands automatically use mounted service account
3. No secrets needed in CI env vars
4. Job applies with RBAC service account
5. Registry credentials via imagePullSecrets (encrypted with SOPS)
SECURITY:
✓ In-cluster auth is more secure (bound to service account)
✓ No kubeconfig stored in secrets
✓ Sensitive data encrypted with SOPS
✓ Principle of least privilege (service account RBAC)
RATIONALE:
The Kubernetes way to run integration tests is via Jobs, not manual pod management.
Jobs are simpler, more idiomatic, and handle all the complexity for us.
CHANGES:
- Remove manual: kubectl run, kubectl wait, kubectl exec
- Use Kubernetes Job (already defined in k8s/integration-test-job.yaml)
- Job handles: pod creation, retry, cleanup, status reporting
- CI only does: apply job, set image, wait, check status
SIMPLIFIED CI FLOW:
1. go vet + go test (unit tests)
2. Build image: api-gateway:<sha>
3. Push: <sha> tag only
4. Apply Job from k8s/integration-test-job.yaml
5. Set job image to new build
6. Wait for job completion
7. Get logs
8. Check job status
9. Promote to latest (if job succeeded)
10. Cleanup job
BENEFITS:
✅ More idiomatic (Kubernetes Job is the standard way)
✅ Simpler CI workflow (fewer manual steps)
✅ Job handles retries, backoff, cleanup automatically
✅ Better status reporting
✅ Declarative (job spec in git, not imperative in CI)
✅ Easier to test locally (just kubectl apply -f k8s/integration-test-job.yaml)
WHAT KUBERNETES JOB HANDLES:
✓ Pod creation and lifecycle
✓ Restart policy and retries
✓ Cleanup on completion
✓ Status tracking
✓ Log aggregation
✓ Resource limits
OPTIMIZATIONS:
- Remove curl-based kubectl installation (inefficient)
- Assume kubectl is available in Gitea runner environment
- Replace port-forward with kubectl exec for test execution
- Tests now run directly inside test pod (not from runner)
- Simpler, faster, more reliable
CI Flow:
1. go vet + go test (unit tests)
2. Build image: api-gateway:<sha>
3. Push: <sha> tag only
4. Deploy test pod with proper labels
5. kubectl exec into pod to run tests
6. Tests run inside pod, can reach services via network policy
7. Promote to latest only if tests pass
8. Cleanup test pod
Add labels to test pod to match network policy selectors:
- app=api-gateway (matches network policy pod selector)
- managed-by=argocd (matches network policy pod selector)
- role=test (identify as test pod)
- test-run=<sha> (track which test run spawned it)
Network policy 'api-gateway' in api namespace already allows egress to:
✅ kube-system (DNS resolution)
✅ poimen (port 8080 - Memory service)
✅ temporal (port 7233 - Workflow service)
✅ storage (ports 80, 9000 - S3/MinIO)
✅ sqs (port 9090 - SQS service)
✅ iam (ports 9000, 9443 - Authentik/IAM)
Test pod inherits same network access as production pods via labels.
No additional network policies needed.
BREAKING CHANGE: CI now requires kubeconfig to run integration tests
Changes:
- Build image with commit SHA tag (NOT latest yet)
- Deploy dedicated test pod from new image
- Run full integration test suite against test pod
- Only promote to latest tag AFTER tests pass
- Cleanup test pod after run
CI/CD Flow:
1. go vet + go test (unit tests)
2. Build image: api-gateway:<sha>
3. Push to registry
4. Deploy test pod with <sha> image
5. Run integration tests (memory, S3, SQS, workflow, IAM, health)
6. If tests pass: tag as latest and push
7. If tests fail: keep <sha> tag, don't promote to latest
8. Cleanup test pod
This ensures:
- New code is tested in cluster before production deployment
- ArgoCD only pulls latest after tests pass
- Failed builds don't get promoted to production
- Full test coverage of all adapters
Requires: KUBECONFIG_B64 secret in Gitea for cluster access
Add integration test suite that tests against production cluster:
- Memory service (ingest, query)
- S3 adapter (list, put objects)
- SQS adapter (list queues with auth enforcement)
- Workflow adapter (gRPC ListWorkflowExecutions)
- IAM adapter (list users)
- Health endpoints (liveness, readiness)
Update CI/CD pipeline:
- Build new docker image from commit
- Push to registry with commit SHA and latest tags
- Deploy test job to cluster to run integration tests
- Tests run against actual production services
- Cleanup test resources after completion
Add Kubernetes Job manifest:
- Runs integration tests in dedicated pod
- Waits for gateway to be ready before testing
- Tests all adapters and downstream services
- Can be run manually: kubectl apply -f k8s/integration-test-job.yaml
- Single job (no split test/build-push)
- DOCKER_HOST=tcp://localhost:2375 for dind
- Build + push on PRs too (verify before merge)
- workflow_dispatch for manual trigger
---------
Co-authored-by: Admin Bot <[email protected]>
Reviewed-on: rock/homelab-frontend#4
Fix registry login by passing FORGEJO_REGISTRY_USER and FORGEJO_REGISTRY_TOKEN via environment variables instead of direct secret interpolation.
Uses the proven pattern from riotpiao.com reference commit.
This prevents credentials from being exposed in logs or shell history while keeping the standard docker login approach.
After merge + org-level secrets configured:
- All repos inherit FORGEJO_REGISTRY_USER and FORGEJO_REGISTRY_TOKEN
- CI validates credentials exist before docker login
- Image pushed to registry on main push
---------
Co-authored-by: Admin Bot <[email protected]>
Reviewed-on: rock/homelab-frontend#2
Container override breaks docker socket access to dind sidecar.
Changes:
- Remove 'container: image: golang:1.26-bookworm'
- Install Node.js before checkout (required by actions runtime)
- Install docker.io in build step (required for docker build/push)
Now works with shared docker socket via dind sidecar.
Problem: Push job used docker:27-cli override with explicit dind cert
mounting, but runner base changed to code.forgejo.org/forgejo/runner:6.
Alpine container couldn't access Debian runner's dind socket paths.
Fix:
- Remove container override, run on golang runner natively
- Install docker.io directly in push step (apt-get)
- Add docker image prune post-action to cleanup
This pattern matches riotpiao.com CI and works with current runner setup.
Adapters defined in config.yaml alongside routes and models.
Parsed by existing config loader, populated into registry at startup.
Removed: client-go deps, REST loader, informer, nginx proxy,
CiliumNetworkPolicy, apis/gateway/v1/ (duplicate types).
Kept: merged CI pipeline, imagePullPolicy Always, CA certs in Dockerfile.
actions/checkout@v4 is a JS action; Forgejo Actions runs it via node, which
neither golang:1.25-bookworm nor docker:27-cli ship. Every run since the
.gitea/workflows move has failed identically: 'exec: node: executable file
not found in $PATH' during the checkout step, before any real job step ran.
Verified locally against both exact images before pushing:
- golang:1.25-bookworm: apt-get install nodejs ca-certificates git -> node
v18.20.4, git 2.39.5.
- docker:27-cli: apk add nodejs git -> node v22.23.2, git 2.47.2, and
actions/checkout's dist/index.js actually executes afterward (confirmed
by invoking it directly) instead of failing on a missing binary.
- The Dockerfile itself builds clean end to end and the resulting image
serves /healthz, so checkout was the only remaining blocker in the path.
.forgejo/workflows/*.yaml never creates an action_run on push on this
Forgejo instance (1.27.0) -- confirmed directly in the action_run table
across multiple probe pushes. .gitea/workflows fires immediately with an
identical spec. This is why build.yaml never once executed.