# -*- 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^X t POST "containers/foo/attach?logs=true&stream=false" 200 \ $'\001\030'$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=null" 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 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 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 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