github.com/containers/podman/v5@v5.1.0-rc1/test/e2e/search_test.go (about)

     1  package integration
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"os"
     8  	"strconv"
     9  	"text/template"
    10  
    11  	"github.com/containers/podman/v5/pkg/domain/entities"
    12  	. "github.com/containers/podman/v5/test/utils"
    13  	. "github.com/onsi/ginkgo/v2"
    14  	. "github.com/onsi/gomega"
    15  )
    16  
    17  type endpoint struct {
    18  	Host string
    19  	Port string
    20  }
    21  
    22  func (e *endpoint) Address() string {
    23  	return fmt.Sprintf("%s:%s", e.Host, e.Port)
    24  }
    25  
    26  var _ = Describe("Podman search", func() {
    27  
    28  	const regFileContents = `
    29  [registries.search]
    30  registries = ['{{.Host}}:{{.Port}}']
    31  
    32  [registries.insecure]
    33  registries = ['{{.Host}}:{{.Port}}']`
    34  	registryFileTmpl := template.Must(template.New("registryFile").Parse(regFileContents))
    35  
    36  	const badRegFileContents = `
    37  [registries.search]
    38  registries = ['{{.Host}}:{{.Port}}']
    39  # empty
    40  [registries.insecure]
    41  registries = []`
    42  	registryFileBadTmpl := template.Must(template.New("registryFileBad").Parse(badRegFileContents))
    43  
    44  	It("podman search", func() {
    45  		search := podmanTest.Podman([]string{"search", "alpine"})
    46  		search.WaitWithDefaultTimeout()
    47  		Expect(search).Should(ExitCleanly())
    48  		Expect(len(search.OutputToStringArray())).To(BeNumerically(">", 1))
    49  		Expect(search.OutputToString()).To(ContainSubstring("alpine"))
    50  	})
    51  
    52  	It("podman search single registry flag", func() {
    53  		search := podmanTest.Podman([]string{"search", "quay.io/skopeo/stable:latest"})
    54  		search.WaitWithDefaultTimeout()
    55  		Expect(search).Should(ExitCleanly())
    56  		Expect(search.OutputToString()).To(ContainSubstring("quay.io/skopeo/stable"))
    57  	})
    58  
    59  	It("podman search image with description", func() {
    60  		search := podmanTest.Podman([]string{"search", "quay.io/podman/stable"})
    61  		search.WaitWithDefaultTimeout()
    62  		Expect(search).Should(ExitCleanly())
    63  		output := string(search.Out.Contents())
    64  		Expect(output).To(MatchRegexp(`(?m)NAME\s+DESCRIPTION$`))
    65  		Expect(output).To(MatchRegexp(`(?m)quay.io/podman/stable\s+.*PODMAN logo`))
    66  	})
    67  
    68  	It("podman search image with --compatible", func() {
    69  		search := podmanTest.Podman([]string{"search", "--compatible", "quay.io/podman/stable"})
    70  		search.WaitWithDefaultTimeout()
    71  		Expect(search).Should(ExitCleanly())
    72  		output := string(search.Out.Contents())
    73  		Expect(output).To(MatchRegexp(`(?m)NAME\s+DESCRIPTION\s+STARS\s+OFFICIAL\s+AUTOMATED$`))
    74  	})
    75  
    76  	It("podman search format flag", func() {
    77  		search := podmanTest.Podman([]string{"search", "--format", "table {{.Index}} {{.Name}}", "testdigest_v2s2"})
    78  		search.WaitWithDefaultTimeout()
    79  		Expect(search).Should(ExitCleanly())
    80  		Expect(len(search.OutputToStringArray())).To(BeNumerically(">", 1))
    81  		Expect(search.OutputToString()).To(ContainSubstring("quay.io/libpod/testdigest_v2s2"))
    82  	})
    83  
    84  	It("podman search format json", func() {
    85  		search := podmanTest.Podman([]string{"search", "--format", "json", "testdigest_v2s1"})
    86  		search.WaitWithDefaultTimeout()
    87  		Expect(search).Should(ExitCleanly())
    88  		Expect(search.OutputToString()).To(BeValidJSON())
    89  		Expect(search.OutputToString()).To(ContainSubstring("quay.io/libpod/testdigest_v2s1"))
    90  		Expect(search.OutputToString()).To(ContainSubstring("Test image used by buildah regression tests"))
    91  
    92  		// Test for https://github.com/containers/podman/issues/11894
    93  		contents := make([]entities.ImageSearchReport, 0)
    94  		err := json.Unmarshal(search.Out.Contents(), &contents)
    95  		Expect(err).ToNot(HaveOccurred())
    96  		Expect(contents).ToNot(BeEmpty(), "No results from image search")
    97  		for _, element := range contents {
    98  			Expect(element.Description).ToNot(HaveSuffix("..."))
    99  		}
   100  	})
   101  
   102  	It("podman search format json list tags", func() {
   103  		search := podmanTest.Podman([]string{"search", "--list-tags", "--format", "json", ALPINE})
   104  		search.WaitWithDefaultTimeout()
   105  		Expect(search).Should(ExitCleanly())
   106  		Expect(search.OutputToString()).To(BeValidJSON())
   107  		Expect(search.OutputToString()).To(ContainSubstring("quay.io/libpod/alpine"))
   108  		Expect(search.OutputToString()).To(ContainSubstring("3.10.2"))
   109  		Expect(search.OutputToString()).To(ContainSubstring("3.2"))
   110  	})
   111  
   112  	// Test for https://github.com/containers/podman/issues/11894
   113  	It("podman search no-trunc=false flag", func() {
   114  		search := podmanTest.Podman([]string{"search", "--no-trunc=false", "alpine", "--format={{.Description}}"})
   115  		search.WaitWithDefaultTimeout()
   116  		Expect(search).Should(ExitCleanly())
   117  
   118  		for _, line := range search.OutputToStringArray() {
   119  			if len(line) > 44 {
   120  				Expect(line).To(HaveSuffix("..."), line+" should have been truncated")
   121  			}
   122  		}
   123  	})
   124  
   125  	It("podman search limit flag", func() {
   126  		search := podmanTest.Podman([]string{"search", "quay.io/alpine"})
   127  		search.WaitWithDefaultTimeout()
   128  		Expect(search).Should(ExitCleanly())
   129  		Expect(len(search.OutputToStringArray())).To(BeNumerically(">", 10))
   130  
   131  		search = podmanTest.Podman([]string{"search", "--limit", "3", "quay.io/alpine"})
   132  		search.WaitWithDefaultTimeout()
   133  		Expect(search).Should(ExitCleanly())
   134  		Expect(search.OutputToStringArray()).To(HaveLen(4))
   135  
   136  		search = podmanTest.Podman([]string{"search", "--limit", "30", "quay.io/alpine"})
   137  		search.WaitWithDefaultTimeout()
   138  		Expect(search).Should(ExitCleanly())
   139  		Expect(search.OutputToStringArray()).To(HaveLen(31))
   140  	})
   141  
   142  	It("podman search with filter stars", func() {
   143  		search := podmanTest.Podman([]string{"search", "--filter", "stars=10", "--format", "{{.Stars}}", "alpine"})
   144  		search.WaitWithDefaultTimeout()
   145  		Expect(search).Should(ExitCleanly())
   146  		output := search.OutputToStringArray()
   147  		for i := 0; i < len(output); i++ {
   148  			Expect(strconv.Atoi(output[i])).To(BeNumerically(">=", 10))
   149  		}
   150  	})
   151  
   152  	It("podman search with filter is-official", func() {
   153  		search := podmanTest.Podman([]string{"search", "--filter", "is-official", "--format", "{{.Official}}", "alpine"})
   154  		search.WaitWithDefaultTimeout()
   155  		Expect(search).Should(ExitCleanly())
   156  		output := search.OutputToStringArray()
   157  		for i := 0; i < len(output); i++ {
   158  			Expect(output[i]).To(Equal("[OK]"))
   159  		}
   160  	})
   161  
   162  	It("podman search with filter is-automated", func() {
   163  		search := podmanTest.Podman([]string{"search", "--filter", "is-automated=false", "--format", "{{.Automated}}", "alpine"})
   164  		search.WaitWithDefaultTimeout()
   165  		Expect(search).Should(ExitCleanly())
   166  		output := search.OutputToStringArray()
   167  		for i := 0; i < len(output); i++ {
   168  			Expect(output[i]).To(Equal(""))
   169  		}
   170  	})
   171  
   172  	It("podman search format list tags with custom", func() {
   173  		search := podmanTest.Podman([]string{"search", "--list-tags", "--format", "{{.Name}}", "--limit", "1", ALPINE})
   174  		search.WaitWithDefaultTimeout()
   175  		Expect(search).Should(ExitCleanly())
   176  		Expect(search.OutputToString()).To(Equal("quay.io/libpod/alpine"))
   177  	})
   178  
   179  	It("podman search attempts HTTP if tls-verify flag is set false", func() {
   180  		if podmanTest.Host.Arch == "ppc64le" {
   181  			Skip("No registry image for ppc64le")
   182  		}
   183  		port := GetPort()
   184  		fakereg := podmanTest.Podman([]string{"run", "-d", "--name", "registry",
   185  			"-p", fmt.Sprintf("%d:5000", port),
   186  			REGISTRY_IMAGE, "/entrypoint.sh", "/etc/docker/registry/config.yml"})
   187  		fakereg.WaitWithDefaultTimeout()
   188  		Expect(fakereg).Should(ExitCleanly())
   189  
   190  		if !WaitContainerReady(podmanTest, "registry", "listening on", 20, 1) {
   191  			Fail("Cannot start docker registry on port %s", port)
   192  		}
   193  		ep := endpoint{Port: strconv.Itoa(port), Host: "localhost"}
   194  		search := podmanTest.Podman([]string{"search",
   195  			fmt.Sprintf("%s/fake/image:andtag", ep.Address()), "--tls-verify=false"})
   196  		search.WaitWithDefaultTimeout()
   197  
   198  		// if this test succeeded, there will be no output (there is no entry named fake/image:andtag in an empty registry)
   199  		// and the exit code will be 0
   200  		Expect(search).Should(ExitCleanly())
   201  		Expect(search.OutputToString()).Should(BeEmpty())
   202  	})
   203  
   204  	It("podman search in local registry", func() {
   205  		if podmanTest.Host.Arch == "ppc64le" {
   206  			Skip("No registry image for ppc64le")
   207  		}
   208  		port := GetPort()
   209  		registry := podmanTest.Podman([]string{"run", "-d", "--name", "registry3",
   210  			"-p", fmt.Sprintf("%d:5000", port), REGISTRY_IMAGE,
   211  			"/entrypoint.sh", "/etc/docker/registry/config.yml"})
   212  		registry.WaitWithDefaultTimeout()
   213  		Expect(registry).Should(ExitCleanly())
   214  
   215  		if !WaitContainerReady(podmanTest, "registry3", "listening on", 20, 1) {
   216  			Fail("Cannot start docker registry on port %s", port)
   217  		}
   218  		ep := endpoint{Port: strconv.Itoa(port), Host: "localhost"}
   219  		err = podmanTest.RestoreArtifact(ALPINE)
   220  		Expect(err).ToNot(HaveOccurred())
   221  		image := fmt.Sprintf("%s/my-alpine", ep.Address())
   222  		push := podmanTest.Podman([]string{"push", "-q", "--tls-verify=false", "--remove-signatures", ALPINE, image})
   223  		push.WaitWithDefaultTimeout()
   224  		Expect(push).Should(ExitCleanly())
   225  		search := podmanTest.Podman([]string{"search", image, "--tls-verify=false"})
   226  		search.WaitWithDefaultTimeout()
   227  
   228  		Expect(search).Should(ExitCleanly())
   229  		Expect(search.OutputToString()).ShouldNot(BeEmpty())
   230  
   231  		// podman search v2 registry with empty query
   232  		searchEmpty := podmanTest.Podman([]string{"search", fmt.Sprintf("%s/", ep.Address()), "--tls-verify=false"})
   233  		searchEmpty.WaitWithDefaultTimeout()
   234  		Expect(searchEmpty).Should(ExitCleanly())
   235  		Expect(searchEmpty.OutputToStringArray()).ToNot(BeEmpty())
   236  		Expect(search.OutputToString()).To(ContainSubstring("my-alpine"))
   237  	})
   238  
   239  	It("podman search attempts HTTP if registry is in registries.insecure and force secure is false", func() {
   240  		if podmanTest.Host.Arch == "ppc64le" {
   241  			Skip("No registry image for ppc64le")
   242  		}
   243  
   244  		port := GetPort()
   245  		ep := endpoint{Port: strconv.Itoa(port), Host: "localhost"}
   246  		registry := podmanTest.Podman([]string{"run", "-d", "-p", fmt.Sprintf("%d:5000", port),
   247  			"--name", "registry4", REGISTRY_IMAGE, "/entrypoint.sh", "/etc/docker/registry/config.yml"})
   248  		registry.WaitWithDefaultTimeout()
   249  		Expect(registry).Should(ExitCleanly())
   250  
   251  		if !WaitContainerReady(podmanTest, "registry4", "listening on", 20, 1) {
   252  			Fail("unable to start registry on port %s", port)
   253  		}
   254  
   255  		err = podmanTest.RestoreArtifact(ALPINE)
   256  		Expect(err).ToNot(HaveOccurred())
   257  		image := fmt.Sprintf("%s/my-alpine", ep.Address())
   258  		push := podmanTest.Podman([]string{"push", "-q", "--tls-verify=false", "--remove-signatures", ALPINE, image})
   259  		push.WaitWithDefaultTimeout()
   260  		Expect(push).Should(ExitCleanly())
   261  
   262  		// registries.conf set up
   263  		var buffer bytes.Buffer
   264  		err = registryFileTmpl.Execute(&buffer, ep)
   265  		Expect(err).ToNot(HaveOccurred())
   266  		podmanTest.setRegistriesConfigEnv(buffer.Bytes())
   267  		err = os.WriteFile(fmt.Sprintf("%s/registry4.conf", tempdir), buffer.Bytes(), 0644)
   268  		Expect(err).ToNot(HaveOccurred())
   269  		if IsRemote() {
   270  			podmanTest.RestartRemoteService()
   271  			defer podmanTest.RestartRemoteService()
   272  		}
   273  
   274  		search := podmanTest.Podman([]string{"search", image})
   275  		search.WaitWithDefaultTimeout()
   276  
   277  		Expect(search).Should(ExitCleanly())
   278  		Expect(search.OutputToString()).To(ContainSubstring("my-alpine"))
   279  
   280  		// cleanup
   281  		resetRegistriesConfigEnv()
   282  	})
   283  
   284  	It("podman search doesn't attempt HTTP if force secure is true", func() {
   285  		if podmanTest.Host.Arch == "ppc64le" {
   286  			Skip("No registry image for ppc64le")
   287  		}
   288  		port := GetPort()
   289  		ep := endpoint{Port: strconv.Itoa(port), Host: "localhost"}
   290  		registry := podmanTest.Podman([]string{"run", "-d", "-p", fmt.Sprintf("%d:5000", port),
   291  			"--name", "registry5", REGISTRY_IMAGE})
   292  		registry.WaitWithDefaultTimeout()
   293  		Expect(registry).Should(ExitCleanly())
   294  
   295  		if !WaitContainerReady(podmanTest, "registry5", "listening on", 20, 1) {
   296  			Fail("Cannot start docker registry on port %s", port)
   297  		}
   298  
   299  		err = podmanTest.RestoreArtifact(ALPINE)
   300  		Expect(err).ToNot(HaveOccurred())
   301  		image := fmt.Sprintf("%s/my-alpine", ep.Address())
   302  		push := podmanTest.Podman([]string{"push", "-q", "--tls-verify=false", "--remove-signatures", ALPINE, image})
   303  		push.WaitWithDefaultTimeout()
   304  		Expect(push).Should(ExitCleanly())
   305  
   306  		var buffer bytes.Buffer
   307  		err = registryFileTmpl.Execute(&buffer, ep)
   308  		Expect(err).ToNot(HaveOccurred())
   309  		podmanTest.setRegistriesConfigEnv(buffer.Bytes())
   310  		err = os.WriteFile(fmt.Sprintf("%s/registry5.conf", tempdir), buffer.Bytes(), 0644)
   311  		Expect(err).ToNot(HaveOccurred())
   312  
   313  		search := podmanTest.Podman([]string{"search", image, "--tls-verify=true"})
   314  		search.WaitWithDefaultTimeout()
   315  
   316  		Expect(search).Should(ExitWithError(125, fmt.Sprintf(`couldn't search registry "localhost:%d": pinging container registry localhost:%d: Get "https://localhost:%d/v2/": http: server gave HTTP response to HTTPS client`, port, port, port)))
   317  		Expect(search.OutputToString()).Should(BeEmpty())
   318  
   319  		// cleanup
   320  		resetRegistriesConfigEnv()
   321  	})
   322  
   323  	It("podman search doesn't attempt HTTP if registry is not listed as insecure", func() {
   324  		if podmanTest.Host.Arch == "ppc64le" {
   325  			Skip("No registry image for ppc64le")
   326  		}
   327  		port := GetPort()
   328  		ep := endpoint{Port: strconv.Itoa(port), Host: "localhost"}
   329  		registry := podmanTest.Podman([]string{"run", "-d", "-p", fmt.Sprintf("%d:5000", port),
   330  			"--name", "registry6", REGISTRY_IMAGE})
   331  		registry.WaitWithDefaultTimeout()
   332  		Expect(registry).Should(ExitCleanly())
   333  
   334  		if !WaitContainerReady(podmanTest, "registry6", "listening on", 20, 1) {
   335  			Fail("Cannot start docker registry on port %s", port)
   336  		}
   337  
   338  		err = podmanTest.RestoreArtifact(ALPINE)
   339  		Expect(err).ToNot(HaveOccurred())
   340  		image := fmt.Sprintf("%s/my-alpine", ep.Address())
   341  		push := podmanTest.Podman([]string{"push", "-q", "--tls-verify=false", "--remove-signatures", ALPINE, image})
   342  		push.WaitWithDefaultTimeout()
   343  		Expect(push).Should(ExitCleanly())
   344  
   345  		var buffer bytes.Buffer
   346  		err = registryFileBadTmpl.Execute(&buffer, ep)
   347  		Expect(err).ToNot(HaveOccurred())
   348  		podmanTest.setRegistriesConfigEnv(buffer.Bytes())
   349  		err = os.WriteFile(fmt.Sprintf("%s/registry6.conf", tempdir), buffer.Bytes(), 0644)
   350  		Expect(err).ToNot(HaveOccurred())
   351  
   352  		if IsRemote() {
   353  			podmanTest.RestartRemoteService()
   354  			defer podmanTest.RestartRemoteService()
   355  		}
   356  
   357  		search := podmanTest.Podman([]string{"search", image})
   358  		search.WaitWithDefaultTimeout()
   359  
   360  		Expect(search).Should(ExitWithError(125, fmt.Sprintf(`couldn't search registry "localhost:%d": pinging container registry localhost:%d: Get "https://localhost:%d/v2/": http: server gave HTTP response to HTTPS client`, port, port, port)))
   361  		Expect(search.OutputToString()).Should(BeEmpty())
   362  
   363  		// cleanup
   364  		resetRegistriesConfigEnv()
   365  	})
   366  
   367  	// search should fail with nonexistent authfile
   368  	It("podman search fail with nonexistent --authfile", func() {
   369  		search := podmanTest.Podman([]string{"search", "--authfile", "/tmp/nonexistent", ALPINE})
   370  		search.WaitWithDefaultTimeout()
   371  		Expect(search).To(ExitWithError(125, "credential file is not accessible: faccessat /tmp/nonexistent: no such file or directory"))
   372  	})
   373  
   374  	// Registry is unreliable (#18484), this is another super-common flake
   375  	It("podman search with wildcards", FlakeAttempts(3), func() {
   376  		search := podmanTest.Podman([]string{"search", "registry.access.redhat.com/*openshift*"})
   377  		search.WaitWithDefaultTimeout()
   378  		Expect(search).Should(ExitCleanly())
   379  		Expect(len(search.OutputToStringArray())).To(BeNumerically(">", 1))
   380  	})
   381  
   382  	It("podman search repository tags", func() {
   383  		search := podmanTest.Podman([]string{"search", "--list-tags", "--limit", "30", "quay.io/podman/stable"})
   384  		search.WaitWithDefaultTimeout()
   385  		Expect(search).Should(ExitCleanly())
   386  		Expect(search.OutputToStringArray()).To(HaveLen(31))
   387  
   388  		search = podmanTest.Podman([]string{"search", "--list-tags", "quay.io/podman/stable"})
   389  		search.WaitWithDefaultTimeout()
   390  		Expect(search).Should(ExitCleanly())
   391  		Expect(len(search.OutputToStringArray())).To(BeNumerically(">", 2))
   392  
   393  		search = podmanTest.Podman([]string{"search", "--filter=is-official", "--list-tags", "quay.io/podman/stable"})
   394  		search.WaitWithDefaultTimeout()
   395  		Expect(search).To(ExitWithError(125, "filters are not applicable to list tags result"))
   396  
   397  		// With trailing slash
   398  		search = podmanTest.Podman([]string{"search", "--list-tags", "quay.io/podman/"})
   399  		search.WaitWithDefaultTimeout()
   400  		Expect(search).To(ExitWithError(125, `reference "podman/" must be a docker reference`))
   401  		Expect(search.OutputToStringArray()).To(BeEmpty())
   402  
   403  		// No trailing slash
   404  		search = podmanTest.Podman([]string{"search", "--list-tags", "quay.io/podman"})
   405  		search.WaitWithDefaultTimeout()
   406  		Expect(search).To(ExitWithError(125, "getting repository tags: fetching tags list: StatusCode: 404"))
   407  		Expect(search.OutputToStringArray()).To(BeEmpty())
   408  	})
   409  
   410  	It("podman search with limit over 100", func() {
   411  		search := podmanTest.Podman([]string{"search", "--limit", "100", "quay.io/podman"})
   412  		search.WaitWithDefaultTimeout()
   413  		Expect(search).Should(ExitCleanly())
   414  		Expect(len(search.OutputToStringArray())).To(BeNumerically("<=", 101))
   415  	})
   416  })