Skip to content

Helm Install Test

Helm Install Test #103

name: Helm Install Test
on:
schedule:
# Run daily at 6 AM UTC
- cron: '0 6 * * *'
workflow_dispatch:
# Explicit permissions are required for GITHUB_TOKEN to pull from GHCR
permissions:
contents: read
packages: read
jobs:
helm-install:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Set up Helm
uses: azure/setup-helm@v5.0.1
- name: Log in to Container Registry
run: |
echo "${{ secrets.GITHUB_TOKEN }}" | helm registry login ghcr.io -u ${{ github.actor }} --password-stdin
- name: Create Kind cluster
uses: helm/kind-action@v1
with:
node_image: kindest/node:v1.29.2
cluster_name: helm-test
- name: Deploy MinIO as S3 backend
run: |
kubectl create namespace s3proxy
cat <<EOF | kubectl apply -n s3proxy -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: minio
spec:
replicas: 1
selector:
matchLabels:
app: minio
template:
metadata:
labels:
app: minio
spec:
containers:
- name: minio
image: minio/minio:latest
args: ["server", "/data"]
env:
- name: MINIO_ROOT_USER
value: minioadmin
- name: MINIO_ROOT_PASSWORD
value: minioadmin
ports:
- containerPort: 9000
---
apiVersion: v1
kind: Service
metadata:
name: minio
spec:
selector:
app: minio
ports:
- port: 9000
EOF
kubectl wait --for=condition=ready pod -l app=minio -n s3proxy --timeout=120s
- name: Create K8s Image Pull Secret & Patch Namespace
run: |
# 1. Create the secret using the workflow token
kubectl create secret docker-registry ghcr-login \
--docker-server=ghcr.io \
--docker-username=${{ github.actor }} \
--docker-password=${{ secrets.GITHUB_TOKEN }} \
--namespace s3proxy \
--dry-run=client -o yaml | kubectl apply -f -
# 2. Patch the default service account to automatically use this secret
# This acts as a fail-safe if the Helm 'imagePullSecrets' set doesn't propagate
kubectl patch serviceaccount default -n s3proxy -p '{"imagePullSecrets": [{"name": "ghcr-login"}]}'
- name: Install chart from GHCR
run: |
OWNER=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]')
helm install s3proxy oci://ghcr.io/${OWNER}/charts/s3proxy-python --version 0.0.0-latest \
--namespace s3proxy \
--set image.repository=ghcr.io/${OWNER}/s3proxy-python \
--set image.tag=latest \
--set image.pullPolicy=Always \
--set "imagePullSecrets[0].name=ghcr-login" \
--set s3.host="http://minio:9000" \
--set secrets.credentials[0].accessKey=minioadmin \
--set secrets.credentials[0].secretKey=minioadmin \
--set secrets.credentials[0].kek=test-encryption-key-for-ci \
--set redis.enabled=true \
--set redis.auth.enabled=true \
--set redis.auth.password=testredispassword123 \
--set dashboard.enabled=true \
--set dashboard.secret=test-dashboard-secret-for-ci \
--set dashboard.frontend.image.repository=ghcr.io/${OWNER}/s3proxy-dashboard \
--set dashboard.frontend.image.tag=latest \
--set dashboard.frontend.image.pullPolicy=Always \
--set frontproxy.enabled=true \
--set replicaCount=3 \
--set resources.limits.cpu=100m \
--set resources.requests.cpu=50m \
--wait \
--timeout 5m
- name: Verify pods are running
run: |
kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=s3proxy-python -n s3proxy --timeout=120s
kubectl get pods -n s3proxy
POD_COUNT=$(kubectl get pods -n s3proxy -l app.kubernetes.io/name=s3proxy-python --no-headers | grep Running | wc -l)
if [ "$POD_COUNT" -lt 3 ]; then
echo "Expected 3 s3proxy pods, got $POD_COUNT"
exit 1
fi
echo "✓ All 3 s3proxy pods running"
- name: Verify front proxy
run: |
set -euo pipefail
echo "=== Front proxy pods ready ==="
kubectl wait --for=condition=ready pod \
-l app.kubernetes.io/component=frontproxy -n s3proxy --timeout=120s
kubectl get pods -n s3proxy -l app.kubernetes.io/component=frontproxy
echo "=== S3 round-trip THROUGH the front proxy ==="
kubectl run frontproxy-test -n s3proxy --rm -i --restart=Never \
--image=amazon/aws-cli:latest \
--env="AWS_ACCESS_KEY_ID=minioadmin" \
--env="AWS_SECRET_ACCESS_KEY=minioadmin" \
--env="AWS_DEFAULT_REGION=us-east-1" \
--command -- /bin/sh -c '
set -e
ENDPOINT="http://s3proxy-python-frontproxy:80"
# HAProxy starts with no backend addresses (server-template + init-addr none)
# and only fills its server slots once it resolves the headless Service via DNS,
# which publishes pod IPs only after the app pods are Ready. Until that first
# resolution lands, every request is 503 <NOSRV>. Wait for a live backend before
# asserting the round-trip.
echo "Waiting for the front proxy to discover a backend..."
for i in $(seq 1 30); do
if aws --endpoint-url $ENDPOINT s3 ls >/dev/null 2>&1; then
echo "Backend is live (attempt $i)"
break
fi
[ "$i" = "30" ] && { echo "✗ front proxy never reached a live backend"; exit 1; }
sleep 2
done
echo "via front proxy - $(date)" > /tmp/fp.txt
ORIG=$(md5sum /tmp/fp.txt | cut -c1-32)
aws --endpoint-url $ENDPOINT s3 mb s3://frontproxy-bucket
aws --endpoint-url $ENDPOINT s3 cp /tmp/fp.txt s3://frontproxy-bucket/fp.txt
aws --endpoint-url $ENDPOINT s3 cp s3://frontproxy-bucket/fp.txt /tmp/down.txt
DOWN=$(md5sum /tmp/down.txt | cut -c1-32)
[ "$ORIG" = "$DOWN" ] || { echo "✗ round-trip via front proxy failed"; exit 1; }
aws --endpoint-url $ENDPOINT s3 rm s3://frontproxy-bucket/fp.txt
aws --endpoint-url $ENDPOINT s3 rb s3://frontproxy-bucket
echo "✓ S3 round-trip through front proxy succeeded"
'
- name: Check health endpoint
run: |
kubectl port-forward svc/s3proxy-python 4433:4433 -n s3proxy &
sleep 5
curl -sf http://localhost:4433/healthz && echo "Health check passed"
- name: Run S3 smoke test
run: |
kubectl run s3-smoke-test -n s3proxy --rm -i --restart=Never \
--image=amazon/aws-cli:latest \
--env="AWS_ACCESS_KEY_ID=minioadmin" \
--env="AWS_SECRET_ACCESS_KEY=minioadmin" \
--env="AWS_DEFAULT_REGION=us-east-1" \
--command -- /bin/sh -c '
set -e
ENDPOINT="http://s3proxy-python:4433"
echo "=== Creating test bucket ==="
aws --endpoint-url $ENDPOINT s3 mb s3://smoke-test-bucket
echo "=== Uploading test file ==="
echo "Hello from CI smoke test - $(date)" > /tmp/test.txt
ORIG_MD5=$(md5sum /tmp/test.txt | cut -c1-32)
aws --endpoint-url $ENDPOINT s3 cp /tmp/test.txt s3://smoke-test-bucket/test.txt
echo "=== Listing bucket ==="
aws --endpoint-url $ENDPOINT s3 ls s3://smoke-test-bucket/
echo "=== Downloading and verifying ==="
aws --endpoint-url $ENDPOINT s3 cp s3://smoke-test-bucket/test.txt /tmp/downloaded.txt
DOWN_MD5=$(md5sum /tmp/downloaded.txt | cut -c1-32)
if [ "$ORIG_MD5" = "$DOWN_MD5" ]; then
echo "✓ Round-trip successful - checksums match"
else
echo "✗ Checksum mismatch!"
exit 1
fi
echo "=== Verifying encryption (raw read from MinIO) ==="
aws --endpoint-url http://minio:9000 s3 cp s3://smoke-test-bucket/test.txt /tmp/raw.txt 2>/dev/null || true
if [ -f /tmp/raw.txt ]; then
RAW_MD5=$(md5sum /tmp/raw.txt | cut -c1-32)
if [ "$ORIG_MD5" != "$RAW_MD5" ]; then
echo "✓ Data is encrypted - raw content differs from original"
else
echo "✗ Data NOT encrypted - raw matches original!"
exit 1
fi
fi
echo "=== Cleanup ==="
aws --endpoint-url $ENDPOINT s3 rm s3://smoke-test-bucket/test.txt
aws --endpoint-url $ENDPOINT s3 rb s3://smoke-test-bucket
echo ""
echo "✓ All smoke tests passed!"
'
- name: Verify dashboard
run: |
set -euo pipefail
ENDPOINT="http://s3proxy-python:4433"
BUCKET="dashboard-ci"
KEY="dashboard-ci.txt"
echo "=== Dashboard pod is ready ==="
kubectl wait --for=condition=ready pod \
-l app.kubernetes.io/component=dashboard -n s3proxy --timeout=120s
kubectl get pods -n s3proxy -l app.kubernetes.io/component=dashboard
echo "=== Upload an object through the proxy (so the dashboard has traffic) ==="
kubectl run dashboard-ci-upload -n s3proxy --rm -i --restart=Never \
--image=amazon/aws-cli:latest \
--env="AWS_ACCESS_KEY_ID=minioadmin" \
--env="AWS_SECRET_ACCESS_KEY=minioadmin" \
--env="AWS_DEFAULT_REGION=us-east-1" \
--command -- /bin/sh -c "
set -e
echo 'dashboard ci object' > /tmp/o.txt
aws --endpoint-url $ENDPOINT s3 mb s3://$BUCKET 2>/dev/null || true
aws --endpoint-url $ENDPOINT s3 cp /tmp/o.txt s3://$BUCKET/$KEY
aws --endpoint-url $ENDPOINT s3 cp s3://$BUCKET/$KEY /tmp/d.txt
test \"\$(cat /tmp/d.txt)\" = 'dashboard ci object'
echo UPLOAD_OK
"
# The dashboard is single-origin behind its own service: nginx serves the
# static SPA and reverse-proxies /dashboard/api to the proxy. Port-forward
# the dashboard service and exercise the whole thing through it.
echo "=== Port-forward the dashboard service ==="
kubectl port-forward svc/s3proxy-python-dashboard 18080:80 -n s3proxy &
PF_PID=$!
trap "kill $PF_PID 2>/dev/null || true" EXIT
BASE="http://localhost:18080/dashboard"
for i in $(seq 1 15); do curl -sf -o /dev/null "$BASE" && break; sleep 1; done
echo "=== 1. Static SPA shell is served ==="
curl -sf "$BASE" | grep -q "_app/" || { echo "✗ SPA shell not served at $BASE"; exit 1; }
curl -sf -o /dev/null "$BASE/login" # SPA fallback for the login route
echo "✓ SPA shell served"
echo "=== 2. Auth is enforced (API reverse-proxied through the dashboard) ==="
CODE=$(curl -s -o /dev/null -w '%{http_code}' "$BASE/api/status")
[ "$CODE" = "401" ] || { echo "✗ status API without auth returned $CODE (expected 401)"; exit 1; }
# The AWS secret key must NOT work as a dashboard password (fallback removed).
CODE=$(curl -s -o /dev/null -w '%{http_code}' -u minioadmin:minioadmin "$BASE/api/status")
[ "$CODE" = "401" ] || { echo "✗ AWS creds accepted as dashboard login ($CODE) — fallback not removed"; exit 1; }
echo "✓ Unauthenticated + AWS-cred requests rejected (401)"
echo "=== 3. Status API works with dashboard credentials (admin/admin) ==="
STATUS=$(curl -sf -u admin:admin "$BASE/api/status")
echo "$STATUS" | grep -q '"status":"Running"' || { echo "✗ status not Running: $STATUS"; exit 1; }
echo "✓ Status API healthy"
echo "=== 4. Bucket listing API shows the uploaded object ==="
BUCKETS=$(curl -sf -u admin:admin "$BASE/api/buckets/$BUCKET")
echo "$BUCKETS" | grep -q "\"$KEY\"" || { echo "✗ '$KEY' not in bucket listing: $BUCKETS"; exit 1; }
echo "✓ Bucket listing shows '$KEY'"
echo "=== 5. Object detail API reports encryption ==="
OBJ=$(curl -sf -u admin:admin "$BASE/api/objects/$BUCKET/$KEY")
echo "$OBJ" | grep -q '"encrypted":true' || { echo "✗ object detail not encrypted: $OBJ"; exit 1; }
echo "✓ Object detail reports encrypted"
echo ""
echo "✓ Dashboard verified end-to-end"
- name: Show logs on failure
if: failure()
run: |
echo "=== Pod Status ==="
kubectl get pods -n s3proxy -o wide
echo ""
echo "=== Describe Failed Pods ==="
kubectl describe pods -n s3proxy -l app.kubernetes.io/name=s3proxy-python
echo ""
echo "=== S3Proxy Logs ==="
kubectl logs -l app.kubernetes.io/name=s3proxy-python -n s3proxy --tail=100
echo ""
echo "=== Front Proxy Logs ==="
kubectl logs -l app.kubernetes.io/component=frontproxy -n s3proxy --tail=100
echo ""
echo "=== MinIO Logs ==="
kubectl logs -l app=minio -n s3proxy --tail=50
echo ""
echo "=== Events ==="
kubectl get events -n s3proxy --sort-by=.lastTimestamp