feat: Tekton-based integration testing (proper K8s CI/CD)
CI / CI (pull_request) Canceled after 41s

Replace ad-hoc K8s Job with proper Tekton TaskRun:

k8s/tekton/integration-test-task.yaml:
  - Tekton Task for integration testing
  - Two stages: migrate + test
  - Runs existing Rust integration tests:
    * it_phase3_phase4 (ingest + persistence)
    * it_unified_query_4_6 (query endpoint)
    * it_temporal_filtering_4_2_fixed (temporal)
    * mem_ingest (extraction pipeline)
    * mem_cli::query (query handler)
  - Reports results to /tekton/results/summary
  - Resource limits: 1Gi mem, 500m CPU

.gitea/workflows/build.yaml:
  - Integrated Tekton trigger after image push
  - Create TaskRun with image SHA
  - Wait for completion (5m timeout)
  - Gate image promotion on test passing
  - Only promote to :latest if tests pass

Pattern (from homelab-frontend):
  1. Build image → push with SHA
  2. Trigger Tekton TaskRun
  3. Wait for result
  4. Gate promotion
  5. Promote to :latest only if tests pass

Benefits:
  ✓ Proper K8s CI/CD framework
  ✓ Reusable Task
  ✓ Better logging/results
  ✓ Proper resource mgmt
  ✓ Matches homelab pattern

Requires:
  - Tekton Pipelines installed in cluster
  - KUBECONFIG_B64 secret in Forgejo
