Merge pull request #23280 from edsantiago/bats-safename

System tests: safe container/image/volume/etc names
This commit is contained in:
openshift-merge-bot[bot]
2024-07-16 18:01:13 +00:00
committed by GitHub
3 changed files with 144 additions and 110 deletions

View File

@ -62,31 +62,33 @@ Labels.created_at | 20[0-9-]\\\+T[0-9:]\\\+Z
# podman history is persistent: it permanently alters our base image.
# Create a dummy image here so we leave our setup as we found it.
# Multiple --name options confirm command-line override (last one wins)
run_podman run --name ignore-me --name my-container $IMAGE true
run_podman commit my-container my-test-image
cname=c_$(safename)
iname=i_$(safename)
run_podman run --name ignore-me --name $cname $IMAGE true
run_podman commit $cname $iname
run_podman images my-test-image --format '{{ .History }}'
is "$output" "localhost/my-test-image:latest" "image history with initial name"
run_podman images $iname --format '{{ .History }}'
is "$output" "localhost/$iname:latest" "image history with initial name"
# Generate two randomish tags; 'tr' because they must be all lower-case
rand_name1="test-image-history-$(random_string 10 | tr A-Z a-z)"
rand_name2="test-image-history-$(random_string 10 | tr A-Z a-z)"
# Generate two randomish tags
rand_name1="test-image-history-1-$(safename)"
rand_name2="test-image-history-2-$(safename)"
# Tag once, rmi, and make sure the tag name appears in history
run_podman tag my-test-image $rand_name1
run_podman tag $iname $rand_name1
run_podman rmi $rand_name1
run_podman images my-test-image --format '{{ .History }}'
is "$output" "localhost/my-test-image:latest, localhost/${rand_name1}:latest" "image history after one tag"
run_podman images $iname --format '{{ .History }}'
is "$output" "localhost/$iname:latest, localhost/${rand_name1}:latest" "image history after one tag"
# Repeat with second tag. Now both tags should be in history
run_podman tag my-test-image $rand_name2
run_podman tag $iname $rand_name2
run_podman rmi $rand_name2
run_podman images my-test-image --format '{{ .History }}'
is "$output" "localhost/my-test-image:latest, localhost/${rand_name2}:latest, localhost/${rand_name1}:latest" \
run_podman images $iname --format '{{ .History }}'
is "$output" "localhost/$iname:latest, localhost/${rand_name2}:latest, localhost/${rand_name1}:latest" \
"image history after two tags"
run_podman rmi my-test-image
run_podman rm my-container
run_podman rmi $iname
run_podman rm $cname
}
@test "podman images - filter" {
@ -102,26 +104,28 @@ Labels.created_at | 20[0-9-]\\\+T[0-9:]\\\+Z
# Create a dummy container, then commit that as an image. We will
# now be able to use before/after/since queries
run_podman run --name mytinycontainer $IMAGE true
run_podman commit -q mytinycontainer mynewimage
cname=c_$(safename)
iname=i_$(safename)
run_podman run --name $cname $IMAGE true
run_podman commit -q $cname $iname
new_iid=$output
# (refactor common options for legibility)
opts='--noheading --no-trunc --format={{.ID}}--{{.Repository}}:{{.Tag}}'
run_podman images ${opts} --filter=after=$iid
is "$output" "sha256:$new_iid--localhost/mynewimage:latest" "filter: after"
is "$output" "sha256:$new_iid--localhost/$iname:latest" "filter: after"
# Same thing, with 'since' instead of 'after'
run_podman images ${opts} --filter=since=$iid
is "$output" "sha256:$new_iid--localhost/mynewimage:latest" "filter: since"
is "$output" "sha256:$new_iid--localhost/$iname:latest" "filter: since"
run_podman images ${opts} --filter=before=mynewimage
run_podman images ${opts} --filter=before=$iname
is "$output" "sha256:$iid--$IMAGE" "filter: before"
# Clean up
run_podman rmi mynewimage
run_podman rm mytinycontainer
run_podman rmi $iname
run_podman rm $cname
}
# Regression test for https://github.com/containers/podman/issues/7651
@ -238,7 +242,7 @@ Labels.created_at | 20[0-9-]\\\+T[0-9:]\\\+Z
}
@test "podman images - rmi -af removes all containers and pods" {
pname=$(random_string)
pname=p_$(safename)
run_podman create --pod new:$pname $IMAGE
run_podman inspect --format '{{.ID}}' $IMAGE
@ -274,7 +278,7 @@ Deleted: $pauseID" "infra images gets removed as well"
}
@test "podman images - rmi -f can remove infra images" {
pname=$(random_string)
pname=p_$(safename)
run_podman create --pod new:$pname $IMAGE
pauseImage=$(pause_image)
@ -299,8 +303,7 @@ Deleted: $pauseID"
}
@test "podman rmi --ignore" {
random_image_name=$(random_string)
random_image_name=${random_image_name,,} # name must be lowercase
random_image_name=i_$(safename)
run_podman 1 rmi $random_image_name
is "$output" "Error: $random_image_name: image not known.*"
run_podman rmi --ignore $random_image_name
@ -313,8 +316,7 @@ Deleted: $pauseID"
run_podman image rm --force bogus
is "$output" "" "Should print no output"
random_image_name=$(random_string)
random_image_name=${random_image_name,,} # name must be lowercase
random_image_name=i_$(safename)
run_podman image tag $IMAGE $random_image_name
run_podman image rm --force bogus $random_image_name
assert "$output" = "Untagged: localhost/$random_image_name:latest" "removed image"
@ -324,24 +326,26 @@ Deleted: $pauseID"
}
@test "podman images - commit docker with comment" {
run_podman run --name my-container -d $IMAGE top
run_podman 125 commit -m comment my-container my-test-image
cname=c_$(safename)
iname=i_$(safename)
run_podman run --name $cname -d $IMAGE top
run_podman 125 commit -m comment $cname $iname
assert "$output" == "Error: messages are only compatible with the docker image format (-f docker)" "podman should fail unless docker format"
# Without -q: verbose output, but only on podman-local, not remote
run_podman commit my-container --format docker -m comment my-test-image1
run_podman commit $cname --format docker -m comment ${iname}_2
if ! is_remote; then
assert "$output" =~ "Getting image.*Writing manif" \
"Without -q, verbose output"
fi
# With -q, both local and remote: only an image ID
run_podman commit -q my-container --format docker -m comment my-test-image2
run_podman commit -q $cname --format docker -m comment ${iname}_3
assert "$output" =~ "^[0-9a-f]{64}\$" \
"With -q, output is a commit ID, no warnings or other output"
run_podman rmi my-test-image1 my-test-image2
run_podman rm my-container --force -t 0
run_podman rmi ${iname}_2 ${iname}_3
run_podman rm $cname --force -t 0
}
@test "podman pull image with additional store" {

View File

@ -122,7 +122,7 @@ echo $rand | 0 | $rand
}
@test "podman run --name" {
randomname=$(random_string 30)
randomname=c_$(safename)
run_podman run -d --name $randomname $IMAGE sleep inf
cid=$output
@ -192,7 +192,8 @@ echo $rand | 0 | $rand
run_podman 1 image exists $NONLOCAL_IMAGE
# Run a container, without --rm; this should block subsequent --rmi
run_podman run --name /keepme $NONLOCAL_IMAGE /bin/true
cname=c_$(safename)
run_podman run --name /$cname $NONLOCAL_IMAGE /bin/true
run_podman image exists $NONLOCAL_IMAGE
# Now try running with --rmi : it should succeed, but not remove the image
@ -202,7 +203,7 @@ echo $rand | 0 | $rand
run_podman image exists $NONLOCAL_IMAGE
# Remove the stray container, and run one more time with --rmi.
run_podman rm /keepme
run_podman rm /$cname
run_podman run --rmi $NONLOCAL_IMAGE /bin/true
run_podman 1 image exists $NONLOCAL_IMAGE
@ -220,7 +221,7 @@ echo $rand | 0 | $rand
# on write.
echo "$(random_string 120)" > $cidfile
cname=$(random_string)
cname=c_$(safename)
run_podman run --name $cname \
--conmon-pidfile=$pidfile \
--cidfile=$cidfile \
@ -255,23 +256,25 @@ echo $rand | 0 | $rand
skip_if_remote "podman-remote does not support docker-archive"
# Create an image that, when run, outputs a random magic string
cname=c_$(safename)
expect=$(random_string 20)
run_podman run --name myc --entrypoint="[\"/bin/echo\",\"$expect\"]" $IMAGE
run_podman run --name $cname --entrypoint="[\"/bin/echo\",\"$expect\"]" $IMAGE
is "$output" "$expect" "podman run --entrypoint echo-randomstring"
# Save it as a tar archive
run_podman commit myc myi
iname=i_$(safename)
run_podman commit $cname $iname
archive=$PODMAN_TMPDIR/archive.tar
run_podman save --quiet myi -o $archive
run_podman save --quiet $iname -o $archive
is "$output" "" "podman save"
# Clean up image and container from container storage...
run_podman rmi myi
run_podman rm myc
run_podman rmi $iname
run_podman rm $cname
# ... then confirm we can run from archive. This re-imports the image
# and runs it, producing our random string as the last line.
run_podman run docker-archive:$archive
run_podman run --name ${cname}_2 docker-archive:$archive
is "${lines[0]}" "Getting image source signatures" "podman run docker-archive, first line of output"
is "$output" ".*Copying blob" "podman run docker-archive"
is "$output" ".*Copying config" "podman run docker-archive"
@ -279,8 +282,8 @@ echo $rand | 0 | $rand
is "${lines[-1]}" "$expect" "podman run docker-archive: expected random string output"
# Clean up container as well as re-imported image
run_podman rm -a
run_podman rmi myi
run_podman rm ${cname}_2
run_podman rmi $iname
# Repeat the above, with podman-create and podman-start.
run_podman create docker-archive:$archive
@ -291,7 +294,7 @@ echo $rand | 0 | $rand
# Clean up.
run_podman rm $cid
run_podman rmi myi
run_podman rmi $iname
}
# #6735 : complex interactions with multiple user namespaces
@ -407,15 +410,16 @@ json-file | f
while read driver do_check; do
msg=$(random_string 15)
run_podman run --name myctr --log-driver $driver $IMAGE echo $msg
cname=c_$(safename)
run_podman run --name $cname --log-driver $driver $IMAGE echo $msg
is "$output" "$msg" "basic output sanity check (driver=$driver)"
# Simply confirm that podman preserved our argument as-is
run_podman inspect --format '{{.HostConfig.LogConfig.Type}}' myctr
run_podman inspect --format '{{.HostConfig.LogConfig.Type}}' $cname
is "$output" "$driver" "podman inspect: driver"
# If LogPath is non-null, check that it exists and has a valid log
run_podman inspect --format '{{.HostConfig.LogConfig.Path}}' myctr
run_podman inspect --format '{{.HostConfig.LogConfig.Path}}' $cname
if [[ $do_check != '-' ]]; then
is "$output" "/.*" "LogPath (driver=$driver)"
if ! test -e "$output"; then
@ -433,17 +437,17 @@ json-file | f
# Cannot perform check
:
else
run_podman logs myctr
run_podman logs $cname
is "$output" "$msg" "podman logs, with driver '$driver'"
fi
else
run_podman 125 logs myctr
run_podman 125 logs $cname
if ! is_remote; then
is "$output" ".*this container is using the 'none' log driver, cannot read logs.*" \
"podman logs, with driver 'none', should fail with error"
fi
fi
run_podman rm myctr
run_podman rm $cname
done < <(parse_table "$tests")
# Invalid log-driver argument
@ -462,13 +466,14 @@ json-file | f
pidfile="${PODMAN_TMPDIR}/$(random_string 20)"
# Multiple --log-driver options to confirm that last one wins
run_podman run --name myctr --log-driver=none --log-driver journald \
cname=c_$(safename)
run_podman run --name $cname --log-driver=none --log-driver journald \
--conmon-pidfile $pidfile $IMAGE echo $msg
journalctl --output cat _PID=$(cat $pidfile)
is "$output" "$msg" "check that journalctl output equals the container output"
run_podman rm myctr
run_podman rm $cname
}
@test "podman run --tz" {
@ -530,18 +535,20 @@ json-file | f
rm -f $new_runtime
}
@test "podman --noout run should print output" {
run_podman --noout run -d --name test $IMAGE echo hi
@test "podman --noout run should not print output" {
cname=c_$(safename)
run_podman --noout run -d --name $cname $IMAGE echo hi
is "$output" "" "output should be empty"
run_podman wait test
run_podman --noout rm test
run_podman wait $cname
run_podman --noout rm $cname
is "$output" "" "output should be empty"
}
@test "podman --noout create should print output" {
run_podman --noout create --name test $IMAGE echo hi
@test "podman --noout create should not print output" {
cname=c_$(safename)
run_podman --noout create --name $cname $IMAGE echo hi
is "$output" "" "output should be empty"
run_podman --noout rm test
run_podman --noout rm $cname
is "$output" "" "output should be empty"
}
@ -549,15 +556,16 @@ json-file | f
outfile=${PODMAN_TMPDIR}/out-results
# first we'll need to run something, write its output to a file, and then read its contents.
run_podman --out $outfile run -d --name test $IMAGE echo hola
cname=c_$(safename)
run_podman --out $outfile run -d --name $cname $IMAGE echo hola
is "$output" "" "output should be redirected"
run_podman wait test
run_podman wait $cname
# compare the container id against the one in the file
run_podman container inspect --format '{{.Id}}' test
run_podman container inspect --format '{{.Id}}' $cname
is "$output" "$(<$outfile)" "container id should match"
run_podman --out /dev/null rm test
run_podman --out /dev/null rm $cname
is "$output" "" "output should be empty"
}
@ -565,22 +573,22 @@ json-file | f
outfile=${PODMAN_TMPDIR}/out-results
# first we'll need to run something, write its output to a file, and then read its contents.
run_podman --out $outfile create --name test $IMAGE echo hola
cname=c_$(safename)
run_podman --out $outfile create --name $cname $IMAGE echo hola
is "$output" "" "output should be redirected"
# compare the container id against the one in the file
run_podman container inspect --format '{{.Id}}' test
run_podman container inspect --format '{{.Id}}' $cname
is "$output" "$(<$outfile)" "container id should match"
run_podman --out /dev/null rm test
run_podman --out /dev/null rm $cname
is "$output" "" "output should be empty"
}
# Regression test for issue #8082
@test "podman run : look up correct image name" {
# Create a 2nd tag for the local image. Force to lower case, and apply it.
local newtag="localhost/$(random_string 10)/$(random_string 8)"
newtag=${newtag,,}
# Create a 2nd tag for the local image.
local newtag="localhost/r_$(safename)/i_$(safename)"
run_podman tag $IMAGE $newtag
# Create a container with the 2nd tag and make sure that it's being
@ -596,7 +604,7 @@ json-file | f
newtag2="${newtag}:$(random_string 6|tr A-Z a-z)"
run_podman tag $IMAGE $newtag2
cname="$(random_string 14|tr A-Z a-z)"
cname="c_$(safename)"
run_podman create --name $cname $newtag2
run_podman inspect --format "{{.ImageName}}" $cname
is "$output" "$newtag2" \
@ -621,7 +629,7 @@ json-file | f
}
@test "podman inspect includes image data" {
randomname=$(random_string 30)
randomname=c_$(safename)
run_podman inspect $IMAGE --format "{{.ID}}"
expected="$IMAGE $output"
@ -650,7 +658,7 @@ json-file | f
run_podman inspect --format '{{.ID}}' $IMAGE
local iid="$output"
random_cname=c$(random_string 15 | tr A-Z a-z)
random_cname=c_$(safename)
local rootless=0
if is_rootless; then
rootless=1
@ -708,8 +716,8 @@ json-file | f
run_podman image mount $IMAGE
romount="$output"
randomname=$(random_string 30)
# FIXME FIXME FIXME: Remove :O once (if) #14504 is fixed!
randomname=c_$(safename)
# :O (overlay) required with rootfs; see #14504
run_podman run --name=$randomname --rootfs $romount:O echo "Hello world"
is "$output" "Hello world"
@ -730,7 +738,7 @@ json-file | f
char_count=700000
# Container name; primarily needed when running podman-remote
cname=mybigdatacontainer
cname=c_$(safename)
# This is one of those cases where BATS is not the best test framework.
# We can't do any output redirection, because 'run' overrides it so
@ -744,14 +752,15 @@ json-file | f
is "${lines[1]}" "$char_count+0 records out" "dd: number of records out"
# We don't have many tests for '-l'. This is as good a place as any
if ! is_remote; then
cname=-l
local dash_l=-l
if is_remote; then
dash_l=$cname
fi
# Now find that log file, and count the NULs in it.
# The log file is of the form '<timestamp> <P|F> <data>', where P|F
# is Partial/Full; I think that's called "kubernetes log format"?
run_podman inspect $cname --format '{{.HostConfig.LogConfig.Path}}'
run_podman inspect $dash_l --format '{{.HostConfig.LogConfig.Path}}'
logfile="$output"
count_zero=$(tr -cd '\0' <$logfile | wc -c)
@ -768,14 +777,14 @@ json-file | f
}
@test "podman run --timeout - basic test" {
cid=timeouttest
cname=c_$(safename)
t0=$SECONDS
run_podman 255 run --name $cid --timeout 2 $IMAGE sleep 60
run_podman 255 run --name $cname --timeout 2 $IMAGE sleep 60
t1=$SECONDS
# Confirm that container is stopped. Podman-remote unfortunately
# cannot tell the difference between "stopped" and "exited", and
# spits them out interchangeably, so we need to recognize either.
run_podman inspect --format '{{.State.Status}} {{.State.ExitCode}}' $cid
run_podman inspect --format '{{.State.Status}} {{.State.ExitCode}}' $cname
is "$output" "\\(stopped\|exited\\) \-1" \
"Status and exit code of stopped container"
@ -785,7 +794,7 @@ json-file | f
assert "$delta_t" -gt 1 "podman stop: ran too quickly!"
assert "$delta_t" -le 6 "podman stop: took too long"
run_podman rm $cid
run_podman rm $cname
}
@test "podman run no /etc/mtab " {
@ -1029,21 +1038,26 @@ EOF
@test "podman run failed --rm " {
port=$(random_free_port)
# Container names must sort alphanumerically
c_ok=c1_$(safename)
c_fail_no_rm=c2_$(safename)
c_fail_with_rm=c3_$(safename)
# Run two containers with the same port bindings. The second must fail
run_podman run -p $port:80 --rm -d --name c_ok $IMAGE top
run_podman 126 run -p $port:80 -d --name c_fail_no_rm $IMAGE top
run_podman 126 run -p $port:80 --rm -d --name c_fail_with_rm $IMAGE top
run_podman run -p $port:80 --rm -d --name $c_ok $IMAGE top
run_podman 126 run -p $port:80 -d --name $c_fail_no_rm $IMAGE top
run_podman 126 run -p $port:80 --rm -d --name $c_fail_with_rm $IMAGE top
# Prior to #15060, the third container would still show up in ps -a
run_podman ps -a --sort names --format '{{.Image}}--{{.Names}}'
is "$output" "$IMAGE--c_fail_no_rm
$IMAGE--c_ok" \
is "$output" "$IMAGE--${c_ok}
$IMAGE--${c_fail_no_rm}" \
"podman ps -a shows running & failed containers, but not failed-with-rm"
run_podman container rm -f -t 0 c_ok c_fail_no_rm
run_podman container rm -f -t 0 $c_ok $c_fail_no_rm
}
@test "podman run --attach stdin prints container ID" {
ctr_name="container-$(random_string 5)"
ctr_name="container-$(safename)"
run_podman run --name $ctr_name --attach stdin $IMAGE echo hello
run_output=$output
run_podman inspect --format "{{.Id}}" $ctr_name
@ -1191,7 +1205,7 @@ EOF
}
@test "podman run bad --name" {
randomname=$(random_string 30)
randomname=c_$(safename)
run_podman 125 create --name "$randomname/bad" $IMAGE
run_podman create --name "/$randomname" $IMAGE
run_podman ps -a --filter name="^/$randomname$" --format '{{ .Names }}'
@ -1253,7 +1267,7 @@ EOF
run_podman run --uidmap=0:1000:200 --rm --rootfs "$romount:idmap=uids=@2000-1-1;gids=@2000-1-1" stat -c %u:%g /testfile
is "$output" "1:1"
myvolume=my-volume-$(random_string)
myvolume=my-volume-$(safename)
run_podman volume create $myvolume
mkdir $romount/volume
run_podman run --rm --uidmap=0:1000:10000 -v volume:/volume:idmap --rootfs $romount stat -c %u:%g /volume
@ -1265,7 +1279,7 @@ EOF
@test "podman run --restart=always -- wait" {
# regression test for #18572 to make sure Podman waits less than 20 seconds
ctr=$(random_string)
ctr=c_$(safename)
run_podman run -d --restart=always --name=$ctr $IMAGE false
PODMAN_TIMEOUT=20 run_podman wait $ctr
is "$output" "1" "container should exit 1"
@ -1284,7 +1298,7 @@ cat >$containersconf <<EOF
[engine]
static_dir="$static_dir"
EOF
ctr=$(random_string)
ctr=c_$(safename)
CONTAINERS_CONF_OVERRIDE=$containersconf PODMAN_TIMEOUT=20 run_podman run --name=$ctr $IMAGE true
CONTAINERS_CONF_OVERRIDE=$containersconf PODMAN_TIMEOUT=20 run_podman inspect --format "{{.ID}}" $ctr
cid="$output"
@ -1370,7 +1384,7 @@ search | $IMAGE |
}
@test "podman create container with conflicting name" {
local cname="$(random_string 10 | tr A-Z a-z)"
local cname=c_$(safename)
local output_msg_ext="^Error: .*: the container name \"$cname\" is already in use by .* You have to remove that container to be able to reuse that name: that name is already in use by an external entity, or use --replace to instruct Podman to do so."
local output_msg="^Error: .*: the container name \"$cname\" is already in use by .* You have to remove that container to be able to reuse that name: that name is already in use, or use --replace to instruct Podman to do so."
if is_remote; then
@ -1433,12 +1447,13 @@ search | $IMAGE |
}
@test "podman run - rm pod if container creation failed with -pod new:" {
run_podman run -d --name foobar $IMAGE hostname
cname=c_$(safename)
run_podman run -d --name $cname $IMAGE hostname
cid=$output
podname=pod$(random_string)
run_podman 125 run --rm --pod "new:$podname" --name foobar $IMAGE hostname
is "$output" ".*creating container storage: the container name \"foobar\" is already in use by"
podname=pod_$(safename)
run_podman 125 run --rm --pod "new:$podname" --name $cname $IMAGE hostname
is "$output" ".*creating container storage: the container name \"$cname\" is already in use by"
# pod should've been cleaned up
# if container creation failed
@ -1467,27 +1482,28 @@ search | $IMAGE |
@test "podman run - stopping loop" {
skip_if_remote "this doesn't work with with the REST service"
run_podman run -d --name testctr --stop-timeout 240 $IMAGE sh -c 'echo READY; sleep 999'
cname=c_$(safename)
run_podman run -d --name $cname --stop-timeout 240 $IMAGE sh -c 'echo READY; sleep 999'
cid="$output"
wait_for_ready testctr
wait_for_ready $cname
run_podman inspect --format '{{ .State.ConmonPid }}' testctr
run_podman inspect --format '{{ .State.ConmonPid }}' $cname
conmon_pid=$output
${PODMAN} stop testctr &
${PODMAN} stop $cname &
stop_pid=$!
timeout=20
while :;do
sleep 0.5
run_podman container inspect --format '{{.State.Status}}' testctr
run_podman container inspect --format '{{.State.Status}}' $cname
if [[ "$output" = "stopping" ]]; then
break
fi
timeout=$((timeout - 1))
if [[ $timeout == 0 ]]; then
run_podman ps -a
die "Timed out waiting for testctr to acknowledge signal"
die "Timed out waiting for container to acknowledge signal"
fi
done
@ -1496,14 +1512,14 @@ search | $IMAGE |
# Unclear why `-t0` is required here, works locally without.
# But it shouldn't hurt and does make the test pass...
PODMAN_TIMEOUT=5 run_podman 125 stop -t0 testctr
PODMAN_TIMEOUT=5 run_podman 125 stop -t0 $cname
is "$output" "Error: container .* conmon exited prematurely, exit code could not be retrieved: internal libpod error" "correct error on missing conmon"
# This should be safe because stop is guaranteed to call cleanup?
run_podman inspect --format "{{ .State.Status }}" testctr
run_podman inspect --format "{{ .State.Status }}" $cname
is "$output" "exited" "container has successfully transitioned to exited state after stop"
run_podman rm -f -t0 testctr
run_podman rm -f -t0 $cname
}
# vim: filetype=sh

View File

@ -1153,6 +1153,20 @@ function random_string() {
head /dev/urandom | tr -dc a-zA-Z0-9 | head -c$length
}
##############
# safename # Returns a pseudorandom string suitable for container/image/etc names
##############
#
# Name will include the bats test number, eg "t123_xyz123". When/if we
# ever parallelize system tests, this will make it possible to check
# for leaks and identify the test that leaked.
#
# String is lower-case so it can be used as an image name
#
function safename() {
echo "t${BATS_SUITE_TEST_NUMBER}_$(random_string 8 | tr A-Z a-z)"
}
#########################
# find_exec_pid_files # Returns nothing or exec_pid hash files
#########################