# -*- sh -*- # # Tests for image-related endpoints # # FIXME: API doesn't support pull yet, so use podman podman pull -q $IMAGE t GET libpod/images/json 200 \ length=1 \ .[0].Id~[0-9a-f]\\{64\\} \ .[0].Names[0]="$IMAGE" iid=$(jq -r '.[0].Id' <<<"$output") # Create an empty manifest and make sure it is not listed # in the compat endpoint. t GET images/json 200 length=1 podman manifest create foo t GET images/json 200 length=1 t GET libpod/images/json 200 length=2 t GET libpod/images/$iid/exists 204 t GET libpod/images/$PODMAN_TEST_IMAGE_NAME:$PODMAN_TEST_IMAGE_TAG/exists 204 t GET libpod/images/${iid}abcdef/exists 404 \ .cause="failed to find image ${iid}abcdef" # FIXME: compare to actual podman info t GET libpod/images/json 200 \ .[0].Id=${iid} t GET libpod/images/$iid/json 200 \ .Id=$iid \ .RepoTags[0]=$IMAGE # Same thing, but with abbreviated image id t GET libpod/images/${iid:0:12}/json 200 \ .Id=$iid \ .RepoTags[0]=$IMAGE # Docker API V1.24 filter parameter compatibility t GET images/json?filter=$IMAGE 200 \ length=1 \ .[0].Names[0]=$IMAGE # Test VirtualSize field is present in API v1.43 (backward compatibility) t GET /v1.43/images/json 200 \ .[0].VirtualSize~[0-9]\\+ # Test VirtualSize field is no longer present in API v1.44+ (deprecated since API v1.43) t GET /v1.44/images/json 200 \ .[0].VirtualSize=null # Negative test case t GET images/json?filter=nonesuch 200 length=0 # FIXME: docker API incompatibility: libpod returns 'id', docker 'sha256:id' t GET images/$iid/json 200 \ .Id=sha256:$iid \ .RepoTags[0]=$IMAGE # Test VirtualSize field is present in API v1.43 for single image inspect (backward compatibility) t GET /v1.43/images/$iid/json 200 \ .VirtualSize~[0-9]\\+ # Test VirtualSize field is no longer present in API v1.44+ for single image inspect (deprecated since API v1.43) t GET /v1.44/images/$iid/json 200 \ .VirtualSize=null t POST "images/create?fromImage=alpine" 200 .error~null .status~".*Download complete.*" t POST "libpod/images/pull?reference=alpine&compatMode=true" 200 .error~null .status~".*Download complete.*" t POST "images/create?fromImage=alpine&tag=latest" 200 \ .status~"Already exists" # 10977 - handle platform parameter correctly # THIS IMAGE MUST NOT BE THE SAME AS $IMAGE t POST "images/create?fromImage=quay.io/libpod/testimage:20221018&platform=linux/arm64" 200 t GET "images/testimage:20221018/json" 200 \ .Architecture=arm64 # 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.*" podman untag docker.io/library/alpine:latest t POST "images/create?fromImage=quay.io/libpod/alpine&tag=sha256:fa93b01658e3a5a1686dc3ae55f170d8de487006fb53a28efcd12ab0710a2e5f" 200 # create image from source with tag # Note the "-" is used to use an empty body and not "{}" which is the default. t POST "images/create?fromSrc=-&repo=myimage&tag=mytag" - 200 t GET "images/myimage:mytag/json" 200 \ .Id~'^sha256:[0-9a-f]\{64\}$' \ .RepoTags[0]="docker.io/library/myimage:mytag" t POST /images/create?fromImage=busybox:invalidtag123 404 # Display the image history t GET libpod/images/nonesuch/history 404 for i in $iid ${iid:0:12} $PODMAN_TEST_IMAGE_NAME:$PODMAN_TEST_IMAGE_TAG; do t GET libpod/images/$i/history 200 \ .[0].Id=$iid \ .[1].Id="" \ .[2].Id="" \ .[3].Id="" \ .[0].Created~[0-9]\\{10\\} \ .[0].Tags[0]="$IMAGE" \ .[0].Size=1024 \ .[1].Size=0 \ .[2].Size=0 \ .[3].Size=0 \ .[0].Comment="" \ .[1].Comment="" \ .[2].Comment="" \ .[3].Comment="FROM localhost/interim-image:latest" \ .[0].CreatedBy~".*/echo.*This container is intended for podman CI testing.*" \ .[1].CreatedBy~".* WORKDIR /home/podman" \ .[2].CreatedBy~".* LABEL created_at=.*" \ .[3].CreatedBy~".* LABEL created_by=test/system/build-testimage" done for i in $iid ${iid:0:12} $PODMAN_TEST_IMAGE_NAME:$PODMAN_TEST_IMAGE_TAG; do t GET images/$i/history 200 \ .[0].Id="sha256:$iid" \ .[1].Id="sha256:" \ .[2].Id="sha256:" \ .[3].Id="sha256:" \ .[0].Created~[0-9]\\{10\\} \ .[0].Tags[0]="$IMAGE" \ .[0].Size=1024 \ .[1].Size=0 \ .[2].Size=0 \ .[3].Size=0 \ .[0].Comment="" \ .[1].Comment="" \ .[2].Comment="" \ .[3].Comment="FROM localhost/interim-image:latest" \ .[0].CreatedBy~".*/echo.*This container is intended for podman CI testing.*" \ .[1].CreatedBy~".* WORKDIR /home/podman" \ .[2].CreatedBy~".* LABEL created_at=.*" \ .[3].CreatedBy~".* LABEL created_by=test/system/build-testimage" done # compat api pull image unauthorized message error # This depends on whether we're using local cache registry or real quay expect_code=401 expect_msg="unauthorized: access to the requested resource is not authorized" if [[ -n "$CI_USE_REGISTRY_CACHE" ]]; then # local registry has no auth, so it can return 404 expect_code=404 expect_msg="manifest unknown: manifest unknown" fi t POST "/images/create?fromImage=quay.io/idonotexist/idonotexist:dummy" $expect_code \ .message="$expect_msg" # Export an image on the local t GET libpod/images/nonesuch/get 404 t GET libpod/images/$iid/get?format=foo 500 t GET libpod/images/$PODMAN_TEST_IMAGE_NAME/get?compress=bar 400 for i in $iid ${iid:0:12} $PODMAN_TEST_IMAGE_NAME:$PODMAN_TEST_IMAGE_TAG; do t GET "libpod/images/$i/get" 200 '[POSIX tar archive]' t GET "libpod/images/$i/get?compress=true" 200 '[POSIX tar archive]' t GET "libpod/images/$i/get?compress=false" 200 '[POSIX tar archive]' done #compat api list images sanity checks t GET images/json?filters='garb1age}' 500 \ .cause="invalid character 'g' looking for beginning of value" t GET images/json?filters='{"label":["testl' 500 \ .cause="unexpected end of JSON input" #libpod api list images sanity checks t GET libpod/images/json?filters='garb1age}' 500 \ .cause="invalid character 'g' looking for beginning of value" t GET libpod/images/json?filters='{"label":["testl' 500 \ .cause="unexpected end of JSON input" # Prune images - bad all input t POST libpod/images/prune?all='garb1age' 500 \ .cause="schema: error converting value for \"all\"" # Prune images - bad filter input t POST images/prune?filters='garb1age}' 500 \ .cause="invalid character 'g' looking for beginning of value" t POST libpod/images/prune?filters='garb1age}' 500 \ .cause="invalid character 'g' looking for beginning of value" ## Prune images with illformed label t POST images/prune?filters='{"label":["tes' 500 \ .cause="unexpected end of JSON input" t POST libpod/images/prune?filters='{"label":["tes' 500 \ .cause="unexpected end of JSON input" #create, list and remove dangling image podman image build -t test:test -<file1 EOF podman image build -t test:test --label xyz --label abc -<file2 EOF t GET images/json?filters='{"dangling":["true"]}' 200 length=1 t POST images/prune?filters='{"dangling":["true"]}' 200 t GET images/json?filters='{"dangling":["true"]}' 200 length=0 #label filter check in libpod and compat t GET images/json?filters='{"label":["xyz","abc"]}' 200 length=1 t GET libpod/images/json?filters='{"label":["xyz"]}' 200 length=1 t DELETE libpod/images/test:test 200 t GET images/json?filters='{"label":["xyz"]}' 200 length=0 t GET libpod/images/json?filters='{"label":["xyz"]}' 200 length=0 # Must not error out: #20469 t POST images/prune?filters='{"dangling":["false"]}' 200 # to be used in prune until filter tests podman image build -t test1:latest -<file3 EOF # image should not be deleted t GET images/json?filters='{"reference":["test1"]}' 200 length=1 t POST images/prune?filters='{"until":["500000"]}' 200 t GET images/json?filters='{"reference":["test1"]}' 200 length=1 t DELETE libpod/images/test1:latest 200 # to be used in prune until filter tests podman image build -t docker.io/library/test1:latest -<file4 EOF podman create --name test1 test1 echo hi t DELETE images/test1:latest 409 podman rm test1 t DELETE images/test1:latest 200 t GET "images/get?names=alpine" 200 '[POSIX tar archive]' # START: Testing variance between Docker API and Podman API # regarding force deleting images. # Podman: Force deleting an image will force remove any # container using the image. # Docker: Force deleting an image will only remove non # running containers using the image. # Create new image podman image build -t docker.io/library/test1:latest - <file4 EOF # Create running container podman run --rm -d --name test_container docker.io/library/test1:latest top # When using the Docker Compat API, force deleting an image # shouldn't force delete any container using the image, only # containers in a non running state should be removed. # https://github.com/containers/podman/issues/25871 t DELETE images/test1:latest?force=true 409 # When using the Podman Libpod API, deleting an image # with a running container will fail. t DELETE libpod/images/test1:latest 409 # When using the Podman Libpod API, force deleting an # image will also force delete all containers using the image. # Verify container exists. t GET libpod/containers/test_container/exists 204 # Delete image with force. t DELETE libpod/images/test1:latest?force=true 200 # Verify container also removed. t GET libpod/containers/test_container/exists 404 # END: Testing variance between Docker API and Podman API # regarding force deleting images. podman pull busybox t GET "images/get?names=alpine&names=busybox" 200 '[POSIX tar archive]' img_cnt=$(tar xf "$WORKDIR/curl.result.out" manifest.json -O | jq "length") is "$img_cnt" 2 "number of images in tar archive" # check build works when uploading container file as a tar, see issue #10660 TMPD=$(mktemp -d podman-apiv2-test.build.XXXXXXXX) function cleanBuildTest() { podman rmi -a -f rm -rf "${TMPD}" &> /dev/null } CONTAINERFILE_TAR="${TMPD}/containerfile.tar" cat > $TMPD/containerfile << EOF FROM $IMAGE EOF tar --format=posix -C $TMPD -cvf ${CONTAINERFILE_TAR} containerfile &> /dev/null t POST "libpod/build?dockerfile=containerfile" $CONTAINERFILE_TAR 200 \ .stream~"STEP 1/1: FROM $IMAGE" # Newer Docker client sets empty cacheFrom for every build command even if it is not used, # following commit makes sure we test such use-case. See https://github.com/containers/podman/pull/16380 #TODO: This test should be extended when buildah's cache-from and cache-to functionally supports # multiple remote-repos t POST "libpod/build?dockerfile=containerfile&cachefrom=[]" $CONTAINERFILE_TAR 200 \ .stream~"STEP 1/1: FROM $IMAGE" # With -q, all we should get is image ID. Test both libpod & compat endpoints. t POST "libpod/build?dockerfile=containerfile&q=true" $CONTAINERFILE_TAR 200 \ .stream~'^[0-9a-f]\{64\}$' t POST "build?dockerfile=containerfile&q=true" $CONTAINERFILE_TAR 200 \ .stream~'^[0-9a-f]\{64\}$' # Override content-type and confirm that libpod rejects, but compat accepts t POST "libpod/build?dockerfile=containerfile" $CONTAINERFILE_TAR application/json 400 \ .cause='Content-Type: application/json is not supported. Should be "application/x-tar"' t POST "build?dockerfile=containerfile" $CONTAINERFILE_TAR application/json 200 \ .stream~"STEP 1/1: FROM $IMAGE" # Libpod: allow building from url: https://github.com/alpinelinux/docker-alpine.git and must ignore any provided tar t POST "libpod/build?remote=https%3A%2F%2Fgithub.com%2Falpinelinux%2Fdocker-alpine.git" $CONTAINERFILE_TAR 200 \ .stream~"STEP 1/5: FROM alpine:" # Build api response header must contain Content-type: application/json t POST "build?dockerfile=containerfile" $CONTAINERFILE_TAR application/json 200 response_headers=$(cat "$WORKDIR/curl.headers.out") like "$response_headers" ".*application/json.*" "header does not contain application/json" # Build api response header must contain Content-type: application/json t POST "build?dockerfile=containerfile&pull=1" $CONTAINERFILE_TAR application/json 200 response_headers=$(cat "$WORKDIR/curl.headers.out") like "$response_headers" ".*application/json.*" "header does not contain application/json" # PR #12091: output from compat API must now include {"aux":{"ID":"sha..."}} t POST "build?dockerfile=containerfile" $CONTAINERFILE_TAR 200 \ '.aux|select(has("ID")).ID~^sha256:[0-9a-f]\{64\}$' t POST libpod/images/prune 200 t POST libpod/images/prune 200 length=0 [] # compat api must allow loading tar which contain multiple images podman pull quay.io/libpod/alpine:latest quay.io/libpod/busybox:latest podman save -o ${TMPD}/test.tar quay.io/libpod/alpine:latest quay.io/libpod/busybox:latest t POST "images/load" ${TMPD}/test.tar 200 \ .stream="Loaded image: quay.io/libpod/busybox:latest,quay.io/libpod/alpine:latest" t GET libpod/images/quay.io/libpod/alpine:latest/exists 204 t GET libpod/images/quay.io/libpod/busybox:latest/exists 204 CONTAINERFILE_WITH_ERR_TAR="${TMPD}/containerfile.tar" cat > $TMPD/containerfile << EOF FROM $IMAGE RUN echo 'some error' >&2 EOF tar --format=posix -C $TMPD -cvf ${CONTAINERFILE_WITH_ERR_TAR} containerfile &> /dev/null t POST "/build?q=1&dockerfile=containerfile" $CONTAINERFILE_WITH_ERR_TAR 200 if [[ $output == *"some error"* ]];then _show_ok 0 "compat quiet build" "[should not contain 'some error']" "$output" else _show_ok 1 "compat quiet build" fi # Do not try a real build here to tests the comma separated syntax as emulation # is slow and may not work everywhere, checking the error is good enough to know # we parsed it correctly on the server I would say t POST "/build?q=1&dockerfile=containerfile&platform=linux/amd64,test" $CONTAINERFILE_WITH_ERR_TAR 400 \ .message="failed to parse query parameter 'platform': \"test\": invalid platform syntax for --platform=\"test\": \"test\": unknown operating system or architecture: invalid argument" cleanBuildTest # compat API vs libpod API event differences: # on image removal, libpod produces 'remove' events. # compat produces 'delete' events. podman image build -t test:test -<