github.com/containers/podman/v4@v4.9.4/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/v4/pkg/domain/entities"
    12  	. "github.com/containers/podman/v4/test/utils"
    13  	. "github.com/onsi/ginkgo/v2"
    14  	. "github.com/onsi/gomega"
    15  	. "github.com/onsi/gomega/gexec"
    16  )
    17  
    18  type endpoint struct {
    19  	Host string
    20  	Port string
    21  }
    22  
    23  func (e *endpoint) Address() string {
    24  	return fmt.Sprintf("%s:%s", e.Host, e.Port)
    25  }
    26  
    27  var _ = Describe("Podman search", func() {
    28  
    29  	const regFileContents = `
    30  [registries.search]
    31  registries = ['{{.Host}}:{{.Port}}']
    32  
    33  [registries.insecure]
    34  registries = ['{{.Host}}:{{.Port}}']`
    35  	registryFileTmpl := template.Must(template.New("registryFile").Parse(regFileContents))
    36  
    37  	const badRegFileContents = `
    38  [registries.search]
    39  registries = ['{{.Host}}:{{.Port}}']
    40  # empty
    41  [registries.insecure]
    42  registries = []`
    43  	registryFileBadTmpl := template.Must(template.New("registryFileBad").Parse(badRegFileContents))
    44  
    45  	It("podman search", func() {
    46  		search := podmanTest.Podman([]string{"search", "alpine"})
    47  		search.WaitWithDefaultTimeout()
    48  		Expect(search).Should(ExitCleanly())
    49  		Expect(len(search.OutputToStringArray())).To(BeNumerically(">", 1))
    50  		Expect(search.OutputToString()).To(ContainSubstring("alpine"))
    51  	})
    52  
    53  	It("podman search single registry flag", func() {
    54  		search := podmanTest.Podman([]string{"search", "quay.io/skopeo/stable:latest"})
    55  		search.WaitWithDefaultTimeout()
    56  		Expect(search).Should(ExitCleanly())
    57  		Expect(search.OutputToString()).To(ContainSubstring("quay.io/skopeo/stable"))
    58  	})
    59  
    60  	It("podman search image with description", func() {
    61  		search := podmanTest.Podman([]string{"search", "quay.io/podman/stable"})
    62  		search.WaitWithDefaultTimeout()
    63  		Expect(search).Should(ExitCleanly())
    64  		output := string(search.Out.Contents())
    65  		Expect(output).To(MatchRegexp(`(?m)NAME\s+DESCRIPTION$`))
    66  		Expect(output).To(MatchRegexp(`(?m)quay.io/podman/stable\s+.*PODMAN logo`))
    67  	})
    68  
    69  	It("podman search image with --compatible", func() {
    70  		search := podmanTest.Podman([]string{"search", "--compatible", "quay.io/podman/stable"})
    71  		search.WaitWithDefaultTimeout()
    72  		Expect(search).Should(ExitCleanly())
    73  		output := string(search.Out.Contents())
    74  		Expect(output).To(MatchRegexp(`(?m)NAME\s+DESCRIPTION\s+STARS\s+OFFICIAL\s+AUTOMATED$`))
    75  	})
    76  
    77  	It("podman search format flag", func() {
    78  		search := podmanTest.Podman([]string{"search", "--format", "table {{.Index}} {{.Name}}", "alpine"})
    79  		search.WaitWithDefaultTimeout()
    80  		Expect(search).Should(ExitCleanly())
    81  		Expect(len(search.OutputToStringArray())).To(BeNumerically(">", 1))
    82  		Expect(search.OutputToString()).To(ContainSubstring("docker.io/library/alpine"))
    83  	})
    84  
    85  	It("podman search format json", func() {
    86  		search := podmanTest.Podman([]string{"search", "--format", "json", "busybox"})
    87  		search.WaitWithDefaultTimeout()
    88  		Expect(search).Should(ExitCleanly())
    89  		Expect(search.OutputToString()).To(BeValidJSON())
    90  		Expect(search.OutputToString()).To(ContainSubstring("docker.io/library/busybox"))
    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", "docker.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", "docker.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", "docker.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(Exit(125))
   317  		Expect(search.OutputToString()).Should(BeEmpty())
   318  		Expect(search.ErrorToString()).To(ContainSubstring("http: server gave HTTP response to HTTPS client"))
   319  
   320  		// cleanup
   321  		resetRegistriesConfigEnv()
   322  	})
   323  
   324  	It("podman search doesn't attempt HTTP if registry is not listed as insecure", func() {
   325  		if podmanTest.Host.Arch == "ppc64le" {
   326  			Skip("No registry image for ppc64le")
   327  		}
   328  		port := GetPort()
   329  		ep := endpoint{Port: strconv.Itoa(port), Host: "localhost"}
   330  		registry := podmanTest.Podman([]string{"run", "-d", "-p", fmt.Sprintf("%d:5000", port),
   331  			"--name", "registry6", REGISTRY_IMAGE})
   332  		registry.WaitWithDefaultTimeout()
   333  		Expect(registry).Should(ExitCleanly())
   334  
   335  		if !WaitContainerReady(podmanTest, "registry6", "listening on", 20, 1) {
   336  			Fail("Cannot start docker registry on port %s", port)
   337  		}
   338  
   339  		err = podmanTest.RestoreArtifact(ALPINE)
   340  		Expect(err).ToNot(HaveOccurred())
   341  		image := fmt.Sprintf("%s/my-alpine", ep.Address())
   342  		push := podmanTest.Podman([]string{"push", "-q", "--tls-verify=false", "--remove-signatures", ALPINE, image})
   343  		push.WaitWithDefaultTimeout()
   344  		Expect(push).Should(ExitCleanly())
   345  
   346  		var buffer bytes.Buffer
   347  		err = registryFileBadTmpl.Execute(&buffer, ep)
   348  		Expect(err).ToNot(HaveOccurred())
   349  		podmanTest.setRegistriesConfigEnv(buffer.Bytes())
   350  		err = os.WriteFile(fmt.Sprintf("%s/registry6.conf", tempdir), buffer.Bytes(), 0644)
   351  		Expect(err).ToNot(HaveOccurred())
   352  
   353  		if IsRemote() {
   354  			podmanTest.RestartRemoteService()
   355  			defer podmanTest.RestartRemoteService()
   356  		}
   357  
   358  		search := podmanTest.Podman([]string{"search", image})
   359  		search.WaitWithDefaultTimeout()
   360  
   361  		Expect(search).Should(Exit(125))
   362  		Expect(search.OutputToString()).Should(BeEmpty())
   363  		Expect(search.ErrorToString()).To(ContainSubstring("http: server gave HTTP response to HTTPS client"))
   364  
   365  		// cleanup
   366  		resetRegistriesConfigEnv()
   367  	})
   368  
   369  	// search should fail with nonexistent authfile
   370  	It("podman search fail with nonexistent --authfile", func() {
   371  		search := podmanTest.Podman([]string{"search", "--authfile", "/tmp/nonexistent", ALPINE})
   372  		search.WaitWithDefaultTimeout()
   373  		Expect(search).To(ExitWithError())
   374  	})
   375  
   376  	// Registry is unreliable (#18484), this is another super-common flake
   377  	It("podman search with wildcards", FlakeAttempts(3), func() {
   378  		search := podmanTest.Podman([]string{"search", "registry.access.redhat.com/*openshift*"})
   379  		search.WaitWithDefaultTimeout()
   380  		Expect(search).Should(ExitCleanly())
   381  		Expect(len(search.OutputToStringArray())).To(BeNumerically(">", 1))
   382  	})
   383  
   384  	It("podman search repository tags", func() {
   385  		search := podmanTest.Podman([]string{"search", "--list-tags", "--limit", "30", "docker.io/library/alpine"})
   386  		search.WaitWithDefaultTimeout()
   387  		Expect(search).Should(ExitCleanly())
   388  		Expect(search.OutputToStringArray()).To(HaveLen(31))
   389  
   390  		search = podmanTest.Podman([]string{"search", "--list-tags", "docker.io/library/alpine"})
   391  		search.WaitWithDefaultTimeout()
   392  		Expect(search).Should(ExitCleanly())
   393  		Expect(len(search.OutputToStringArray())).To(BeNumerically(">", 2))
   394  
   395  		search = podmanTest.Podman([]string{"search", "--filter=is-official", "--list-tags", "docker.io/library/alpine"})
   396  		search.WaitWithDefaultTimeout()
   397  		Expect(search).To(ExitWithError())
   398  
   399  		search = podmanTest.Podman([]string{"search", "--list-tags", "docker.io/library/"})
   400  		search.WaitWithDefaultTimeout()
   401  		Expect(search.OutputToStringArray()).To(BeEmpty())
   402  	})
   403  
   404  	It("podman search with limit over 100", func() {
   405  		search := podmanTest.Podman([]string{"search", "--limit", "100", "quay.io/podman"})
   406  		search.WaitWithDefaultTimeout()
   407  		Expect(search).Should(ExitCleanly())
   408  		Expect(len(search.OutputToStringArray())).To(BeNumerically("<=", 101))
   409  	})
   410  })