github.com/containers/podman/v5@v5.1.0-rc1/test/e2e/manifest_test.go (about) 1 package integration 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "os" 7 "path/filepath" 8 "strings" 9 10 "github.com/containers/common/libimage/define" 11 podmanRegistry "github.com/containers/podman/v5/hack/podman-registry-go" 12 . "github.com/containers/podman/v5/test/utils" 13 "github.com/containers/storage/pkg/archive" 14 . "github.com/onsi/ginkgo/v2" 15 . "github.com/onsi/gomega" 16 . "github.com/onsi/gomega/gexec" 17 imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" 18 ) 19 20 // validateManifestHasAllArchs checks that the specified manifest has all 21 // the archs in `imageList` 22 func validateManifestHasAllArchs(path string) error { 23 data, err := os.ReadFile(path) 24 if err != nil { 25 return err 26 } 27 var result struct { 28 Manifests []struct { 29 Platform struct { 30 Architecture string 31 } 32 } 33 } 34 if err := json.Unmarshal(data, &result); err != nil { 35 return err 36 } 37 archs := map[string]bool{ 38 "amd64": false, 39 "arm64": false, 40 "ppc64le": false, 41 "s390x": false, 42 } 43 for _, m := range result.Manifests { 44 archs[m.Platform.Architecture] = true 45 } 46 return nil 47 } 48 49 // Internal function to verify instance compression 50 func verifyInstanceCompression(descriptor []imgspecv1.Descriptor, compression string, arch string) bool { 51 for _, instance := range descriptor { 52 if instance.Platform.Architecture != arch { 53 continue 54 } 55 if compression == "zstd" { 56 // if compression is zstd annotations must contain 57 val, ok := instance.Annotations["io.github.containers.compression.zstd"] 58 if ok && val == "true" { 59 return true 60 } 61 } else if len(instance.Annotations) == 0 { 62 return true 63 } 64 } 65 return false 66 } 67 68 var _ = Describe("Podman manifest", func() { 69 70 const ( 71 imageList = "docker://quay.io/libpod/testimage:00000004" 72 imageListInstance = "docker://quay.io/libpod/testimage@sha256:1385ce282f3a959d0d6baf45636efe686c1e14c3e7240eb31907436f7bc531fa" 73 imageListARM64InstanceDigest = "sha256:1385ce282f3a959d0d6baf45636efe686c1e14c3e7240eb31907436f7bc531fa" 74 imageListAMD64InstanceDigest = "sha256:1462c8e885d567d534d82004656c764263f98deda813eb379689729658a133fb" 75 imageListPPC64LEInstanceDigest = "sha256:9b7c3300f5f7cfe94e3101a28d1f0a28728f8dbc854fb16dd545b7e5aa351785" 76 imageListS390XInstanceDigest = "sha256:cb68b7bfd2f4f7d36006efbe3bef04b57a343e0839588476ca336d9ff9240dbf" 77 ) 78 79 It("create w/o image and attempt push w/o dest", func() { 80 for _, amend := range []string{"--amend", "-a"} { 81 session := podmanTest.Podman([]string{"manifest", "create", "foo"}) 82 session.WaitWithDefaultTimeout() 83 Expect(session).Should(ExitCleanly()) 84 85 session = podmanTest.Podman([]string{"manifest", "create", "foo"}) 86 session.WaitWithDefaultTimeout() 87 Expect(session).To(ExitWithError(125, `image name "localhost/foo:latest" is already associated with image `)) 88 89 session = podmanTest.Podman([]string{"manifest", "push", "--all", "foo"}) 90 session.WaitWithDefaultTimeout() 91 // Push should actually fail since it's not valid registry 92 Expect(session).To(ExitWithError(125, "requested access to the resource is denied")) 93 Expect(session.OutputToString()).To(Not(ContainSubstring("accepts 2 arg(s), received 1"))) 94 95 session = podmanTest.Podman([]string{"manifest", "create", amend, "foo"}) 96 session.WaitWithDefaultTimeout() 97 Expect(session).Should(ExitCleanly()) 98 99 session = podmanTest.Podman([]string{"manifest", "rm", "foo"}) 100 session.WaitWithDefaultTimeout() 101 Expect(session).Should(ExitCleanly()) 102 } 103 }) 104 105 It("create w/ image", func() { 106 session := podmanTest.Podman([]string{"manifest", "create", "foo", imageList}) 107 session.WaitWithDefaultTimeout() 108 Expect(session).Should(ExitCleanly()) 109 }) 110 111 It("inspect", func() { 112 session := podmanTest.Podman([]string{"manifest", "inspect", BB}) 113 session.WaitWithDefaultTimeout() 114 Expect(session).Should(ExitCleanly()) 115 116 session = podmanTest.Podman([]string{"manifest", "inspect", "quay.io/libpod/busybox"}) 117 session.WaitWithDefaultTimeout() 118 Expect(session).Should(ExitCleanly()) 119 120 // inspect manifest of single image 121 session = podmanTest.Podman([]string{"manifest", "inspect", "quay.io/libpod/busybox@sha256:6655df04a3df853b029a5fac8836035ac4fab117800c9a6c4b69341bb5306c3d"}) 122 session.WaitWithDefaultTimeout() 123 Expect(session).Should(Exit(0)) 124 // yet another warning message that is not seen by remote client 125 stderr := session.ErrorToString() 126 if IsRemote() { 127 Expect(stderr).Should(Equal("")) 128 } else { 129 Expect(stderr).Should(ContainSubstring("The manifest type application/vnd.docker.distribution.manifest.v2+json is not a manifest list but a single image.")) 130 } 131 }) 132 133 It("add w/ inspect", func() { 134 session := podmanTest.Podman([]string{"manifest", "create", "foo"}) 135 session.WaitWithDefaultTimeout() 136 Expect(session).Should(ExitCleanly()) 137 id := strings.TrimSpace(string(session.Out.Contents())) 138 139 session = podmanTest.Podman([]string{"manifest", "inspect", id}) 140 session.WaitWithDefaultTimeout() 141 Expect(session).Should(ExitCleanly()) 142 143 session = podmanTest.Podman([]string{"manifest", "add", "--arch=arm64", "foo", imageListInstance}) 144 session.WaitWithDefaultTimeout() 145 Expect(session).Should(ExitCleanly()) 146 147 session = podmanTest.Podman([]string{"manifest", "inspect", "foo"}) 148 session.WaitWithDefaultTimeout() 149 Expect(session).Should(ExitCleanly()) 150 Expect(session.OutputToString()).To(ContainSubstring(imageListARM64InstanceDigest)) 151 }) 152 153 It("add with new version", func() { 154 // Following test must pass for both podman and podman-remote 155 session := podmanTest.Podman([]string{"manifest", "create", "foo"}) 156 session.WaitWithDefaultTimeout() 157 Expect(session).Should(ExitCleanly()) 158 id := strings.TrimSpace(string(session.Out.Contents())) 159 160 session = podmanTest.Podman([]string{"manifest", "inspect", id}) 161 session.WaitWithDefaultTimeout() 162 Expect(session).Should(ExitCleanly()) 163 164 session = podmanTest.Podman([]string{"manifest", "add", "--os-version", "7.7.7", "foo", imageListInstance}) 165 session.WaitWithDefaultTimeout() 166 Expect(session).Should(ExitCleanly()) 167 168 session = podmanTest.Podman([]string{"manifest", "inspect", "foo"}) 169 session.WaitWithDefaultTimeout() 170 Expect(session).Should(ExitCleanly()) 171 Expect(session.OutputToString()).To(ContainSubstring("7.7.7")) 172 }) 173 174 It("tag", func() { 175 session := podmanTest.Podman([]string{"manifest", "create", "foobar"}) 176 session.WaitWithDefaultTimeout() 177 Expect(session).Should(ExitCleanly()) 178 session = podmanTest.Podman([]string{"manifest", "add", "foobar", "quay.io/libpod/busybox"}) 179 session.WaitWithDefaultTimeout() 180 Expect(session).Should(ExitCleanly()) 181 session = podmanTest.Podman([]string{"tag", "foobar", "foobar2"}) 182 session.WaitWithDefaultTimeout() 183 Expect(session).Should(ExitCleanly()) 184 session = podmanTest.Podman([]string{"manifest", "inspect", "foobar"}) 185 session.WaitWithDefaultTimeout() 186 Expect(session).Should(ExitCleanly()) 187 session2 := podmanTest.Podman([]string{"manifest", "inspect", "foobar2"}) 188 session2.WaitWithDefaultTimeout() 189 Expect(session2).Should(ExitCleanly()) 190 Expect(session2.OutputToString()).To(Equal(session.OutputToString())) 191 }) 192 193 It("push with --add-compression and --force-compression", func() { 194 if podmanTest.Host.Arch == "ppc64le" { 195 Skip("No registry image for ppc64le") 196 } 197 if isRootless() { 198 err := podmanTest.RestoreArtifact(REGISTRY_IMAGE) 199 Expect(err).ToNot(HaveOccurred()) 200 } 201 lock := GetPortLock("5007") 202 defer lock.Unlock() 203 session := podmanTest.Podman([]string{"run", "-d", "--name", "registry", "-p", "5007:5000", REGISTRY_IMAGE, "/entrypoint.sh", "/etc/docker/registry/config.yml"}) 204 session.WaitWithDefaultTimeout() 205 Expect(session).Should(ExitCleanly()) 206 207 if !WaitContainerReady(podmanTest, "registry", "listening on", 20, 1) { 208 Skip("Cannot start docker registry.") 209 } 210 211 session = podmanTest.Podman([]string{"build", "-q", "--platform", "linux/amd64", "-t", "imageone", "build/basicalpine"}) 212 session.WaitWithDefaultTimeout() 213 Expect(session).Should(ExitCleanly()) 214 215 session = podmanTest.Podman([]string{"build", "-q", "--platform", "linux/arm64", "-t", "imagetwo", "build/basicalpine"}) 216 session.WaitWithDefaultTimeout() 217 Expect(session).Should(ExitCleanly()) 218 219 session = podmanTest.Podman([]string{"manifest", "create", "foobar"}) 220 session.WaitWithDefaultTimeout() 221 Expect(session).Should(ExitCleanly()) 222 session = podmanTest.Podman([]string{"manifest", "add", "foobar", "containers-storage:localhost/imageone:latest"}) 223 session.WaitWithDefaultTimeout() 224 Expect(session).Should(ExitCleanly()) 225 session = podmanTest.Podman([]string{"manifest", "add", "foobar", "containers-storage:localhost/imagetwo:latest"}) 226 session.WaitWithDefaultTimeout() 227 Expect(session).Should(ExitCleanly()) 228 229 push := podmanTest.Podman([]string{"manifest", "push", "--all", "--compression-format", "gzip", "--add-compression", "zstd", "--tls-verify=false", "--remove-signatures", "foobar", "localhost:5007/list"}) 230 push.WaitWithDefaultTimeout() 231 Expect(push).Should(Exit(0)) 232 output := push.ErrorToString() 233 // 4 images must be pushed two for gzip and two for zstd 234 Expect(output).To(ContainSubstring("Copying 4 images generated from 2 images in list")) 235 236 session = podmanTest.Podman([]string{"run", "--rm", "--net", "host", "quay.io/skopeo/stable", "inspect", "--tls-verify=false", "--raw", "docker://localhost:5007/list:latest"}) 237 session.WaitWithDefaultTimeout() 238 Expect(session).Should(Exit(0)) 239 var index imgspecv1.Index 240 inspectData := []byte(session.OutputToString()) 241 err := json.Unmarshal(inspectData, &index) 242 Expect(err).ToNot(HaveOccurred()) 243 244 Expect(verifyInstanceCompression(index.Manifests, "zstd", "amd64")).Should(BeTrue()) 245 Expect(verifyInstanceCompression(index.Manifests, "zstd", "arm64")).Should(BeTrue()) 246 Expect(verifyInstanceCompression(index.Manifests, "gzip", "arm64")).Should(BeTrue()) 247 Expect(verifyInstanceCompression(index.Manifests, "gzip", "amd64")).Should(BeTrue()) 248 249 // Note: Pushing again with --force-compression should produce the correct response the since blobs will be correctly force-pushed again. 250 push = podmanTest.Podman([]string{"manifest", "push", "--all", "--add-compression", "zstd", "--tls-verify=false", "--compression-format", "gzip", "--force-compression", "--remove-signatures", "foobar", "localhost:5007/list"}) 251 push.WaitWithDefaultTimeout() 252 Expect(push).Should(Exit(0)) 253 output = push.ErrorToString() 254 // 4 images must be pushed two for gzip and two for zstd 255 Expect(output).To(ContainSubstring("Copying 4 images generated from 2 images in list")) 256 257 session = podmanTest.Podman([]string{"run", "--rm", "--net", "host", "quay.io/skopeo/stable", "inspect", "--tls-verify=false", "--raw", "docker://localhost:5007/list:latest"}) 258 session.WaitWithDefaultTimeout() 259 Expect(session).Should(ExitCleanly()) 260 inspectData = []byte(session.OutputToString()) 261 err = json.Unmarshal(inspectData, &index) 262 Expect(err).ToNot(HaveOccurred()) 263 264 Expect(verifyInstanceCompression(index.Manifests, "zstd", "amd64")).Should(BeTrue()) 265 Expect(verifyInstanceCompression(index.Manifests, "zstd", "arm64")).Should(BeTrue()) 266 Expect(verifyInstanceCompression(index.Manifests, "gzip", "arm64")).Should(BeTrue()) 267 Expect(verifyInstanceCompression(index.Manifests, "gzip", "amd64")).Should(BeTrue()) 268 269 // same thing with add_compression from config file should work and without --add-compression flag in CLI 270 confFile := filepath.Join(podmanTest.TempDir, "containers.conf") 271 err = os.WriteFile(confFile, []byte(`[engine] 272 add_compression = ["zstd"]`), 0o644) 273 Expect(err).ToNot(HaveOccurred()) 274 os.Setenv("CONTAINERS_CONF", confFile) 275 276 push = podmanTest.Podman([]string{"manifest", "push", "--all", "--tls-verify=false", "--compression-format", "gzip", "--force-compression", "--remove-signatures", "foobar", "localhost:5007/list"}) 277 push.WaitWithDefaultTimeout() 278 Expect(push).Should(Exit(0)) 279 output = push.ErrorToString() 280 // 4 images must be pushed two for gzip and two for zstd 281 Expect(output).To(ContainSubstring("Copying 4 images generated from 2 images in list")) 282 283 session = podmanTest.Podman([]string{"run", "--rm", "--net", "host", "quay.io/skopeo/stable", "inspect", "--tls-verify=false", "--raw", "docker://localhost:5007/list:latest"}) 284 session.WaitWithDefaultTimeout() 285 Expect(session).Should(ExitCleanly()) 286 inspectData = []byte(session.OutputToString()) 287 err = json.Unmarshal(inspectData, &index) 288 Expect(err).ToNot(HaveOccurred()) 289 290 Expect(verifyInstanceCompression(index.Manifests, "zstd", "amd64")).Should(BeTrue()) 291 Expect(verifyInstanceCompression(index.Manifests, "zstd", "arm64")).Should(BeTrue()) 292 Expect(verifyInstanceCompression(index.Manifests, "gzip", "arm64")).Should(BeTrue()) 293 Expect(verifyInstanceCompression(index.Manifests, "gzip", "amd64")).Should(BeTrue()) 294 295 // Note: Pushing again with --force-compression=false should produce in-correct/wrong result since blobs are already present in registry so they will be reused 296 // ignoring our compression priority ( this is expected behaviour of c/image and --force-compression is introduced to mitigate this behaviour ). 297 push = podmanTest.Podman([]string{"manifest", "push", "--all", "--add-compression", "zstd", "--force-compression=false", "--tls-verify=false", "--remove-signatures", "foobar", "localhost:5007/list"}) 298 push.WaitWithDefaultTimeout() 299 Expect(push).Should(Exit(0)) 300 output = push.ErrorToString() 301 // 4 images must be pushed two for gzip and two for zstd 302 Expect(output).To(ContainSubstring("Copying 4 images generated from 2 images in list")) 303 304 session = podmanTest.Podman([]string{"run", "--rm", "--net", "host", "quay.io/skopeo/stable", "inspect", "--tls-verify=false", "--raw", "docker://localhost:5007/list:latest"}) 305 session.WaitWithDefaultTimeout() 306 Expect(session).Should(ExitCleanly()) 307 inspectData = []byte(session.OutputToString()) 308 err = json.Unmarshal(inspectData, &index) 309 Expect(err).ToNot(HaveOccurred()) 310 311 Expect(verifyInstanceCompression(index.Manifests, "zstd", "amd64")).Should(BeTrue()) 312 Expect(verifyInstanceCompression(index.Manifests, "zstd", "arm64")).Should(BeTrue()) 313 // blobs of zstd will be wrongly reused for gzip instances without --force-compression 314 Expect(verifyInstanceCompression(index.Manifests, "gzip", "arm64")).Should(BeFalse()) 315 // blobs of zstd will be wrongly reused for gzip instances without --force-compression 316 Expect(verifyInstanceCompression(index.Manifests, "gzip", "amd64")).Should(BeFalse()) 317 }) 318 319 It("add --all", func() { 320 session := podmanTest.Podman([]string{"manifest", "create", "foo"}) 321 session.WaitWithDefaultTimeout() 322 Expect(session).Should(ExitCleanly()) 323 session = podmanTest.Podman([]string{"manifest", "add", "--all", "foo", imageList}) 324 session.WaitWithDefaultTimeout() 325 Expect(session).Should(ExitCleanly()) 326 session = podmanTest.Podman([]string{"manifest", "inspect", "foo"}) 327 session.WaitWithDefaultTimeout() 328 Expect(session).Should(ExitCleanly()) 329 Expect(session.OutputToString()).To( 330 And( 331 ContainSubstring(imageListAMD64InstanceDigest), 332 ContainSubstring(imageListARM64InstanceDigest), 333 ContainSubstring(imageListPPC64LEInstanceDigest), 334 ContainSubstring(imageListS390XInstanceDigest), 335 )) 336 }) 337 338 It("add --annotation", func() { 339 session := podmanTest.Podman([]string{"manifest", "create", "foo"}) 340 session.WaitWithDefaultTimeout() 341 Expect(session).Should(ExitCleanly()) 342 session = podmanTest.Podman([]string{"manifest", "add", "--annotation", "hoge", "foo", imageList}) 343 session.WaitWithDefaultTimeout() 344 Expect(session).Should(ExitWithError(125, "no value given for annotation")) 345 346 session = podmanTest.Podman([]string{"manifest", "add", "--annotation", "hoge=fuga", "--annotation", "key=val,withcomma", "foo", imageList}) 347 session.WaitWithDefaultTimeout() 348 Expect(session).Should(ExitCleanly()) 349 session = podmanTest.Podman([]string{"manifest", "inspect", "foo"}) 350 session.WaitWithDefaultTimeout() 351 Expect(session).Should(ExitCleanly()) 352 353 var inspect define.ManifestListData 354 err := json.Unmarshal(session.Out.Contents(), &inspect) 355 Expect(err).ToNot(HaveOccurred()) 356 Expect(inspect.Manifests[0].Annotations).To(Equal(map[string]string{"hoge": "fuga", "key": "val,withcomma"})) 357 }) 358 359 It("add --os", func() { 360 session := podmanTest.Podman([]string{"manifest", "create", "foo"}) 361 session.WaitWithDefaultTimeout() 362 Expect(session).Should(ExitCleanly()) 363 session = podmanTest.Podman([]string{"manifest", "add", "--os", "bar", "foo", imageList}) 364 session.WaitWithDefaultTimeout() 365 Expect(session).Should(ExitCleanly()) 366 session = podmanTest.Podman([]string{"manifest", "inspect", "foo"}) 367 session.WaitWithDefaultTimeout() 368 Expect(session).Should(ExitCleanly()) 369 Expect(session.OutputToString()).To(ContainSubstring(`"os": "bar"`)) 370 }) 371 372 It("annotate", func() { 373 session := podmanTest.Podman([]string{"manifest", "create", "foo"}) 374 session.WaitWithDefaultTimeout() 375 Expect(session).Should(ExitCleanly()) 376 session = podmanTest.Podman([]string{"manifest", "add", "foo", imageListInstance}) 377 session.WaitWithDefaultTimeout() 378 Expect(session).Should(ExitCleanly()) 379 session = podmanTest.Podman([]string{"manifest", "annotate", "--annotation", "hello=world,withcomma", "--arch", "bar", "foo", imageListARM64InstanceDigest}) 380 session.WaitWithDefaultTimeout() 381 Expect(session).Should(ExitCleanly()) 382 session = podmanTest.Podman([]string{"manifest", "inspect", "foo"}) 383 session.WaitWithDefaultTimeout() 384 Expect(session).Should(ExitCleanly()) 385 Expect(session.OutputToString()).To(ContainSubstring(`"architecture": "bar"`)) 386 // Check added annotation 387 Expect(session.OutputToString()).To(ContainSubstring(`"hello": "world,withcomma"`)) 388 }) 389 390 It("remove digest", func() { 391 session := podmanTest.Podman([]string{"manifest", "create", "foo"}) 392 session.WaitWithDefaultTimeout() 393 Expect(session).Should(ExitCleanly()) 394 session = podmanTest.Podman([]string{"manifest", "add", "--all", "foo", imageList}) 395 session.WaitWithDefaultTimeout() 396 Expect(session).Should(ExitCleanly()) 397 session = podmanTest.Podman([]string{"manifest", "inspect", "foo"}) 398 session.WaitWithDefaultTimeout() 399 Expect(session).Should(ExitCleanly()) 400 Expect(session.OutputToString()).To(ContainSubstring(imageListARM64InstanceDigest)) 401 session = podmanTest.Podman([]string{"manifest", "remove", "foo", imageListARM64InstanceDigest}) 402 session.WaitWithDefaultTimeout() 403 Expect(session).Should(ExitCleanly()) 404 session = podmanTest.Podman([]string{"manifest", "inspect", "foo"}) 405 session.WaitWithDefaultTimeout() 406 Expect(session).Should(ExitCleanly()) 407 Expect(session.OutputToString()).To( 408 And( 409 ContainSubstring(imageListAMD64InstanceDigest), 410 ContainSubstring(imageListPPC64LEInstanceDigest), 411 ContainSubstring(imageListS390XInstanceDigest), 412 Not( 413 ContainSubstring(imageListARM64InstanceDigest)), 414 )) 415 }) 416 417 It("remove not-found", func() { 418 session := podmanTest.Podman([]string{"manifest", "create", "foo"}) 419 session.WaitWithDefaultTimeout() 420 Expect(session).Should(ExitCleanly()) 421 session = podmanTest.Podman([]string{"manifest", "add", "foo", imageList}) 422 session.WaitWithDefaultTimeout() 423 Expect(session).Should(ExitCleanly()) 424 bogusID := "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" 425 session = podmanTest.Podman([]string{"manifest", "remove", "foo", bogusID}) 426 session.WaitWithDefaultTimeout() 427 428 // FIXME-someday: figure out why message differs in podman-remote 429 expectMessage := "removing from manifest list foo: " 430 if IsRemote() { 431 expectMessage += "removing from manifest foo" 432 } else { 433 expectMessage += fmt.Sprintf(`no instance matching digest %q found in manifest list: file does not exist`, bogusID) 434 } 435 Expect(session).To(ExitWithError(125, expectMessage)) 436 437 session = podmanTest.Podman([]string{"manifest", "rm", "foo"}) 438 session.WaitWithDefaultTimeout() 439 Expect(session).Should(ExitCleanly()) 440 }) 441 442 It("push --all", func() { 443 SkipIfRemote("manifest push to dir not supported in remote mode") 444 session := podmanTest.Podman([]string{"manifest", "create", "foo"}) 445 session.WaitWithDefaultTimeout() 446 Expect(session).Should(ExitCleanly()) 447 session = podmanTest.Podman([]string{"manifest", "add", "--all", "foo", imageList}) 448 session.WaitWithDefaultTimeout() 449 Expect(session).Should(ExitCleanly()) 450 dest := filepath.Join(podmanTest.TempDir, "pushed") 451 err := os.MkdirAll(dest, os.ModePerm) 452 Expect(err).ToNot(HaveOccurred()) 453 defer func() { 454 os.RemoveAll(dest) 455 }() 456 session = podmanTest.Podman([]string{"manifest", "push", "-q", "--all", "foo", "dir:" + dest}) 457 session.WaitWithDefaultTimeout() 458 Expect(session).Should(ExitCleanly()) 459 460 err = validateManifestHasAllArchs(filepath.Join(dest, "manifest.json")) 461 Expect(err).ToNot(HaveOccurred()) 462 }) 463 464 It("push", func() { 465 SkipIfRemote("manifest push to dir not supported in remote mode") 466 session := podmanTest.Podman([]string{"manifest", "create", "foo"}) 467 session.WaitWithDefaultTimeout() 468 Expect(session).Should(ExitCleanly()) 469 session = podmanTest.Podman([]string{"manifest", "add", "--all", "foo", imageList}) 470 session.WaitWithDefaultTimeout() 471 Expect(session).Should(ExitCleanly()) 472 dest := filepath.Join(podmanTest.TempDir, "pushed") 473 err := os.MkdirAll(dest, os.ModePerm) 474 Expect(err).ToNot(HaveOccurred()) 475 defer func() { 476 os.RemoveAll(dest) 477 }() 478 session = podmanTest.Podman([]string{"push", "-q", "foo", "dir:" + dest}) 479 session.WaitWithDefaultTimeout() 480 Expect(session).Should(ExitCleanly()) 481 482 err = validateManifestHasAllArchs(filepath.Join(dest, "manifest.json")) 483 Expect(err).ToNot(HaveOccurred()) 484 }) 485 486 It("push with compression-format and compression-level", func() { 487 SkipIfRemote("manifest push to dir not supported in remote mode") 488 dockerfile := `FROM ` + CITEST_IMAGE + ` 489 RUN touch /file 490 ` 491 podmanTest.BuildImage(dockerfile, "localhost/test", "false") 492 493 session := podmanTest.Podman([]string{"manifest", "create", "foo"}) 494 session.WaitWithDefaultTimeout() 495 Expect(session).Should(ExitCleanly()) 496 497 session = podmanTest.Podman([]string{"manifest", "add", "foo", "containers-storage:localhost/test"}) 498 session.WaitWithDefaultTimeout() 499 Expect(session).Should(ExitCleanly()) 500 501 // Invalid compression format specified, it must fail 502 tmpDir := filepath.Join(podmanTest.TempDir, "wrong-compression") 503 session = podmanTest.Podman([]string{"manifest", "push", "--compression-format", "gzip", "--compression-level", "50", "foo", "oci:" + tmpDir}) 504 session.WaitWithDefaultTimeout() 505 Expect(session).Should(ExitWithError(125, "invalid compression level")) 506 507 dest := filepath.Join(podmanTest.TempDir, "pushed") 508 err := os.MkdirAll(dest, os.ModePerm) 509 Expect(err).ToNot(HaveOccurred()) 510 defer func() { 511 os.RemoveAll(dest) 512 }() 513 session = podmanTest.Podman([]string{"push", "-q", "--compression-format=zstd", "foo", "oci:" + dest}) 514 session.WaitWithDefaultTimeout() 515 Expect(session).Should(ExitCleanly()) 516 517 foundZstdFile := false 518 519 blobsDir := filepath.Join(dest, "blobs", "sha256") 520 521 blobs, err := os.ReadDir(blobsDir) 522 Expect(err).ToNot(HaveOccurred()) 523 524 for _, f := range blobs { 525 blobPath := filepath.Join(blobsDir, f.Name()) 526 527 sourceFile, err := os.ReadFile(blobPath) 528 Expect(err).ToNot(HaveOccurred()) 529 530 compressionType := archive.DetectCompression(sourceFile) 531 if compressionType == archive.Zstd { 532 foundZstdFile = true 533 break 534 } 535 } 536 Expect(foundZstdFile).To(BeTrue(), "found zstd file") 537 }) 538 539 It("push progress", func() { 540 SkipIfRemote("manifest push to dir not supported in remote mode") 541 542 session := podmanTest.Podman([]string{"manifest", "create", "foo", imageList}) 543 session.WaitWithDefaultTimeout() 544 Expect(session).Should(ExitCleanly()) 545 546 dest := filepath.Join(podmanTest.TempDir, "pushed") 547 err := os.MkdirAll(dest, os.ModePerm) 548 Expect(err).ToNot(HaveOccurred()) 549 defer func() { 550 os.RemoveAll(dest) 551 }() 552 553 session = podmanTest.Podman([]string{"push", "foo", "-q", "dir:" + dest}) 554 session.WaitWithDefaultTimeout() 555 Expect(session).Should(ExitCleanly()) 556 Expect(session.ErrorToString()).To(BeEmpty()) 557 558 session = podmanTest.Podman([]string{"push", "foo", "dir:" + dest}) 559 session.WaitWithDefaultTimeout() 560 Expect(session).Should(Exit(0)) 561 output := session.ErrorToString() 562 Expect(output).To(ContainSubstring("Writing manifest list to image destination")) 563 Expect(output).To(ContainSubstring("Storing list signatures")) 564 }) 565 566 It("push must retry", func() { 567 SkipIfRemote("warning is not relayed in remote setup") 568 session := podmanTest.Podman([]string{"manifest", "create", "foo", imageList}) 569 session.WaitWithDefaultTimeout() 570 Expect(session).Should(ExitCleanly()) 571 572 push := podmanTest.Podman([]string{"manifest", "push", "--all", "--tls-verify=false", "--remove-signatures", "foo", "localhost:7000/bogus"}) 573 push.WaitWithDefaultTimeout() 574 Expect(push).Should(ExitWithError(125, "Failed, retrying in 1s ... (1/3)")) 575 Expect(push.ErrorToString()).To(MatchRegexp("Copying blob.*Failed, retrying in 1s \\.\\.\\. \\(1/3\\).*Copying blob.*Failed, retrying in 2s")) 576 }) 577 578 It("authenticated push", func() { 579 registryOptions := &podmanRegistry.Options{ 580 PodmanPath: podmanTest.PodmanBinary, 581 PodmanArgs: podmanTest.MakeOptions(nil, false, false), 582 Image: "docker-archive:" + imageTarPath(REGISTRY_IMAGE), 583 } 584 585 // Special case for remote: invoke local podman, with all 586 // network/storage/other args 587 if IsRemote() { 588 registryOptions.PodmanArgs = getRemoteOptions(podmanTest, nil) 589 } 590 registry, err := podmanRegistry.StartWithOptions(registryOptions) 591 Expect(err).ToNot(HaveOccurred()) 592 defer func() { 593 err := registry.Stop() 594 Expect(err).ToNot(HaveOccurred()) 595 }() 596 597 session := podmanTest.Podman([]string{"manifest", "create", "foo"}) 598 session.WaitWithDefaultTimeout() 599 Expect(session).Should(ExitCleanly()) 600 601 session = podmanTest.Podman([]string{"tag", CITEST_IMAGE, "localhost:" + registry.Port + "/citest:latest"}) 602 session.WaitWithDefaultTimeout() 603 Expect(session).Should(ExitCleanly()) 604 605 push := podmanTest.Podman([]string{"push", "-q", "--tls-verify=false", "--creds=" + registry.User + ":" + registry.Password, "--format=v2s2", "localhost:" + registry.Port + "/citest:latest"}) 606 push.WaitWithDefaultTimeout() 607 // Cannot ExitCleanly() because this sometimes warns "Failed, retrying in 1s" 608 Expect(push).Should(Exit(0)) 609 610 session = podmanTest.Podman([]string{"manifest", "add", "--tls-verify=false", "--creds=" + registry.User + ":" + registry.Password, "foo", "localhost:" + registry.Port + "/citest:latest"}) 611 session.WaitWithDefaultTimeout() 612 Expect(session).Should(ExitCleanly()) 613 614 push = podmanTest.Podman([]string{"manifest", "push", "--tls-verify=false", "--creds=" + registry.User + ":" + registry.Password, "foo", "localhost:" + registry.Port + "/credstest"}) 615 push.WaitWithDefaultTimeout() 616 Expect(push).Should(Exit(0)) 617 output := push.ErrorToString() 618 Expect(output).To(ContainSubstring("Copying blob ")) 619 Expect(output).To(ContainSubstring("Copying config ")) 620 Expect(output).To(ContainSubstring("Writing manifest to image destination")) 621 622 push = podmanTest.Podman([]string{"manifest", "push", "--compression-format=gzip", "--compression-level=2", "--tls-verify=false", "--creds=podmantest:wrongpasswd", "foo", "localhost:" + registry.Port + "/credstest"}) 623 push.WaitWithDefaultTimeout() 624 Expect(push).To(ExitWithError(125, ": authentication required")) 625 626 // push --rm after pull image (#15033) 627 push = podmanTest.Podman([]string{"manifest", "push", "-q", "--rm", "--tls-verify=false", "--creds=" + registry.User + ":" + registry.Password, "foo", "localhost:" + registry.Port + "/rmtest"}) 628 push.WaitWithDefaultTimeout() 629 Expect(push).Should(ExitCleanly()) 630 631 session = podmanTest.Podman([]string{"images", "-q", "foo"}) 632 session.WaitWithDefaultTimeout() 633 Expect(session).Should(ExitCleanly()) 634 Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0)) 635 }) 636 637 It("push with error", func() { 638 session := podmanTest.Podman([]string{"manifest", "push", "badsrcvalue", "baddestvalue"}) 639 session.WaitWithDefaultTimeout() 640 Expect(session).Should(ExitWithError(125, "retrieving local image from image name badsrcvalue: badsrcvalue: image not known")) 641 }) 642 643 It("push --rm to local directory", func() { 644 SkipIfRemote("manifest push to dir not supported in remote mode") 645 session := podmanTest.Podman([]string{"manifest", "create", "foo"}) 646 session.WaitWithDefaultTimeout() 647 Expect(session).Should(ExitCleanly()) 648 session = podmanTest.Podman([]string{"manifest", "add", "foo", imageList}) 649 session.WaitWithDefaultTimeout() 650 Expect(session).Should(ExitCleanly()) 651 dest := filepath.Join(podmanTest.TempDir, "pushed") 652 err := os.MkdirAll(dest, os.ModePerm) 653 Expect(err).ToNot(HaveOccurred()) 654 defer func() { 655 os.RemoveAll(dest) 656 }() 657 session = podmanTest.Podman([]string{"manifest", "push", "-q", "--purge", "foo", "dir:" + dest}) 658 session.WaitWithDefaultTimeout() 659 Expect(session).Should(ExitCleanly()) 660 session = podmanTest.Podman([]string{"manifest", "push", "-p", "foo", "dir:" + dest}) 661 session.WaitWithDefaultTimeout() 662 Expect(session).Should(ExitWithError(125, "retrieving local image from image name foo: foo: image not known")) 663 664 session = podmanTest.Podman([]string{"images", "-q", "foo"}) 665 session.WaitWithDefaultTimeout() 666 Expect(session).Should(ExitCleanly()) 667 Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0)) 668 669 // push --rm after pull image (#15033) 670 session = podmanTest.Podman([]string{"pull", "-q", "quay.io/libpod/testdigest_v2s2"}) 671 session.WaitWithDefaultTimeout() 672 Expect(session).Should(ExitCleanly()) 673 674 session = podmanTest.Podman([]string{"manifest", "create", "bar"}) 675 session.WaitWithDefaultTimeout() 676 Expect(session).Should(ExitCleanly()) 677 session = podmanTest.Podman([]string{"manifest", "add", "bar", "quay.io/libpod/testdigest_v2s2"}) 678 session.WaitWithDefaultTimeout() 679 Expect(session).Should(ExitCleanly()) 680 session = podmanTest.Podman([]string{"manifest", "push", "-q", "--rm", "bar", "dir:" + dest}) 681 session.WaitWithDefaultTimeout() 682 Expect(session).Should(ExitCleanly()) 683 session = podmanTest.Podman([]string{"images", "-q", "bar"}) 684 session.WaitWithDefaultTimeout() 685 Expect(session).Should(ExitCleanly()) 686 Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0)) 687 688 session = podmanTest.Podman([]string{"manifest", "rm", "foo", "bar"}) 689 session.WaitWithDefaultTimeout() 690 Expect(session).Should(ExitWithError(1, " 2 errors occurred:")) 691 Expect(session.ErrorToString()).To(ContainSubstring("* foo: image not known")) 692 Expect(session.ErrorToString()).To(ContainSubstring("* bar: image not known")) 693 }) 694 695 It("exists", func() { 696 manifestList := "manifest-list" 697 session := podmanTest.Podman([]string{"manifest", "create", manifestList}) 698 session.WaitWithDefaultTimeout() 699 Expect(session).Should(ExitCleanly()) 700 701 session = podmanTest.Podman([]string{"manifest", "exists", manifestList}) 702 session.WaitWithDefaultTimeout() 703 Expect(session).Should(ExitCleanly()) 704 705 session = podmanTest.Podman([]string{"manifest", "exists", "no-manifest"}) 706 session.WaitWithDefaultTimeout() 707 Expect(session).Should(Exit(1)) 708 }) 709 710 It("rm should not remove referenced images", func() { 711 manifestList := "manifestlist" 712 imageName := "quay.io/libpod/busybox" 713 714 session := podmanTest.Podman([]string{"pull", "-q", imageName}) 715 session.WaitWithDefaultTimeout() 716 Expect(session).Should(ExitCleanly()) 717 718 session = podmanTest.Podman([]string{"manifest", "create", manifestList}) 719 session.WaitWithDefaultTimeout() 720 Expect(session).Should(ExitCleanly()) 721 722 session = podmanTest.Podman([]string{"manifest", "add", manifestList, imageName}) 723 session.WaitWithDefaultTimeout() 724 Expect(session).Should(ExitCleanly()) 725 726 session = podmanTest.Podman([]string{"manifest", "rm", manifestList}) 727 session.WaitWithDefaultTimeout() 728 Expect(session).Should(ExitCleanly()) 729 730 // image should still show up 731 session = podmanTest.Podman([]string{"image", "exists", imageName}) 732 session.WaitWithDefaultTimeout() 733 Expect(session).Should(ExitCleanly()) 734 }) 735 736 It("manifest rm should not remove image and should be able to remove tagged manifest list", func() { 737 // manifest rm should fail with `image is not a manifest list` 738 session := podmanTest.Podman([]string{"manifest", "rm", ALPINE}) 739 session.WaitWithDefaultTimeout() 740 Expect(session).Should(ExitWithError(125, "image is not a manifest list")) 741 742 manifestName := "testmanifest:sometag" 743 session = podmanTest.Podman([]string{"manifest", "create", manifestName}) 744 session.WaitWithDefaultTimeout() 745 Expect(session).Should(ExitCleanly()) 746 747 // verify if manifest exists 748 session = podmanTest.Podman([]string{"manifest", "exists", manifestName}) 749 session.WaitWithDefaultTimeout() 750 Expect(session).Should(ExitCleanly()) 751 752 // manifest rm should be able to remove tagged manifest list 753 session = podmanTest.Podman([]string{"manifest", "rm", manifestName}) 754 session.WaitWithDefaultTimeout() 755 Expect(session).Should(ExitCleanly()) 756 757 // verify that manifest should not exist 758 session = podmanTest.Podman([]string{"manifest", "exists", manifestName}) 759 session.WaitWithDefaultTimeout() 760 Expect(session).Should(Exit(1)) 761 }) 762 })