github.com/containers/podman/v5@v5.1.0-rc1/test/apiv2/10-images.at (about) 1 # -*- sh -*- 2 # 3 # Tests for image-related endpoints 4 # 5 6 # FIXME: API doesn't support pull yet, so use podman 7 podman pull -q $IMAGE 8 9 t GET libpod/images/json 200 \ 10 .[0].Id~[0-9a-f]\\{64\\} 11 iid=$(jq -r '.[0].Id' <<<"$output") 12 13 # Create an empty manifest and make sure it is not listed 14 # in the compat endpoint. 15 t GET images/json 200 length=1 16 podman manifest create foo 17 t GET images/json 200 length=1 18 t GET libpod/images/json 200 length=2 19 20 t GET libpod/images/$iid/exists 204 21 t GET libpod/images/$PODMAN_TEST_IMAGE_NAME/exists 204 22 t GET libpod/images/${iid}abcdef/exists 404 \ 23 .cause="failed to find image ${iid}abcdef" 24 25 # FIXME: compare to actual podman info 26 t GET libpod/images/json 200 \ 27 .[0].Id=${iid} 28 29 t GET libpod/images/$iid/json 200 \ 30 .Id=$iid \ 31 .RepoTags[0]=$IMAGE 32 33 # Same thing, but with abbreviated image id 34 t GET libpod/images/${iid:0:12}/json 200 \ 35 .Id=$iid \ 36 .RepoTags[0]=$IMAGE 37 38 # Docker API V1.24 filter parameter compatibility 39 t GET images/json?filter=$IMAGE 200 \ 40 length=1 \ 41 .[0].Names[0]=$IMAGE 42 43 # Negative test case 44 t GET images/json?filter=nonesuch 200 length=0 45 46 # FIXME: docker API incompatibility: libpod returns 'id', docker 'sha256:id' 47 t GET images/$iid/json 200 \ 48 .Id=sha256:$iid \ 49 .RepoTags[0]=$IMAGE 50 51 t POST "images/create?fromImage=alpine" 200 .error~null .status~".*Download complete.*" 52 t POST "libpod/images/pull?reference=alpine&compatMode=true" 200 .error~null .status~".*Download complete.*" 53 54 t POST "images/create?fromImage=alpine&tag=latest" 200 55 56 # 10977 - handle platform parameter correctly 57 t POST "images/create?fromImage=quay.io/libpod/testimage:20240123&platform=linux/arm64" 200 58 t GET "images/testimage:20240123/json" 200 \ 59 .Architecture=arm64 60 61 # Make sure that new images are pulled 62 old_iid=$(podman image inspect --format "{{.ID}}" docker.io/library/alpine:latest) 63 podman rmi -f docker.io/library/alpine:latest 64 podman tag $IMAGE docker.io/library/alpine:latest 65 t POST "images/create?fromImage=alpine" 200 .error~null .status~".*$old_iid.*" 66 podman untag docker.io/library/alpine:latest 67 68 t POST "images/create?fromImage=quay.io/libpod/alpine&tag=sha256:fa93b01658e3a5a1686dc3ae55f170d8de487006fb53a28efcd12ab0710a2e5f" 200 69 70 # create image from source with tag 71 # Note the "-" is used to use an empty body and not "{}" which is the default. 72 t POST "images/create?fromSrc=-&repo=myimage&tag=mytag" - 200 73 t GET "images/myimage:mytag/json" 200 \ 74 .Id~'^sha256:[0-9a-f]\{64\}$' \ 75 .RepoTags[0]="docker.io/library/myimage:mytag" 76 t POST /images/create?fromImage=busybox:invalidtag123 404 77 78 # Display the image history 79 t GET libpod/images/nonesuch/history 404 80 81 for i in $iid ${iid:0:12} $PODMAN_TEST_IMAGE_NAME; do 82 t GET libpod/images/$i/history 200 \ 83 .[0].Id=$iid \ 84 .[0].Created~[0-9]\\{10\\} \ 85 .[0].Tags[0]="$IMAGE" \ 86 .[0].Size=0 \ 87 .[0].Comment= 88 done 89 90 for i in $iid ${iid:0:12} $PODMAN_TEST_IMAGE_NAME; do 91 t GET images/$i/history 200 \ 92 .[0].Id="sha256:"$iid \ 93 .[0].Created~[0-9]\\{10\\} \ 94 .[0].Tags[0]="$IMAGE" \ 95 .[0].Size=0 \ 96 .[0].Comment= 97 done 98 99 # compat api pull image unauthorized message error 100 t POST "/images/create?fromImage=quay.io/idonotexist/idonotexist:dummy" 401 \ 101 .message="unauthorized: access to the requested resource is not authorized" 102 103 # Export an image on the local 104 t GET libpod/images/nonesuch/get 404 105 t GET libpod/images/$iid/get?format=foo 500 106 t GET libpod/images/$PODMAN_TEST_IMAGE_NAME/get?compress=bar 400 107 108 for i in $iid ${iid:0:12} $PODMAN_TEST_IMAGE_NAME; do 109 t GET "libpod/images/$i/get" 200 '[POSIX tar archive]' 110 t GET "libpod/images/$i/get?compress=true" 200 '[POSIX tar archive]' 111 t GET "libpod/images/$i/get?compress=false" 200 '[POSIX tar archive]' 112 done 113 114 #compat api list images sanity checks 115 t GET images/json?filters='garb1age}' 500 \ 116 .cause="invalid character 'g' looking for beginning of value" 117 t GET images/json?filters='{"label":["testl' 500 \ 118 .cause="unexpected end of JSON input" 119 120 #libpod api list images sanity checks 121 t GET libpod/images/json?filters='garb1age}' 500 \ 122 .cause="invalid character 'g' looking for beginning of value" 123 t GET libpod/images/json?filters='{"label":["testl' 500 \ 124 .cause="unexpected end of JSON input" 125 126 # Prune images - bad all input 127 t POST libpod/images/prune?all='garb1age' 500 \ 128 .cause="schema: error converting value for \"all\"" 129 130 # Prune images - bad filter input 131 t POST images/prune?filters='garb1age}' 500 \ 132 .cause="invalid character 'g' looking for beginning of value" 133 t POST libpod/images/prune?filters='garb1age}' 500 \ 134 .cause="invalid character 'g' looking for beginning of value" 135 136 ## Prune images with illformed label 137 t POST images/prune?filters='{"label":["tes' 500 \ 138 .cause="unexpected end of JSON input" 139 t POST libpod/images/prune?filters='{"label":["tes' 500 \ 140 .cause="unexpected end of JSON input" 141 142 143 #create, list and remove dangling image 144 podman image build -t test:test -<<EOF 145 from alpine 146 RUN >file1 147 EOF 148 149 podman image build -t test:test --label xyz --label abc -<<EOF 150 from alpine 151 RUN >file2 152 EOF 153 154 t GET images/json?filters='{"dangling":["true"]}' 200 length=1 155 t POST images/prune?filters='{"dangling":["true"]}' 200 156 t GET images/json?filters='{"dangling":["true"]}' 200 length=0 157 158 #label filter check in libpod and compat 159 t GET images/json?filters='{"label":["xyz","abc"]}' 200 length=1 160 t GET libpod/images/json?filters='{"label":["xyz"]}' 200 length=1 161 162 t DELETE libpod/images/test:test 200 163 164 t GET images/json?filters='{"label":["xyz"]}' 200 length=0 165 t GET libpod/images/json?filters='{"label":["xyz"]}' 200 length=0 166 167 # Must not error out: #20469 168 t POST images/prune?filters='{"dangling":["false"]}' 200 169 170 # to be used in prune until filter tests 171 podman image build -t test1:latest -<<EOF 172 from alpine 173 RUN >file3 174 EOF 175 176 # image should not be deleted 177 t GET images/json?filters='{"reference":["test1"]}' 200 length=1 178 t POST images/prune?filters='{"until":["500000"]}' 200 179 t GET images/json?filters='{"reference":["test1"]}' 200 length=1 180 181 t DELETE libpod/images/test1:latest 200 182 183 # to be used in prune until filter tests 184 podman image build -t docker.io/library/test1:latest -<<EOF 185 from alpine 186 RUN >file4 187 EOF 188 podman create --name test1 test1 echo hi 189 190 t DELETE images/test1:latest 409 191 podman rm test1 192 t DELETE images/test1:latest 200 193 194 t GET "images/get?names=alpine" 200 '[POSIX tar archive]' 195 196 podman pull busybox 197 t GET "images/get?names=alpine&names=busybox" 200 '[POSIX tar archive]' 198 img_cnt=$(tar xf "$WORKDIR/curl.result.out" manifest.json -O | jq "length") 199 is "$img_cnt" 2 "number of images in tar archive" 200 201 # check build works when uploading container file as a tar, see issue #10660 202 TMPD=$(mktemp -d podman-apiv2-test.build.XXXXXXXX) 203 function cleanBuildTest() { 204 podman rmi -a -f 205 rm -rf "${TMPD}" &> /dev/null 206 } 207 CONTAINERFILE_TAR="${TMPD}/containerfile.tar" 208 cat > $TMPD/containerfile << EOF 209 FROM $IMAGE 210 EOF 211 tar --format=posix -C $TMPD -cvf ${CONTAINERFILE_TAR} containerfile &> /dev/null 212 213 t POST "libpod/build?dockerfile=containerfile" $CONTAINERFILE_TAR 200 \ 214 .stream~"STEP 1/1: FROM $IMAGE" 215 216 # Newer Docker client sets empty cacheFrom for every build command even if it is not used, 217 # following commit makes sure we test such use-case. See https://github.com/containers/podman/pull/16380 218 #TODO: This test should be extended when buildah's cache-from and cache-to functionally supports 219 # multiple remote-repos 220 t POST "libpod/build?dockerfile=containerfile&cachefrom=[]" $CONTAINERFILE_TAR 200 \ 221 .stream~"STEP 1/1: FROM $IMAGE" 222 223 # With -q, all we should get is image ID. Test both libpod & compat endpoints. 224 t POST "libpod/build?dockerfile=containerfile&q=true" $CONTAINERFILE_TAR 200 \ 225 .stream~'^[0-9a-f]\{64\}$' 226 t POST "build?dockerfile=containerfile&q=true" $CONTAINERFILE_TAR 200 \ 227 .stream~'^[0-9a-f]\{64\}$' 228 229 # Override content-type and confirm that libpod rejects, but compat accepts 230 t POST "libpod/build?dockerfile=containerfile" $CONTAINERFILE_TAR application/json 400 \ 231 .cause='Content-Type: application/json is not supported. Should be "application/x-tar"' 232 t POST "build?dockerfile=containerfile" $CONTAINERFILE_TAR application/json 200 \ 233 .stream~"STEP 1/1: FROM $IMAGE" 234 235 # Libpod: allow building from url: https://github.com/alpinelinux/docker-alpine.git and must ignore any provided tar 236 t POST "libpod/build?remote=https%3A%2F%2Fgithub.com%2Falpinelinux%2Fdocker-alpine.git" $CONTAINERFILE_TAR 200 \ 237 .stream~"STEP 1/5: FROM alpine:" 238 239 # Build api response header must contain Content-type: application/json 240 t POST "build?dockerfile=containerfile" $CONTAINERFILE_TAR application/json 200 241 response_headers=$(cat "$WORKDIR/curl.headers.out") 242 like "$response_headers" ".*application/json.*" "header does not contain application/json" 243 244 # Build api response header must contain Content-type: application/json 245 t POST "build?dockerfile=containerfile&pull=1" $CONTAINERFILE_TAR application/json 200 246 response_headers=$(cat "$WORKDIR/curl.headers.out") 247 like "$response_headers" ".*application/json.*" "header does not contain application/json" 248 249 # PR #12091: output from compat API must now include {"aux":{"ID":"sha..."}} 250 t POST "build?dockerfile=containerfile" $CONTAINERFILE_TAR 200 \ 251 '.aux|select(has("ID")).ID~^sha256:[0-9a-f]\{64\}$' 252 253 t POST libpod/images/prune 200 254 t POST libpod/images/prune 200 length=0 [] 255 256 # compat api must allow loading tar which contain multiple images 257 podman pull quay.io/libpod/alpine:latest quay.io/libpod/busybox:latest 258 podman save -o ${TMPD}/test.tar quay.io/libpod/alpine:latest quay.io/libpod/busybox:latest 259 t POST "images/load" ${TMPD}/test.tar 200 \ 260 .stream="Loaded image: quay.io/libpod/busybox:latest,quay.io/libpod/alpine:latest" 261 t GET libpod/images/quay.io/libpod/alpine:latest/exists 204 262 t GET libpod/images/quay.io/libpod/busybox:latest/exists 204 263 264 CONTAINERFILE_WITH_ERR_TAR="${TMPD}/containerfile.tar" 265 cat > $TMPD/containerfile << EOF 266 FROM $IMAGE 267 RUN echo 'some error' >&2 268 EOF 269 tar --format=posix -C $TMPD -cvf ${CONTAINERFILE_WITH_ERR_TAR} containerfile &> /dev/null 270 t POST "/build?q=1&dockerfile=containerfile" $CONTAINERFILE_WITH_ERR_TAR 200 271 if [[ $output == *"some error"* ]];then 272 _show_ok 0 "compat quiet build" "[should not contain 'some error']" "$output" 273 else 274 _show_ok 1 "compat quiet build" 275 fi 276 277 cleanBuildTest 278 279 # compat API vs libpod API event differences: 280 # on image removal, libpod produces 'remove' events. 281 # compat produces 'delete' events. 282 podman image build -t test:test -<<EOF 283 from $IMAGE 284 EOF 285 286 START=$(date +%s) 287 288 t DELETE libpod/images/test:test 200 289 # HACK HACK HACK There is a race around events being added to the journal 290 # This sleep seems to avoid the race. 291 # If it fails and begins to flake, investigate a retry loop. 292 sleep 1 293 t GET "libpod/events?stream=false&since=$START" 200 \ 294 'select(.status | contains("remove")).Action=remove' 295 t GET "events?stream=false&since=$START" 200 \ 296 'select(.status | contains("delete")).Action=delete' 297 298 # Test image removal with `noprune={true,false}` 299 podman create --name c_test1 $IMAGE true 300 podman commit -q c_test1 i_test1 301 podman create --name c_test2 i_test1 true 302 podman commit -q c_test2 i_test2 303 podman create --name c_test3 i_test2 true 304 podman commit -q c_test3 i_test3 305 306 t GET libpod/images/i_test1/json 200 307 iid_test1=$(jq -r '.Id' <<<"$output") 308 t GET libpod/images/i_test2/json 200 309 iid_test2=$(jq -r '.Id' <<<"$output") 310 t GET libpod/images/i_test3/json 200 311 iid_test3=$(jq -r '.Id' <<<"$output") 312 313 podman untag $iid_test1 314 podman untag $iid_test2 315 316 podman rm -af 317 318 # Deleting i_test3 with --no-prune must not remove _2 and _1. 319 t DELETE images/$iid_test3?noprune=true 200 320 t GET libpod/images/i_test3/exists 404 321 t GET libpod/images/$iid_test1/exists 204 322 t GET libpod/images/$iid_test2/exists 204 323 324 t DELETE images/$iid_test2?noprune=false 200 325 t GET libpod/images/$iid_test1/exists 404 326 t GET libpod/images/$iid_test2/exists 404 327 328 # If the /resolve tests fail, make sure to use ../registries.conf for the 329 # podman-service. 330 331 # With an alias, we only get one item back. 332 t GET libpod/images/podman-desktop-test123:this/resolve 200 \ 333 .Names[0]="florent.fr/will/like:this" 334 335 # If no alias matches, we will get a candidate for each unqualified-search 336 # registry. 337 t GET libpod/images/no-alias-for-sure/resolve 200 \ 338 .Names[0]="docker.io/library/no-alias-for-sure:latest" \ 339 .Names[1]="quay.io/no-alias-for-sure:latest" \ 340 .Names[2]="registry.fedoraproject.org/no-alias-for-sure:latest" 341 342 # Test invalid input. 343 t GET libpod/images/noCAPITALcharAllowed/resolve 400 \ 344 .cause="repository name must be lowercase" 345 346 347 START=$(date +%s.%N) 348 # test pull-error API response 349 podman pull --retry 0 localhost:5000/idonotexist || true 350 t GET "libpod/events?stream=false&since=$START" 200 \ 351 .status=pull-error \ 352 .Action=pull-error \ 353 .Actor.Attributes.name="localhost:5000/idonotexist" \ 354 .Actor.Attributes.error~".*connection refused" 355 356 # vim: filetype=sh