fix: rewrite Tekton integration tests for X-Service routing
CI / CI (pull_request) Failing after 50s

FIXES:
- Remove stale files: k8s/argocd-apps/, k8s/tekton/base/, overlays/
  (Tekton infra is in homelab repo, not here)
- Fix step.resources → step.computeResources (Tekton v1 API)
- Fix Task: use curl sidecar pattern instead of distroless image
  (distroless has no shell/curl/go)
- Fix routing: use X-Service + X-Resource headers, not path-based
- Extract test script to scripts/integration-test.sh (ConfigMap mount)
- Install kubectl in CI runner (was missing)
- Prune README to essentials

TASK ARCHITECTURE:
  sidecar: gateway image (mounts config secret, runs on localhost)
  step: curlimages/curl (runs integration-test.sh from ConfigMap)

TEST COVERAGE:
  health, header validation, memory, s3, sqs, workflow, iam
This commit is contained in:
Admin Bot
2026-09-13 21:12:15 +09:00
parent ba6958e6f3
commit 6eb53a4d1c
8 changed files with 265 additions and 319 deletions
+49 -55
View File
@@ -17,10 +17,13 @@ jobs:
name: CI
runs-on: golang
steps:
- name: Install Node.js and Docker
- name: Install dependencies
run: |
apt-get update
apt-get install -y nodejs docker.io
apt-get install -y docker.io curl
curl -sLO "https://dl.k8s.io/release/$(curl -sL https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
chmod +x kubectl && mv kubectl /usr/local/bin/
kubectl version --client
- name: Checkout code
uses: actions/checkout@v4
@@ -48,90 +51,81 @@ jobs:
docker build --no-cache \
-t "${IMAGE}:${{ steps.sha.outputs.short_sha }}" \
-f Dockerfile .
echo "Built image: ${IMAGE}:${{ steps.sha.outputs.short_sha }}"
- name: Push test image (SHA tag only, not latest yet)
run: |
docker push "${IMAGE}:${{ steps.sha.outputs.short_sha }}"
echo "✓ Pushed test image: ${IMAGE}:${{ steps.sha.outputs.short_sha }}"
- name: Push image (SHA tag)
run: docker push "${IMAGE}:${{ steps.sha.outputs.short_sha }}"
- name: Setup kubeconfig for Tekton trigger
# ── Tekton integration tests ─────────────────────────────
- name: Setup kubeconfig
run: |
mkdir -p ~/.kube
echo "${KUBECONFIG_B64}" | base64 -d > ~/.kube/config
kubectl cluster-info
env:
KUBECONFIG_B64: ${{ secrets.KUBECONFIG_B64 }}
continue-on-error: true
- name: Trigger integration tests via Tekton PipelineRun
- name: Trigger Tekton PipelineRun
id: tekton
run: |
echo "Triggering integration tests via Tekton..."
SHA="${{ steps.sha.outputs.short_sha }}"
RUN_NAME="integration-test-${SHA}"
# Create PipelineRun to run integration tests
kubectl create -f - << 'YAML'
# Clean up any previous run with the same name
kubectl delete pipelinerun "${RUN_NAME}" -n api --ignore-not-found
# Create PipelineRun — spins up gateway sidecar + curl tests
cat <<YAML | kubectl create -f -
apiVersion: tekton.dev/v1
kind: PipelineRun
metadata:
name: integration-test-${{ steps.sha.outputs.short_sha }}
name: ${RUN_NAME}
namespace: api
labels:
pr-id: "${{ github.event.pull_request.number || 'main' }}"
commit-sha: "${{ steps.sha.outputs.short_sha }}"
commit-sha: "${SHA}"
spec:
pipelineRef:
name: integration-test-pipeline
params:
- name: image
value: ${IMAGE}:${{ steps.sha.outputs.short_sha }}
- name: test-timeout
value: "5m"
value: "${IMAGE}:${SHA}"
YAML
echo "✓ PipelineRun created: integration-test-${{ steps.sha.outputs.short_sha }}"
echo "✓ PipelineRun created: ${RUN_NAME}"
# Wait for PipelineRun completion
echo "Waiting for tests to complete (max 10 minutes)..."
kubectl wait --for=condition=Succeeded \
pipelineruns/integration-test-${{ steps.sha.outputs.short_sha }} \
-n api --timeout=10m 2>/dev/null || \
kubectl wait --for=condition=Failed \
pipelineruns/integration-test-${{ steps.sha.outputs.short_sha }} \
-n api --timeout=1s 2>/dev/null || true
# Wait for completion (Succeeded or Failed)
echo "Waiting for tests (timeout 5m)..."
if kubectl wait pipelinerun/"${RUN_NAME}" -n api \
--for=condition=Succeeded --timeout=5m 2>/dev/null; then
echo "result=pass" >> $GITHUB_OUTPUT
else
echo "result=fail" >> $GITHUB_OUTPUT
fi
# Get test results
echo ""
echo "=== Test Results ==="
RESULT=$(kubectl get pipelinerun integration-test-${{ steps.sha.outputs.short_sha }} \
-n api -o jsonpath='{.status.conditions[0].reason}')
TEST_MESSAGE=$(kubectl get pipelinerun integration-test-${{ steps.sha.outputs.short_sha }} \
-n api -o jsonpath='{.status.taskRuns[*].status.taskResults[?(@.name=="result")].value}')
echo "PipelineRun Status: $RESULT"
echo "Test Result: $TEST_MESSAGE"
# Get logs
# Print logs + results
echo ""
echo "=== Test Logs ==="
kubectl logs -n api pipelinerun/integration-test-${{ steps.sha.outputs.short_sha }} || true
kubectl logs -n api "pipelinerun/${RUN_NAME}" --all-containers 2>/dev/null || true
echo ""
REASON=$(kubectl get pipelinerun "${RUN_NAME}" -n api \
-o jsonpath='{.status.conditions[0].reason}')
SUMMARY=$(kubectl get pipelinerun "${RUN_NAME}" -n api \
-o jsonpath='{.status.results[?(@.name=="test-summary")].value}')
echo "Status: ${REASON}"
echo "Summary: ${SUMMARY}"
# Determine if tests passed
if [ "$RESULT" = "Succeeded" ]; then
echo "✓ Integration tests PASSED"
exit 0
else
echo "✗ Integration tests FAILED"
exit 1
fi
continue-on-error: false
- name: Gate on test result
if: steps.tekton.outputs.result != 'pass'
run: |
echo "✗ Integration tests FAILED — image NOT promoted"
exit 1
- name: Promote image to latest (only if tests passed)
if: success()
# ── Promote only after tests pass ────────────────────────
- name: Promote image to latest
run: |
docker pull "${IMAGE}:${{ steps.sha.outputs.short_sha }}"
docker tag "${IMAGE}:${{ steps.sha.outputs.short_sha }}" "${IMAGE}:latest"
docker push "${IMAGE}:latest"
echo "✓ Promoted ${IMAGE}:${{ steps.sha.outputs.short_sha }} to latest"
echo "✓ Promoted to latest"
- name: Cleanup
if: always()
run: docker image prune -a --force 2>&1 | tail -3 || true
run: docker image prune -af 2>&1 | tail -3 || true
-33
View File
@@ -1,33 +0,0 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: tekton-pipelines
namespace: argocd
labels:
app.kubernetes.io/name: tekton
app.kubernetes.io/part-of: homelab
spec:
project: default
source:
repoURL: https://github.com/tektoncd/operator.git
targetRevision: main
path: config/release
destination:
server: https://kubernetes.default.svc
namespace: tekton-pipelines
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- Validate=false
retry:
limit: 5
backoff:
duration: 5s
factor: 2
maxDuration: 3m
+28 -100
View File
@@ -1,49 +1,32 @@
# Tekton Integration Testing
# Tekton Integration Tests
Tekton Pipelines for running integration tests on API Gateway changes before merging to main.
Curl-based integration tests for the API gateway, orchestrated by Tekton.
## Architecture
## How It Works
```
Gitea CI (builds image:sha)
Creates PipelineRun
Tekton Controller (watches PipelineRun)
Runs Task: integration-test
Task runs tests in container
Reports pass/fail to PipelineRun status
CI reads status and promotes image (if pass)
ArgoCD deploys new image
CI pushes image:sha → creates PipelineRun → Tekton spins up gateway sidecar
→ runs curl tests → reports pass/fail → CI promotes to :latest if pass
```
## Components
The Task runs the gateway image as a **sidecar** (same pod, localhost),
then executes `scripts/integration-test.sh` which tests every adapter
via `X-Service` + `X-Resource` header routing.
### Task: `integration-test`
- **File**: `task-integration-test.yaml`
- **Purpose**: Run integration tests in a container
- **Inputs**: Image to test, timeout
- **Outputs**: pass/fail result, message
- **Security**: Non-root user, resource limits
## Files
### Pipeline: `integration-test-pipeline`
- **File**: `pipeline-integration-test.yaml`
- **Purpose**: Orchestrate integration test execution
- **Tasks**: Runs the integration-test task
- **Results**: Aggregates task results for CI consumption
| File | Purpose |
|------|---------|
| `task-integration-test.yaml` | Task: sidecar gateway + curl test step |
| `pipeline-integration-test.yaml` | Pipeline: wraps the Task |
| `scripts/integration-test.sh` | Test script (mounted as ConfigMap) |
| `kustomization.yaml` | Generates ConfigMap from script |
## Usage
### Manual Trigger
## Manual Run
```bash
# Create a PipelineRun to test an image
kubectl create -f - << 'YAML'
kubectl apply -k k8s/tekton/
kubectl create -f - <<'EOF'
apiVersion: tekton.dev/v1
kind: PipelineRun
metadata:
@@ -54,77 +37,22 @@ spec:
name: integration-test-pipeline
params:
- name: image
value: forgejo.riotpiao.com/rock/api-gateway:abc123
- name: test-timeout
value: "5m"
YAML
value: forgejo.riotpiao.com/rock/api-gateway:latest
EOF
# Watch test progress
kubectl logs -f -n api pipelinerun/integration-test-manual
# Check results
kubectl get pipelinerun -n api integration-test-manual -o yaml
# Watch
kubectl logs -f -n api pipelinerun/integration-test-manual -c step-run-tests
```
### CI Trigger
## Updating Tests
CI automatically creates PipelineRun with:
- Image tag: current commit SHA
- Timeout: 5 minutes
- Labels: PR ID, commit SHA for traceability
## Management
Tekton is managed by ArgoCD Application: `tekton-pipelines` (in `k8s/argocd-apps/tekton.yaml`)
To update:
1. Edit manifest files
2. Commit to git
3. ArgoCD syncs automatically
Do NOT manually apply manifests - let ArgoCD manage everything.
## Monitoring
Edit `scripts/integration-test.sh`, then:
```bash
# List all PipelineRuns
kubectl get pipelineruns -n api
# Watch a specific run
kubectl logs -f -n api pipelinerun/integration-test-<sha>
# Get detailed status
kubectl describe pipelinerun -n api integration-test-<sha>
kubectl apply -k k8s/tekton/ # recreates ConfigMap
```
## Results
## Tekton Infrastructure
PipelineRun status contains:
- `status.conditions[0].reason`: Succeeded | Failed | Unknown
- `status.taskRuns[*].status.taskResults`: Test outputs
- Pod logs: Detailed test output
## Best Practices
1. **DRY**: Task and Pipeline are parameterized, reusable
2. **SOLID**: Single responsibility (Task runs tests, Pipeline orchestrates)
3. **GitOps**: Everything in git, managed by ArgoCD
4. **Security**: Non-root containers, resource limits, no hardcoded values
5. **Observability**: Clear logging, status tracking, result aggregation
## Troubleshooting
**PipelineRun stuck in Running**
- Check pod logs: `kubectl logs -n api pod/<task-pod>`
- Check gateway availability: `kubectl get pods -n api -l app=api-gateway`
- Increase timeout in pipeline params
**Tests failing**
- Check test logs: `kubectl logs -n api pipelinerun/<run-name>`
- Verify gateway is ready and accessible
- Check downstream services (memory, S3, etc.)
**Image not promoted**
- CI only promotes if PipelineRun succeeds
- Check PipelineRun status: `kubectl get pipelinerun <name> -n api -o yaml`
- Review CI logs in Gitea for error details
Tekton Pipelines is installed in `~/workplace/homelab` via ArgoCD
(`k8s/argocd/apps/06-ci-cd.yaml` → vendored `k8s/infra/tekton/release.yaml`).
-44
View File
@@ -1,44 +0,0 @@
# Tekton Pipelines Release manifest
# Source: https://storage.googleapis.com/tekton-releases/pipeline/latest/release.yaml
# This is managed by ArgoCD - do NOT manually apply
# ArgoCD syncs this from git
apiVersion: v1
kind: Namespace
metadata:
name: tekton-pipelines
labels:
managed-by: argocd
---
# CRDs and RBAC are part of the full release manifest
# Using a reference approach for cleaner GitOps
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: tekton-pipelines
namespace: argocd
spec:
generators:
- list:
elements:
- name: tekton-pipelines
template:
metadata:
name: tekton-pipelines
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/tektoncd/operator
targetRevision: main
path: config/release
destination:
server: https://kubernetes.default.svc
namespace: tekton-pipelines
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
+7 -6
View File
@@ -1,7 +1,5 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
metadata:
name: api-gateway-tekton
namespace: api
@@ -9,7 +7,10 @@ resources:
- task-integration-test.yaml
- pipeline-integration-test.yaml
commonLabels:
app: api-gateway
component: testing
managed-by: argocd
generatorOptions:
disableNameSuffixHash: true
configMapGenerator:
- name: integration-test-script
files:
- scripts/integration-test.sh
+10 -15
View File
@@ -7,29 +7,24 @@ metadata:
app: api-gateway
component: testing
spec:
description: Pipeline to run integration tests for API gateway
description: >
Run integration tests against a gateway image.
Spins up the image as a sidecar, tests via curl, reports pass/fail.
params:
- name: image
type: string
description: Container image to test (repo:tag)
default: "forgejo.riotpiao.com/rock/api-gateway:latest"
- name: test-timeout
type: string
default: "5m"
description: Test execution timeout
description: "Container image to test (repo:sha)"
results:
- name: test-result
description: Overall test result (pass/fail)
value: $(tasks.run-integration-tests.results.result)
- name: test-message
description: Test summary message
value: $(tasks.run-integration-tests.results.message)
description: "pass or fail"
value: $(tasks.integration-test.results.result)
- name: test-summary
description: "e.g. 8/8 passed"
value: $(tasks.integration-test.results.summary)
tasks:
- name: run-integration-tests
- name: integration-test
taskRef:
name: integration-test
params:
- name: image
value: $(params.image)
- name: timeout
value: $(params.test-timeout)
+115
View File
@@ -0,0 +1,115 @@
#!/bin/sh
set -e
# Integration test runner for API gateway.
# Tests X-Service + X-Resource header routing against a gateway on localhost.
#
# Required env:
# GW — gateway base URL (e.g. http://localhost:8080)
# RESULTS_DIR — directory to write Tekton results
PASS=0; FAIL=0; TOTAL=0
assert() {
NAME="$1"; EXPECT="$2"
shift 2
# remaining args are the full curl flags
TOTAL=$((TOTAL + 1))
CODE=$(curl -s -o /dev/null -w '%{http_code}' "$@" 2>/dev/null || echo "000")
if [ "$CODE" = "$EXPECT" ]; then
echo "${NAME} (${CODE})"
PASS=$((PASS + 1))
else
echo "${NAME} — expected ${EXPECT}, got ${CODE}"
FAIL=$((FAIL + 1))
fi
}
# ── Wait for sidecar gateway to be fully ready ──
echo "⏳ Waiting for gateway sidecar..."
READY=false
for i in $(seq 1 60); do
CODE=$(curl -s -o /dev/null -w '%{http_code}' "${GW}/healthz" 2>/dev/null || echo "000")
if [ "$CODE" = "200" ]; then
sleep 1
C2=$(curl -s -o /dev/null -w '%{http_code}' "${GW}/healthz" 2>/dev/null || echo "000")
C3=$(curl -s -o /dev/null -w '%{http_code}' "${GW}/healthz" 2>/dev/null || echo "000")
if [ "$C2" = "200" ] && [ "$C3" = "200" ]; then
READY=true
echo "✓ Gateway ready (stable after 3 checks)"
break
fi
fi
sleep 2
done
if [ "$READY" = "false" ]; then
echo "✗ Gateway never became ready"
echo "fail" > "${RESULTS_DIR}/result"
echo "0/0 gateway timeout" > "${RESULTS_DIR}/summary"
exit 1
fi
echo ""
echo "═══ Integration Tests ═══"
echo ""
# ── Health (path-based, no headers) ──
echo "▸ Health"
assert "GET /healthz" 200 \
-X GET "${GW}/healthz"
assert "GET /readyz" 200 \
-X GET "${GW}/readyz"
# ── Routing: missing headers → 400 ──
echo "▸ Header validation"
assert "no X-Service → 400" 400 \
-X GET "${GW}/"
assert "X-Service without X-Resource → 400" 400 \
-X GET -H "X-Service: memory" "${GW}/"
assert "unknown service → 404" 404 \
-X GET -H "X-Service: nonexistent" -H "X-Resource: foo" "${GW}/"
# ── Memory service (POST, no auth) ──
echo "▸ Memory service"
assert "memory/query (POST)" 200 \
-X POST -H "X-Service: memory" -H "X-Resource: query" \
-H "Content-Type: application/json" -d '{"query":"test"}' "${GW}/"
assert "memory/ingest (POST)" 200 \
-X POST -H "X-Service: memory" -H "X-Resource: ingest" \
-H "Content-Type: application/json" \
-d '{"content":"integration test","metadata":{"source":"tekton"}}' "${GW}/"
# ── S3 service (GET, no auth → MinIO 403) ──
echo "▸ S3 service"
assert "s3/list-objects (GET → 403)" 403 \
-X GET -H "X-Service: s3" -H "X-Resource: list-objects" "${GW}/"
# ── SQS service (GET, auth required → 401) ──
echo "▸ SQS service"
assert "sqs/list-queues (GET → 401)" 401 \
-X GET -H "X-Service: sqs" -H "X-Resource: list-queues" "${GW}/"
# ── Workflow service (gRPC, GET) ──
echo "▸ Workflow service"
assert "workflow/list (GET → upstream err)" 502 \
-X GET -H "X-Service: workflow" -H "X-Resource: list" "${GW}/"
# ── IAM service (GET, Authentik) ──
echo "▸ IAM service"
assert "iam/list-users (GET → Authentik redirect)" 302 \
-X GET -H "X-Service: iam" -H "X-Resource: list-users" "${GW}/"
echo ""
echo "═══ Results: ${PASS}/${TOTAL} passed, ${FAIL} failed ═══"
# Write Tekton results
if [ "$FAIL" -eq 0 ]; then
echo "pass" > "${RESULTS_DIR}/result"
else
echo "fail" > "${RESULTS_DIR}/result"
fi
echo "${PASS}/${TOTAL} passed, ${FAIL} failed" > "${RESULTS_DIR}/summary"
[ "$FAIL" -eq 0 ]
+51 -61
View File
@@ -7,79 +7,69 @@ metadata:
app: api-gateway
component: testing
spec:
description: Run integration tests for API gateway
description: >
Spin up a gateway pod from the given image as a sidecar,
run curl-based integration tests, report pass/fail.
params:
- name: image
type: string
description: Container image to test (including tag)
- name: timeout
description: "Container image to test (repo:tag)"
- name: gateway-port
type: string
default: "5m"
description: Test timeout
default: "8080"
results:
- name: result
description: Test result (pass/fail)
type: string
- name: message
description: Test summary message
- name: summary
type: string
sidecars:
- name: gateway
image: $(params.image)
env:
- name: LISTEN_ADDR
value: "0.0.0.0:$(params.gateway-port)"
- name: CONFIG_PATH
value: /etc/gateway/config.yaml
- name: LOG_LEVEL
value: info
- name: AUTH_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: api-gw-client-secret
key: client-secret
optional: true
volumeMounts:
- name: gateway-config
mountPath: /etc/gateway
readOnly: true
steps:
- name: run-tests
image: $(params.image)
securityContext:
runAsNonRoot: true
runAsUser: 65532
allowPrivilegeEscalation: false
image: curlimages/curl:8.13.0
env:
- name: GATEWAY_URL
value: "http://api-gateway:8080"
- name: CI
value: "true"
script: |
#!/bin/sh
set -e
echo "🧪 Starting integration tests..."
echo "Image: $(params.image)"
echo "Gateway: $GATEWAY_URL"
echo ""
# Wait for gateway to be ready
echo "Waiting for gateway service..."
for i in $(seq 1 30); do
if curl -s $GATEWAY_URL/healthz > /dev/null 2>&1; then
echo "✓ Gateway is ready"
break
fi
echo "Attempt $i/30: Waiting for gateway..."
sleep 2
done
# Run integration tests
echo "Running integration tests..."
if go test -v -tags=integration -timeout=$(params.timeout) ./internal/integration/...; then
echo "pass" | tee $(results.result.path)
echo "✓ All integration tests passed" | tee $(results.message.path)
exit 0
else
echo "fail" | tee $(results.result.path)
echo "✗ Some integration tests failed" | tee $(results.message.path)
exit 1
fi
- name: GW
value: "http://localhost:$(params.gateway-port)"
- name: RESULTS_DIR
value: /tekton/results
command: ["sh", "/scripts/integration-test.sh"]
volumeMounts:
- name: tmp
mountPath: /tmp
- name: home
mountPath: /home/nonroot
resources:
- name: test-script
mountPath: /scripts
readOnly: true
computeResources:
requests:
cpu: 250m
memory: 512Mi
cpu: 100m
memory: 64Mi
limits:
cpu: 500m
memory: 1Gi
cpu: 200m
memory: 128Mi
volumes:
- name: tmp
emptyDir: {}
- name: home
emptyDir: {}
- name: gateway-config
secret:
secretName: api-gateway-config
- name: test-script
configMap:
name: integration-test-script
defaultMode: 0755