github.com/containers/podman/v4@v4.9.4/test/e2e/manifest_test.go (about)

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