Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
178 changes: 178 additions & 0 deletions .github/workflows/test-e2e-helm.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
name: 'Test: E2E Helm Chart'

on:
workflow_dispatch:
inputs:
branch:
description: 'Branch to test'
required: false
default: ''
helm-chart-ref:
description: 'n8n-hosting branch/tag for Helm chart'
required: false
default: 'krider2010/helm-chart-update'
mode:
description: 'Deployment mode to test'
required: true
type: choice
options:
- standalone
- queue
default: 'standalone'

env:
DOCKER_IMAGE: ghcr.io/${{ github.repository }}:ci-${{ github.run_id }}
HELM_CHART_REF: ${{ inputs.helm-chart-ref || 'krider2010/helm-chart-update' }}

jobs:
build:
name: 'Build Docker Image'
runs-on: blacksmith-4vcpu-ubuntu-2204
permissions:
packages: write
contents: read
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: ${{ inputs.branch || github.ref }}
fetch-depth: 1

- name: Login to GHCR
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build and push to GHCR
uses: ./.github/actions/setup-nodejs
with:
build-command: 'pnpm build:docker'
enable-docker-cache: true
env:
INCLUDE_TEST_CONTROLLER: 'true'
IMAGE_BASE_NAME: ghcr.io/${{ github.repository }}
IMAGE_TAG: ci-${{ github.run_id }}
RUNNERS_IMAGE_BASE_NAME: ghcr.io/${{ github.repository_owner }}/runners

helm-e2e:
name: 'Helm E2E (${{ inputs.mode }})'
needs: build
runs-on: blacksmith-4vcpu-ubuntu-2204
timeout-minutes: 30
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: ${{ inputs.branch || github.ref }}
fetch-depth: 1

- name: Setup Environment
uses: ./.github/actions/setup-nodejs
with:
build-command: 'pnpm build'

- name: Install Browsers
working-directory: packages/testing/playwright
run: pnpm exec playwright install chromium

- name: Install Helm
uses: azure/setup-helm@bf6a7d304bc2fdb57e0331155b7ebf2c504acf0a # v4

- name: Install kubectl
uses: azure/setup-kubectl@c0c8b32d33a5244f1e5947304550403b63930415 # v4

- name: Start K3s + Helm stack
run: |
npx tsx packages/testing/containers/helm-start-stack.ts \
--mode "${{ inputs.mode }}" \
--image "${{ env.DOCKER_IMAGE }}" \
--chart-ref "${{ env.HELM_CHART_REF }}" \
--url-file /tmp/n8n-helm-url.txt \
--kubeconfig-file /tmp/n8n-helm-kubeconfig.txt &

echo "Waiting for Helm stack to be ready..."
for i in $(seq 1 60); do
if [ -f /tmp/n8n-helm-url.txt ]; then
echo "Stack ready!"
break
fi
if [ "$i" -eq 60 ]; then
echo "Timeout waiting for Helm stack"
exit 1
fi
sleep 10
done

N8N_URL=$(cat /tmp/n8n-helm-url.txt)
echo "n8n URL: ${N8N_URL}"
echo "N8N_BASE_URL=${N8N_URL}" >> "$GITHUB_ENV"

KUBECONFIG_PATH=$(cat /tmp/n8n-helm-kubeconfig.txt)
echo "KUBECONFIG=${KUBECONFIG_PATH}" >> "$GITHUB_ENV"

- name: Run Building Blocks E2E Tests
working-directory: packages/testing/playwright
run: |
N8N_BASE_URL="${{ env.N8N_BASE_URL }}" \
RESET_E2E_DB=true \
npx playwright test \
--project=e2e \
tests/e2e/building-blocks/ \
--workers=1 \
--retries=0 \
--reporter=list

