frontend: Live-sync editor with server updates and add merge option #10232
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # "Setup minikube as CI step in GitHub Actions" | |
| # https://minikube.sigs.k8s.io/docs/tutorials/setup_minikube_in_github_actions/ | |
| # https://github.com/marketplace/actions/setup-minikube | |
| name: Build Container and test | |
| on: | |
| pull_request: | |
| paths: | |
| - 'backend/**' | |
| - 'frontend/**' | |
| - Makefile | |
| - '.github/**' | |
| - Dockerfile | |
| - Dockerfile.plugins | |
| - 'e2e-tests/**' | |
| push: | |
| branches: | |
| - main | |
| - rc-* | |
| - testing-rc-* | |
| permissions: | |
| contents: read | |
| jobs: | |
| build: | |
| runs-on: ubuntu-22.04 | |
| name: build discover and deploy | |
| permissions: | |
| actions: write # needed to upload artifacts | |
| steps: | |
| - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 | |
| - uses: actions/setup-node@1a4442cacd436585916779262731d5b162bc6ec7 # v3.8.2 | |
| with: | |
| node-version: 20.x | |
| - name: Check if sha256 lines changed in this PR for verifying image digest changes | |
| id: check-sha | |
| if: github.event_name == 'pull_request' | |
| run: | | |
| set -euo pipefail | |
| echo "Checking only Dockerfile and Dockerfile.plugins for sha256 changes" | |
| # Prefer the base SHA from the PR event, which works for both forks and same-repo branches. | |
| BASE_SHA="${{ github.event.pull_request.base.sha }}" | |
| echo "Initial diff target from PR base SHA: $BASE_SHA" | |
| # If the base SHA is not present in the local clone (e.g., shallow fetch), try fetching the base ref. | |
| if ! git cat-file -e "$BASE_SHA^{commit}" 2>/dev/null; then | |
| echo "Base SHA not found locally, trying to fetch base ref" | |
| BASE_REF="${{ github.event.pull_request.base.ref }}" | |
| git fetch origin "$BASE_REF" --depth=5000 || git fetch origin "$BASE_REF" || true | |
| if git show-ref --verify --quiet "refs/remotes/origin/${BASE_REF}"; then | |
| BASE_SHA="origin/${BASE_REF}" | |
| echo "Using origin/${BASE_REF} as diff target" | |
| else | |
| echo "Warning: could not fetch base ref; falling back to PR base SHA (may fail if missing)" | |
| fi | |
| fi | |
| # Try three-dot first; if there is no merge base (e.g., unrelated histories), | |
| # fall back to a simple two-dot diff which doesn't require a merge base. | |
| if git merge-base "$BASE_SHA" HEAD >/dev/null 2>&1; then | |
| DIFF_RANGE="${BASE_SHA}...HEAD" | |
| else | |
| echo "No merge-base between $BASE_SHA and HEAD; using two-dot diff" | |
| DIFF_RANGE="${BASE_SHA}..HEAD" | |
| fi | |
| if git diff -U0 "$DIFF_RANGE" -- Dockerfile Dockerfile.plugins | grep -E '^[+-].*sha256:' >/dev/null; then | |
| echo "sha_changed=true" | |
| echo "sha_changed=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "sha_changed=false" | |
| echo "sha_changed=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Verify container image digests | |
| if: github.event_name == 'pull_request' && steps.check-sha.outputs.sha_changed == 'true' | |
| run: npm run image:verify-image-digests | |
| - name: Start Cluster 1 | |
| uses: helm/kind-action@0025e74a8c7512023d06dc019c617aa3cf561fde # v1.0.0 | |
| with: | |
| cluster_name: test | |
| - name: Rename Cluster 1 kind context | |
| run: kubectl config rename-context kind-test test | |
| - name: Start Cluster 2 | |
| uses: helm/kind-action@0025e74a8c7512023d06dc019c617aa3cf561fde # v1.0.0 | |
| with: | |
| cluster_name: test2 | |
| - name: Rename Cluster 2 kind context | |
| run: kubectl config rename-context kind-test2 test2 | |
| - name: Use Cluster 1 context | |
| run: kubectl config use-context test | |
| # now you can run kubectl to see the pods in the cluster | |
| - name: Try the cluster! | |
| run: kubectl get pods -A | |
| - name: Restore image-cache Folder | |
| id: cache-image-restore2 | |
| uses: actions/cache/restore@0c907a75c2c80ebcb7f088228285e798b750cf8f # v4.2.1 | |
| with: | |
| path: ~/image-cache | |
| # cache the container image. All the paths this PR depends on except the e2e-tests folder for the key. | |
| key: ${{ runner.os }}-image-${{ hashFiles('backend/pkg/**', 'backend/cmd/**', 'backend/go.*', 'frontend/src/**', 'frontend/package.json', 'frontend/package-lock.json', 'Makefile', '.github/workflows/build-container.yml', 'Dockerfile', 'Dockerfile.plugins') }} | |
| - name: Restore Cached Docker Images | |
| if: steps.cache-image-restore2.outputs.cache-hit == 'true' | |
| run: | | |
| export SHELL=/bin/bash | |
| mkdir -p ~/image-cache | |
| gzip -dc ~/image-cache/headlamp-plugins-test.tar.gz | docker load | |
| gzip -dc ~/image-cache/headlamp.tar.gz | docker load | |
| - name: Make a .plugins folder for testing later | |
| if: steps.cache-image-restore2.outputs.cache-hit != 'true' | |
| run: | | |
| echo "Extract pod-counter plugin into .plugins folder, which will be copied into image later by 'make image'." | |
| cd plugins/examples/pod-counter | |
| npm ci | |
| npm run build | |
| cd ../../../ | |
| cd plugins/headlamp-plugin | |
| npm ci | |
| node bin/headlamp-plugin.js extract ../examples/pod-counter ../../.plugins/ | |
| cd ../../ | |
| ls -laR .plugins | |
| - name: Remove unnecessary files | |
| run: | | |
| sudo rm -rf /usr/share/dotnet | |
| sudo rm -rf "$AGENT_TOOLSDIRECTORY" | |
| sudo rm -rf /usr/local/lib/android | |
| sudo rm -rf /opt/ghc | |
| sudo rm -rf /usr/share/swift | |
| sudo apt-get clean | |
| - name: Build image | |
| if: steps.cache-image-restore2.outputs.cache-hit != 'true' | |
| run: | | |
| export SHELL=/bin/bash | |
| DOCKER_IMAGE_VERSION=latest make image | |
| DOCKER_IMAGE_VERSION=latest DOCKER_PLUGINS_IMAGE_NAME=headlamp-plugins-test make build-plugins-container | |
| echo -n "verifying images:" | |
| docker images | |
| - name: Import images to kind | |
| run: | | |
| export SHELL=/bin/bash | |
| # Load images into Cluster 1 (test) | |
| kind load docker-image ghcr.io/headlamp-k8s/headlamp-plugins-test:latest --name test | |
| kind load docker-image ghcr.io/headlamp-k8s/headlamp:latest --name test | |
| # Load Headlamp image into Cluster 2 (test2) so it can run in-cluster tests there | |
| kind load docker-image ghcr.io/headlamp-k8s/headlamp:latest --name test2 | |
| - name: Test .plugins folder | |
| if: steps.cache-image-restore2.outputs.cache-hit != 'true' | |
| run: | | |
| export SHELL=/bin/bash | |
| echo "----------------------------" | |
| echo "Test .plugins folder is copied to the right place in the image by 'make image'" | |
| echo "--- Files in the image /headlamp/ folder: ---" | |
| docker run --rm --entrypoint=/bin/sh ghcr.io/headlamp-k8s/headlamp:latest -c "cd /headlamp/ && find ." | |
| echo "----- Checking if the .plugins/ are copied to the right place in the image -----" | |
| docker run --rm --entrypoint=/bin/sh ghcr.io/headlamp-k8s/headlamp:latest -c "set -e; (cd /headlamp/plugins && [ -e pod-counter/package.json ] && [ -e pod-counter/main.js ]) || exit 1" | |
| echo "----- Checking if the plugins/example folder match copied docker plugins -----" | |
| # List contents of /plugins inside the container | |
| docker_output=$(docker run --rm --entrypoint=/bin/sh ghcr.io/headlamp-k8s/headlamp-plugins-test:latest -c "set -e; ls /plugins || exit 1") | |
| # Get the list of folders inside the examples folder | |
| examples_folder="plugins/examples" | |
| examples_content=$(ls "$examples_folder") | |
| # Check if the Docker output matches the examples folder content | |
| if [[ "$docker_output" == "$examples_content" ]]; then | |
| echo "Docker output matches examples folder content" | |
| else | |
| echo "Docker output does not match examples folder content" | |
| echo "Docker output: $docker_output" | |
| echo "----------------------------" | |
| echo "Examples content: $examples_content" | |
| exit 1 | |
| fi | |
| - name: Multi-Cluster Setup | |
| run: | | |
| # Use Cluster 1 context | |
| kubectl config use-context test | |
| export TEST_CA_DATA=$(kubectl config view --raw --minify -o jsonpath='{.clusters[0].cluster.certificate-authority-data}') | |
| export TEST_SERVER="https://$(kubectl get nodes -o=jsonpath='{.items[0].status.addresses[?(@.type=="InternalIP")].address}'):6443" | |
| kubectl create serviceaccount headlamp-admin --namespace kube-system | |
| kubectl create clusterrolebinding headlamp-admin --serviceaccount=kube-system:headlamp-admin --clusterrole=cluster-admin | |
| echo "HEADLAMP_TEST_TOKEN=$(kubectl create token headlamp-admin --duration 24h -n kube-system)" >> $GITHUB_ENV | |
| # Use Cluster 2 context | |
| kubectl config use-context test2 | |
| export TEST2_CA_DATA=$(kubectl config view --raw --minify -o jsonpath='{.clusters[0].cluster.certificate-authority-data}') | |
| export TEST2_SERVER="https://$(kubectl get nodes -o=jsonpath='{.items[0].status.addresses[?(@.type=="InternalIP")].address}'):6443" | |
| kubectl create serviceaccount headlamp-admin --namespace kube-system | |
| kubectl create clusterrolebinding headlamp-admin --serviceaccount=kube-system:headlamp-admin --clusterrole=cluster-admin | |
| echo "HEADLAMP_TEST2_TOKEN=$(kubectl create token headlamp-admin --duration 24h -n kube-system)" >> $GITHUB_ENV | |
| envsubst < e2e-tests/kubernetes-headlamp-ci.yaml | kubectl --context=test apply -f - | |
| - name: Run e2e tests | |
| run: | | |
| echo "------------------------------------sleeping 12...------------------------------------" | |
| sleep 12 | |
| kubectl config use-context test | |
| kubectl get services --all-namespaces | |
| kubectl get deployments -n kube-system | |
| echo "------------------Waiting for headlamp deployment to be available...------------------" | |
| kubectl wait deployment -n kube-system headlamp --for condition=Available=True --timeout=30s | |
| echo "----------------------------------Opening the service----------------------------------" | |
| IP_ADDRESS=$(kubectl get nodes -o=jsonpath='{.items[0].status.addresses[?(@.type=="InternalIP")].address}') | |
| SERVICE_PORT=$(kubectl get services headlamp -n kube-system -o=jsonpath='{.spec.ports[0].nodePort}') | |
| export SERVICE_URL="http://${IP_ADDRESS}:${SERVICE_PORT}" | |
| echo $SERVICE_URL | |
| curl -L $SERVICE_URL | grep -q "Headlamp: Kubernetes Web UI" | |
| echo "--------------------------------Export Tokens Received From Previous Step--------------------------------" | |
| export HEADLAMP_TEST_TOKEN=${HEADLAMP_TEST_TOKEN} | |
| export HEADLAMP_TEST2_TOKEN=${HEADLAMP_TEST2_TOKEN} | |
| echo "---------------------------------Certificate handling---------------------------------" | |
| export KUBECONFIG=$HOME/.kube/config | |
| ca_data=$(yq e '.clusters[0].cluster."certificate-authority-data"' $KUBECONFIG | base64 --decode) | |
| echo "$ca_data" > ca.crt | |
| kubectl config set-cluster kind-test --certificate-authority=$(pwd)/ca.crt --server=https://${IP_ADDRESS}:${SERVICE_PORT} | |
| kubectl config unset clusters.kind-test.certificate-authority-data | |
| cc_data=$(yq e '.users[0].user."client-certificate-data"' $KUBECONFIG | base64 --decode) | |
| echo "$cc_data" > client.crt | |
| ck_data=$(yq e '.users[0].user."client-key-data"' $KUBECONFIG | base64 --decode) | |
| echo "$ck_data" > client.key | |
| kubectl config set-credentials admin@kind-test --client-certificate=$(pwd)/client.crt --client-key=$(pwd)/client.key | |
| kubectl config unset [email protected] | |
| kubectl config unset [email protected] | |
| echo "Modified kubeconfig:" | |
| cat $KUBECONFIG | |
| echo "-----------------------------Running playwright e2e tests-----------------------------" | |
| cd e2e-tests | |
| npm ci | |
| npx playwright install --with-deps | |
| HEADLAMP_TEST_URL=$SERVICE_URL npx playwright test | |
| exit_code=$? | |
| if [ $exit_code -ne 0 ]; then | |
| echo "Playwright tests failed with exit code $exit_code" | |
| exit 1 | |
| else | |
| echo "Playwright tests passed successfully" | |
| fi | |
| - name: Deploy Headlamp in in-cluster mode and run TS API tests | |
| run: | | |
| # Reuse Cluster 2 context for in-cluster tests | |
| kubectl config use-context test2 | |
| # Setup service account and RBAC for Headlamp | |
| kubectl create serviceaccount headlamp --namespace kube-system || true | |
| kubectl create clusterrolebinding headlamp --serviceaccount=kube-system:headlamp --clusterrole=cluster-admin || true | |
| kubectl get serviceaccount headlamp -n kube-system | |
| # Deploy Headlamp with in-cluster mode | |
| kubectl apply -f e2e-tests/kubernetes-headlamp-incluster-ci.yaml | |
| echo "Waiting for headlamp deployment to be available (in-cluster)..." | |
| kubectl wait deployment -n kube-system headlamp --for condition=Available=True --timeout=120s | |
| kubectl get pods -n kube-system -l app.kubernetes.io/name=headlamp | |
| echo "Checking headlamp pod status (in-cluster)..." | |
| kubectl logs -n kube-system -l app.kubernetes.io/name=headlamp --tail=50 || true | |
| # Start port-forward in background | |
| echo "Starting port-forward to headlamp service (in-cluster)..." | |
| kubectl port-forward -n kube-system service/headlamp 8080:80 & | |
| PORT_FORWARD_PID=$! | |
| # Wait for port-forward to be ready | |
| sleep 5 | |
| # Verify port-forward is working | |
| if ! kill -0 $PORT_FORWARD_PID 2>/dev/null; then | |
| echo "❌ Port-forward failed to start for in-cluster test" | |
| exit 1 | |
| fi | |
| export HEADLAMP_TEST_URL="http://localhost:8080" | |
| echo "In-cluster Headlamp URL: $HEADLAMP_TEST_URL" | |
| # Get service account token for API checks | |
| echo "Getting service account token for in-cluster test..." | |
| export HEADLAMP_SA_TOKEN=$(kubectl create token headlamp --duration=1h -n kube-system) | |
| if [ -z "$HEADLAMP_SA_TOKEN" ]; then | |
| echo "❌ Failed to get service account token for in-cluster test" | |
| kill $PORT_FORWARD_PID 2>/dev/null || true | |
| exit 1 | |
| fi | |
| echo "✅ Service account token obtained" | |
| echo "Running in-cluster API Playwright tests..." | |
| # Run the TypeScript in-cluster API tests | |
| cd e2e-tests | |
| npm ci | |
| npx playwright install --with-deps | |
| npx playwright test tests/incluster-api.spec.ts | |
| EXIT_CODE=$? | |
| # Cleanup port-forward | |
| kill $PORT_FORWARD_PID 2>/dev/null || true | |
| if [ $EXIT_CODE -ne 0 ]; then | |
| echo "❌ In-cluster API tests failed with exit code $EXIT_CODE" | |
| exit $EXIT_CODE | |
| fi | |
| # Clear disk space by removing unnecessary files, apt files and uninstall some playwright dependencies | |
| - name: Clear Disk Space | |
| if: steps.cache-image-restore2.outputs.cache-hit != 'true' | |
| run: | | |
| export SHELL=/bin/bash | |
| sudo rm -rf /var/lib/apt/lists/* | |
| sudo apt-get remove -y libgbm-dev libxcb-dri3-0 libxcb-dri2-0 libxcb-xfixes0 libxcb-shape0 libxcb-shm0 libxcb-render0 libxcb-glx0 || true | |
| sudo rm -rf e2e-tests/node_modules/ | |
| kind delete cluster --name test | |
| kind delete cluster --name test2 | |
| - name: Save Docker Images to Tar files in image-cache Folder | |
| if: steps.cache-image-restore2.outputs.cache-hit != 'true' | |
| run: | | |
| export SHELL=/bin/bash | |
| mkdir -p ~/image-cache | |
| docker save ghcr.io/headlamp-k8s/headlamp-plugins-test | gzip -9 > ~/image-cache/headlamp-plugins-test.tar.gz | |
| docker save ghcr.io/headlamp-k8s/headlamp | gzip -9 > ~/image-cache/headlamp.tar.gz | |
| - name: Cache image-cache Folder | |
| if: steps.cache-image-restore2.outputs.cache-hit != 'true' | |
| id: cache-image-save | |
| uses: actions/cache/save@0c907a75c2c80ebcb7f088228285e798b750cf8f # v4.2.1 | |
| with: | |
| path: ~/image-cache | |
| key: ${{ steps.cache-image-restore2.outputs.cache-primary-key }} | |
| - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 | |
| if: always() | |
| with: | |
| name: e2e-tests-report | |
| path: e2e-tests/playwright-report/ | |
| retention-days: 30 |