Skip to content

Commit bb71e47

Browse files
authored
Merge pull request #4229 from headlamp-k8s/e2e_add_incluster
CI: test: add in-cluster Headlamp e2e test
2 parents 47c10d4 + f5e5043 commit bb71e47

File tree

3 files changed

+193
-0
lines changed

3 files changed

+193
-0
lines changed

.github/workflows/build-container.yml

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,8 +137,12 @@ jobs:
137137
- name: Import images to kind
138138
run: |
139139
export SHELL=/bin/bash
140+
# Load images into Cluster 1 (test)
140141
kind load docker-image ghcr.io/headlamp-k8s/headlamp-plugins-test:latest --name test
141142
kind load docker-image ghcr.io/headlamp-k8s/headlamp:latest --name test
143+
144+
# Load Headlamp image into Cluster 2 (test2) so it can run in-cluster tests there
145+
kind load docker-image ghcr.io/headlamp-k8s/headlamp:latest --name test2
142146
- name: Test .plugins folder
143147
if: steps.cache-image-restore2.outputs.cache-hit != 'true'
144148
run: |
@@ -231,6 +235,70 @@ jobs:
231235
else
232236
echo "Playwright tests passed successfully"
233237
fi
238+
- name: Deploy Headlamp in in-cluster mode and run TS API tests
239+
run: |
240+
# Reuse Cluster 2 context for in-cluster tests
241+
kubectl config use-context test2
242+
243+
# Setup service account and RBAC for Headlamp
244+
kubectl create serviceaccount headlamp --namespace kube-system || true
245+
kubectl create clusterrolebinding headlamp --serviceaccount=kube-system:headlamp --clusterrole=cluster-admin || true
246+
kubectl get serviceaccount headlamp -n kube-system
247+
248+
# Deploy Headlamp with in-cluster mode
249+
kubectl apply -f e2e-tests/kubernetes-headlamp-incluster-ci.yaml
250+
251+
echo "Waiting for headlamp deployment to be available (in-cluster)..."
252+
kubectl wait deployment -n kube-system headlamp --for condition=Available=True --timeout=120s
253+
kubectl get pods -n kube-system -l app.kubernetes.io/name=headlamp
254+
255+
echo "Checking headlamp pod status (in-cluster)..."
256+
kubectl logs -n kube-system -l app.kubernetes.io/name=headlamp --tail=50 || true
257+
258+
# Start port-forward in background
259+
echo "Starting port-forward to headlamp service (in-cluster)..."
260+
kubectl port-forward -n kube-system service/headlamp 8080:80 &
261+
PORT_FORWARD_PID=$!
262+
263+
# Wait for port-forward to be ready
264+
sleep 5
265+
266+
# Verify port-forward is working
267+
if ! kill -0 $PORT_FORWARD_PID 2>/dev/null; then
268+
echo "❌ Port-forward failed to start for in-cluster test"
269+
exit 1
270+
fi
271+
272+
export HEADLAMP_TEST_URL="http://localhost:8080"
273+
echo "In-cluster Headlamp URL: $HEADLAMP_TEST_URL"
274+
275+
# Get service account token for API checks
276+
echo "Getting service account token for in-cluster test..."
277+
export HEADLAMP_SA_TOKEN=$(kubectl create token headlamp --duration=1h -n kube-system)
278+
279+
if [ -z "$HEADLAMP_SA_TOKEN" ]; then
280+
echo "❌ Failed to get service account token for in-cluster test"
281+
kill $PORT_FORWARD_PID 2>/dev/null || true
282+
exit 1
283+
fi
284+
285+
echo "✅ Service account token obtained"
286+
echo "Running in-cluster API Playwright tests..."
287+
288+
# Run the TypeScript in-cluster API tests
289+
cd e2e-tests
290+
npm ci
291+
npx playwright install --with-deps
292+
npx playwright test tests/incluster-api.spec.ts
293+
EXIT_CODE=$?
294+
295+
# Cleanup port-forward
296+
kill $PORT_FORWARD_PID 2>/dev/null || true
297+
298+
if [ $EXIT_CODE -ne 0 ]; then
299+
echo "❌ In-cluster API tests failed with exit code $EXIT_CODE"
300+
exit $EXIT_CODE
301+
fi
234302
# Clear disk space by removing unnecessary files, apt files and uninstall some playwright dependencies
235303
- name: Clear Disk Space
236304
if: steps.cache-image-restore2.outputs.cache-hit != 'true'
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Source: headlamp/templates/service.yaml
2+
apiVersion: v1
3+
kind: Service
4+
metadata:
5+
name: headlamp
6+
namespace: kube-system
7+
spec:
8+
type: ClusterIP
9+
ports:
10+
- port: 80
11+
targetPort: 4466
12+
selector:
13+
app.kubernetes.io/name: headlamp
14+
app.kubernetes.io/instance: headlamp
15+
---
16+
# Source: headlamp/templates/deployment.yaml
17+
apiVersion: apps/v1
18+
kind: Deployment
19+
metadata:
20+
name: headlamp
21+
namespace: kube-system
22+
labels:
23+
app.kubernetes.io/name: headlamp
24+
app.kubernetes.io/instance: headlamp
25+
spec:
26+
replicas: 1
27+
selector:
28+
matchLabels:
29+
app.kubernetes.io/name: headlamp
30+
app.kubernetes.io/instance: headlamp
31+
template:
32+
metadata:
33+
labels:
34+
app.kubernetes.io/name: headlamp
35+
app.kubernetes.io/instance: headlamp
36+
spec:
37+
serviceAccountName: headlamp
38+
containers:
39+
- name: headlamp
40+
securityContext:
41+
privileged: false
42+
runAsGroup: 101
43+
runAsNonRoot: true
44+
runAsUser: 100
45+
image: "ghcr.io/headlamp-k8s/headlamp:latest"
46+
imagePullPolicy: Never
47+
args:
48+
- "-in-cluster"
49+
ports:
50+
- name: http
51+
containerPort: 4466
52+
protocol: TCP
53+
nodeSelector:
54+
'kubernetes.io/os': linux
55+
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
* Copyright 2025 The Kubernetes Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
/*
18+
* In-cluster API integration tests.
19+
*
20+
* These tests assume:
21+
* - Headlamp is running in-cluster mode and exposed locally via port-forward
22+
* at HEADLAMP_TEST_URL (e.g. http://localhost:8080)
23+
* - A service account token with cluster-admin permissions is available in
24+
* HEADLAMP_SA_TOKEN
25+
*/
26+
27+
import { expect, test } from '@playwright/test';
28+
29+
const baseURL = process.env.HEADLAMP_TEST_URL || 'http://localhost:8080';
30+
const saToken = process.env.HEADLAMP_SA_TOKEN || '';
31+
32+
// Only run these tests when explicitly configured (e.g. from CI in in-cluster job).
33+
// In all other environments, they are skipped so they don't interfere with normal e2e runs.
34+
const shouldRun = !!saToken;
35+
36+
test.describe('Headlamp in-cluster API', () => {
37+
test.skip(!shouldRun, 'HEADLAMP_SA_TOKEN is not set; skipping in-cluster API tests');
38+
39+
test('can list namespaces via /clusters/main/api/v1/namespaces', async ({ request }) => {
40+
const response = await request.get('/clusters/main/api/v1/namespaces', {
41+
headers: {
42+
Authorization: `Bearer ${saToken}`,
43+
},
44+
baseURL,
45+
});
46+
47+
expect(response.status(), 'expected 200 from namespaces API').toBe(200);
48+
49+
const body = await response.json();
50+
51+
// Basic sanity checks – structure may vary between clusters, so keep it loose
52+
expect(body).toHaveProperty('items');
53+
expect(Array.isArray(body.items)).toBe(true);
54+
});
55+
56+
test('can list nodes via /clusters/main/api/v1/nodes', async ({ request }) => {
57+
const response = await request.get('/clusters/main/api/v1/nodes', {
58+
headers: {
59+
Authorization: `Bearer ${saToken}`,
60+
},
61+
baseURL,
62+
});
63+
64+
expect(response.status(), 'expected 200 from nodes API').toBe(200);
65+
66+
const body = await response.json();
67+
expect(body).toHaveProperty('items');
68+
expect(Array.isArray(body.items)).toBe(true);
69+
});
70+
});

0 commit comments

Comments
 (0)