- name: Debug K8s State
if: failure()
run: |
echo "=== Pod Status ==="
kubectl get pods -o wide
echo ""
echo "=== Pod Descriptions ==="
kubectl describe pods -l app.kubernetes.io/name=n8n
echo ""
echo "=== Recent Events ==="
kubectl get events --sort-by=.lastTimestamp | tail -30
echo ""
echo "=== n8n Pod Logs (last 100 lines) ==="
kubectl logs -l app.kubernetes.io/name=n8n --tail=100 || true
echo ""
echo "=== Health Check ==="
curl -s -o /dev/null -w "HTTP %{http_code}" "${{ env.N8N_BASE_URL }}/healthz/readiness" || echo "UNREACHABLE"

- name: Upload Failure Artifacts
if: failure()
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: helm-e2e-report-${{ inputs.mode }}
path: |
packages/testing/playwright/test-results/
packages/testing/playwright/playwright-report/
retention-days: 7

cleanup-ghcr:
name: 'Cleanup GHCR Image'
needs: [build, helm-e2e]
if: always()
runs-on: blacksmith-2vcpu-ubuntu-2204
permissions:
packages: write
contents: read
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
sparse-checkout: .github/scripts
sparse-checkout-cone-mode: false

- name: Delete images from GHCR
continue-on-error: true
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GHCR_ORG: ${{ github.repository_owner }}
GHCR_REPO: ${{ github.event.repository.name }}
run: node .github/scripts/cleanup-ghcr-images.mjs --tag ci-${{ github.run_id }}
175 changes: 175 additions & 0 deletions packages/testing/containers/HELM-TESTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
# Helm Chart E2E Testing

Test n8n Helm chart deployments using K3s (lightweight Kubernetes) inside Docker, powered by `@testcontainers/k3s`.

## Prerequisites

### Required

