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  })