Files
podman/test/apiv2/20-containers.at
Paul Holzinger df9344ac4b compat container create: match duplicate mounts correctly
The logic which checks for duplicated volumes here did not work
correctly because it used filepath.Clean(). However the writes to the
volDestinations map did not thus the string no longer matched when you
included a final slash for example.

So we can either call Clean() on all or no paths. I decided to call it
on no path because this is what we do right now. Just the check did it.

Fixed #18454

Signed-off-by: Paul Holzinger <pholzing@redhat.com>
2023-05-04 11:57:02 +02:00

627 lines
19 KiB
Bash

# -*- sh -*-
#
# test container-related endpoints
#
# WORKDIR=/data
ENV_WORKDIR_IMG=quay.io/libpod/testimage:20200929
MultiTagName=localhost/test/testformultitag:tag
podman pull $IMAGE &>/dev/null
podman tag $IMAGE $MultiTagName
podman pull $ENV_WORKDIR_IMG &>/dev/null
# Unimplemented
#t POST libpod/containers/create '' 201 'sdf'
# Ensure clean slate
podman rm -a -f &>/dev/null
t GET "libpod/containers/json (at start: clean slate)" 200 \
"[]" \
length=0
# check content type: https://github.com/containers/podman/issues/14647
response_headers=$(cat "$WORKDIR/curl.headers.out")
like "$response_headers" ".*Content-Type: application/json.*" "header does not contain application/json"
# Regression test for #12904 (race condition in logging code)
mytext="hi-there-$(random_string 15)"
podman run --rm -d --replace --name foo $IMAGE sh -c "echo $mytext;sleep 42"
# Logs output is prepended by ^A^Y (stdout = 1, length = 25 (with newline))
# Looks like it is missing the required 0 bytes from the message, why?
t POST "containers/foo/attach?logs=true&stream=false" 200 \
$'\001\031'$mytext
t POST "containers/foo/kill" 204
podman run -v /tmp:/tmp $IMAGE true
t GET libpod/containers/json 200 length=0
# bad all input
t GET libpod/containers/json?all='garb1age' 500 \
.cause="schema: error converting value for \"all\""
t GET libpod/containers/json?all=true 200 \
length=1 \
.[0].Id~[0-9a-f]\\{64\\} \
.[0].Image=$IMAGE \
.[0].Command[0]="true" \
.[0].State~\\\(exited\\\|stopped\\\) \
.[0].ExitCode=0 \
.[0].Mounts~.*/tmp \
.[0].IsInfra=false
# Test compat API for Network Settings (.Network is N/A when rootless)
network_expect="Networks.slirp4netns.NetworkID=slirp4netns"
if root; then
network_expect="Networks.podman.NetworkID=podman"
fi
t GET /containers/json?all=true 200 \
length=1 \
.[0].Id~[0-9a-f]\\{64\\} \
.[0].Image=$IMAGE \
.[0].Mounts~.*/tmp \
.[0].NetworkSettings.$network_expect
# compat API imageid with sha256: prefix
t GET containers/json?limit=1 200 \
.[0].ImageID~sha256:[0-9a-f]\\{64\\}
# Make sure `limit` works.
t GET libpod/containers/json?limit=1 200 \
length=1 \
.[0].Id~[0-9a-f]\\{64\\} \
.[0].Image=$IMAGE \
.[0].Command[0]="true" \
.[0].State~\\\(exited\\\|stopped\\\) \
.[0].ExitCode=0 \
.[0].IsInfra=false
# Make sure `last` works, which is an alias for `limit`.
# See https://github.com/containers/podman/issues/6413.
t GET libpod/containers/json?last=1 200 \
length=1 \
.[0].Id~[0-9a-f]\\{64\\} \
.[0].Image=$IMAGE \
.[0].Command[0]="true" \
.[0].State~\\\(exited\\\|stopped\\\) \
.[0].ExitCode=0 \
.[0].IsInfra=false
cid=$(jq -r '.[0].Id' <<<"$output")
if root; then
t GET libpod/containers/stats?containers='[$cid]' 200
else
if have_cgroupsv2; then
t GET libpod/containers/stats?containers='[$cid]' 200
else
t GET libpod/containers/stats?containers='[$cid]' 409
fi
fi
# max_usage is not set for cgroupv2
if have_cgroupsv2; then
t GET libpod/containers/stats?containers='[$cid]' 200 \
.memory_stats.max_usage=null
fi
t DELETE libpod/containers/$cid 200 .[0].Id=$cid
# Issue #14676: make sure the stats show the memory limit specified for the container
if root; then
CTRNAME=ctr-with-limit
podman run --name $CTRNAME -d -m 512m -v /tmp:/tmp $IMAGE top
t GET libpod/containers/$CTRNAME/stats?stream=false 200 \
.memory_stats.limit=536870912 \
.Id~[0-9a-f]\\{64\\}
# Make sure docker compat endpoint shows "id" lowercase
t GET containers/$CTRNAME/stats?stream=false 200 \
.memory_stats.limit=536870912 \
.id~[0-9a-f]\\{64\\}
t GET containers/$CTRNAME/top?stream=false 200 \
.Titles='[
"PID",
"USER",
"TIME",
"COMMAND"
]'
podman rm -f $CTRNAME
fi
# Issue #15765: make sure the memory limit is capped
if root; then
CTRNAME=ctr-with-limit
podman run --name $CTRNAME -d -m 512m -v /tmp:/tmp $IMAGE top
t GET libpod/containers/$CTRNAME/stats?stream=false 200 \
.memory_stats.limit!=18446744073709552000
podman rm -f $CTRNAME
fi
# Issue #6799: it should be possible to start a container, even w/o args.
t POST libpod/containers/create?name=test_noargs Image=${IMAGE} 201 \
.Id~[0-9a-f]\\{64\\}
cid=$(jq -r '.Id' <<<"$output")
# Prior to the fix in #6835, this would fail 500 "args must not be empty"
t POST libpod/containers/${cid}/start 204
# Container should exit almost immediately. Wait for it, confirm successful run
t POST "libpod/containers/${cid}/wait?condition=stopped&condition=exited" 200 '0'
t GET libpod/containers/${cid}/json 200 \
.Id=$cid \
.State.Status~\\\(exited\\\|stopped\\\) \
.State.Running=false \
.State.ExitCode=0 \
.Config.Umask=0022 # regression check for #15036
t DELETE libpod/containers/$cid 200 .[0].Id=$cid
CNAME=myfoo
podman run -d --name $CNAME $IMAGE top
t GET libpod/containers/json?all=true 200 \
.[0].Id~[0-9a-f]\\{64\\}
cid=$(jq -r '.[0].Id' <<<"$output")
# No such container
t POST "libpod/commit?container=nonesuch" 404
# Comment can only be used with docker format, not OCI
cparam="repo=newrepo&comment=foo&author=bob"
t POST "libpod/commit?container=$CNAME&$cparam" 500 \
.cause="messages are only compatible with the docker image format (-f docker)"
# Commit a new image from the container
t POST "libpod/commit?container=$CNAME" 200 \
.Id~[0-9a-f]\\{64\\}
iid=$(jq -r '.Id' <<<"$output")
t GET libpod/images/$iid/json 200 \
.RepoTags[0]=null \
.Author="" \
.Comment=""
# Commit a new image w/o tag
cparam="repo=newrepo&comment=foo&author=bob&format=docker"
t POST "libpod/commit?container=$CNAME&$cparam" 200
t GET libpod/images/newrepo:latest/json 200 \
.RepoTags[0]=localhost/newrepo:latest \
.Author=bob \
.Comment=foo
# Commit a new image w/ specified tag and author
cparam="repo=newrepo&tag=v1&author=alice"
t POST "libpod/commit?container=$cid&$cparam&pause=false" 200
t GET libpod/images/newrepo:v1/json 200 \
.RepoTags[0]=localhost/newrepo:v1 \
.Author=alice
# Commit a new image w/ full parameters
cparam="repo=newrepo&tag=v2&comment=bar&author=eric"
cparam="$cparam&format=docker&changes=CMD=/bin/foo"
t POST "libpod/commit?container=${cid:0:12}&$cparam&pause=true" 200
t GET libpod/images/newrepo:v2/json 200 \
.RepoTags[0]=localhost/newrepo:v2 \
.Author=eric \
.Comment=bar \
.Config.Cmd[-1]="/bin/foo"
# Create a container for testing the container initializing later
podman create -t -i --name myctr $IMAGE ls
# Check configuration before initializing
t GET libpod/containers/myctr/json 200 \
.Id~[0-9a-f]\\{64\\} \
.State.Status="created" \
.State.Pid=0 \
.ResolvConfPath="" \
.HostnamePath="" \
.HostsPath="" \
.NetworkSettings.SandboxKey=""
cpid_file=$(jq -r '.ConmonPidFile' <<<"$output")
userdata_path=$(dirname $cpid_file)
# Initializing the container
t POST libpod/containers/myctr/init 204
# Check configuration after initializing
t GET libpod/containers/myctr/json 200 \
.Id~[0-9a-f]\\{64\\} \
.State.Status="initialized" \
.State.Pid~[0-9]\\{1\,8\\} \
.ResolvConfPath=$userdata_path/resolv.conf \
.HostnamePath=$userdata_path/hostname \
.HostsPath=$userdata_path/hosts \
.NetworkSettings.SandboxKey~.*/netns/netns- \
.OCIConfigPath~.*config\.json \
.GraphDriver.Data.MergedDir~.*merged
# Test TS are in UTC
t GET containers/myctr/json 200 \
.Created~.*Z \
.State.StartedAt~.*Z \
.State.FinishedAt~.*Z
t DELETE images/localhost/newrepo:latest?force=true 200
t DELETE images/localhost/newrepo:v1?force=true 200
t DELETE images/localhost/newrepo:v2?force=true 200
t DELETE libpod/containers/$cid?force=true 200 .[0].Id=$cid
t DELETE libpod/containers/myctr 200
t DELETE libpod/containers/bogus 404
# test apiv2 create container with correct entrypoint and cmd
# --data '{"Image":"quay.io/libpod/alpine_labels:latest","Entrypoint":["echo"],"Cmd":["param1","param2"]}'
t POST containers/create \
Image=$IMAGE \
Entrypoint='["echo"]' \
Cmd='["param1","param2"]' \
201 \
.Id~[0-9a-f]\\{64\\}
cid=$(jq -r '.Id' <<<"$output")
t GET containers/$cid/json 200 \
.Config.Entrypoint[0]="echo" \
.Config.Cmd[0]="param1" \
.Config.Cmd[1]="param2" \
.Path="echo" \
.Args[0]="param1" \
.Args[1]="param2"
t DELETE containers/$cid 204
# test only set the entrypoint, Cmd should be []
t POST containers/create \
Image=$IMAGE \
Entrypoint='["echo","param1"]' \
201 \
.Id~[0-9a-f]\\{64\\}
cid=$(jq -r '.Id' <<<"$output")
t GET containers/$cid/json 200 \
.Config.Entrypoint[0]="echo" \
.Config.Entrypoint[1]="param1" \
.Config.Cmd='[]' \
.Path="echo" \
.Args[0]="param1"
# create a running container for after
t POST containers/create Image=$IMAGE Entrypoint='["top"]' 201 \
.Id~[0-9a-f]\\{64\\}
cid_top=$(jq -r '.Id' <<<"$output")
t GET containers/${cid_top}/json 200 \
.Config.Entrypoint[0]="top" \
.Config.Cmd='[]' \
.Path="top" \
.NetworkSettings.Networks.podman.NetworkID=podman
t POST containers/${cid_top}/start 204
# make sure the container is running
t GET containers/${cid_top}/json 200 \
.State.Status="running"
# 0 means unlimited, need same with docker
t GET containers/json?limit=0 200 \
.[0].Id~[0-9a-f]\\{64\\}
t GET 'containers/json?limit=0&all=1' 200 \
.[0].Id~[0-9a-f]\\{64\\} \
.[1].Id~[0-9a-f]\\{64\\}
t GET containers/json?limit=2 200 length=2
# Filter with two ids should return both container
t GET containers/json?filters='{"id":["'${cid}'","'${cid_top}'"]}&all=1' 200 length=2
# Filter with two ids and status running should return only 1 container
t GET containers/json?filters='{"id":["'${cid}'","'${cid_top}'"],"status":["running"]}&all=1' 200 \
length=1 \
.[0].Id=${cid_top}
t POST containers/${cid_top}/stop 204
t DELETE containers/$cid 204
t DELETE containers/$cid_top 204
# test the WORKDIR and StopSignal
t POST containers/create \
Image=$ENV_WORKDIR_IMG \
WorkingDir=/dataDir \
StopSignal=\"9\" \
201 \
.Id~[0-9a-f]\\{64\\}
cid=$(jq -r '.Id' <<<"$output")
t GET containers/$cid/json 200 \
.Config.WorkingDir="/dataDir" \
.Config.StopSignal="9"
t DELETE containers/$cid 204
# when the image had multi tags, the container's Image should be correct
# Fixes https://github.com/containers/podman/issues/8547
t POST containers/create Image=${MultiTagName} 201 \
.Id~[0-9a-f]\\{64\\}
cid=$(jq -r '.Id' <<<"$output")
t GET containers/$cid/json 200 \
.Config.Image=${MultiTagName} \
.Image~sha256:[0-9a-f]\\{64\\}
t DELETE containers/$cid 204
t DELETE images/${MultiTagName} 200
# vim: filetype=sh
# Test Volumes field adds an anonymous volume
t POST containers/create Image=$IMAGE Volumes='{"/test":{}}' 201 \
.Id~[0-9a-f]\\{64\\}
cid=$(jq -r '.Id' <<<"$output")
t GET containers/$cid/json 200 \
.Mounts[0].Destination="/test"
t DELETE containers/$cid?v=true 204
# Test Volumes with bind mount, for some reason docker-py sets this #18454
t POST containers/create Image=$IMAGE Volumes='{"/test/":{}}' HostConfig='{"Binds":["/tmp:/test/:ro"]}' 201 \
.Id~[0-9a-f]\\{64\\}
cid=$(jq -r '.Id' <<<"$output")
t GET containers/$cid/json 200 \
.Mounts[0].Destination="/test/"
t DELETE containers/$cid?v=true 204
# test port mapping
podman run -d --rm --name bar -p 8080:9090 $IMAGE top
t GET containers/json 200 \
.[0].Ports[0].PrivatePort=9090 \
.[0].Ports[0].PublicPort=8080 \
.[0].Ports[0].Type="tcp"
podman stop bar
#compat api list containers sanity checks
t GET containers/json?filters='garb1age}' 500 \
.cause="invalid character 'g' looking for beginning of value"
t GET containers/json?filters='{"label":["testl' 500 \
.cause="unexpected end of JSON input"
#libpod api list containers sanity checks
t GET libpod/containers/json?filters='{"status":["removing"]}' 200 length=0
t GET libpod/containers/json?filters='{"status":["bogus"]}' 500 \
.cause="invalid argument"
t GET libpod/containers/json?filters='garb1age}' 500 \
.cause="invalid character 'g' looking for beginning of value"
t GET libpod/containers/json?filters='{"label":["testl' 500 \
.cause="unexpected end of JSON input"
# Prune containers - bad filter input
t POST containers/prune?filters='garb1age}' 500 \
.cause="invalid character 'g' looking for beginning of value"
t POST libpod/containers/prune?filters='garb1age}' 500 \
.cause="invalid character 'g' looking for beginning of value"
# Prune containers with illformed label
t POST containers/prune?filters='{"label":["tes' 500 \
.cause="unexpected end of JSON input"
t POST libpod/containers/prune?filters='{"label":["tes' 500 \
.cause="unexpected end of JSON input"
t GET libpod/containers/json?filters='{"label":["testlabel"]}' 200 length=0
# libpod api: do not use list filters for prune
t POST libpod/containers/prune?filters='{"name":["anyname"]}' 500 \
.cause="name is an invalid filter"
t POST libpod/containers/prune?filters='{"id":["anyid"]}' 500 \
.cause="id is an invalid filter"
t POST libpod/containers/prune?filters='{"network":["anynetwork"]}' 500 \
.cause="network is an invalid filter"
# compat api: do not use list filters for prune
t POST containers/prune?filters='{"name":["anyname"]}' 500 \
.cause="name is an invalid filter"
t POST containers/prune?filters='{"id":["anyid"]}' 500 \
.cause="id is an invalid filter"
t POST containers/prune?filters='{"network":["anynetwork"]}' 500 \
.cause="network is an invalid filter"
# Test CPU limit (NanoCPUs)
t POST containers/create Image=$IMAGE HostConfig='{"NanoCpus":500000}' 201 \
.Id~[0-9a-f]\\{64\\}
cid=$(jq -r '.Id' <<<"$output")
t GET containers/$cid/json 200 \
.HostConfig.NanoCpus=500000
t DELETE containers/$cid?v=true 204
# Test Compat Create with default network mode (#10569)
t POST containers/create Image=$IMAGE HostConfig='{"NetworkMode":"default"}' 201 \
.Id~[0-9a-f]\\{64\\}
cid=$(jq -r '.Id' <<<"$output")
t GET containers/$cid/json 200 \
.HostConfig.NetworkMode="bridge"
t DELETE containers/$cid?v=true 204
# test create with default netns="host"
stop_service
CONTAINERS_CONF=$TESTS_DIR/containers.host-netns.conf start_service
# check that the default docker netns "default" is rewritten to "host"
# when the containers.conf explicitly uses "host"
t POST containers/create Image=$IMAGE HostConfig='{"NetworkMode":"default"}' 201 \
.Id~[0-9a-f]\\{64\\}
cid=$(jq -r '.Id' <<<"$output")
t GET containers/$cid/json 200 \
.HostConfig.NetworkMode="host"
t DELETE containers/$cid?v=true 204
# Restart with the default containers.conf for next tests.
stop_service
start_service
# Test Compat Create with healthcheck, check default values
t POST containers/create Image=$IMAGE Cmd='["top"]' Healthcheck='{"Test":["true"]}' 201 \
.Id~[0-9a-f]\\{64\\}
cid=$(jq -r '.Id' <<<"$output")
t GET containers/$cid/json 200 \
.Config.Healthcheck.Interval=30000000000 \
.Config.Healthcheck.Timeout=30000000000 \
.Config.Healthcheck.Retries=3
# compat api: Test for mount options support
# Sigh, JSON can't handle octal. 0755(octal) = 493(decimal)
payload='{"Mounts":[{"Type":"tmpfs","Target":"/mnt/scratch","TmpfsOptions":{"SizeBytes":1024,"Mode":493}}]}'
t POST containers/create Image=$IMAGE HostConfig="$payload" 201 .Id~[0-9a-f]\\{64\\}
cid=$(jq -r '.Id' <<<"$output")
t GET containers/$cid/json 200 \
.HostConfig.Tmpfs['"/mnt/scratch"']~.*size=1024.* \
.HostConfig.Tmpfs['"/mnt/scratch"']~.*mode=755.*
t DELETE containers/$cid?v=true 204
# compat api: tmpfs without mount options
payload='{"Mounts":[{"Type":"tmpfs","Target":"/mnt/scratch"}]}'
t POST containers/create Image=$IMAGE HostConfig="$payload" 201 .Id~[0-9a-f]\\{64\\}
cid=$(jq -r '.Id' <<<"$output")
t GET containers/$cid/json 200 \
.HostConfig.Tmpfs['"/mnt/scratch"']~.*tmpcopyup.* \
t DELETE containers/$cid?v=true 204
# compat api: bind mount without mount options
payload='{"Mounts":[{"Type":"bind","Source":"/tmp","Target":"/mnt"}]}'
t POST containers/create Image=$IMAGE HostConfig="$payload" 201 .Id~[0-9a-f]\\{64\\}
cid=$(jq -r '.Id' <<<"$output")
t GET containers/$cid/json 200 \
.HostConfig.Binds[0]~/tmp:/mnt:.* \
t DELETE containers/$cid?v=true 204
# test apiv2 create/commit
t POST containers/create \
Image=$IMAGE \
Entrypoint='["echo"]' \
Cmd='["param1","param2"]' \
201 \
.Id~[0-9a-f]\\{64\\}
cid=$(jq -r '.Id' <<<"$output")
# No such container
t POST "commit?container=nonesuch" 404
cparam="repo=newrepo&tag=v3&comment=abcd&author=eric"
cparam="$cparam&format=docker&changes=CMD%20/bin/bar%0aEXPOSE%209090"
t POST "commit?container=${cid:0:12}&$cparam" 201 \
.Id~[0-9a-f]\\{64\\}
iid=$(jq -r '.Id' <<<"$output")
t GET images/$iid/json 200 \
.RepoTags[0]=docker.io/library/newrepo:v3 \
.Config.ExposedPorts~.*"9090/tcp" \
.Config.Cmd~.*"/bin/bar" \
.Comment="abcd"
t DELETE containers/$cid 204
t DELETE images/docker.io/library/newrepo:v3?force=false 200
# test create without default no_hosts
t POST containers/create \
Image=$IMAGE \
201 \
.Id~[0-9a-f]\\{64\\}
cid=$(jq -r '.Id' <<<"$output")
t POST libpod/containers/$cid/init 204
t GET libpod/containers/$cid/json 200
cpid_file=$(jq -r '.ConmonPidFile' <<<"$output")
userdata_path=$(dirname $cpid_file)
t GET libpod/containers/$cid/json 200 \
.HostsPath=$userdata_path/hosts
t DELETE containers/$cid 204
# test create with default no_hosts=true
stop_service
CONTAINERS_CONF=$TESTS_DIR/containers.no_hosts.conf start_service
# check docker and libpod endpoint
for endpoint in containers/create libpod/containers/create; do
t POST $endpoint \
Image=$IMAGE \
201 \
.Id~[0-9a-f]\\{64\\}
cid=$(jq -r '.Id' <<<"$output")
t POST libpod/containers/$cid/init 204
t GET libpod/containers/$cid/json 200 \
.HostsPath=""
t DELETE containers/$cid 204
done
stop_service
start_service
# Our states are different from Docker's.
# Regression test for #14700 (Docker compat returning unknown "initialized" for status.status) to ensure the stay compatible
podman create --name status-test $IMAGE sh -c "sleep 3"
t GET containers/status-test/json 200 .State.Status="created"
podman init status-test
t GET containers/status-test/json 200 .State.Status="created"
podman start status-test
t GET containers/status-test/json 200 .State.Status="running"
podman pause status-test
t GET containers/status-test/json 200 .State.Status="paused"
podman unpause status-test
t GET containers/status-test/json 200 .State.Status="running"
podman stop status-test &
sleep 1
t GET containers/status-test/json 200 .State.Status="stopping"
sleep 3
t GET containers/status-test/json 200 .State.Status="exited"
# test podman generate spec as input for the api
cname=specgen$(random_string 10)
podman create --name=$cname $IMAGE
TMPD=$(mktemp -d podman-apiv2-test.build.XXXXXXXX)
podman generate spec -f ${TMPD}/myspec.json -c $cname
# Create a container based on that spec
t POST libpod/containers/create ${TMPD}/myspec.json 201 \
.Id~[0-9a-f]\\{64\\}
# Verify
t GET libpod/containers/$cname/json 200 \
.ImageName=$IMAGE \
.Name=$cname
if root; then
podman run -dt --name=updateCtr alpine
echo '{"Memory":{"Limit":500000}, "CPU":{"Shares":123}}' >${TMPD}/update.json
t POST libpod/containers/updateCtr/update ${TMPD}/update.json 201
# Verify
echo '{ "AttachStdout":true,"Cmd":["cat","/sys/fs/cgroup/cpu.weight"]}' >${TMPD}/exec.json
t POST containers/updateCtr/exec ${TMPD}/exec.json 201 .Id~[0-9a-f]\\{64\\}
eid=$(jq -r '.Id' <<<"$output")
# 002 is the byte length
t POST exec/$eid/start 200 $'\001\0025'
podman rm -f updateCtr
fi
rm -rf $TMPD
podman container rm -fa