This commit is contained in:
2026-09-14 23:00:57 +09:00
parent ce6c93d3b5
commit a72719a68f
6 changed files with 244 additions and 328 deletions
+94 -1
View File
@@ -71,7 +71,100 @@ jobs:
docker push "${IMAGE}:${{ steps.sha.outputs.short_sha }}"
echo "Pushed: ${IMAGE}:${{ steps.sha.outputs.short_sha }}"
- name: Prune unused images and cleanup
- name: Install kubectl
run: |
apt-get update
apt-get install -y kubectl
- name: Setup kubeconfig for Tekton
run: |
mkdir -p ~/.kube
echo "${KUBECONFIG_B64}" | base64 -d > ~/.kube/config
chmod 600 ~/.kube/config
kubectl cluster-info 2>&1 | head -3
echo "✓ kubeconfig ready"
env:
KUBECONFIG_B64: ${{ secrets.KUBECONFIG_B64 }}
- name: Trigger Tekton TaskRun (integration tests)
id: tekton
run: |
SHA="${{ steps.sha.outputs.short_sha }}"
RUN_NAME="integration-test-${SHA}"
NAMESPACE="poimen"
IMAGE="${REGISTRY}/riotpiao-poimen/poimen-memory:${SHA}"
echo "Triggering Tekton TaskRun: ${RUN_NAME}"
echo "Image: ${IMAGE}"
echo ""
# Create TaskRun
cat <<YAML | kubectl create -f -
apiVersion: tekton.dev/v1
kind: TaskRun
metadata:
name: ${RUN_NAME}
namespace: ${NAMESPACE}
labels:
commit-sha: "${SHA}"
spec:
taskRef:
name: poimen-integration-test
params:
- name: image
value: "${IMAGE}"
YAML
echo "✓ TaskRun created"
echo ""
echo "Waiting for completion (timeout 5m)..."
# Wait for TaskRun to complete
if kubectl wait taskrun/${RUN_NAME} -n ${NAMESPACE} \
--for=condition=Succeeded --timeout=5m 2>/dev/null; then
echo "result=pass" >> $GITHUB_OUTPUT
echo "✓ Tests passed"
else
echo "result=fail" >> $GITHUB_OUTPUT
echo "✗ Tests failed or timed out"
fi
# Print test summary
echo ""
echo "=== TaskRun Status ==="
REASON=$(kubectl get taskrun ${RUN_NAME} -n ${NAMESPACE} \
-o jsonpath='{.status.conditions[0].reason}')
SUMMARY=$(kubectl get taskrun ${RUN_NAME} -n ${NAMESPACE} \
-o jsonpath='{.status.results[?(@.name=="summary")].value}')
echo "Status: ${REASON}"
echo "Summary: ${SUMMARY}"
# Print logs
echo ""
echo "=== Test Logs ==="
POD=$(kubectl get pod -n ${NAMESPACE} \
-l tekton.dev/taskRun=${RUN_NAME} -o name | head -1)
kubectl logs -n ${NAMESPACE} "${POD}" -c step-test 2>/dev/null | tail -200 || true
- name: Gate on test result
if: steps.tekton.outputs.result != 'pass'
run: |
echo "✗ Integration tests FAILED"
echo "Image NOT promoted to :latest"
exit 1
- name: Promote image to latest
run: |
docker login -u "${REGISTRY_USER}" -p "${REGISTRY_TOKEN}" "${REGISTRY}"
docker tag "${IMAGE}:${{ steps.sha.outputs.short_sha }}" "${IMAGE}:latest"
docker push "${IMAGE}:latest"
echo "✓ Promoted to :latest"
env:
REGISTRY_USER: ${{ secrets.FORGEJO_REGISTRY_USER }}
REGISTRY_TOKEN: ${{ secrets.FORGEJO_REGISTRY_TOKEN }}
- name: Cleanup
if: always()
run: |
docker image prune -a --force 2>&1 | tail -3 || true
cargo clean || true
-133
View File
@@ -1,133 +0,0 @@
name: Integration Test
on:
workflow_run:
workflows: [CI]
types: [completed]
branches: [main]
workflow_dispatch:
inputs:
image_sha:
description: 'Image SHA to test (defaults to latest on main)'
required: false
env:
REGISTRY: forgejo.riotpiao.com
IMAGE: forgejo.riotpiao.com/riotpiao-poimen/poimen-memory
NAMESPACE: poimen
jobs:
integration-test:
name: K8s Integration Test
runs-on: rust
if: github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success'
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Get image SHA
id: image
run: |
if [ -n "${{ github.event.inputs.image_sha }}" ]; then
SHA="${{ github.event.inputs.image_sha }}"
else
SHA="$(git rev-parse --short HEAD)"
fi
echo "sha=$SHA" >> $GITHUB_OUTPUT
echo "Image SHA: $SHA"
- name: Install kubectl
run: |
apt-get update
apt-get install -y kubectl postgresql-client
- name: Setup kubeconfig
run: |
mkdir -p ~/.kube
echo "${{ secrets.KUBECONFIG_B64 }}" | base64 -d > ~/.kube/config
chmod 600 ~/.kube/config
# Verify cluster access
kubectl cluster-info
kubectl get nodes
- name: Verify image exists in registry
run: |
IMAGE="${{ env.IMAGE }}:${{ steps.image.outputs.sha }}"
echo "Checking if image exists: $IMAGE"
# Use registry API to verify image exists
if docker pull "$IMAGE" 2>/dev/null; then
echo "✓ Image found in registry"
else
echo "✗ Image not found"
exit 1
fi
env:
DOCKER_CONFIG: /tmp/docker
continue-on-error: true
- name: Apply integration test Job
run: |
IMAGE_SHA="${{ steps.image.outputs.sha }}"
echo "Creating integration test Job with image: $IMAGE_SHA"
echo ""
# Substitute image SHA in manifest
cat k8s/test/integration-test-job.yaml | \
sed "s|IMAGE_SHA|$IMAGE_SHA|g" | \
kubectl apply -f - -n ${{ env.NAMESPACE }}
echo "✓ Job submitted"
echo ""
# Wait for job to complete
kubectl wait --for=condition=complete job/poimen-memory-integration-test \
-n ${{ env.NAMESPACE }} \
--timeout=600s || {
echo ""
echo "✗ Job did not complete in time"
echo ""
echo "Pod logs:"
kubectl logs -l test=integration -n ${{ env.NAMESPACE }} --all-containers=true --tail=100
exit 1
}
- name: Collect test results
if: always()
run: |
echo "=========================================="
echo "Integration Test Results"
echo "=========================================="
echo ""
echo "Job status:"
kubectl describe job poimen-memory-integration-test -n ${{ env.NAMESPACE }} | tail -20
echo ""
echo "Pod logs:"
kubectl logs -l test=integration -n ${{ env.NAMESPACE }} --all-containers=true || true
echo ""
# Get job status
STATUS=$(kubectl get job poimen-memory-integration-test \
-n ${{ env.NAMESPACE }} \
-o jsonpath='{.status.succeeded}')
if [ "$STATUS" = "1" ]; then
echo "✓ Integration test PASSED"
exit 0
else
echo "✗ Integration test FAILED"
exit 1
fi
- name: Cleanup test Job
if: always()
run: |
echo "Cleaning up test resources..."
kubectl delete job poimen-memory-integration-test \
-n ${{ env.NAMESPACE }} \
--ignore-not-found=true
echo "✓ Cleanup complete"
+150
View File
@@ -0,0 +1,150 @@
---
# Tekton Task: Integration Tests for Poimen Memory Service
#
# Executes:
# 1. Database migrations
# 2. Integration test suites (cargo test)
# 3. Reports results
#
# Parameters:
# - image: Docker image with SHA to test
#
# Results:
# - summary: Test summary (pass/fail + count)
apiVersion: tekton.dev/v1
kind: Task
metadata:
name: poimen-integration-test
namespace: poimen
spec:
params:
- name: image
type: string
description: "Docker image SHA to test (e.g., forgejo.riotpiao.com/riotpiao-poimen/poimen-memory:abc123)"
results:
- name: summary
description: "Test summary: PASS or FAIL + test count"
steps:
# Step 1: Apply database migrations
- name: migrate
image: $(params.image)
env:
- name: DB_HOST
value: "memory-db-rw.poimen.svc.cluster.local"
- name: DB_PORT
value: "5432"
- name: DB_NAME
value: "memory"
- name: DB_USER
value: "app"
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: memory-db-app
key: password
script: |
#!/bin/bash
set -e
echo "=========================================="
echo "Step 1: Database Migrations"
echo "=========================================="
echo ""
# Run migrations
/app/migrations/run_migrations.sh
echo ""
echo "✓ Migrations complete"
# Step 2: Run integration tests
- name: test
image: $(params.image)
env:
- name: DATABASE_URL
value: "postgresql://[email protected]:5432/memory"
- name: RUST_LOG
value: "info,mem_cli=debug,mem_ingest=debug,mem_store=debug"
- name: MEM_AUTH_MODE
value: "none"
- name: SQLX_OFFLINE
value: "true"
- name: PGPASSWORD
valueFrom:
secretKeyRef:
name: memory-db-app
key: password
script: |
#!/bin/bash
set -e
echo "=========================================="
echo "Step 2: Integration Tests"
echo "=========================================="
echo ""
TEST_SUITES=(
"it_phase3_phase4"
"it_unified_query_4_6"
"it_temporal_filtering_4_2_fixed"
)
PASSED=0
FAILED=0
for suite in "${TEST_SUITES[@]}"; do
echo "Running: $suite"
if cargo test --test "$suite" --lib 2>&1 | tail -50; then
((PASSED++))
echo "✓ $suite passed"
else
((FAILED++))
echo "✗ $suite failed"
fi
echo ""
done
# Run unit tests
echo "Running unit tests..."
if cargo test --lib mem_ingest 2>&1 | tail -100; then
echo "✓ mem_ingest passed"
else
((FAILED++))
echo "✗ mem_ingest failed"
fi
echo ""
if cargo test --lib mem_cli::query 2>&1 | tail -100; then
echo "✓ mem_cli::query passed"
else
((FAILED++))
echo "✗ mem_cli::query failed"
fi
echo ""
echo "=========================================="
echo "Test Summary: $PASSED passed, $FAILED failed"
echo "=========================================="
if [ $FAILED -eq 0 ]; then
echo "PASS: All integration tests passed"
echo "PASS: All integration tests passed" > /tekton/results/summary
exit 0
else
echo "FAIL: $FAILED test suite(s) failed"
echo "FAIL: $FAILED test suite(s) failed" > /tekton/results/summary
exit 1
fi
resources:
requests:
memory: "1Gi"
cpu: "500m"
limits:
memory: "2Gi"
cpu: "2000m"
-23
View File
@@ -1,23 +0,0 @@
apiVersion: ENC[AES256_GCM,data:rSo=,iv:bdZ6ucHQdbMkAYckaHgPX1O37sNpRzemzgtZWnNvuyc=,tag:NnqNKDxkgVuMSJCjUz9d/A==,type:str]
kind: ENC[AES256_GCM,data:i8C5n+0g,iv:1hgoEM2lCfFJybqqu+LKLcPb0oDVBbZfaO4hNU2IHCM=,tag:DgArPgsw6T0CzbKVmkNFlQ==,type:str]
metadata:
name: ENC[AES256_GCM,data:hS5ed588kPm8qyKEk48JbnSAOA==,iv:Icy0jUqXw0kaeliFYReCrX/4CvJybWSALLt1vVOqDJ0=,tag:3eJJXmWynVA7ynRA7tIODw==,type:str]
namespace: ENC[AES256_GCM,data:TaDa/Qh4,iv:wQelDyrVDxNslhz7GBFA6LhziZ7jFUGMIzT4ymYolJI=,tag:rtZ755RHlseJvgWslzim1Q==,type:str]
type: ENC[AES256_GCM,data:FhwlDJfv,iv:5ne0JFyExRdmrhuG1A37FThKpFEhUhrJeFnFOvpgHcM=,tag:lQ/RbeQJ/x/3nnhKLcB20A==,type:str]
stringData:
DATABASE_URL: ENC[AES256_GCM,data:1OmlGtPPtpocOr1U9aW76+z20TKtGvC7M6IIEFmACjdhcascOoHuGAVWlUxjGDrqM6w5DF+zXvR43E49ZsSnTvorGH+abCIfn3SpsYo21YazgBKt3A/ewu2bnjIw/RPfJ6AB7FRPdg==,iv:A1eytAyxTH5u0Hy6kThxiuwweOozyE8fiXl1JdH6vD4=,tag:D5I5Eg2Zbivm12YCvgpPGw==,type:str]
sops:
age:
- enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBLQXA5RWh3aEFCNDZGT3dR
L2FGckR3MGFodzRqUDRodDRHdTdUOWV0SGhVCk92bnl1SjdSQUl5QTFQanVRSzR1
SFp2MXhUbEdpUm1ES21mdHlPdHg2dlUKLS0tIFNjbXVHNHZONS9UZVNnU0IvQ1J5
clovQ0NRVytGcG1oaktWc25SVmhyK2cKZ11ZIXu3wEGUsuXkrwDG+jGjb6a9xrVc
1r8PWJYj5VwcT0HZO+le3A6wfmNZgCzZY6pIYE9rIHexUXcEUZiSug==
-----END AGE ENCRYPTED FILE-----
recipient: age1e5fq3hwxy78psus2nfvmtmua36g0u3suk78ephw6246l974d2utsvn0hla
lastmodified: "2026-09-14T13:48:10Z"
mac: ENC[AES256_GCM,data:aYO2ghlPJn5cq82i4hLBoAa4j5nxt4pekhoHB4OPAeZDbMXipgXa0Ha+HJtPreHh/w/6VaXnEGFtWujKHwvIoL8c5ffQK6Mqw9DSYVV0OnSFJ50/JgRvMLVFLqoqUBoiwQOyYopVjIEeqMFNslN7WvpsHCK9NRxgO1Klf97rE0w=,iv:gWlQ1VseNC/rxp7X/N8tTNRtTIckwMbrMNODQ1mHSj4=,tag:wxhucPCkXrhyvhfwwAgRbw==,type:str]
unencrypted_suffix: _unencrypted
version: 3.13.2
-171
View File
@@ -1,171 +0,0 @@
---
# Integration Test Job - Runs existing Rust integration tests in K8s
#
# Two-stage execution:
# 1. migrate: Apply database migrations
# 2. test: Run existing integration tests (cargo test)
#
# Tests executed:
# - it_phase3_phase4: Ingest + persistence tests
# - it_unified_query_4_6: Query endpoint tests
# - it_temporal_filtering_4_2_fixed: Temporal query tests
# - mem_ingest: Extraction pipeline tests
# - mem_cli::query: Query handler tests
apiVersion: batch/v1
kind: Job
metadata:
name: poimen-memory-integration-test
namespace: poimen
labels:
app: poimen-memory
test: integration
component: ci-cd
spec:
backoffLimit: 0
activeDeadlineSeconds: 600
ttlSecondsAfterFinished: 3600
template:
metadata:
labels:
app: poimen-memory
test: integration
spec:
serviceAccountName: memory-app
restartPolicy: Never
containers:
# Stage 1: Apply migrations
- name: migrate
image: forgejo.riotpiao.com/riotpiao-poimen/poimen-memory:IMAGE_SHA
imagePullPolicy: IfNotPresent
command:
- /bin/bash
- -c
- |
set -e
echo "=========================================="
echo "Applying Database Migrations"
echo "=========================================="
echo ""
# Run migrations script
/app/migrations/run_migrations.sh
echo ""
echo "✓ Migrations complete"
env:
- name: DB_HOST
value: "memory-db-rw.poimen.svc.cluster.local"
- name: DB_PORT
value: "5432"
- name: DB_NAME
value: "memory"
- name: DB_USER
value: "app"
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: memory-db-app
key: password
resources:
requests:
memory: "256Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
# Stage 2: Run integration tests
- name: test
image: forgejo.riotpiao.com/riotpiao-poimen/poimen-memory:IMAGE_SHA
imagePullPolicy: IfNotPresent
command:
- /bin/bash
- -c
- |
set -e
echo "=========================================="
echo "Running Integration Tests"
echo "=========================================="
echo ""
# Run existing integration tests
echo "Test Suite 1: Ingest + Persistence (it_phase3_phase4)"
cargo test --test it_phase3_phase4 --lib 2>&1 | tail -50 || TEST_FAILED=1
echo ""
echo "Test Suite 2: Unified Query (it_unified_query_4_6)"
cargo test --test it_unified_query_4_6 --lib 2>&1 | tail -50 || TEST_FAILED=1
echo ""
echo "Test Suite 3: Temporal Filtering (it_temporal_filtering_4_2_fixed)"
cargo test --test it_temporal_filtering_4_2_fixed --lib 2>&1 | tail -50 || TEST_FAILED=1
echo ""
echo "Test Suite 4: Ingest Pipeline Unit Tests"
cargo test --lib mem_ingest 2>&1 | tail -100 || TEST_FAILED=1
echo ""
echo "Test Suite 5: Query Handler Tests"
cargo test --lib mem_cli::query 2>&1 | tail -100 || TEST_FAILED=1
echo ""
echo "=========================================="
echo "Integration Tests Complete"
echo "=========================================="
if [ -n "$TEST_FAILED" ]; then
echo "✗ Some tests failed"
exit 1
fi
echo "✓ All tests passed"
env:
- name: DATABASE_URL
value: "postgresql://[email protected]:5432/memory"
- name: RUST_LOG
value: "info,mem_cli=debug,mem_ingest=debug,mem_store=debug"
- name: MEM_AUTH_MODE
value: "none"
- name: SQLX_OFFLINE
value: "true"
- name: PGPASSWORD
valueFrom:
secretKeyRef:
name: memory-db-app
key: password
resources:
requests:
memory: "1Gi"
cpu: "500m"
limits:
memory: "2Gi"
cpu: "2000m"
livenessProbe:
exec:
command:
- /bin/sh
- -c
- test -f /proc/self/status
initialDelaySeconds: 10
periodSeconds: 30
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: memory-app
namespace: poimen
labels:
app: poimen-memory