- **Docker Desktop** (macOS/Windows) or **Docker Engine** (Linux)
- **Privileged container support** — K3s runs as a privileged container
- **helm** CLI — runs on your host machine ([install](https://helm.sh/docs/intro/install/))
- **kubectl** CLI — runs on your host machine ([install](https://kubernetes.io/docs/tasks/tools/))
- **n8n Docker image** — built locally (`pnpm build:docker`) or pulled from Docker Hub

### Not Required

- Kind, Minikube, or any other K8s distribution
- Kubernetes cluster access
- Any K8s tooling beyond helm + kubectl

### Privileged Container Compatibility

| Environment | Supported | Notes |
|---|---|---|
| Docker Desktop (macOS/Windows) | Yes | Privileged enabled by default |
| Docker Engine (Linux) | Yes | Standard daemon supports it |
| GitHub Actions (ubuntu runners) | Yes | Docker socket available |
| Blacksmith runners | Yes | Standard Docker-capable VMs |
| Rootless Docker | **No** | K3s requires privileged mode |
| Docker-in-Docker | Depends | Outer container needs `--privileged` |
| Podman | **No** | K3s requires Docker-compatible runtime |

## How It Works

```
Host Machine (helm, kubectl)
├── KUBECONFIG=/tmp/helm-kubeconfig-*.yaml
└── Docker
└── K3s Container (privileged, NodePort 30080 → host random port)
├── containerd (K3s runtime)
│ └── n8n image (preloaded from host Docker)
└── Kubernetes control plane
├── n8n Pod (Helm-deployed)
└── Service (NodePort 30080 → Pod 5678)

Playwright ──── http://localhost:<host-port> ──→ NodePort ──→ n8n Pod
```

1. `@testcontainers/k3s` starts K3s inside a Docker container with NodePort 30080 exposed
2. The n8n Docker image is exported from host Docker and imported into K3s's containerd
3. Host `helm` installs the n8n chart using a kubeconfig pointing at the K3s API
4. The n8n service is patched to NodePort, routing traffic through K3s's exposed port
5. Playwright tests connect via `N8N_BASE_URL=http://localhost:<host-port>`

## Local Usage

```bash
# 1. Build the n8n Docker image (or use a Docker Hub image)
pnpm build:docker

# 2. Start the Helm stack (takes ~60-120s)
cd packages/testing/containers
pnpm stack:helm

# 3. In another terminal, use the printed KUBECONFIG for debugging
export KUBECONFIG=/tmp/helm-kubeconfig-*.yaml
kubectl get pods
kubectl logs -l app.kubernetes.io/name=n8n

# 4. Run tests
N8N_BASE_URL=http://localhost:<port> RESET_E2E_DB=true \
npx playwright test tests/e2e/building-blocks/ --workers=1

# 5. Cleanup
pnpm stack:helm:clean
```

### Test a Specific Version Matrix

```bash
# Test n8n 1.80.0 against chart version v1.2.0
pnpm stack:helm --image n8nio/n8n:1.80.0 --chart-ref v1.2.0

# Test latest n8n against a chart PR branch
pnpm stack:helm --chart-ref fix/pvc-permissions

# Test a GHCR image (e.g., from CI)
pnpm stack:helm --image ghcr.io/n8n-io/n8n:ci-12345
```

### CLI Options

```
--mode <mode> standalone (SQLite, default) or queue (PostgreSQL + Redis + workers)
--image <image> n8n Docker image (default: n8nio/n8n:local)
--chart-ref <ref> Git branch/tag for n8n-hosting (default: krider2010/helm-chart-update)
--chart-repo <url> Git repo URL (default: https://github.com/n8n-io/n8n-hosting.git)
--k3s-image <image> K3s image (default: rancher/k3s:v1.32.2-k3s1)
--url-file <path> Write URL to file when ready (for CI automation)
--help Show help
```

## CI Usage

The `test-e2e-helm.yml` workflow handles everything:

- **Trigger:** Push to `helm-container-test` branch, or manual dispatch
- **Build:** Creates n8n Docker image, pushes to GHCR
- **Test:** Starts K3s, installs Helm chart, runs building-blocks E2E tests
- **Cleanup:** Removes ephemeral GHCR image

Manual dispatch also accepts `helm-chart-ref` to test a specific chart version.

## Troubleshooting

### K3s won't start

Check that Docker supports privileged containers:
```bash
docker run --rm --privileged alpine echo "privileged works"
```

### Image not found in K3s

Ensure the n8n Docker image exists locally:
```bash
docker images | grep n8nio/n8n
```

If empty, run `pnpm build:docker` first.

### Helm install times out

Use kubectl to inspect the cluster:
```bash
export KUBECONFIG=/tmp/helm-kubeconfig-*.yaml
kubectl get pods -o wide
kubectl describe pod -l app.kubernetes.io/name=n8n
kubectl get events --sort-by=.lastTimestamp
```

### Port not accessible

NodePort routing is stateless (kube-proxy), so connectivity issues typically indicate the pod is unhealthy. Check pod status:
```bash
export KUBECONFIG=/tmp/helm-kubeconfig-*.yaml
kubectl get pods -o wide
kubectl describe pod -l app.kubernetes.io/name=n8n
kubectl version --client
helm version
```

### Slow startup

First run is slower due to K3s image pull. Typical timings:

| Phase | First Run | Subsequent |
|---|---|---|
| K3s start | ~15-30s | ~5s (reuse) |
| Image preload | ~10-20s | ~10-20s |
| Helm install | ~5-10s | ~5-10s |
| n8n boot | ~15-30s | ~15-30s |
| **Total** | **~60-120s** | **~40-80s** |

## Comparison: Testcontainers vs K3s + Helm

| | Testcontainers (Stream 1) | K3s + Helm (Stream 2) |
|---|---|---|
| **Purpose** | Feature testing | Deployment validation |
| **Speed** | 5-15s startup | 60-120s startup |
| **K8s features** | None | PVC, RBAC, securityContext, NetworkPolicy |
| **Custom services** | Kafka, Mailpit, OIDC, etc. | Only what the Helm chart defines |
| **What it proves** | "n8n works with X" | "this chart config deploys correctly" |
| **When to run** | Every PR | On demand, nightly, pre-release |
| **Prerequisites** | Docker | Docker + helm + kubectl |
Loading