diff options
-rw-r--r-- | pkg/api/handlers/compat/images_build.go | 7 | ||||
-rw-r--r-- | pkg/api/server/register_images.go | 2 | ||||
-rw-r--r-- | test/apiv2/01-basic.at | 10 | ||||
-rw-r--r-- | test/apiv2/10-images.at | 8 | ||||
-rw-r--r-- | test/apiv2/12-imagesMore.at | 10 | ||||
-rw-r--r-- | test/apiv2/20-containers.at | 53 | ||||
-rw-r--r-- | test/apiv2/22-stop.at | 16 | ||||
-rw-r--r-- | test/apiv2/25-containersMore.at | 10 | ||||
-rw-r--r-- | test/apiv2/26-containersWait.at | 10 | ||||
-rw-r--r-- | test/apiv2/30-volumes.at | 47 | ||||
-rw-r--r-- | test/apiv2/35-networks.at | 13 | ||||
-rw-r--r-- | test/apiv2/40-pods.at | 40 | ||||
-rw-r--r-- | test/apiv2/44-mounts.at | 11 | ||||
-rw-r--r-- | test/apiv2/45-system.at | 34 | ||||
-rw-r--r-- | test/apiv2/50-secrets.at | 8 | ||||
-rw-r--r-- | test/apiv2/README.md | 11 | ||||
-rw-r--r-- | test/apiv2/rest_api/__init__.py | 26 | ||||
-rw-r--r-- | test/apiv2/rest_api/test_rest_v2_0_0.py | 59 | ||||
-rwxr-xr-x | test/apiv2/test-apiv2 | 36 | ||||
-rw-r--r-- | test/system/260-sdnotify.bats | 13 |
20 files changed, 241 insertions, 183 deletions
diff --git a/pkg/api/handlers/compat/images_build.go b/pkg/api/handlers/compat/images_build.go index 392f688dd..7751b91a7 100644 --- a/pkg/api/handlers/compat/images_build.go +++ b/pkg/api/handlers/compat/images_build.go @@ -445,6 +445,13 @@ loop: logrus.Warnf("Failed to json encode error %v", err) } flush() + for _, tag := range query.Tag { + m.Stream = fmt.Sprintf("Successfully tagged %s\n", tag) + if err := enc.Encode(m); err != nil { + logrus.Warnf("Failed to json encode error %v", err) + } + flush() + } } } break loop diff --git a/pkg/api/server/register_images.go b/pkg/api/server/register_images.go index f6a8a37ca..3d86e5d38 100644 --- a/pkg/api/server/register_images.go +++ b/pkg/api/server/register_images.go @@ -652,6 +652,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // example: | // (build details...) // Successfully built 8ba084515c724cbf90d447a63600c0a6 + // Successfully tagged your_image:latest // 400: // $ref: "#/responses/BadParamError" // 500: @@ -1485,7 +1486,6 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // description: output from build process // example: | // (build details...) - // Successfully built 8ba084515c724cbf90d447a63600c0a6 // 400: // $ref: "#/responses/BadParamError" // 500: diff --git a/test/apiv2/01-basic.at b/test/apiv2/01-basic.at index 1357e0ca6..788007069 100644 --- a/test/apiv2/01-basic.at +++ b/test/apiv2/01-basic.at @@ -30,18 +30,18 @@ done # Garbage tests - requests that should yield errors # t GET /nonesuch 404 -t POST /nonesuch '' 404 +t POST /nonesuch 404 t GET container/nonesuch/json 404 t GET libpod/containers/nonesuch/json 404 #### FIXME: maybe someday: t GET 'libpod/containers/json?a=b' 400 # Method not allowed -t POST /_ping '' 405 +t POST /_ping 405 t DELETE /_ping 405 -t POST libpod/containers/json '' 405 -t POST libpod/pods/abc '' 405 -t POST info '' 405 +t POST libpod/containers/json 405 +t POST libpod/pods/abc 405 +t POST info 405 t GET libpod/containers/create 405 # diff --git a/test/apiv2/10-images.at b/test/apiv2/10-images.at index f866422e2..4ebaeff45 100644 --- a/test/apiv2/10-images.at +++ b/test/apiv2/10-images.at @@ -41,18 +41,18 @@ t GET images/$iid/json 200 \ .Id=sha256:$iid \ .RepoTags[0]=$IMAGE -t POST "images/create?fromImage=alpine" '' 200 .error~null .status~".*Download complete.*" +t POST "images/create?fromImage=alpine" 200 .error~null .status~".*Download complete.*" -t POST "images/create?fromImage=alpine&tag=latest" '' 200 +t POST "images/create?fromImage=alpine&tag=latest" 200 # Make sure that new images are pulled old_iid=$(podman image inspect --format "{{.ID}}" docker.io/library/alpine:latest) podman rmi -f docker.io/library/alpine:latest podman tag $IMAGE docker.io/library/alpine:latest -t POST "images/create?fromImage=alpine" '' 200 .error~null .status~".*$old_iid.*" +t POST "images/create?fromImage=alpine" 200 .error~null .status~".*$old_iid.*" podman untag $IMAGE docker.io/library/alpine:latest -t POST "images/create?fromImage=quay.io/libpod/alpine&tag=sha256:fa93b01658e3a5a1686dc3ae55f170d8de487006fb53a28efcd12ab0710a2e5f" '' 200 +t POST "images/create?fromImage=quay.io/libpod/alpine&tag=sha256:fa93b01658e3a5a1686dc3ae55f170d8de487006fb53a28efcd12ab0710a2e5f" 200 # Display the image history t GET libpod/images/nonesuch/history 404 diff --git a/test/apiv2/12-imagesMore.at b/test/apiv2/12-imagesMore.at index ce3049106..144b83194 100644 --- a/test/apiv2/12-imagesMore.at +++ b/test/apiv2/12-imagesMore.at @@ -17,10 +17,10 @@ t GET libpod/images/$IMAGE/tree 200 \ .Tree~^Image # Tag nonesuch image -t POST "libpod/images/nonesuch/tag?repo=myrepo&tag=mytag" '' 404 +t POST "libpod/images/nonesuch/tag?repo=myrepo&tag=mytag" 404 # Tag the image -t POST "libpod/images/$IMAGE/tag?repo=localhost:5000/myrepo&tag=mytag" '' 201 +t POST "libpod/images/$IMAGE/tag?repo=localhost:5000/myrepo&tag=mytag" 201 t GET libpod/images/$IMAGE/json 200 \ .RepoTags[1]=localhost:5000/myrepo:mytag @@ -41,13 +41,13 @@ if [ -z "${GOT_DIGEST}" ] ; then fi # Push to local registry -t POST "images/localhost:5000/myrepo/push?tlsVerify=false&tag=mytag" '' 200 +t POST "images/localhost:5000/myrepo/push?tlsVerify=false&tag=mytag" 200 # Untag the image -t POST "libpod/images/$iid/untag?repo=localhost:5000/myrepo&tag=mytag" '' 201 +t POST "libpod/images/$iid/untag?repo=localhost:5000/myrepo&tag=mytag" 201 # Try to push non-existing image -t POST "images/localhost:5000/idonotexist/push?tlsVerify=false" '' 200 +t POST "images/localhost:5000/idonotexist/push?tlsVerify=false" 200 jq -re 'select(.errorDetail)' <<<"$output" &>/dev/null || echo -e "${red}not ok: error message not found in output${nc}" 1>&2 t GET libpod/images/$IMAGE/json 200 \ diff --git a/test/apiv2/20-containers.at b/test/apiv2/20-containers.at index 383d92ef3..478717700 100644 --- a/test/apiv2/20-containers.at +++ b/test/apiv2/20-containers.at @@ -31,12 +31,16 @@ t GET libpod/containers/json?all=true 200 \ .[0].ExitCode=0 \ .[0].IsInfra=false -# Test compat API for Network Settings +# Test compat API for Network Settings (.Network is N/A when rootless) +network_expect= +if root; then + network_expect='.[0].NetworkSettings.Networks.podman.NetworkID=podman' +fi t GET /containers/json?all=true 200 \ length=1 \ .[0].Id~[0-9a-f]\\{64\\} \ .[0].Image=$IMAGE \ - .[0].NetworkSettings.Networks.podman.NetworkID=podman + $network_expect # Make sure `limit` works. t GET libpod/containers/json?limit=1 200 \ @@ -68,9 +72,9 @@ 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 +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 POST "libpod/containers/${cid}/wait?condition=stopped&condition=exited" 200 '0' t GET libpod/containers/${cid}/json 200 \ .Id=$cid \ .State.Status~\\\(exited\\\|stopped\\\) \ @@ -85,15 +89,15 @@ t GET libpod/containers/json?all=true 200 \ cid=$(jq -r '.[0].Id' <<<"$output") # No such container -t POST "libpod/commit?container=nonesuch" '' 404 +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 \ +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 \ +t POST "libpod/commit?container=$CNAME" 200 \ .Id~[0-9a-f]\\{64\\} iid=$(jq -r '.Id' <<<"$output") t GET libpod/images/$iid/json 200 \ @@ -103,7 +107,7 @@ t GET libpod/images/$iid/json 200 \ # 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 POST "libpod/commit?container=$CNAME&$cparam" 200 t GET libpod/images/newrepo:latest/json 200 \ .RepoTags[0]=localhost/newrepo:latest \ .Author=bob \ @@ -111,7 +115,7 @@ t GET libpod/images/newrepo:latest/json 200 \ # 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 POST "libpod/commit?container=$cid&$cparam&pause=false" 200 t GET libpod/images/newrepo:v1/json 200 \ .RepoTags[0]=localhost/newrepo:v1 \ .Author=alice @@ -119,7 +123,7 @@ t GET libpod/images/newrepo:v1/json 200 \ # 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 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 \ @@ -143,7 +147,7 @@ cpid_file=$(jq -r '.ConmonPidFile' <<<"$output") userdata_path=$(dirname $cpid_file) # Initializing the container -t POST libpod/containers/myctr/init '' 204 +t POST libpod/containers/myctr/init 204 # Check configuration after initializing t GET libpod/containers/myctr/json 200 \ @@ -167,7 +171,11 @@ 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 \ +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 \ @@ -180,7 +188,10 @@ t GET containers/$cid/json 200 \ t DELETE containers/$cid 204 # test only set the entrypoint, Cmd should be [] -t POST containers/create '"Image":"'$IMAGE'","Entrypoint":["echo","param1"]' 201 \ +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 \ @@ -191,14 +202,14 @@ t GET containers/$cid/json 200 \ .Args[0]="param1" # create a running container for after -t POST containers/create '"Image":"'$IMAGE'","Entrypoint":["top"]' 201 \ +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" -t POST containers/${cid_top}/start '' 204 +t POST containers/${cid_top}/start 204 # make sure the container is running t GET containers/${cid_top}/json 200 \ .State.Status="running" @@ -220,13 +231,17 @@ t GET containers/json?filters='{"id":["'${cid}'","'${cid_top}'"],"status":["runn length=1 \ .[0].Id=${cid_top} -t POST containers/${cid_top}/stop "" 204 +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 \ +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 \ @@ -247,7 +262,7 @@ t DELETE images/${MultiTagName}?force=true 200 # vim: filetype=sh # Test Volumes field adds an anonymous volume -t POST containers/create '"Image":"'$IMAGE'","Volumes":{"/test":{}}' 201 \ +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 \ @@ -266,7 +281,7 @@ t GET containers/json 200 \ podman stop bar # Test CPU limit (NanoCPUs) -t POST containers/create '"Image":"'$IMAGE'","HostConfig":{"NanoCpus":500000}' 201 \ +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 \ diff --git a/test/apiv2/22-stop.at b/test/apiv2/22-stop.at index 11318ca81..91bc9937d 100644 --- a/test/apiv2/22-stop.at +++ b/test/apiv2/22-stop.at @@ -8,17 +8,17 @@ podman pull $IMAGE &>/dev/null # stop, by name podman run -dt --name mytop $IMAGE top &>/dev/null -t GET libpod/containers/mytop/json 200 .State.Status=running -t POST libpod/containers/mytop/stop "" 204 -t GET libpod/containers/mytop/json 200 .State.Status~\\\(exited\\\|stopped\\\) -t DELETE libpod/containers/mytop 204 +t GET libpod/containers/mytop/json 200 .State.Status=running +t POST libpod/containers/mytop/stop 204 +t GET libpod/containers/mytop/json 200 .State.Status~\\\(exited\\\|stopped\\\) +t DELETE libpod/containers/mytop 204 # stop, by ID # Remember that podman() hides all output; we need to get our CID via inspect podman run -dt --name mytop $IMAGE top -t GET libpod/containers/mytop/json 200 .State.Status=running +t GET libpod/containers/mytop/json 200 .State.Status=running cid=$(jq -r .Id <<<"$output") -t POST libpod/containers/$cid/stop "" 204 -t GET libpod/containers/mytop/json 200 .State.Status~\\\(exited\\\|stopped\\\) -t DELETE libpod/containers/mytop 204 +t POST libpod/containers/$cid/stop 204 +t GET libpod/containers/mytop/json 200 .State.Status~\\\(exited\\\|stopped\\\) +t DELETE libpod/containers/mytop 204 diff --git a/test/apiv2/25-containersMore.at b/test/apiv2/25-containersMore.at index b88c798eb..39bfa2e32 100644 --- a/test/apiv2/25-containersMore.at +++ b/test/apiv2/25-containersMore.at @@ -17,7 +17,7 @@ t GET libpod/containers/nonesuch/exists 404 t GET libpod/containers/foo/exists 204 # Pause the container -t POST libpod/containers/foo/pause '' 204 +t POST libpod/containers/foo/pause 204 t GET libpod/containers/foo/json 200 \ .Id~[0-9a-f]\\{64\\} \ @@ -27,7 +27,7 @@ t GET libpod/containers/foo/json 200 \ .Name=foo # Unpause the container -t POST libpod/containers/foo/unpause '' 204 +t POST libpod/containers/foo/unpause 204 t GET libpod/containers/foo/json 200 \ .Id~[0-9a-f]\\{64\\} \ @@ -44,11 +44,11 @@ t GET libpod/containers/foo/top 200 \ t GET libpod/containers/nonesuch/top 404 # Mount the container to host filesystem -t POST libpod/containers/foo/mount '' 200 +t POST libpod/containers/foo/mount 200 like "$output" ".*merged" "Check container mount" # Unmount the container -t POST libpod/containers/foo/unmount '' 204 +t POST libpod/containers/foo/unmount 204 t DELETE libpod/containers/foo?force=true 204 @@ -85,7 +85,7 @@ podman run $IMAGE true podman run $IMAGE true podman run $IMAGE true -t POST libpod/containers/prune '' 200 +t POST libpod/containers/prune 200 t GET libpod/containers/json 200 \ length=0 # vim: filetype=sh diff --git a/test/apiv2/26-containersWait.at b/test/apiv2/26-containersWait.at index 3f530c3f0..6a628e55a 100644 --- a/test/apiv2/26-containersWait.at +++ b/test/apiv2/26-containersWait.at @@ -13,15 +13,15 @@ podman rm -a -f &>/dev/null CTR="WaitTestingCtr" -t POST "containers/nonExistent/wait?condition=next-exit" '' 404 +t POST "containers/nonExistent/wait?condition=next-exit" 404 podman create --name "${CTR}" --entrypoint '["sleep", "0.5"]' "${IMAGE}" -t POST "containers/${CTR}/wait?condition=non-existent-cond" '' 400 +t POST "containers/${CTR}/wait?condition=non-existent-cond" 400 -t POST "containers/${CTR}/wait?condition=not-running" '' 200 +t POST "containers/${CTR}/wait?condition=not-running" 200 -t POST "containers/${CTR}/wait?condition=next-exit" '' 200 & +t POST "containers/${CTR}/wait?condition=next-exit" 200 & child_pid=$! podman start "${CTR}" wait "${child_pid}" @@ -37,7 +37,7 @@ if kill -2 "${child_pid}" 2> "/dev/null"; then WAIT_TEST_ERROR="1" fi -t POST "containers/${CTR}/wait?condition=removed" '' 200 & +t POST "containers/${CTR}/wait?condition=removed" 200 & child_pid=$! podman container rm "${CTR}" wait "${child_pid}" diff --git a/test/apiv2/30-volumes.at b/test/apiv2/30-volumes.at index cf4b3d3ea..c27c638bb 100644 --- a/test/apiv2/30-volumes.at +++ b/test/apiv2/30-volumes.at @@ -13,25 +13,34 @@ t POST libpod/volumes/create name=foo1 201 \ .CreatedAt~[0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}.* \ .Labels={} \ .Options={} -t POST libpod/volumes/create '' 201 +t POST libpod/volumes/create 201 t POST libpod/volumes/create \ - '"Name":"foo2","Label":{"testlabel":"testonly"},"Options":{"type":"tmpfs","o":"nodev,noexec"}}' 201 \ - .Name=foo2 \ - .Labels.testlabel=testonly \ - .Options.type=tmpfs \ - .Options.o=nodev,noexec + Name=foo2 \ + Label='{"testlabel":"testonly"}' \ + Options='{"type":"tmpfs","o":"nodev,noexec"}}' \ + 201 \ + .Name=foo2 \ + .Labels.testlabel=testonly \ + .Options.type=tmpfs \ + .Options.o=nodev,noexec t POST libpod/volumes/create \ - '"Name":"foo3","Label":{"testlabel":""},"Options":{"type":"tmpfs","o":"nodev,noexec"}}' 201 \ - .Name=foo3 \ - .Labels.testlabel="" \ - .Options.type=tmpfs \ - .Options.o=nodev,noexec + Name=foo3 \ + Label='{"testlabel":""}' \ + Options='{"type":"tmpfs","o":"nodev,noexec"}}' \ + 201 \ + .Name=foo3 \ + .Labels.testlabel="" \ + .Options.type=tmpfs \ + .Options.o=nodev,noexec t POST libpod/volumes/create \ - '"Name":"foo4","Label":{"testlabel1":"testonly"},"Options":{"type":"tmpfs","o":"nodev,noexec"}}' 201 \ - .Name=foo4 \ - .Labels.testlabel1=testonly \ - .Options.type=tmpfs \ - .Options.o=nodev,noexec + Name=foo4 \ + Label='{"testlabel1":"testonly"}' \ + Options='{"type":"tmpfs","o":"nodev,noexec"}}' \ + 201 \ + .Name=foo4 \ + .Labels.testlabel1=testonly \ + .Options.type=tmpfs \ + .Options.o=nodev,noexec # Negative test # We have created a volume named "foo1" @@ -78,15 +87,15 @@ t DELETE libpod/volumes/foo1 404 \ .response=404 ## Prune volumes with label matching 'testlabel1=testonly' -t POST libpod/volumes/prune?filters='{"label":["testlabel1=testonly"]}' "" 200 +t POST libpod/volumes/prune?filters='{"label":["testlabel1=testonly"]}' 200 t GET libpod/volumes/json?filters='{"label":["testlabel1=testonly"]}' 200 length=0 ## Prune volumes with label matching 'testlabel' -t POST libpod/volumes/prune?filters='{"label":["testlabel"]}' "" 200 +t POST libpod/volumes/prune?filters='{"label":["testlabel"]}' 200 t GET libpod/volumes/json?filters='{"label":["testlabel"]}' 200 length=0 ## Prune volumes -t POST libpod/volumes/prune "" 200 +t POST libpod/volumes/prune 200 #After prune volumes, there should be no volume existing t GET libpod/volumes/json 200 length=0 diff --git a/test/apiv2/35-networks.at b/test/apiv2/35-networks.at index d3bbaf32b..98786e914 100644 --- a/test/apiv2/35-networks.at +++ b/test/apiv2/35-networks.at @@ -6,18 +6,21 @@ t GET networks/non-existing-network 404 \ .cause='network not found' -t POST libpod/networks/create?name=network1 '' 200 \ +t POST libpod/networks/create?name=network1 200 \ .Filename~.*/network1\\.conflist # --data '{"Subnet":{"IP":"10.10.254.0","Mask":[255,255,255,0]},"Labels":{"abc":"val"}}' -t POST libpod/networks/create?name=network2 '"Subnet":{"IP":"10.10.254.0","Mask":[255,255,255,0]},"Labels":{"abc":"val"}' 200 \ +t POST libpod/networks/create?name=network2 \ + Subnet='{"IP":"10.10.254.0","Mask":[255,255,255,0]}' \ + Labels='{"abc":"val"}' \ + 200 \ .Filename~.*/network2\\.conflist # test for empty mask -t POST libpod/networks/create '"Subnet":{"IP":"10.10.1.0","Mask":[]}' 500 \ +t POST libpod/networks/create Subnet='{"IP":"10.10.1.0","Mask":[]}' 500 \ .cause~'.*cannot be empty' # test for invalid mask -t POST libpod/networks/create '"Subnet":{"IP":"10.10.1.0","Mask":[0,255,255,0]}' 500 \ +t POST libpod/networks/create Subnet='{"IP":"10.10.1.0","Mask":[0,255,255,0]}' 500 \ .cause~'.*mask is invalid' # network list @@ -55,7 +58,7 @@ t GET networks/a7662f44d65029fd4635c91feea3d720a57cef52e2a9fcc7772b69072cc1ccd1 .Scope=local # network create docker -t POST networks/create '"Name":"net3","IPAM":{"Config":[]}' 201 +t POST networks/create Name=net3\ IPAM='{"Config":[]}' 201 # network delete docker t DELETE networks/net3 204 diff --git a/test/apiv2/40-pods.at b/test/apiv2/40-pods.at index ce65105d2..f3272c41e 100644 --- a/test/apiv2/40-pods.at +++ b/test/apiv2/40-pods.at @@ -25,70 +25,70 @@ t POST "libpod/pods/create (dup pod)" name=foo 409 \ #t POST libpod/pods/create a=b 400 .cause='bad parameter' # FIXME: unimplemented -t POST libpod/pods/foo/start '' 200 \ +t POST libpod/pods/foo/start 200 \ .Errs=null \ .Id=$pod_id -t POST libpod/pods/foo/start '' 304 \ +t POST libpod/pods/foo/start 304 \ -t POST libpod/pods/fakename/start '' 404 \ +t POST libpod/pods/fakename/start 404 \ .cause="no such pod" \ .message="no pod with name or ID fakename found: no such pod" if root || have_cgroupsv2; then - t POST libpod/pods/foo/pause '' 200 + t POST libpod/pods/foo/pause 200 else # Rootless cgroupsv1 : unsupported - t POST "libpod/pods/foo/pause (rootless cgroups v1)" '' 500 \ + t POST "libpod/pods/foo/pause (rootless cgroups v1)" 500 \ .cause="this container does not have a cgroup" \ .message~".*pause pods containing rootless containers with cgroup V1" fi -t POST libpod/pods/foo/unpause '' 200 -t POST "libpod/pods/foo/unpause (2nd unpause in a row)" '' 200 -t POST "libpod/pods/fakename/unpause" '' 404\ +t POST libpod/pods/foo/unpause 200 +t POST "libpod/pods/foo/unpause (2nd unpause in a row)" 200 +t POST "libpod/pods/fakename/unpause" 404\ .cause="no such pod" \ .message="no pod with name or ID fakename found: no such pod" -t POST libpod/pods/foo/stop '' 200 \ +t POST libpod/pods/foo/stop 200 \ .Errs=null \ .Id=$pod_id -t POST "libpod/pods/foo/stop (pod is already stopped)" '' 304 -t POST "libpod/pods/fakename/stop" '' 404\ +t POST "libpod/pods/foo/stop (pod is already stopped)" 304 +t POST "libpod/pods/fakename/stop" 404\ .cause="no such pod" \ .message="no pod with name or ID fakename found: no such pod" -t POST libpod/pods/foo/restart '' 200 \ +t POST libpod/pods/foo/restart 200 \ .Errs=null \ .Id=$pod_id -t POST "libpod/pods/bar/restart (restart on nonexistent pod)" '' 404 +t POST "libpod/pods/bar/restart (restart on nonexistent pod)" 404 t POST libpod/pods/create name=bar 201 .Id~[0-9a-f]\\{64\\} pod_bar_id=$(jq -r .Id <<<"$output") -t POST libpod/pods/bar/restart '' 200 \ +t POST libpod/pods/bar/restart 200 \ .Errs=null \ .Id=$pod_bar_id -t GET libpod/pods/bar/json 200 \ +t GET libpod/pods/bar/json 200 \ .State=Running -t POST libpod/pods/bar/restart '' 200 \ +t POST libpod/pods/bar/restart 200 \ .Errs=null \ .Id=$pod_bar_id -t POST "libpod/pods/bar/stop?t=invalid" '' 400 \ +t POST "libpod/pods/bar/stop?t=invalid" 400 \ .cause="schema: error converting value for \"t\"" \ .message~"failed to parse parameters for" podman run -d --pod bar busybox sleep 999 -t POST libpod/pods/bar/stop?t=1 '' 200 \ +t POST libpod/pods/bar/stop?t=1 200 \ .Errs=null \ .Id=$pod_bar_id -t POST libpod/pods/bar/start '' 200 +t POST libpod/pods/bar/start 200 t GET libpod/pods/stats?all=true 200 is $(jq '. | length' <<<"$output") 3 "stats?all=true: number of records found" @@ -118,7 +118,7 @@ t GET libpod/pods/foo/top?ps_args=args,pid 200 \ # FIXME: I'm not sure what 'prune' is supposed to do; as of 20200224 it # just returns 200 (ok) with empty result list. -#t POST libpod/pods/prune '' 200 # FIXME: 2020-02-24 returns 200 {} +#t POST libpod/pods/prune 200 # FIXME: 2020-02-24 returns 200 {} #t POST libpod/pods/prune 'a=b' 400 # FIXME: 2020-02-24 returns 200 # Clean up; and try twice, making sure that the second time fails diff --git a/test/apiv2/44-mounts.at b/test/apiv2/44-mounts.at index 5dc560852..901245da6 100644 --- a/test/apiv2/44-mounts.at +++ b/test/apiv2/44-mounts.at @@ -4,7 +4,12 @@ podman pull $IMAGE &>/dev/null # Test various HostConfig options tmpfs_name="/mytmpfs" -t POST containers/create?name=hostconfig_test '"Image":"'$IMAGE'","Cmd":["df"],"HostConfig":{"Binds":["/tmp/doesnotexist:/test1"],"TmpFs":{"'$tmpfs_name'":"rw"}}' 201 \ +t POST containers/create?name=hostconfig_test \ + Image=$IMAGE \ + Cmd='["df"]' \ + HostConfig='{"Binds":["/tmp/doesnotexist:/test1"]' \ + TmpFs="{\"$tmpfs_name\":\"rw\"}}" \ + 201 \ .Id~[0-9a-f]\\{64\\} cid=$(jq -r '.Id' <<<"$output") @@ -13,8 +18,8 @@ t GET containers/${cid}/json 200 \ .HostConfig.Tmpfs[\"${tmpfs_name}\"]~rw, # Run the container, verify output -t POST containers/${cid}/start '' 204 -t POST containers/${cid}/wait '' 200 +t POST containers/${cid}/start 204 +t POST containers/${cid}/wait 200 t GET containers/${cid}/logs?stdout=true 200 like "$(<$WORKDIR/curl.result.out)" ".* ${tmpfs_name}" \ diff --git a/test/apiv2/45-system.at b/test/apiv2/45-system.at index ad4bdf4f7..364b87c56 100644 --- a/test/apiv2/45-system.at +++ b/test/apiv2/45-system.at @@ -27,22 +27,28 @@ t GET libpod/system/df 200 '.Volumes[0].VolumeName=foo1' # Create two more volumes to test pruneing t POST libpod/volumes/create \ - '"Name":"foo2","Label":{"testlabel1":""},"Options":{"type":"tmpfs","o":"nodev,noexec"}}' 201 \ - .Name=foo2 \ - .Driver=local \ - .Mountpoint=$volumepath/foo2/_data \ - .CreatedAt~[0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}.* \ - .Labels.testlabel1="" \ - .Options.o=nodev,noexec + Name=foo2 \ + Label='{"testlabel1":""}' \ + Options='{"type":"tmpfs","o":"nodev,noexec"}}' \ + 201 \ + .Name=foo2 \ + .Driver=local \ + .Mountpoint=$volumepath/foo2/_data \ + .CreatedAt~[0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}.* \ + .Labels.testlabel1="" \ + .Options.o=nodev,noexec t POST libpod/volumes/create \ - '"Name":"foo3","Label":{"testlabel1":"testonly"},"Options":{"type":"tmpfs","o":"nodev,noexec"}}' 201 \ - .Name=foo3 \ - .Driver=local \ - .Mountpoint=$volumepath/foo3/_data \ - .CreatedAt~[0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}.* \ - .Labels.testlabel1=testonly \ - .Options.o=nodev,noexec + Name=foo3 \ + Label='{"testlabel1":"testonly"}' \ + Options='{"type":"tmpfs","o":"nodev,noexec"}}' \ + 201 \ + .Name=foo3 \ + .Driver=local \ + .Mountpoint=$volumepath/foo3/_data \ + .CreatedAt~[0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}.* \ + .Labels.testlabel1=testonly \ + .Options.o=nodev,noexec t GET system/df 200 '.Volumes | length=3' t GET libpod/system/df 200 '.Volumes | length=3' diff --git a/test/apiv2/50-secrets.at b/test/apiv2/50-secrets.at index c4ffb5883..034ec080a 100644 --- a/test/apiv2/50-secrets.at +++ b/test/apiv2/50-secrets.at @@ -4,14 +4,14 @@ # # secret create -t POST secrets/create '"Name":"mysecret","Data":"c2VjcmV0"' 200\ +t POST secrets/create Name=mysecret Data=c2VjcmV0 200\ .ID~.* \ # secret create unsupported labels -t POST secrets/create '"Name":"mysecret","Data":"c2VjcmV0","Labels":{"fail":"fail"}' 400 +t POST secrets/create Name=mysecret Data=c2VjcmV0 Labels='{"fail":"fail"}' 400 # secret create name already in use -t POST secrets/create '"Name":"mysecret","Data":"c2VjcmV0"' 409 +t POST secrets/create Name=mysecret Data=c2VjcmV0 409 # secret inspect t GET secrets/mysecret 200 \ @@ -36,4 +36,4 @@ t DELETE secrets/mysecret 204 t DELETE secrets/bogus 404 # secret update not implemented -t POST secrets/mysecret/update "" 501 +t POST secrets/mysecret/update 501 diff --git a/test/apiv2/README.md b/test/apiv2/README.md index 252d6454e..19727cec7 100644 --- a/test/apiv2/README.md +++ b/test/apiv2/README.md @@ -52,9 +52,14 @@ Notes: If there's no leading slash, `t` prepends `/v1.40`. This is a simple convenience for simplicity of writing tests. -* When method is POST, the argument after the endpoint must be a series -of POST arguments in the form 'key=value', separated by commas. `t` will -convert those to JSON form for passing to the server. +* When method is POST, the argument(s) after the endpoint may be a series +of POST parameters in the form 'key=value', separated by spaces: + t POST myentrypoint 200 ! no params + t POST myentrypoint id=$id 200 ! just one + t POST myentrypoint id=$id filter='{"foo":"bar"}' 200 ! two, with json + t POST myentrypoint name=$name badparam='["foo","bar"]' 500 ! etc... +`t` will convert the param list to JSON form for passing to the server. +A numeric status code terminates processing of POST parameters. * The final arguments are one or more expected string results. If an argument starts with a dot, `t` will invoke `jq` on the output to diff --git a/test/apiv2/rest_api/__init__.py b/test/apiv2/rest_api/__init__.py index b7b8a7649..0ad6b51b3 100644 --- a/test/apiv2/rest_api/__init__.py +++ b/test/apiv2/rest_api/__init__.py @@ -3,6 +3,7 @@ import json import os import shutil import subprocess +import sys import tempfile @@ -27,7 +28,9 @@ class Podman(object): self.cmd.append("--root=" + os.path.join(self.anchor_directory, "crio")) self.cmd.append("--runroot=" + os.path.join(self.anchor_directory, "crio-run")) - os.environ["CONTAINERS_REGISTRIES_CONF"] = os.path.join(self.anchor_directory, "registry.conf") + os.environ["CONTAINERS_REGISTRIES_CONF"] = os.path.join( + self.anchor_directory, "registry.conf" + ) p = configparser.ConfigParser() p.read_dict( { @@ -114,13 +117,20 @@ class Podman(object): check = kwargs.get("check", False) shell = kwargs.get("shell", False) - return subprocess.run( - cmd, - shell=shell, - check=check, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - ) + try: + return subprocess.run( + cmd, + shell=shell, + check=check, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + except subprocess.CalledProcessError as e: + if e.stdout: + sys.stdout.write("\nRun Stdout:\n" + e.stdout.decode("utf-8")) + if e.stderr: + sys.stderr.write("\nRun Stderr:\n" + e.stderr.decode("utf-8")) + raise def tear_down(self): shutil.rmtree(self.anchor_directory, ignore_errors=True) diff --git a/test/apiv2/rest_api/test_rest_v2_0_0.py b/test/apiv2/rest_api/test_rest_v2_0_0.py index 8a78f5185..c0b61ea85 100644 --- a/test/apiv2/rest_api/test_rest_v2_0_0.py +++ b/test/apiv2/rest_api/test_rest_v2_0_0.py @@ -50,23 +50,20 @@ class TestApi(unittest.TestCase): def setUp(self): super().setUp() - try: - TestApi.podman.run("run", "alpine", "/bin/ls", check=True) - except subprocess.CalledProcessError as e: - if e.stdout: - sys.stdout.write("\nRun Stdout:\n" + e.stdout.decode("utf-8")) - if e.stderr: - sys.stderr.write("\nRun Stderr:\n" + e.stderr.decode("utf-8")) - raise + TestApi.podman.run("run", "alpine", "/bin/ls", check=True) + + def tearDown(self) -> None: + super().tearDown() + + TestApi.podman.run("pod", "rm", "--all", "--force", check=True) + TestApi.podman.run("rm", "--all", "--force", check=True) @classmethod def setUpClass(cls): super().setUpClass() TestApi.podman = Podman() - TestApi.service = TestApi.podman.open( - "system", "service", "tcp:localhost:8080", "--time=0" - ) + TestApi.service = TestApi.podman.open("system", "service", "tcp:localhost:8080", "--time=0") # give the service some time to be ready... time.sleep(2) @@ -243,9 +240,7 @@ class TestApi(unittest.TestCase): def test_post_create_compat(self): """Create network and connect container during create""" - net = requests.post( - PODMAN_URL + "/v1.40/networks/create", json={"Name": "TestNetwork"} - ) + net = requests.post(PODMAN_URL + "/v1.40/networks/create", json={"Name": "TestNetwork"}) self.assertEqual(net.status_code, 201, net.text) create = requests.post( @@ -454,15 +449,11 @@ class TestApi(unittest.TestCase): self.assertIn(k, o) def test_network_compat(self): - name = "Network_" + "".join( - random.choice(string.ascii_letters) for i in range(10) - ) + name = "Network_" + "".join(random.choice(string.ascii_letters) for i in range(10)) # Cannot test for 0 existing networks because default "podman" network always exists - create = requests.post( - PODMAN_URL + "/v1.40/networks/create", json={"Name": name} - ) + create = requests.post(PODMAN_URL + "/v1.40/networks/create", json={"Name": name}) self.assertEqual(create.status_code, 201, create.content) obj = json.loads(create.content) self.assertIn(type(obj), (dict,)) @@ -492,9 +483,7 @@ class TestApi(unittest.TestCase): self.assertEqual(inspect.status_code, 404, inspect.content) # network prune - prune_name = "Network_" + "".join( - random.choice(string.ascii_letters) for i in range(10) - ) + prune_name = "Network_" + "".join(random.choice(string.ascii_letters) for i in range(10)) prune_create = requests.post( PODMAN_URL + "/v1.40/networks/create", json={"Name": prune_name} ) @@ -506,9 +495,7 @@ class TestApi(unittest.TestCase): self.assertTrue(prune_name in obj["NetworksDeleted"]) def test_volumes_compat(self): - name = "Volume_" + "".join( - random.choice(string.ascii_letters) for i in range(10) - ) + name = "Volume_" + "".join(random.choice(string.ascii_letters) for i in range(10)) ls = requests.get(PODMAN_URL + "/v1.40/volumes") self.assertEqual(ls.status_code, 200, ls.content) @@ -524,9 +511,7 @@ class TestApi(unittest.TestCase): for k in required_keys: self.assertIn(k, obj) - create = requests.post( - PODMAN_URL + "/v1.40/volumes/create", json={"Name": name} - ) + create = requests.post(PODMAN_URL + "/v1.40/volumes/create", json={"Name": name}) self.assertEqual(create.status_code, 201, create.content) # See https://docs.docker.com/engine/api/v1.40/#operation/VolumeCreate @@ -703,21 +688,15 @@ class TestApi(unittest.TestCase): """Verify issue #8865""" pod_name = list() - pod_name.append( - "Pod_" + "".join(random.choice(string.ascii_letters) for i in range(10)) - ) - pod_name.append( - "Pod_" + "".join(random.choice(string.ascii_letters) for i in range(10)) - ) + pod_name.append("Pod_" + "".join(random.choice(string.ascii_letters) for i in range(10))) + pod_name.append("Pod_" + "".join(random.choice(string.ascii_letters) for i in range(10))) r = requests.post( _url("/pods/create"), json={ "name": pod_name[0], "no_infra": False, - "portmappings": [ - {"host_ip": "127.0.0.1", "host_port": 8889, "container_port": 89} - ], + "portmappings": [{"host_ip": "127.0.0.1", "host_port": 8889, "container_port": 89}], }, ) self.assertEqual(r.status_code, 201, r.text) @@ -736,9 +715,7 @@ class TestApi(unittest.TestCase): json={ "name": pod_name[1], "no_infra": False, - "portmappings": [ - {"host_ip": "127.0.0.1", "host_port": 8889, "container_port": 89} - ], + "portmappings": [{"host_ip": "127.0.0.1", "host_port": 8889, "container_port": 89}], }, ) self.assertEqual(r.status_code, 201, r.text) diff --git a/test/apiv2/test-apiv2 b/test/apiv2/test-apiv2 index e32d6bc62..9f6bf257f 100755 --- a/test/apiv2/test-apiv2 +++ b/test/apiv2/test-apiv2 @@ -156,19 +156,23 @@ function _bump() { # jsonify # convert 'foo=bar,x=y' to json {"foo":"bar","x":"y"} ############# function jsonify() { - # split by comma - local -a settings_in - read -ra settings_in <<<"$1" - # convert each to double-quoted form local -a settings_out - for i in ${settings_in[*]}; do - settings_out+=$(sed -e 's/\(.*\)=\(.*\)/"\1":"\2"/' <<<$i) + for i in "$@"; do + # Each argument is of the form foo=bar. Separate into left and right. + local lhs + local rhs + IFS='=' read lhs rhs <<<"$i" + + # If right-hand side already includes double quotes, do nothing + if [[ ! $rhs =~ \" ]]; then + rhs="\"${rhs}\"" + fi + settings_out+=("\"${lhs}\":${rhs}") done - # ...and wrap inside braces. - # FIXME: handle commas - echo "{${settings_out[*]}}" + # ...and wrap inside braces, with comma separator if multiple fields + (IFS=','; echo "{${settings_out[*]}}") } ####### @@ -180,11 +184,19 @@ function t() { local curl_args local testname="$method $path" - # POST requests require an extra params arg + # POST requests may be followed by one or more key=value pairs. + # Slurp the command line until we see a 3-digit status code. if [[ $method = "POST" ]]; then - curl_args="-d $(jsonify $1)" + local -a post_args + for arg; do + case "$arg" in + *=*) post_args+=("$arg"); shift ;; + [1-9][0-9][0-9]) break;; + *) die "Internal error: invalid POST arg '$arg'" ;; + esac + done + curl_args="-d $(jsonify ${post_args[@]})" testname="$testname [$curl_args]" - shift fi # entrypoint path can include a descriptive comment; strip it off diff --git a/test/system/260-sdnotify.bats b/test/system/260-sdnotify.bats index a5fa0f4e6..8bf49eb1d 100644 --- a/test/system/260-sdnotify.bats +++ b/test/system/260-sdnotify.bats @@ -42,14 +42,22 @@ function _start_socat() { _SOCAT_LOG="$PODMAN_TMPDIR/socat.log" rm -f $_SOCAT_LOG - socat unix-recvfrom:"$NOTIFY_SOCKET",fork \ - system:"(cat;echo) >> $_SOCAT_LOG" & + # Execute in subshell so we can close fd3 (which BATS uses). + # This is a superstitious ritual to try to avoid leaving processes behind, + # and thus prevent CI hangs. + (exec socat unix-recvfrom:"$NOTIFY_SOCKET",fork \ + system:"(cat;echo) >> $_SOCAT_LOG" 3>&-) & _SOCAT_PID=$! } # Stop the socat background process and clean up logs function _stop_socat() { if [[ -n "$_SOCAT_PID" ]]; then + # Kill all child processes, then the process itself. + # This is a superstitious incantation to avoid leaving processes behind. + # The '|| true' is because only f35 leaves behind socat processes; + # f33 (and perhaps others?) behave nicely. ARGH! + pkill -P $_SOCAT_PID || true kill $_SOCAT_PID fi _SOCAT_PID= @@ -57,6 +65,7 @@ function _stop_socat() { if [[ -n "$_SOCAT_LOG" ]]; then rm -f $_SOCAT_LOG fi + _SOCAT_LOG= } # Check that MAINPID=xxxxx points to a running conmon process |