From e53df19908c28024c30beed11549404fbfba1c1f Mon Sep 17 00:00:00 2001 From: Chris Hofstaedtler Date: Sun, 12 Nov 2023 02:43:28 +0100 Subject: [PATCH] GitHub Actions: build and test grml-debootstrap in PRs --- .github/workflows/test-build.yml | 100 +++++++++++++++++++++++ .gitignore | 3 + .travis.yml | 15 ---- tests/build-vm-and-test.sh | 78 ++++++++++++++++++ tests/docker-build-deb.sh | 44 ++++++++++ tests/docker-build-vm.sh | 51 ++++++++++++ tests/docker-install-deb.sh | 19 +++++ tests/gha-build-deb.sh | 15 ++++ {travis => tests}/goss.yaml | 0 tests/serial-console-connection | 105 ++++++++++++++++++++++++ tests/test-vm.sh | 140 +++++++++++++++++++++++++++++++ travis/build-vm.sh | 21 ----- travis/execute.sh | 172 --------------------------------------- travis/serial-console-connection | 112 ------------------------- 14 files changed, 555 insertions(+), 320 deletions(-) create mode 100644 .github/workflows/test-build.yml delete mode 100644 .travis.yml create mode 100755 tests/build-vm-and-test.sh create mode 100755 tests/docker-build-deb.sh create mode 100755 tests/docker-build-vm.sh create mode 100755 tests/docker-install-deb.sh create mode 100755 tests/gha-build-deb.sh rename {travis => tests}/goss.yaml (100%) create mode 100755 tests/serial-console-connection create mode 100755 tests/test-vm.sh delete mode 100755 travis/build-vm.sh delete mode 100755 travis/execute.sh delete mode 100755 travis/serial-console-connection diff --git a/.github/workflows/test-build.yml b/.github/workflows/test-build.yml new file mode 100644 index 0000000..e5cc365 --- /dev/null +++ b/.github/workflows/test-build.yml @@ -0,0 +1,100 @@ +name: test-build +on: + pull_request: +concurrency: + group: "${{ github.ref }}" + cancel-in-progress: true +jobs: + build-debian: + strategy: + # Keep other matrix jobs running, even if one fails. + fail-fast: false + matrix: + host_release: + - unstable + - trixie + - bookworm + - bullseye + + # We want a working shell, qemu, python and docker. Specific version should not matter (much). + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - run: ./tests/gha-build-deb.sh + name: "Build .deb for ${{matrix.host_release}}" + env: + HOST_RELEASE: ${{matrix.host_release}} + + - name: Archive built .deb + uses: actions/upload-artifact@v3 + with: + name: deb-${{matrix.host_release}} + if-no-files-found: error + path: | + *.deb + + test-debian: + needs: build-debian + strategy: + # Keep other matrix jobs running, even if one fails. + fail-fast: false + matrix: + host_release: + - unstable + - trixie + - bookworm + - bullseye + + release: + - trixie + - bookworm + - bullseye + - buster + - stretch + + debootstrap: + - '' + - mmdebstrap + + exclude: + # debootstrap in bullseye is too old. + - host_release: bullseye + release: trixie + # unclear how to pass --no-check-gpg to mmdebstrap + - release: stretch + debootstrap: mmdebstrap + + # We want a working shell, qemu, python and docker. Specific version should not matter (much). + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Download built deb + uses: actions/download-artifact@v3 + with: + name: deb-${{matrix.host_release}} + + - run: ./tests/build-vm-and-test.sh setup + name: "Setup test environment" + + - run: ./tests/build-vm-and-test.sh run + name: "Build VM image using grml-debootstrap on host ${{matrix.host_release}} for ${{matrix.release}} using debootstrap=${{matrix.debootstrap}}" + env: + HOST_RELEASE: ${{matrix.host_release}} + RELEASE: ${{matrix.release}} + DEBOOTSTRAP: ${{matrix.debootstrap}} + + - run: ./tests/build-vm-and-test.sh test + name: "Test built VM image for ${{matrix.release}}" + env: + RELEASE: ${{matrix.release}} + + - name: Archive VM test results + uses: actions/upload-artifact@v3 + with: + name: vm-results-${{matrix.host_release}}-${{matrix.release}}-${{matrix.debootstrap}} + if-no-files-found: error + path: tests/results/ diff --git a/.gitignore b/.gitignore index 74c16de..06e1213 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ *.box packer_cache *_output +qemu.img +tests/dgoss +tests/goss diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 98b0e4b..0000000 --- a/.travis.yml +++ /dev/null @@ -1,15 +0,0 @@ -language: generic -sudo: required - -services: - - docker - -env: - - TRAVIS_DEBIAN_DISTRIBUTION=buster - - TRAVIS_DEBIAN_DISTRIBUTION=unstable TRAVIS_DEBIAN_INCREMENT_VERSION_NUMBER=true - -script: - - ./travis/execute.sh - -matrix: - fast_finish: true diff --git a/tests/build-vm-and-test.sh b/tests/build-vm-and-test.sh new file mode 100755 index 0000000..1dc4bbc --- /dev/null +++ b/tests/build-vm-and-test.sh @@ -0,0 +1,78 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Install an already built grml-debootstrap.deb in docker and use it to +# build a test VM image. Then run this VM image in qemu and check if it +# boots. + +set -eu -o pipefail + +usage() { + echo "Usage: $0 setup" + echo " then: $0 run" + echo " then: $0 test" + echo "WARNING: $0 is potentially dangerous and may destroy the host system and/or any data." + exit 0 +} + +if [ "${1:-}" == "--help" ] || [ "${1:-}" == "help" ]; then + usage +fi + +if [ -z "${1:-}" ]; then + echo "$0: unknown parameters, see --help" >&2 + exit 1 +fi + +set -x + +if [ ! -d ./tests ]; then + echo "$0: Started from incorrect working directory" >&2 + exit 1 +fi + +if [ "$1" == "setup" ]; then + [ -x ./tests/goss ] || curl -fsSL https://goss.rocks/install | GOSS_DST="$(pwd)/tests" sh + sudo apt-get update + sudo apt-get -y install qemu-system-x86 kpartx python3-pexpect python3-serial + # TODO: docker.io + exit 0 +fi + +# Debian version to install using grml-debootstrap +RELEASE="${RELEASE:-bookworm}" + +TARGET="${TARGET:-qemu.img}" + +# debootstrap to use, default empty (let grml-debootstrap decide) +DEBOOTSTRAP="${DEBOOTSTRAP:-}" + +if [ "$1" == "run" ]; then + # Debian version on which grml-debootstrap will *run* + HOST_RELEASE="${HOST_RELEASE:-bookworm}" + + DEB_NAME=$(ls ./grml-debootstrap*.deb || true) + if [ -z "$DEB_NAME" ]; then + echo "$0: No grml-debootstrap*.deb found, aborting" >&2 + exit 1 + fi + + # we need to run in privileged mode to be able to use loop devices + exec docker run --privileged --rm -i \ + -v "$(pwd)":/code \ + -e TERM="$TERM" \ + -e DEBOOTSTRAP="$DEBOOTSTRAP" \ + -w /code \ + debian:"$HOST_RELEASE" \ + bash -c './tests/docker-install-deb.sh '"$DEB_NAME"' && ./tests/docker-build-vm.sh '"$(id -u)"' '"/code/$TARGET"' '"$RELEASE" + +elif [ "$1" == "test" ]; then + # run tests from inside Debian system + exec ./tests/test-vm.sh "$PWD/$TARGET" "$RELEASE" + +else + echo "$0: unknown parameters, see --help" >&2 + exit 1 +fi + +# EOF diff --git a/tests/docker-build-deb.sh b/tests/docker-build-deb.sh new file mode 100755 index 0000000..f9a8c8d --- /dev/null +++ b/tests/docker-build-deb.sh @@ -0,0 +1,44 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Build a grml-debootstrap.deb. +# To be run inside docker, as this script assumes it can modify the running OS. + +set -eu -o pipefail +set -x + +if [ "${1:-}" != "--autobuild" ]; then + echo "$0: Only intended for CI scenarios, will destroy source files and modify running OS." >&2 + exit 1 +fi +BUILD_NUMBER="${2:-}" +if [ -z "$BUILD_NUMBER" ]; then + echo "$0: missing build number in arguments" >&2 + exit 1 +fi + +apt-get update +apt-get install --no-install-recommends -y build-essential devscripts equivs + +SOURCEDIR=$PWD + +cd /tmp +mk-build-deps -ir -t 'apt-get -o Debug::pkgProblemResolver=yes --no-install-recommends -y' "$SOURCEDIR"/debian/control + +dpkg-source -b "$SOURCEDIR" +dpkg-source -x ./*.dsc builddir +cd builddir + +OLD_VERSION=$(dpkg-parsechangelog -SVersion) + +cat > debian/changelog < $(date -R) +EOT + +dpkg-buildpackage -b --no-sign + +mv ../*deb "$SOURCEDIR"/ diff --git a/tests/docker-build-vm.sh b/tests/docker-build-vm.sh new file mode 100755 index 0000000..07578f7 --- /dev/null +++ b/tests/docker-build-vm.sh @@ -0,0 +1,51 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Use an already installed grml-debootstrap to build a VM image, then +# run it in qemu. Installs goss inside the VM. + +set -eu -o pipefail + +if [ "$#" -ne 3 ]; then + echo "$0: Invalid arguments" >&2 + echo "Expect: $0 HOST_UID TARGET RELEASE" >&2 + exit 1 +fi +HOST_UID="$1" +TARGET="$2" +RELEASE="$3" + +if [ -n "${DEBOOTSTRAP:-}" ] && [ "${DEBOOTSTRAP:-}" != "debootstrap" ]; then + apt-get install -y "${DEBOOTSTRAP}" +fi + +set -x + +case "${RELEASE:-}" in + stretch) + MIRROR='http://archive.debian.org/debian' + EXTRAOPT=--debopt=--no-check-gpg + ;; + *) + MIRROR='http://deb.debian.org/debian' + EXTRAOPT='' + ;; +esac + + +echo " ****************************************************************** " +echo " * Running grml-debootstrap" + +grml-debootstrap \ + --force \ + --vmfile \ + --vmsize 3G \ + --target "$TARGET" \ + --bootappend "console=ttyS0,115200 console=tty0 vga=791" \ + --password grml \ + --release "$RELEASE" \ + --hostname "$RELEASE" \ + --mirror "$MIRROR" \ + $EXTRAOPT + +chown "$HOST_UID" "$TARGET" diff --git a/tests/docker-install-deb.sh b/tests/docker-install-deb.sh new file mode 100755 index 0000000..9ba38d0 --- /dev/null +++ b/tests/docker-install-deb.sh @@ -0,0 +1,19 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Install an already built grml-debootstrap.deb. +# Wrapper around apt-get install for usage inside docker. + +set -eu -o pipefail + +if [ "$#" -ne 1 ]; then + echo "$0: Invalid arguments" >&2 + echo "Expect: $0 DEB_NAME" >&2 + exit 1 +fi +DEB_NAME="$1" + +apt-get update +# docker images can be relatively old, especially for unstable. +apt-get -y upgrade +apt-get -y install "$DEB_NAME" diff --git a/tests/gha-build-deb.sh b/tests/gha-build-deb.sh new file mode 100755 index 0000000..a031c96 --- /dev/null +++ b/tests/gha-build-deb.sh @@ -0,0 +1,15 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Entrypoint for GitHub Actions to build a grml-debootstrap.deb. + +set -eu -o pipefail +set -x + +if [ -z "${CI:-}" ] || [ -z "${GITHUB_RUN_NUMBER:-}" ]; then + echo "Running outside of CI pipeline." >&2 + exit 1 +fi + +docker run --privileged -v "$(pwd)":/code --rm -i debian:"$HOST_RELEASE" \ + bash -c 'TERM='"$TERM"' cd /code && ./tests/docker-build-deb.sh --autobuild '"$GITHUB_RUN_NUMBER" diff --git a/travis/goss.yaml b/tests/goss.yaml similarity index 100% rename from travis/goss.yaml rename to tests/goss.yaml diff --git a/tests/serial-console-connection b/tests/serial-console-connection new file mode 100755 index 0000000..97c623b --- /dev/null +++ b/tests/serial-console-connection @@ -0,0 +1,105 @@ +#!/usr/bin/env python +import argparse +import pexpect +import serial +import sys +import time +from pexpect import fdpexpect, TIMEOUT + +parser = argparse.ArgumentParser(description='Connect to serial console ' + + 'to execute stuff') +parser.add_argument('--port', required=True, + help='serial console device to connect ' + + 'to (e.g. /dev/pts/X)') +parser.add_argument('--hostname', default="buster", + help='hostname of the system for login process ' + + '(default: buster)') +parser.add_argument('--user', default="root", + help='user name to use for login (default: root)') +parser.add_argument('--password', default="grml", + help='password for login (default: grml)') +parser.add_argument('--tries', default="12", type=int, + help='Number of retries for finding the login prompt') +parser.add_argument('--poweroff', action="store_true", default=False, + help='send "poweroff" command after all other commands') +parser.add_argument('command', nargs='+', + help='command to execute after logging in') + + + +def print_ser_lines(ser): + for line in ser.readlines(): + print("<<", line) # line will be a binary string + + +def write_ser_line(ser, text): + print(">>", text) + ser.write(("%s\n" % text).encode()) + ser.flush() + + +def login(ser, hostname, user, password, timeout=5): + + child = fdpexpect.fdspawn(ser.fileno()) + child.sendline("\n") + + try: + child.expect("%s@%s" % (user, hostname), timeout=timeout) + return + except: + pass + + print("Waiting for login prompt...") + child.expect("%s login:" % hostname, timeout=timeout) + print("Logging in...") + write_ser_line(ser, user) + time.sleep(1) + write_ser_line(ser, password) + time.sleep(1) + + print("Waiting for shell prompt...") + child.expect("%s@%s" % (user, hostname), timeout=timeout) + + +def main(): + args = parser.parse_args() + hostname = args.hostname + password = args.password + port = args.port + user = args.user + commands = args.command + + ser = serial.Serial(port, 115200) + ser.flushInput() + ser.flushOutput() + + success = False + for i in range(args.tries): + try: + print("Logging into %s via serial console [try %s]" % (port, i)) + login(ser, hostname, user, password) + success = True + break + except Exception as except_inst: + print("Login failure (try %s):" % (i, ), except_inst, file=sys.stderr) + time.sleep(5) + + if success: + write_ser_line(ser, "") + ser.timeout = 5 + print_ser_lines(ser) + print("Running commands...") + for command in commands: + write_ser_line(ser, command) + print_ser_lines(ser) + if args.poweroff: + print("Sending final poweroff command...") + write_ser_line(ser, "poweroff") + ser.flush() + # after poweroff, the serial device will probably vanish. do not attempt reading from it anymore. + + if not success: + sys.exit(1) + +if __name__ == "__main__": + main() diff --git a/tests/test-vm.sh b/tests/test-vm.sh new file mode 100755 index 0000000..85e46c5 --- /dev/null +++ b/tests/test-vm.sh @@ -0,0 +1,140 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Run previously built VM image in qemu and check if it boots. +# Requires virtiofs support in VM. + +set -eu -o pipefail + +if [ "$#" -ne 2 ]; then + echo "$0: Invalid arguments" >&2 + echo "Expect: $0 VM_IMAGE VM_HOSTNAME" >&2 + exit 1 +fi +set -x + +VM_IMAGE="$1" +VM_HOSTNAME="$2" + +TEST_PWD="$PWD" +TEST_TMPDIR=$(mktemp -d) +TESTS_RESULTSDIR="$PWD/tests/results" +echo "Working in $TEST_TMPDIR, writing results to $TESTS_RESULTSDIR" +mkdir -p "$TESTS_RESULTSDIR" + +bailout() { + if [ -n "${QEMU_PID:-}" ] ; then + # shellcheck disable=SC2009 + ps --pid="${QEMU_PID}" -o pid= | grep -q '.' && kill "${QEMU_PID:-}" + fi + + rm -rf "${TEST_TMPDIR}" + + [ -n "${1:-}" ] && EXIT_CODE="$1" || EXIT_CODE=1 + exit "$EXIT_CODE" +} +trap bailout 1 2 3 6 14 15 + +# Setup test runner +cp ./tests/goss "$TEST_TMPDIR"/ +cp ./tests/goss.yaml "$TEST_TMPDIR"/ +cat < "$TEST_TMPDIR"/testrunner +#!/bin/bash +# Do not set -eu, we want to continue even if individual commands fail. +set -x +echo "INSIDE_VM $0 running" +mkdir results + +# Collect information from VM first +lsb_release -a > results/lsb_release.txt +uname -a > results/uname-a.txt +systemctl list-units > results/systemctl_list-units.txt +systemctl status > results/systemctl_status.txt +fdisk -l > results/fdisk-l.txt +hostname -f > results/hostname-f.txt 2>&1 +journalctl -b > results/journalctl-b.txt +dpkg -l > results/dpkg-l.txt + +# Run tests +./goss --gossfile goss.yaml validate --format tap > results/goss.tap +# Detection of testrunner success hinges on goss.exitcode file. +echo \$? > results/goss.exitcode + +echo "INSIDE_VM $0 finished" +EOT +chmod a+rx "$TEST_TMPDIR"/testrunner + +cd "$TEST_TMPDIR" + +MOUNT_TAG=host0 +qemu-system-x86_64 -hda "${VM_IMAGE}" -m 2048 \ + -display none -vnc :0 \ + -virtfs local,path="$TEST_TMPDIR",mount_tag="$MOUNT_TAG",security_model=none,id=host0 \ + -serial pty &>qemu.log & +QEMU_PID="$!" + +timeout=30 +success=0 +while [ "$timeout" -gt 0 ] ; do + ((timeout--)) + if grep -q 'char device redirected to ' qemu.log ; then + success=1 + sleep 1 + break + else + echo "No serial console from Qemu found yet [$timeout retries left]" + sleep 1 + fi +done + +if [ "$success" = "1" ] ; then + serial_port=$(awk '/char device redirected/ {print $5}' qemu.log) +else + echo "Error: Failed to identify serial console port." >&2 + cat qemu.log + exit 1 +fi + +timeout=30 +success=0 +while [ "$timeout" -gt 0 ] ; do + ((timeout--)) + if [ -c "$serial_port" ] ; then + success=1 + sleep 1 + break + else + echo "No block device for serial console found yet [$timeout retries left]" + sleep 1 + fi +done + +if [ "$success" = "0" ] ; then + echo "Error: can't access serial console block device." >&2 + exit 1 +fi + +"$TEST_PWD"/tests/serial-console-connection \ + --tries 180 \ + --port "$serial_port" \ + --hostname "$VM_HOSTNAME" \ + --poweroff \ + "mount -t 9p -o trans=virtio,version=9p2000.L,rw $MOUNT_TAG /mnt && cd /mnt && ./testrunner" + +if [ ! -d results ] || [ ! -f ./results/goss.tap ] || [ ! -f ./results/goss.exitcode ]; then + echo "Running tests inside VM failed for unknown reason" >&2 + RC=1 +else + RC=$(cat results/goss.exitcode) + echo "goss exitcode: $RC" + + cat results/goss.tap +fi + +echo "Finished serial console connection [timeout=${timeout}]." + +mv results/* "$TESTS_RESULTSDIR/" + +bailout $RC + +# EOF diff --git a/travis/build-vm.sh b/travis/build-vm.sh deleted file mode 100755 index 217813b..0000000 --- a/travis/build-vm.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash - -set -eu -o pipefail - -TARGET="${TARGET:-/code/qemu.img}" -RELEASE="${RELEASE:-buster}" - -cd "$(dirname "$TARGET")" -apt update -apt -y install ./grml-debootstrap*.deb - -grml-debootstrap \ - --force \ - --vmfile \ - --vmsize 3G \ - --target "$TARGET" \ - --bootappend "console=ttyS0,115200 console=tty0 vga=791" \ - --password grml \ - --release "$RELEASE" \ - --hostname "$RELEASE" \ - # EOF diff --git a/travis/execute.sh b/travis/execute.sh deleted file mode 100755 index 5b4f47a..0000000 --- a/travis/execute.sh +++ /dev/null @@ -1,172 +0,0 @@ -#!/bin/bash - -set -eu -o pipefail -set -x - -if [ -z "${TRAVIS:-}" ] ; then - echo "Running outside of Travis." - - if [ "$#" -ne 1 ] ; then - echo "Usage: $(basename "$0") ./grml-debootstrap*.deb" >&2 - exit 1 - else - GRML_DEBOOTSTRAP_DEB="$1" - if [ "$(dirname "$(realpath "$GRML_DEBOOTSTRAP_DEB")")" != "$(pwd)" ] ; then - echo "Error: the grml-debootstrap*.deb needs to be inside $(pwd) to be shared with docker container." >&2 - exit 1 - fi - fi -fi - -RELEASE="${RELEASE:-buster}" -export RELEASE - -TARGET="${TARGET:-qemu.img}" - -bailout() { - if [ -n "${QEMU_PID:-}" ] ; then - # shellcheck disable=SC2009 - ps --pid="${QEMU_PID}" -o pid= | grep -q '.' && kill "${QEMU_PID:-}" - fi - - if [ -f "${TARGET:-}" ] ; then - sudo kpartx -dv "$(realpath "${TARGET}")" - fi - - if [ -n "${LOOP_DISK:-}" ] ; then - if sudo dmsetup ls | grep -q "${LOOP_DISK}"; then - sudo kpartx -d "/dev/${LOOP_DISK}" - fi - fi - - local loopmount - loopmount="$(sudo losetup -a | grep "$(realpath "${TARGET}")" | cut -f1 -d: || true)" - - if [ -n "${loopmount:-}" ] ; then - sudo losetup -d "${loopmount}" - fi - - [ -n "${1:-}" ] && EXIT_CODE="$1" || EXIT_CODE=1 - exit "$EXIT_CODE" -} -trap bailout 1 2 3 6 14 15 - -# run shellcheck tests -docker run koalaman/shellcheck:stable --version -docker run --rm -v "$(pwd)":/code koalaman/shellcheck:stable -e SC2181 -e SC2001 /code/chroot-script /code/grml-debootstrap - -# build Debian package -if [ -z "${TRAVIS:-}" ] ; then - echo "Not running under Travis, installing local grml-debootstrap package ${GRML_DEBOOTSTRAP_DEB}." -else - if ! [ "${TRAVIS_DEBIAN_DISTRIBUTION:-}" = "unstable" ] ; then - echo "TRAVIS_DEBIAN_DISTRIBUTION is $TRAVIS_DEBIAN_DISTRIBUTION and not unstable, skipping VM build tests." - exit 0 - fi - wget -O- https://travis.debian.net/script.sh | sh - - # copy only the binary from the TRAVIS_DEBIAN_INCREMENT_VERSION_NUMBER=true build - cp ../grml-debootstrap_*travis*deb . -fi - -# we need to run in privileged mode to be able to use loop devices -docker run --privileged -v "$(pwd)":/code --rm -i -t debian:buster /code/travis/build-vm.sh - -[ -x ./goss ] || curl -fsSL https://goss.rocks/install | GOSS_DST="$(pwd)" sh - -# Ubuntu trusty (14.04LTS) doesn't have realpath in coreutils yet -if ! command -v realpath &>/dev/null ; then - REALPATH_PACKAGE=realpath -fi - -sudo apt-get update -sudo apt-get -y install qemu-system-x86 kpartx python-pexpect python-serial ${REALPATH_PACKAGE:-} - -# run tests from inside Debian system -DEVINFO=$(sudo kpartx -asv "${TARGET}") -LOOP_PART="${DEVINFO##add map }" -LOOP_PART="${LOOP_PART// */}" -LOOP_DISK="${LOOP_PART%p*}" -IMG_FILE="/dev/mapper/$LOOP_PART" - -MNTPOINT="$(mktemp -d)" -sudo mount "$IMG_FILE" "${MNTPOINT}" - -sudo cp ./goss "${MNTPOINT}"/usr/local/bin/goss -sudo cp ./travis/goss.yaml "${MNTPOINT}"/root/goss.yaml - -sudo umount "${MNTPOINT}" -sudo kpartx -dv "$(realpath "${TARGET}")" -if sudo dmsetup ls | grep -q "${LOOP_DISK}"; then - sudo kpartx -d "/dev/${LOOP_DISK}" -fi - -rmdir "$MNTPOINT" - -sudo chown "$(id -un)" qemu.img -rm -f ./serial0 -mkfifo ./serial0 -qemu-system-x86_64 -hda qemu.img -display none -vnc :0 \ - -device virtio-serial-pci \ - -chardev pipe,id=ch0,path=./serial0 \ - -device virtserialport,chardev=ch0,name=serial0 \ - -serial pty &>qemu.log & -QEMU_PID="$!" - -timeout=30 -success=0 -while [ "$timeout" -gt 0 ] ; do - ((timeout--)) - if grep -q 'char device redirected to ' qemu.log ; then - success=1 - sleep 1 - break - else - echo "No serial console from Qemu found yet [$timeout retries left]" - sleep 1 - fi -done - -if [ "$success" = "1" ] ; then - serial_port=$(awk '/char device redirected/ {print $5}' qemu.log) -else - echo "Error: Failed to identify serial console port." >&2 - exit 1 -fi - -timeout=30 -success=0 -while [ "$timeout" -gt 0 ] ; do - ((timeout--)) - if [ -c "$serial_port" ] ; then - success=1 - sleep 1 - break - else - echo "No block device for serial console found yet [$timeout retries left]" - sleep 1 - fi -done - -if [ "$success" = "0" ] ; then - echo "Error: can't access serial console block device." >&2 - exit 1 -fi - -sudo chown "$(id -un)" "$serial_port" -./travis/serial-console-connection --port "$serial_port" --hostname "$RELEASE" --pipefile "serial0" --vmoutput "vm-output.txt" - -cat vm-output.txt - -RC=0 -if grep -q '^failure_exit' vm-output.txt ; then - echo "We noticed failing tests." - RC=1 -else - echo "All tests passed." -fi - -echo "Finished serial console connection [timeout=${timeout}]." - -bailout $RC - -# EOF diff --git a/travis/serial-console-connection b/travis/serial-console-connection deleted file mode 100755 index ff3fbd0..0000000 --- a/travis/serial-console-connection +++ /dev/null @@ -1,112 +0,0 @@ -#!/usr/bin/env python - -from __future__ import print_function -import argparse -import pexpect -import serial -import sys -import time -from pexpect import fdpexpect - -parser = argparse.ArgumentParser(description='Connect to serial console ' + - 'to execute stuff') -parser.add_argument('--port', required=True, - help='serial console device to connect ' + - 'to (e.g. /dev/pts/X)') -parser.add_argument('--hostname', default="buster", - help='hostname of the system for login process ' + - '(default: buster)') -parser.add_argument('--pipefile', default="./serial0", - help='file name for named pipe file (for ' + - 'interacting between host + VM via QEMU ' + - '(default: ./serial0)') -parser.add_argument('--vmoutput', default="vm-output.log", - help='filename for VM output (default: vm-output.log)') -parser.add_argument('--user', default="root", - help='user name to use for login (default: root)') -parser.add_argument('--password', default="grml", - help='password for login (default: grml)') -args = parser.parse_args() - - -def execute(port, hostname, user, baudrate=115200, timeout=5): - ser = serial.Serial(port, baudrate) - ser.flushInput() - ser.flushOutput() - ser.write("\n") - ser.flush() - - child = fdpexpect.fdspawn(ser.fileno()) - child.sendline("") - try: - print("Begin of execution inside VM") - child.expect("%s@%s" % (user, hostname), timeout=timeout) - child.sendline("/usr/local/bin/goss --gossfile /root/goss.yaml " + - "validate --format tap > /root/goss.tap ; " + - "echo $? > /root/goss.exitcode\n") - # NOTE - the serial0 is hardcoded here - child.sendline("cat /root/goss.tap > /dev/virtio-ports/serial0\n") - child.sendline("grep -q '^0' /root/goss.exitcode && " + - "echo clean_exit > /dev/virtio-ports/serial0\n") - child.sendline("grep -q '^0' /root/goss.exitcode || " + - "echo failure_exit > /dev/virtio-ports/serial0\n") - child.sendline("poweroff\n") - print("End of execution inside VM") - except Exception as except_inst: - print("Execution inside VM failed: ", except_inst) - - -def login(port, hostname, user, password, - baudrate=115200, timeout=5): - ser = serial.Serial(port, baudrate) - ser.flushInput() - ser.flushOutput() - - child = fdpexpect.fdspawn(ser.fileno()) - child.sendline("\n") - - try: - child.expect("root@%s" % hostname, timeout=timeout) - return - except: - pass - - print("Checking for login prompt...") - child.expect("%s login:" % hostname, timeout=timeout) - ser.write("%s\n" % user) - ser.flush() - time.sleep(1) - ser.write("%s\n" % password) - ser.flush() - time.sleep(1) - print("login ok...") - - -if __name__ == "__main__": - hostname = args.hostname - password = args.password - pipefile = args.pipefile - port = args.port - user = args.user - vmoutput = args.vmoutput - - with open(pipefile, 'r') as output_pipe: - success = False - for i in range(12): - try: - print("Logging into {0} via serial " - "console [try {1}]".format(port, i)) - login(port, hostname, user, password) - success = True - break - except Exception as except_inst: - print("Login failure (try {0}):".format(i), - except_inst, file=sys.stderr) - time.sleep(5) - - if success: - execute(port, hostname, user) - with open(vmoutput, 'w') as fp: - output = output_pipe.read() - print(output) - fp.write(output) -- 2.1.4