github.com/containers/podman/v4@v4.9.4/pkg/bindings/test/images_test.go (about)

     1  package bindings_test
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"net/http"
     7  	"os"
     8  	"path/filepath"
     9  	"time"
    10  
    11  	podmanRegistry "github.com/containers/podman/v4/hack/podman-registry-go"
    12  	"github.com/containers/podman/v4/libpod/define"
    13  	"github.com/containers/podman/v4/pkg/bindings"
    14  	"github.com/containers/podman/v4/pkg/bindings/containers"
    15  	"github.com/containers/podman/v4/pkg/bindings/images"
    16  	"github.com/containers/podman/v4/pkg/domain/entities"
    17  	. "github.com/onsi/ginkgo/v2"
    18  	. "github.com/onsi/gomega"
    19  	. "github.com/onsi/gomega/gexec"
    20  	. "github.com/onsi/gomega/gstruct"
    21  )
    22  
    23  var _ = Describe("Podman images", func() {
    24  	var (
    25  		// tempdir    string
    26  		// err        error
    27  		// podmanTest *PodmanTestIntegration
    28  		bt  *bindingTest
    29  		s   *Session
    30  		err error
    31  	)
    32  
    33  	BeforeEach(func() {
    34  		// tempdir, err = CreateTempDirInTempDir()
    35  		// if err != nil {
    36  		//	os.Exit(1)
    37  		// }
    38  		// podmanTest = PodmanTestCreate(tempdir)
    39  		// podmanTest.Setup()
    40  		// podmanTest.SeedImages()
    41  		bt = newBindingTest()
    42  		bt.RestoreImagesFromCache()
    43  		s = bt.startAPIService()
    44  		time.Sleep(1 * time.Second)
    45  		err := bt.NewConnection()
    46  		Expect(err).ToNot(HaveOccurred())
    47  	})
    48  
    49  	AfterEach(func() {
    50  		// podmanTest.Cleanup()
    51  		// f := CurrentSpecReport()
    52  		// processTestResult(f)
    53  		s.Kill()
    54  		bt.cleanup()
    55  	})
    56  
    57  	It("inspect image", func() {
    58  		// Inspect invalid image be 404
    59  		_, err = images.GetImage(bt.conn, "foobar5000", nil)
    60  		Expect(err).To(HaveOccurred())
    61  		code, _ := bindings.CheckResponseCode(err)
    62  		Expect(code).To(BeNumerically("==", http.StatusNotFound))
    63  
    64  		// Inspect by short name
    65  		data, err := images.GetImage(bt.conn, alpine.shortName, nil)
    66  		Expect(err).ToNot(HaveOccurred())
    67  
    68  		// Inspect with full ID
    69  		_, err = images.GetImage(bt.conn, data.ID, nil)
    70  		Expect(err).ToNot(HaveOccurred())
    71  
    72  		// Inspect with partial ID
    73  		_, err = images.GetImage(bt.conn, data.ID[0:12], nil)
    74  		Expect(err).ToNot(HaveOccurred())
    75  
    76  		// Inspect by long name
    77  		_, err = images.GetImage(bt.conn, alpine.name, nil)
    78  		Expect(err).ToNot(HaveOccurred())
    79  		// TODO it looks like the images API always returns size regardless
    80  		// of bool or not. What should we do ?
    81  		// Expect(data.Size).To(BeZero())
    82  
    83  		options := new(images.GetOptions).WithSize(true)
    84  		// Enabling the size parameter should result in size being populated
    85  		data, err = images.GetImage(bt.conn, alpine.name, options)
    86  		Expect(err).ToNot(HaveOccurred())
    87  		Expect(data.Size).To(BeNumerically(">", 0))
    88  	})
    89  
    90  	// Test to validate the remove image api
    91  	It("remove image", func() {
    92  		// NOTE that removing an image that does not exist will still
    93  		// return a 200 http status.  The response, however, includes
    94  		// the exit code that podman-remote should exit with.
    95  		//
    96  		// The libpod/images/remove endpoint supports batch removal of
    97  		// images for performance reasons and for hiding the logic of
    98  		// deciding which exit code to use from the client.
    99  		response, errs := images.Remove(bt.conn, []string{"foobar5000"}, nil)
   100  		Expect(errs).ToNot(BeEmpty())
   101  		Expect(response.ExitCode).To(BeNumerically("==", 1)) // podman-remote would exit with 1
   102  
   103  		// Remove an image by name, validate image is removed and error is nil
   104  		inspectData, err := images.GetImage(bt.conn, busybox.shortName, nil)
   105  		Expect(err).ToNot(HaveOccurred())
   106  		response, errs = images.Remove(bt.conn, []string{busybox.shortName}, nil)
   107  		Expect(errs).To(BeEmpty())
   108  
   109  		Expect(inspectData.ID).To(Equal(response.Deleted[0]))
   110  		_, err = images.GetImage(bt.conn, busybox.shortName, nil)
   111  		code, _ := bindings.CheckResponseCode(err)
   112  		Expect(code).To(BeNumerically("==", http.StatusNotFound))
   113  
   114  		// Start a container with alpine image
   115  		var top = "top"
   116  		_, err = bt.RunTopContainer(&top, nil)
   117  		Expect(err).ToNot(HaveOccurred())
   118  		// we should now have a container called "top" running
   119  		containerResponse, err := containers.Inspect(bt.conn, "top", nil)
   120  		Expect(err).ToNot(HaveOccurred())
   121  		Expect(containerResponse.Name).To(Equal("top"))
   122  
   123  		// try to remove the image "alpine". This should fail since we are not force
   124  		// deleting hence image cannot be deleted until the container is deleted.
   125  		_, errs = images.Remove(bt.conn, []string{alpine.shortName}, nil)
   126  		code, _ = bindings.CheckResponseCode(errs[0])
   127  		Expect(code).To(BeNumerically("==", -1))
   128  
   129  		// Removing the image "alpine" where force = true
   130  		options := new(images.RemoveOptions).WithForce(true)
   131  		_, errs = images.Remove(bt.conn, []string{alpine.shortName}, options)
   132  		Expect(errs).To(Or(HaveLen(0), BeNil()))
   133  		// To be extra sure, check if the previously created container
   134  		// is gone as well.
   135  		_, err = containers.Inspect(bt.conn, "top", nil)
   136  		code, _ = bindings.CheckResponseCode(err)
   137  		Expect(code).To(BeNumerically("==", http.StatusNotFound))
   138  
   139  		// Now make sure both images are gone.
   140  		_, err = images.GetImage(bt.conn, busybox.shortName, nil)
   141  		code, _ = bindings.CheckResponseCode(err)
   142  		Expect(code).To(BeNumerically("==", http.StatusNotFound))
   143  
   144  		_, err = images.GetImage(bt.conn, alpine.shortName, nil)
   145  		code, _ = bindings.CheckResponseCode(err)
   146  		Expect(code).To(BeNumerically("==", http.StatusNotFound))
   147  	})
   148  
   149  	// Tests to validate the image tag command.
   150  	It("tag image", func() {
   151  
   152  		// Validates if invalid image name is given a bad response is encountered.
   153  		err = images.Tag(bt.conn, "dummy", "demo", alpine.shortName, nil)
   154  		Expect(err).To(HaveOccurred())
   155  		code, _ := bindings.CheckResponseCode(err)
   156  		Expect(code).To(BeNumerically("==", http.StatusNotFound))
   157  
   158  		// Validates if the image is tagged successfully.
   159  		err = images.Tag(bt.conn, alpine.shortName, "demo", alpine.shortName, nil)
   160  		Expect(err).ToNot(HaveOccurred())
   161  
   162  		// Validates if name updates when the image is retagged.
   163  		_, err := images.GetImage(bt.conn, "alpine:demo", nil)
   164  		Expect(err).ToNot(HaveOccurred())
   165  
   166  	})
   167  
   168  	// Test to validate the List images command.
   169  	It("List image", func() {
   170  		// Array to hold the list of images returned
   171  		imageSummary, err := images.List(bt.conn, nil)
   172  		// There Should be no errors in the response.
   173  		Expect(err).ToNot(HaveOccurred())
   174  		// Since in the begin context two images are created the
   175  		// list context should have only 2 images
   176  		Expect(imageSummary).To(HaveLen(2))
   177  
   178  		// Adding one more image. There Should be no errors in the response.
   179  		// And the count should be three now.
   180  		bt.Pull("testimage:20200929")
   181  		imageSummary, err = images.List(bt.conn, nil)
   182  		Expect(err).ToNot(HaveOccurred())
   183  		Expect(len(imageSummary)).To(BeNumerically(">=", 2))
   184  
   185  		// Validate the image names.
   186  		var names []string
   187  		for _, i := range imageSummary {
   188  			names = append(names, i.RepoTags...)
   189  		}
   190  		Expect(names).To(ContainElement(alpine.name))
   191  		Expect(names).To(ContainElement(busybox.name))
   192  
   193  		// List  images with a filter
   194  		filters := make(map[string][]string)
   195  		filters["reference"] = []string{alpine.name}
   196  		options := new(images.ListOptions).WithFilters(filters).WithAll(false)
   197  		filteredImages, err := images.List(bt.conn, options)
   198  		Expect(err).ToNot(HaveOccurred())
   199  		Expect(filteredImages).To(HaveLen(1))
   200  
   201  		// List  images with a bad filter
   202  		filters["name"] = []string{alpine.name}
   203  		options = new(images.ListOptions).WithFilters(filters)
   204  		_, err = images.List(bt.conn, options)
   205  		Expect(err).To(HaveOccurred())
   206  		code, _ := bindings.CheckResponseCode(err)
   207  		Expect(code).To(BeNumerically("==", http.StatusInternalServerError))
   208  	})
   209  
   210  	It("Image Exists", func() {
   211  		// exists on bogus image should be false, with no error
   212  		exists, err := images.Exists(bt.conn, "foobar", nil)
   213  		Expect(err).ToNot(HaveOccurred())
   214  		Expect(exists).To(BeFalse())
   215  
   216  		// exists with shortname should be true
   217  		exists, err = images.Exists(bt.conn, alpine.shortName, nil)
   218  		Expect(err).ToNot(HaveOccurred())
   219  		Expect(exists).To(BeTrue())
   220  
   221  		// exists with fqname should be true
   222  		exists, err = images.Exists(bt.conn, alpine.name, nil)
   223  		Expect(err).ToNot(HaveOccurred())
   224  		Expect(exists).To(BeTrue())
   225  	})
   226  
   227  	It("Load|Import Image", func() {
   228  		// load an image
   229  		_, errs := images.Remove(bt.conn, []string{alpine.name}, nil)
   230  		Expect(errs).To(BeEmpty())
   231  		exists, err := images.Exists(bt.conn, alpine.name, nil)
   232  		Expect(err).ToNot(HaveOccurred())
   233  		Expect(exists).To(BeFalse())
   234  		f, err := os.Open(filepath.Join(ImageCacheDir, alpine.tarballName))
   235  		Expect(err).ToNot(HaveOccurred())
   236  		defer f.Close()
   237  		names, err := images.Load(bt.conn, f)
   238  		Expect(err).ToNot(HaveOccurred())
   239  		Expect(names.Names[0]).To(Equal(alpine.name))
   240  		exists, err = images.Exists(bt.conn, alpine.name, nil)
   241  		Expect(err).ToNot(HaveOccurred())
   242  		Expect(exists).To(BeTrue())
   243  
   244  		// load with a repo name
   245  		f, err = os.Open(filepath.Join(ImageCacheDir, alpine.tarballName))
   246  		Expect(err).ToNot(HaveOccurred())
   247  		_, errs = images.Remove(bt.conn, []string{alpine.name}, nil)
   248  		Expect(errs).To(BeEmpty())
   249  		exists, err = images.Exists(bt.conn, alpine.name, nil)
   250  		Expect(err).ToNot(HaveOccurred())
   251  		Expect(exists).To(BeFalse())
   252  		names, err = images.Load(bt.conn, f)
   253  		Expect(err).ToNot(HaveOccurred())
   254  		Expect(names.Names[0]).To(Equal(alpine.name))
   255  
   256  		// load with a bad repo name should trigger a 500
   257  		_, errs = images.Remove(bt.conn, []string{alpine.name}, nil)
   258  		Expect(errs).To(BeEmpty())
   259  		exists, err = images.Exists(bt.conn, alpine.name, nil)
   260  		Expect(err).ToNot(HaveOccurred())
   261  		Expect(exists).To(BeFalse())
   262  	})
   263  
   264  	It("Export Image", func() {
   265  		// Export an image
   266  		exportPath := filepath.Join(bt.tempDirPath, alpine.tarballName)
   267  		w, err := os.Create(filepath.Join(bt.tempDirPath, alpine.tarballName))
   268  		Expect(err).ToNot(HaveOccurred())
   269  		defer w.Close()
   270  		err = images.Export(bt.conn, []string{alpine.name}, w, nil)
   271  		Expect(err).ToNot(HaveOccurred())
   272  		_, err = os.Stat(exportPath)
   273  		Expect(err).ToNot(HaveOccurred())
   274  
   275  		// TODO how do we verify that a format change worked?
   276  	})
   277  
   278  	It("Import Image", func() {
   279  		// load an image
   280  		_, errs := images.Remove(bt.conn, []string{alpine.name}, nil)
   281  		Expect(errs).To(BeEmpty())
   282  		exists, err := images.Exists(bt.conn, alpine.name, nil)
   283  		Expect(err).ToNot(HaveOccurred())
   284  		Expect(exists).To(BeFalse())
   285  		f, err := os.Open(filepath.Join(ImageCacheDir, alpine.tarballName))
   286  		Expect(err).ToNot(HaveOccurred())
   287  		defer f.Close()
   288  		changes := []string{"CMD /bin/foobar"}
   289  		testMessage := "test_import"
   290  		options := new(images.ImportOptions).WithMessage(testMessage).WithChanges(changes).WithReference(alpine.name)
   291  		_, err = images.Import(bt.conn, f, options)
   292  		Expect(err).ToNot(HaveOccurred())
   293  		exists, err = images.Exists(bt.conn, alpine.name, nil)
   294  		Expect(err).ToNot(HaveOccurred())
   295  		Expect(exists).To(BeTrue())
   296  		data, err := images.GetImage(bt.conn, alpine.name, nil)
   297  		Expect(err).ToNot(HaveOccurred())
   298  		Expect(data.Comment).To(Equal(testMessage))
   299  
   300  	})
   301  
   302  	It("History Image", func() {
   303  		// a bogus name should return a 404
   304  		_, err := images.History(bt.conn, "foobar", nil)
   305  		Expect(err).To(HaveOccurred())
   306  		code, _ := bindings.CheckResponseCode(err)
   307  		Expect(code).To(BeNumerically("==", http.StatusNotFound))
   308  
   309  		var foundID bool
   310  		data, err := images.GetImage(bt.conn, alpine.name, nil)
   311  		Expect(err).ToNot(HaveOccurred())
   312  		history, err := images.History(bt.conn, alpine.name, nil)
   313  		Expect(err).ToNot(HaveOccurred())
   314  		for _, i := range history {
   315  			if i.ID == data.ID {
   316  				foundID = true
   317  				break
   318  			}
   319  		}
   320  		Expect(foundID).To(BeTrue())
   321  	})
   322  
   323  	It("Search for an image", func() {
   324  		reports, err := images.Search(bt.conn, "alpine", nil)
   325  		Expect(err).ToNot(HaveOccurred())
   326  		Expect(len(reports)).To(BeNumerically(">", 1))
   327  		var foundAlpine bool
   328  		for _, i := range reports {
   329  			if i.Name == "docker.io/library/alpine" {
   330  				foundAlpine = true
   331  				break
   332  			}
   333  		}
   334  		Expect(foundAlpine).To(BeTrue())
   335  
   336  		// Search for alpine with a limit of 10
   337  		options := new(images.SearchOptions).WithLimit(10)
   338  		reports, err = images.Search(bt.conn, "docker.io/alpine", options)
   339  		Expect(err).ToNot(HaveOccurred())
   340  		Expect(len(reports)).To(BeNumerically("<=", 10))
   341  
   342  		filters := make(map[string][]string)
   343  		filters["stars"] = []string{"100"}
   344  		// Search for alpine with stars greater than 100
   345  		options = new(images.SearchOptions).WithFilters(filters)
   346  		reports, err = images.Search(bt.conn, "docker.io/alpine", options)
   347  		Expect(err).ToNot(HaveOccurred())
   348  		for _, i := range reports {
   349  			Expect(i.Stars).To(BeNumerically(">=", 100))
   350  		}
   351  
   352  		//	Search with a fqdn
   353  		reports, err = images.Search(bt.conn, "quay.io/podman/stable", nil)
   354  		Expect(err).ToNot(HaveOccurred(), "Error in images.Search()")
   355  		Expect(reports).ToNot(BeEmpty())
   356  	})
   357  
   358  	It("Prune images", func() {
   359  		options := new(images.PruneOptions).WithAll(true)
   360  		results, err := images.Prune(bt.conn, options)
   361  		Expect(err).NotTo(HaveOccurred())
   362  		Expect(results).ToNot(BeEmpty())
   363  	})
   364  
   365  	// TODO: we really need to extent to pull tests once we have a more sophisticated CI.
   366  	It("Image Pull", func() {
   367  		rawImage := "docker.io/library/busybox:latest"
   368  
   369  		var writer bytes.Buffer
   370  		pullOpts := new(images.PullOptions).WithProgressWriter(&writer)
   371  		pulledImages, err := images.Pull(bt.conn, rawImage, pullOpts)
   372  		Expect(err).NotTo(HaveOccurred())
   373  		Expect(pulledImages).To(HaveLen(1))
   374  		output := writer.String()
   375  		Expect(output).To(ContainSubstring("Trying to pull "))
   376  		Expect(output).To(ContainSubstring("Getting image source signatures"))
   377  
   378  		exists, err := images.Exists(bt.conn, rawImage, nil)
   379  		Expect(err).NotTo(HaveOccurred())
   380  		Expect(exists).To(BeTrue())
   381  
   382  		// Make sure the normalization AND the full-transport reference works.
   383  		_, err = images.Pull(bt.conn, "docker://"+rawImage, nil)
   384  		Expect(err).NotTo(HaveOccurred())
   385  
   386  		// The v2 endpoint only supports the docker transport.  Let's see if that's really true.
   387  		_, err = images.Pull(bt.conn, "bogus-transport:bogus.com/image:reference", nil)
   388  		Expect(err).To(HaveOccurred())
   389  	})
   390  
   391  	It("Image Push", func() {
   392  		registryOptions := &podmanRegistry.Options{
   393  			PodmanPath: getPodmanBinary(),
   394  		}
   395  		registry, err := podmanRegistry.StartWithOptions(registryOptions)
   396  		Expect(err).ToNot(HaveOccurred())
   397  
   398  		var writer bytes.Buffer
   399  		pushOpts := new(images.PushOptions).WithUsername(registry.User).WithPassword(registry.Password).WithSkipTLSVerify(true).WithProgressWriter(&writer).WithQuiet(false)
   400  		err = images.Push(bt.conn, alpine.name, fmt.Sprintf("localhost:%s/test:latest", registry.Port), pushOpts)
   401  		Expect(err).ToNot(HaveOccurred())
   402  
   403  		output := writer.String()
   404  		Expect(output).To(ContainSubstring("Copying blob "))
   405  		Expect(output).To(ContainSubstring("Copying config "))
   406  		Expect(output).To(ContainSubstring("Writing manifest to image destination"))
   407  	})
   408  
   409  	It("Build no options", func() {
   410  		results, err := images.Build(bt.conn, []string{"fixture/Containerfile"}, entities.BuildOptions{})
   411  		Expect(err).ToNot(HaveOccurred())
   412  		Expect(*results).To(MatchFields(IgnoreMissing, Fields{
   413  			"ID":         Not(BeEmpty()),
   414  			"SaveFormat": ContainSubstring(define.OCIArchive),
   415  		}))
   416  	})
   417  })