From aea6ecf86e2dcaa196da5de15c4363c9c2b36273 Mon Sep 17 00:00:00 2001 From: Andrii Nikitin Date: Tue, 25 Apr 2023 11:48:29 +0200 Subject: [PATCH] ci(podman): add state tests in simple container --- .github/workflows/test.yaml | 20 ++++++ t/01-smoke-server.sh | 12 ++++ t/02-smoke-client.sh | 29 ++++++++ t/10-password.sh | 62 +++++++++++++++++ t/Makefile | 8 +++ t/README.md | 77 +++++++++++++++++++++ t/lib/Dockerfile.debian.11 | 19 ++++++ t/lib/Dockerfile.opensuse.leap | 21 ++++++ t/lib/test-in-container-systemd.sh | 106 +++++++++++++++++++++++++++++ 9 files changed, 354 insertions(+) create mode 100644 .github/workflows/test.yaml create mode 100755 t/01-smoke-server.sh create mode 100755 t/02-smoke-client.sh create mode 100755 t/10-password.sh create mode 100644 t/Makefile create mode 100644 t/README.md create mode 100644 t/lib/Dockerfile.debian.11 create mode 100644 t/lib/Dockerfile.opensuse.leap create mode 100755 t/lib/test-in-container-systemd.sh diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 00000000..68055887 --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,20 @@ +name: CI + +on: [push, pull_request, workflow_dispatch] + +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + img: + - opensuse.leap + - debian.11 + steps: + - uses: actions/checkout@v2 + - name: Test + run: | + cd t && \ + make test_container + env: + T_IMAGE: ${{ matrix.img }} diff --git a/t/01-smoke-server.sh b/t/01-smoke-server.sh new file mode 100755 index 00000000..2a639280 --- /dev/null +++ b/t/01-smoke-server.sh @@ -0,0 +1,12 @@ +#!lib/test-in-container-systemd.sh mariadb + +set -ex + +salt-call --local state.apply 'mysql' +salt-call --local state.apply 'mysql.remove_test_database' + +mysql -e 'select user(), current_user(), version()' + +service mysql status + +echo success diff --git a/t/02-smoke-client.sh b/t/02-smoke-client.sh new file mode 100755 index 00000000..b97870c4 --- /dev/null +++ b/t/02-smoke-client.sh @@ -0,0 +1,29 @@ +#!lib/test-in-container-systemd.sh mariadb-client + +set -ex + +######################### +# workaround for https://github.com/saltstack-formulas/mysql-formula/issues/267 +( grep -qi debian /etc/*release && mkdir -p /etc/mysql ) || \ + ( grep -qi suse /etc/*release && mkdir -p /etc/my.cnf.d ) || \ + : +######################### + +salt-call --local state.apply 'mysql.client' + +mariadb -V || mysql -V + +# check mysqld service is not installed +rc=0 +service mysql status || rc=$? +test $rc -gt 0 + +rc=0 +service mysql status || rc=$? +test $rc -gt 0 + +rc=0 +service mariadb status || rc=$? +test $rc -gt 0 + +echo success diff --git a/t/10-password.sh b/t/10-password.sh new file mode 100755 index 00000000..38388961 --- /dev/null +++ b/t/10-password.sh @@ -0,0 +1,62 @@ +#!lib/test-in-container-systemd.sh mariadb + +set -ex + +# hack the pillar +mkdir -p /srv/pillar +echo " +mysql: + server: + mysqld: + max_allowed_packet: 1111040 + database: + - testdb + user: + myuser1: + password: 'mypass123' + host: localhost + databases: + - database: testdb + grants: ['all'] +" > /srv/pillar/testdata.sls + +echo ' +{{ saltenv }}: + "*": + - testdata +' >> /srv/pillar/top.sls + + +salt-call --local pillar.get mysql:user:myuser1:password +salt-call --local pillar.item mysql:user:myuser1:password | grep mypass123 + +pass=$(echo $(salt-call --local pillar.get mysql:user:myuser1:password) | tail -n 1 | grep -Eo '[^ ]+$') +test "$pass" == mypass123 + +salt-call --local state.apply 'mysql' +salt-call --local state.apply 'mysql.remove_test_database' + +set -a +shopt -s expand_aliases +alias sql="mariadb -h 127.0.0.1 -umyuser1 -p$pass -e" +( + +sql 'select user(), current_user(), version()' + +packet="$(sql 'show variables like "max_allowed_packet"' -Nb)" +test 1111040 == ${packet//[!0-9]/} + +echo test access denied to mysql database +rc=0 +sql 'select user, host from mysql.user' || rc=$? +test "$rc" -gt 0 + +echo test wrong password is denied +rc=0 +sql 'select user(), current_user(), version()' -p"wrongpassword" || rc=$? +test "$rc" -gt 0 +) + +service mariadb status || service mysql status + +echo success diff --git a/t/Makefile b/t/Makefile new file mode 100644 index 00000000..f4bf8236 --- /dev/null +++ b/t/Makefile @@ -0,0 +1,8 @@ + + +test_container: + ( for f in *.sh; do ./$$f && continue; echo FAIL $$f; exit 1 ; done ) + +test_container_fast: + ( for f in *.sh; do T_CACHE_PACKAGES=1 ./$$f && continue; echo FAIL $$f; exit 1 ; done ) + diff --git a/t/README.md b/t/README.md new file mode 100644 index 00000000..248e0aa1 --- /dev/null +++ b/t/README.md @@ -0,0 +1,77 @@ +Scripts to test SLS changes in podman containers +------------------- + +The goal is to cover following workflow: + +* Change files related to an individual state. +* Spawn a container with a local standalone salt node and apply the states. +* Check basic bash commands to verify outcome. + +The test is set of bash commands. +The script relies on shebang to prepare an image and spawn a container with the sls files. +It is also ready to test salt states using set of commands `salt-call --local`. + +###### Example: Run test for mysql states: + +```bash +cd t +./01-smoke-server.sh +``` + +#### Challenge 1: By default, a container is destroyed when the test finishes. + +This is to simplify re-run of tests and do not flood machine with leftover containers after tests. +To make sure container stays around after faiure - set environment variable *T_PAUSE_ON_FAILURE* to 1 + +###### Example: Connect to the container after test failure + +```bash +> # terminal 1 +> echo fail >> 01-smoke-server.sh +> T_PAUSE_ON_FAILURE=1 ./01-smoke-server.sh +... +bash: line 18: fail: command not found +Test failed, press any key to finish +``` +The terminal will wait for any input to finish the test and clean up the container. +Now use another terminal window to check the running podman container and get into it for eventual troubleshooting: + +```bash +> # terminal 2 +> podman ps +CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES +2a37d23503fa localhost/mysql.formula.t38c8b1d778efa61c676f1aa99b2aded5.image:latest 4 minutes ago Up 4 minutes ago mysql.formula.t38c8b1d778efa61c676f1aa99b2aded5.01-smoke-server.sh +> # use copy container id to start bash in it (hint: or bash completion should work for container name as well) +> podman exec -it mysql.formuls.t38c8b1d778efa61c676f1aa99b2aded5.01-smoke-server.sh bash +2a37d23503fa:/opt/project # ls +bin encrypted_pillar_recipients ENCRYPTION.md FORMULAS.yaml gpgkeys pillar README.md salt t test +2a37d23503fa:/opt/project # # now we are inside the container and can troubleshoot outcome of salt commands +2a37d23503fa:/opt/project # rcmysql status +* mariadb.service - MariaDB database server + Loaded: loaded (/usr/lib/systemd/system/mariadb.service; disabled; vendor preset: disabled) + Active: active (running) since Fri 2023-03-17 12:45:24 UTC; 10min ago +``` + +#### Challenge 2: Vary OS in container. + +Create new file Dockerfile.%osname% similar to existing Docker files in t/lib. +Use environment variable T_IMAGE=%osname% to let scripts use corresponding Dockerfile. +By default tests are run with t/Dockerfile.opensuse.leap + +#### Challenge 3: Cache packages inside test image. + +(Currently works only with default docker image, i.e. T_IMAGE is empty). +Downloading and installing packages may be time consuming, so sometimes it may be advantageous to have an option to pre-install required packages inside image, in which the test will be run. (So the test will concentrate on verifying other aspects of salt states, without spending time on installing packages). +At the same time the CI needs to verify salt states related to installation, so it must run the test without such caching. +Such caching is implemented as optional parameters to shebang command in the test scripts. These parameters are ignored unless global variable *T_CACHE_PACKAGES* is set to 1. + +```bash +> # check parameter in shebang: +> head -n 1 01-smoke-server.sh +#!lib/test-in-container-systemd.sh mariadb +> # run the test in a container with preinstalled mariadb package as specified above: +> T_CACHE_PACKAGES=1 ./01-smoke-server.sh +> # run the test in a container without preinstalled mariadb package (will take longer, but will verify package installation as part of test) +> ./01-smoke-server.sh +``` + diff --git a/t/lib/Dockerfile.debian.11 b/t/lib/Dockerfile.debian.11 new file mode 100644 index 00000000..043ab6f0 --- /dev/null +++ b/t/lib/Dockerfile.debian.11 @@ -0,0 +1,19 @@ +FROM debian:11 +ENV container podman + +ENV LANG en_US.UTF-8 + +# these are needed to run test +RUN apt-get -y update && apt-get -y upgrade +RUN apt-get -y install systemd salt-minion curl sudo + +##DUMMY + +RUN mkdir -p /srv/salt/ && \ + sed -i 's^\#*\s*file_client: .*$^file_client: local\nsystemd.scope: False\nenable_fqdns_grains: False^' /etc/salt/minion + +ADD mysql /srv/salt/mysql + +WORKDIR /opt/project + +ENTRYPOINT ["/bin/systemd"] diff --git a/t/lib/Dockerfile.opensuse.leap b/t/lib/Dockerfile.opensuse.leap new file mode 100644 index 00000000..5328ff96 --- /dev/null +++ b/t/lib/Dockerfile.opensuse.leap @@ -0,0 +1,21 @@ +FROM registry.opensuse.org/opensuse/leap +ENV container podman + +ENV LANG en_US.UTF-8 + +RUN test ! -f /var/log/zypper.log || mv /var/log/zypper.log /var/log/zypper.log.preinstalled + +# these are needed to run test +RUN zypper -vvvn install systemd salt-minion sudo + +##DUMMY + +RUN mkdir -p /srv/salt/ && \ + sed -i 's^\#*\s*file_client: .*$^file_client: local\nsystemd.scope: False\nenable_fqdns_grains: False^' /etc/salt/minion && \ + sed -i '/pam_systemd.so/d' /etc/pam.d/common-session-pc # delete pam_systemd , otherwise sudo will hang + +ADD mysql /srv/salt/mysql + +WORKDIR /opt/project + +ENTRYPOINT ["/usr/lib/systemd/systemd"] diff --git a/t/lib/test-in-container-systemd.sh b/t/lib/test-in-container-systemd.sh new file mode 100755 index 00000000..5f4ca1fa --- /dev/null +++ b/t/lib/test-in-container-systemd.sh @@ -0,0 +1,106 @@ +#!/bin/bash +# +# Copyright (C) 2023 SUSE LLC +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, see . + +last=${@:$#} # last parameter +other=${*%${!#}} # all parameters except the last + +testcase=$last + +set -euo pipefail +PODMAN=podman +image=${T_IMAGE-opensuse.leap} +( +PODMAN_info="$($PODMAN info >/dev/null 2>&1)" || $PODMAN info +[ -n "$testcase" ] || (echo No testcase provided; exit 1) +[ -f "$testcase" ] || (echo Cannot find file "$testcase"; exit 1 ) +) >&2 + +thisdir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +basename=$(basename "$testcase") +basename=${basename,,} +basename=${basename//:/_} + +othermd5= +test -z "$other" || othermd5=($(echo "$other" | md5sum -)) + +ident=mysql.formula.t$othermd5 +containername="$ident.${basename,,}" + +# we use variable T_CACHE_PACKAGES to speedup testing and make sure that +( +if test -z "$other" || \ + test "${T_IMAGE-}" != "" || \ + test "${T_CACHE_PACKAGES:-}" != 1 +then + cat $thisdir/Dockerfile.${image} +else + ar="" + in="zypper -vvvn in " + for pkg in $other vim; do + if [[ $pkg == *:* ]]; then + n=${pkg//*:} + ar="$ar zypper -n ar http://download.opensuse.org/repositories/$pkg/\\\\\$releasever/ $n;" + in="$in $n" + else + in="$in $pkg" + fi + done + test -z "$ar" || ar="$ar zypper --gpg-auto-import-keys ref; " + + sed "s,\#\#DUMMY,RUN $ar $in,g" $thisdir/Dockerfile.${T_IMAGE} +fi + +) | cat | $PODMAN build -t $ident.image -f - $thisdir/../.. + +map_port="" +[ -z "${EXPOSE_PORT:-}" ] || map_port="-p $EXPOSE_PORT:80" +$PODMAN run --privileged --rm $map_port --name "$containername" -d -v"$thisdir/../..":/opt/project --add-host localhost:127.0.0.1 -- $ident.image + +in_cleanup=0 + +ret=111 + +function cleanup { + [ "$in_cleanup" != 1 ] || return + in_cleanup=1 + if [ "$ret" != 0 ] && [ -n "${T_PAUSE_ON_FAILURE-}" ]; then + read -rsn1 -p"Test failed, press any key to finish";echo + fi + [ "$ret" == 0 ] || echo FAIL $basename + $PODMAN stop -t 0 "$containername" >&/dev/null || : +} + +trap cleanup INT TERM EXIT +counter=1 + +# wait container start +until [ $counter -gt 10 ]; do + sleep 0.5 + $PODMAN exec "$containername" pwd >& /dev/null && break + ((counter++)) +done + +$PODMAN exec "$containername" pwd >& /dev/null || (echo Cannot start container; exit 1 ) >&2 + +echo "$*" +# [ -z $initscript ] || echo "bash -xe /opt/project/t/$initscript" | $PODMAN exec -i "$containername" bash -x + +set +e +$PODMAN exec -e TESTCASE="$testcase" -i "$containername" bash -xe < "$testcase" +ret=$? +( exit $ret ) +