diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 91172d1..3000bf0 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -7,6 +7,7 @@ on: - "docker-github-actions" - "release/v1" - "beta" + - "bugfix/e2e" tags: - "*" jobs: diff --git a/.github/workflows/e2e-test.yml b/.github/workflows/e2e-test.yml new file mode 100644 index 0000000..30a7085 --- /dev/null +++ b/.github/workflows/e2e-test.yml @@ -0,0 +1,83 @@ +name: E2E Test (reusable) + +on: + workflow_call: + inputs: + image-artifact-name: + required: true + type: string + distro-name: + required: true + type: string + docker-context: + required: false + type: string + default: 'testing/' + timeout-minutes: + required: false + type: number + default: 45 + poll-interval: + required: false + type: number + default: 5 + max-poll-iterations: + required: false + type: number + default: 360 + +jobs: + e2e-test: + runs-on: ubuntu-latest + timeout-minutes: ${{ inputs.timeout-minutes }} + steps: + - uses: actions/checkout@v4 + + - name: Download image from build + uses: actions/download-artifact@v4 + with: + name: ${{ inputs.image-artifact-name }} + path: image/ + + - name: Build test Docker image + run: DOCKER_BUILDKIT=0 docker build -t e2e-test ${{ inputs.docker-context }} + + - name: Start E2E test container + run: | + mkdir -p artifacts + IMG=$(find image/ -name '*.img' | head -1) + docker run -d --name e2e-test \ + -v "$PWD/artifacts:/output" \ + -v "$(realpath $IMG):/input/image.img:ro" \ + -e ARTIFACTS_DIR=/output \ + -e DISTRO_NAME="${{ inputs.distro-name }}" \ + e2e-test + + - name: Wait for tests to complete + run: | + for i in $(seq 1 ${{ inputs.max-poll-iterations }}); do + [ -f artifacts/exit-code ] && break + sleep ${{ inputs.poll-interval }} + done + if [ ! -f artifacts/exit-code ]; then + echo "ERROR: Tests did not complete within timeout" + docker logs e2e-test 2>&1 | tail -80 + exit 1 + fi + echo "Tests finished with exit code: $(cat artifacts/exit-code)" + cat artifacts/test-results.txt 2>/dev/null || true + + - name: Collect logs and stop container + if: always() + run: | + docker logs e2e-test > artifacts/container.log 2>&1 || true + docker stop e2e-test 2>/dev/null || true + + - name: Check test result + run: exit "$(cat artifacts/exit-code 2>/dev/null || echo 1)" + + - uses: actions/upload-artifact@v4 + if: always() + with: + name: e2e-test-results + path: artifacts/ diff --git a/src/distro_testing/scripts/prepare-image.sh b/src/distro_testing/scripts/prepare-image.sh index d78de60..7a7dcc6 100755 --- a/src/distro_testing/scripts/prepare-image.sh +++ b/src/distro_testing/scripts/prepare-image.sh @@ -23,6 +23,13 @@ write /etc/fstab "proc /proc proc defaults 0 0\n/dev/vda1 /boot/firmware vfat de -rm /etc/systemd/system/multi-user.target.wants/userconfig.service -rm /usr/lib/systemd/system/userconfig.service +# userconfig.service is what normally drops /etc/sudoers.d/010_pi-nopasswd. +# Since we remove it, seed the file ourselves so test scripts (post-boot hooks, +# apt-get installs, etc.) can use passwordless sudo over a non-TTY SSH session. +mkdir-p /etc/sudoers.d +write /etc/sudoers.d/010_pi-nopasswd "pi ALL=(ALL) NOPASSWD: ALL\n" +chmod 0440 /etc/sudoers.d/010_pi-nopasswd + mkdir-p /etc/ssh/sshd_config.d write /etc/ssh/sshd_config.d/99-qemu-test.conf "PasswordAuthentication yes\nPermitRootLogin yes\nKbdInteractiveAuthentication yes\n" @@ -71,17 +78,20 @@ chmod 0644 /etc/ssh/ssh_host_ecdsa_key.pub chmod 0644 /etc/ssh/ssh_host_ed25519_key.pub download /etc/shadow /tmp/shadow.bak +download /etc/passwd /tmp/passwd.bak umount / GFEOF -echo 'Setting pi user password...' +echo 'Setting pi user password and shell...' sed -i "s|^pi:[^:]*:|pi:${PIPASS}:|" /tmp/shadow.bak +sed -i 's|^pi:\(.*\):/usr/sbin/nologin$|pi:\1:/bin/bash|' /tmp/passwd.bak guestfish -a "$OUTPUT_IMAGE" </dev/null) +OUTPUT=$(ssh_cmd 'echo hello world' 2>/dev/null) if [ "$OUTPUT" = "hello world" ]; then echo " Output: '$OUTPUT'"