mirror of
https://github.com/containers/podman.git
synced 2025-07-31 12:22:29 +08:00

**podman compose** is a thin wrapper around an external compose provider such as docker-compose or podman-compose. This means that `podman compose` is executing another tool that implements the compose functionality but sets up the environment in a way to let the compose provider communicate transparently with the local Podman socket. The specified options as well the command and argument are passed directly to the compose provider. The default compose providers are `docker-compose` and `podman-compose`. If installed, `docker-compose` takes precedence since it is the original implementation of the Compose specification and is widely used on the supported platforms (i.e., Linux, Mac OS, Windows). If you want to change the default behavior or have a custom installation path for your provider of choice, please change the `compose_provider` field in `containers.conf(5)`. You may also set the `PODMAN_COMPOSE_PROVIDER` environment variable. Signed-off-by: Valentin Rothberg <vrothberg@redhat.com>
401 lines
11 KiB
Bash
Executable File
401 lines
11 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# Usage: test-compose [testname]
|
|
#
|
|
ME=$(basename $0)
|
|
|
|
###############################################################################
|
|
# BEGIN stuff you can but probably shouldn't customize
|
|
|
|
# Directory where this script and all subtests live
|
|
TEST_ROOTDIR=$(realpath $(dirname $0))
|
|
|
|
# Podman executable
|
|
PODMAN_BIN=$(realpath $TEST_ROOTDIR/../../bin)/podman
|
|
|
|
# Local path to docker socket with unix prefix
|
|
# The path will be changed for rootless users
|
|
DOCKER_SOCK=/var/run/docker.sock
|
|
|
|
# END stuff you can but probably shouldn't customize
|
|
###############################################################################
|
|
# BEGIN setup
|
|
|
|
export TMPDIR=${TMPDIR:-/var/tmp}
|
|
WORKDIR=$(mktemp --tmpdir -d $ME.tmp.XXXXXX)
|
|
|
|
# Log of all HTTP requests and responses; always make '.log' point to latest
|
|
LOGBASE=${TMPDIR}/$ME.log
|
|
LOG=${LOGBASE}.$(date +'%Y%m%dT%H%M%S')
|
|
ln -sf $LOG $LOGBASE
|
|
|
|
# Keep track of test count and failures in files, not variables, because
|
|
# variables don't carry back up from subshells.
|
|
testcounter_file=$WORKDIR/.testcounter
|
|
failures_file=$WORKDIR/.failures
|
|
|
|
echo 0 >$testcounter_file
|
|
echo 0 >$failures_file
|
|
|
|
# END setup
|
|
###############################################################################
|
|
# BEGIN infrastructure code - the helper functions used in tests themselves
|
|
|
|
#################
|
|
# is_rootless # Check if we run as normal user
|
|
#################
|
|
function is_rootless() {
|
|
[ "$(id -u)" -ne 0 ]
|
|
}
|
|
|
|
#########
|
|
# die # Exit error with a message to stderr
|
|
#########
|
|
function die() {
|
|
echo "$ME: $*" >&2
|
|
exit 1
|
|
}
|
|
|
|
########
|
|
# is # Simple comparison
|
|
########
|
|
function is() {
|
|
local actual=$1
|
|
local expect=$2
|
|
local testname=$3
|
|
|
|
if [[ "$actual" = "$expect" ]]; then
|
|
# On success, include expected value; this helps readers understand
|
|
_show_ok 1 "$testname=$expect"
|
|
return
|
|
fi
|
|
_show_ok 0 "$testname" "$expect" "$actual"
|
|
}
|
|
|
|
##########
|
|
# like # Compare, but allowing patterns
|
|
##########
|
|
function like() {
|
|
local actual=$1
|
|
local expect=$2
|
|
local testname=$3
|
|
|
|
# "is" (equality) is a subset of "like", but one that expr fails on if
|
|
# the expected result has shell-special characters like '['. Treat it
|
|
# as a special case.
|
|
|
|
if [[ "$actual" = "$expect" ]]; then
|
|
_show_ok 1 "$testname=$expect"
|
|
return
|
|
fi
|
|
|
|
if expr "$actual" : ".*$expect" &>/dev/null; then
|
|
# On success, include expected value; this helps readers understand
|
|
_show_ok 1 "$testname ('$actual') ~ $expect"
|
|
return
|
|
fi
|
|
_show_ok 0 "$testname" "~ $expect" "$actual"
|
|
}
|
|
|
|
##############
|
|
# _show_ok # Helper for is() and like(): displays 'ok' or 'not ok'
|
|
##############
|
|
function _show_ok() {
|
|
local ok=$1
|
|
local testname=$2
|
|
|
|
# If output is a tty, colorize pass/fail
|
|
local red=
|
|
local green=
|
|
local reset=
|
|
local bold=
|
|
if [ -t 1 ]; then
|
|
red='\e[31m'
|
|
green='\e[32m'
|
|
reset='\e[0m'
|
|
bold='\e[1m'
|
|
fi
|
|
|
|
_bump $testcounter_file
|
|
count=$(<$testcounter_file)
|
|
|
|
# "skip" is a special case of "ok". Assume that our caller has included
|
|
# the magical '# skip - reason" comment string.
|
|
if [[ $ok == "skip" ]]; then
|
|
# colon-plus: replace green with yellow, but only if green is non-null
|
|
green="${green:+\e[33m}"
|
|
ok=1
|
|
fi
|
|
if [ $ok -eq 1 ]; then
|
|
echo -e "${green}ok $count $testname${reset}"
|
|
echo "ok $count $testname" >>$LOG
|
|
return
|
|
fi
|
|
|
|
# Failed
|
|
local expect=$3
|
|
local actual=$4
|
|
printf "${red}not ok $count $testname${reset}\n"
|
|
# Not all errors include actual/expect
|
|
if [[ -n "$expect" || -n "$actual" ]]; then
|
|
printf "${red}# expected: %s${reset}\n" "$expect"
|
|
printf "${red}# actual: ${bold}%s${reset}\n" "$actual"
|
|
fi
|
|
|
|
echo "not ok $count $testname" >>$LOG
|
|
echo " expected: $expect" >>$LOG
|
|
|
|
_bump $failures_file
|
|
}
|
|
|
|
###########
|
|
# _bump # Increment a counter in a file
|
|
###########
|
|
function _bump() {
|
|
local file=$1
|
|
|
|
count=$(<$file)
|
|
echo $(( $count + 1 )) >| $file
|
|
}
|
|
|
|
###############
|
|
# test_port # Run curl against a port, check results against expectation
|
|
###############
|
|
function test_port() {
|
|
local port="$1" # e.g. 5000
|
|
local op="$2" # '=' or '~'
|
|
local expect="$3" # what to expect from curl output
|
|
|
|
# -s -S means "silent, but show errors"
|
|
local actual
|
|
actual=$(curl --retry 3 --retry-all-errors -s -S http://127.0.0.1:$port/)
|
|
local curl_rc=$?
|
|
|
|
if [ $curl_rc -ne 0 ]; then
|
|
_show_ok 0 "$testname - curl (port $port) failed with status $curl_rc"
|
|
echo "# podman ps -a:"
|
|
$PODMAN_BIN --storage-driver=vfs --root $WORKDIR/root --runroot $WORKDIR/runroot ps -a
|
|
if type -p ss; then
|
|
echo "# ss -tulpn:"
|
|
ss -tulpn
|
|
echo "# podman unshare --rootless-cni ss -tulpn:"
|
|
$PODMAN_BIN --storage-driver=vfs --root $WORKDIR/root --runroot $WORKDIR/runroot unshare --rootless-cni ss -tulpn
|
|
fi
|
|
echo "# cat $WORKDIR/server.log:"
|
|
cat $WORKDIR/server.log
|
|
echo "# cat $logfile:"
|
|
cat $logfile
|
|
return
|
|
fi
|
|
|
|
case "$op" in
|
|
'=') is "$actual" "$expect" "$testname : port $port" ;;
|
|
'~') like "$actual" "$expect" "$testname : port $port" ;;
|
|
*) die "Invalid operator '$op'" ;;
|
|
esac
|
|
}
|
|
|
|
|
|
###################
|
|
# start_service # Run the socket listener
|
|
###################
|
|
service_pid=
|
|
function start_service() {
|
|
test -x $PODMAN_BIN || die "Not found: $PODMAN_BIN"
|
|
|
|
# FIXME: use ${testname} subdir but we can't: 50-char limit in runroot
|
|
if ! is_rootless; then
|
|
rm -rf $WORKDIR/{root,runroot,cni}
|
|
else
|
|
$PODMAN_BIN unshare rm -rf $WORKDIR/{root,runroot,cni}
|
|
fi
|
|
rm -f $DOCKER_SOCK
|
|
mkdir --mode 0755 $WORKDIR/{root,runroot,cni}
|
|
chcon --reference=/var/lib/containers $WORKDIR/root
|
|
|
|
$PODMAN_BIN \
|
|
--log-level debug \
|
|
--storage-driver=vfs \
|
|
--root $WORKDIR/root \
|
|
--runroot $WORKDIR/runroot \
|
|
--cgroup-manager=systemd \
|
|
--network-config-dir $WORKDIR/cni \
|
|
system service \
|
|
--time 0 unix://$DOCKER_SOCK \
|
|
&> $WORKDIR/server.log &
|
|
service_pid=$!
|
|
|
|
# Wait (FIXME: how do we test the socket?)
|
|
local _timeout=5
|
|
while [ $_timeout -gt 0 ]; do
|
|
# FIXME: should we actually try a read or write?
|
|
test -S $DOCKER_SOCK && return
|
|
sleep 1
|
|
_timeout=$(( $_timeout - 1 ))
|
|
done
|
|
cat $WORKDIR/server.log
|
|
die "Timed out waiting for service"
|
|
}
|
|
|
|
############
|
|
# podman # Needed by some test scripts to invoke the actual podman binary
|
|
############
|
|
function podman() {
|
|
echo "\$ podman $*" >>$WORKDIR/output.log
|
|
output=$($PODMAN_BIN \
|
|
--storage-driver=vfs \
|
|
--root $WORKDIR/root \
|
|
--runroot $WORKDIR/runroot \
|
|
--network-config-dir $WORKDIR/cni \
|
|
"$@")
|
|
echo -n "$output" >>$WORKDIR/output.log
|
|
}
|
|
|
|
###################
|
|
# random_string # Returns a pseudorandom human-readable string
|
|
###################
|
|
function random_string() {
|
|
# Numeric argument, if present, is desired length of string
|
|
local length=${1:-10}
|
|
|
|
head /dev/urandom | tr -dc a-zA-Z0-9 | head -c$length
|
|
}
|
|
|
|
# END infrastructure code
|
|
###############################################################################
|
|
# BEGIN sanity checks
|
|
|
|
for tool in curl docker-compose; do
|
|
type $tool &>/dev/null || die "$ME: Required tool '$tool' not found"
|
|
done
|
|
|
|
# END sanity checks
|
|
###############################################################################
|
|
# BEGIN entry handler (subtest invoker)
|
|
|
|
# When rootless use a socket path accessible by the rootless user
|
|
if is_rootless; then
|
|
DOCKER_SOCK="$WORKDIR/docker.sock"
|
|
fi
|
|
|
|
# export DOCKER_HOST docker-compose will use it
|
|
DOCKER_HOST="unix://$DOCKER_SOCK"
|
|
export DOCKER_HOST
|
|
|
|
# Identify the tests to run. If called with args, use those as globs.
|
|
tests_to_run=()
|
|
if [ -n "$*" ]; then
|
|
shopt -s nullglob
|
|
for i; do
|
|
match=(${TEST_ROOTDIR}/*${i}*/docker-compose.yml)
|
|
if [ ${#match} -eq 0 ]; then
|
|
die "No match for $TEST_ROOTDIR/*$i*.curl"
|
|
fi
|
|
tests_to_run+=("${match[@]}")
|
|
done
|
|
shopt -u nullglob
|
|
else
|
|
tests_to_run=(${TEST_ROOTDIR}/*/docker-compose.yml)
|
|
fi
|
|
|
|
# Too hard to precompute the number of tests; just spit it out at the end.
|
|
n_tests=0
|
|
|
|
# We aren't really TAP 13; this helps logformatter recognize our output as BATS
|
|
echo "TAP version 13"
|
|
|
|
for t in "${tests_to_run[@]}"; do
|
|
testdir="$(dirname $t)"
|
|
testname="$(basename $testdir)"
|
|
|
|
if [ -e $test_dir/SKIP ]; then
|
|
reason="$(<$test_dir/SKIP)"
|
|
if [ -n "$reason" ]; then
|
|
reason=" - $reason"
|
|
fi
|
|
_show_ok skip "$testname # skip$reason"
|
|
continue
|
|
fi
|
|
|
|
start_service
|
|
|
|
logfile=$WORKDIR/$testname.log
|
|
(
|
|
cd $testdir || die "Cannot cd $testdir"
|
|
|
|
# setup file may be used for creating temporary directories/files.
|
|
# We source it so that envariables defined in it will get back to us.
|
|
if [ -e setup.sh ]; then
|
|
. setup.sh
|
|
fi
|
|
if [ -e teardown.sh ]; then
|
|
trap '. teardown.sh' 0
|
|
fi
|
|
|
|
podman compose up -d &> $logfile
|
|
docker_compose_rc=$?
|
|
if [[ $docker_compose_rc -ne 0 ]]; then
|
|
_show_ok 0 "$testname - up" "[ok]" "status=$docker_compose_rc"
|
|
sed -e 's/^/# /' <$logfile
|
|
podman compose down >>$logfile 2>&1 # No status check here
|
|
exit 1
|
|
fi
|
|
_show_ok 1 "$testname - up"
|
|
|
|
# Run tests. This is likely to be a series of 'test_port' checks
|
|
# but may also include podman commands to inspect labels, state
|
|
if [ -e tests.sh ]; then
|
|
. tests.sh
|
|
fi
|
|
# FIXME: if any tests fail, try 'podman logs' on container?
|
|
|
|
if [ -n "$COMPOSE_WAIT" ]; then
|
|
echo -n "Pausing due to \$COMPOSE_WAIT. Press ENTER to continue: "
|
|
read keepgoing
|
|
fi
|
|
|
|
# Done. Clean up.
|
|
podman compose down &>> $logfile
|
|
rc=$?
|
|
if [[ $rc -eq 0 ]]; then
|
|
_show_ok 1 "$testname - down"
|
|
else
|
|
_show_ok 0 "$testname - down" "[ok]" "rc=$rc"
|
|
# FIXME: show error
|
|
fi
|
|
)
|
|
|
|
kill $service_pid
|
|
wait $service_pid
|
|
|
|
# FIXME: otherwise we get EBUSY
|
|
if ! is_rootless; then
|
|
umount $WORKDIR/root/overlay &>/dev/null
|
|
else
|
|
$PODMAN_BIN unshare umount $WORKDIR/root/overlay &>/dev/null
|
|
fi
|
|
|
|
# FIXME: run 'podman ps'?
|
|
# rm -rf $WORKDIR/${testname}
|
|
done
|
|
|
|
# END entry handler
|
|
###############################################################################
|
|
|
|
# Clean up
|
|
|
|
test_count=$(<$testcounter_file)
|
|
failure_count=$(<$failures_file)
|
|
|
|
if [ -z "$PODMAN_TESTS_KEEP_WORKDIR" ]; then
|
|
if ! is_rootless; then
|
|
rm -rf $WORKDIR
|
|
else
|
|
$PODMAN_BIN unshare rm -rf $WORKDIR
|
|
fi
|
|
fi
|
|
|
|
echo "1..${test_count}"
|
|
|
|
exit $failure_count
|