Files
podman/pkg/bindings/test/images_test.go
Valentin Rothberg f241efb179 bindings rmi test: clarify behavior
The libpod/images/remove endpoint is not very REST-ish but, after some
debate, was decided to be implemented as for the following reasons.

First, it allows for batch removing images which improves performance
significantly.  Note that Docker does support `rmi -a`!

Second, it allows for hiding the logic of setting the right exit code to
use from the client and keep all the logic on the server.

Hence, when removing an image that does not exist, the server will
return a 200. The response, however, includes the error message to be
used *and* the exit code that podman-remote will use.

Fixes: #12441
Signed-off-by: Valentin Rothberg <rothberg@redhat.com>
2021-12-16 15:31:29 +01:00

394 lines
14 KiB
Go

package bindings_test
import (
"net/http"
"os"
"path/filepath"
"time"
"github.com/containers/podman/v3/pkg/bindings"
"github.com/containers/podman/v3/pkg/bindings/containers"
"github.com/containers/podman/v3/pkg/bindings/images"
"github.com/containers/podman/v3/pkg/domain/entities"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
. "github.com/onsi/gomega/gexec"
. "github.com/onsi/gomega/gstruct"
)
var _ = Describe("Podman images", func() {
var (
// tempdir string
// err error
// podmanTest *PodmanTestIntegration
bt *bindingTest
s *Session
err error
)
BeforeEach(func() {
// tempdir, err = CreateTempDirInTempDir()
// if err != nil {
// os.Exit(1)
// }
// podmanTest = PodmanTestCreate(tempdir)
// podmanTest.Setup()
// podmanTest.SeedImages()
bt = newBindingTest()
bt.RestoreImagesFromCache()
s = bt.startAPIService()
time.Sleep(1 * time.Second)
err := bt.NewConnection()
Expect(err).ToNot(HaveOccurred())
})
AfterEach(func() {
// podmanTest.Cleanup()
// f := CurrentGinkgoTestDescription()
// processTestResult(f)
s.Kill()
bt.cleanup()
})
It("inspect image", func() {
// Inspect invalid image be 404
_, err = images.GetImage(bt.conn, "foobar5000", nil)
Expect(err).ToNot(BeNil())
code, _ := bindings.CheckResponseCode(err)
Expect(code).To(BeNumerically("==", http.StatusNotFound))
// Inspect by short name
data, err := images.GetImage(bt.conn, alpine.shortName, nil)
Expect(err).ToNot(HaveOccurred())
// Inspect with full ID
_, err = images.GetImage(bt.conn, data.ID, nil)
Expect(err).ToNot(HaveOccurred())
// Inspect with partial ID
_, err = images.GetImage(bt.conn, data.ID[0:12], nil)
Expect(err).ToNot(HaveOccurred())
// Inspect by long name
_, err = images.GetImage(bt.conn, alpine.name, nil)
Expect(err).ToNot(HaveOccurred())
// TODO it looks like the images API always returns size regardless
// of bool or not. What should we do ?
// Expect(data.Size).To(BeZero())
options := new(images.GetOptions).WithSize(true)
// Enabling the size parameter should result in size being populated
data, err = images.GetImage(bt.conn, alpine.name, options)
Expect(err).ToNot(HaveOccurred())
Expect(data.Size).To(BeNumerically(">", 0))
})
// Test to validate the remove image api
It("remove image", func() {
// NOTE that removing an image that does not exist will still
// return a 200 http status. The response, however, includes
// the exit code that podman-remote should exit with.
//
// The libpod/images/remove endpoint supports batch removal of
// images for performance reasons and for hiding the logic of
// deciding which exit code to use from the client.
response, errs := images.Remove(bt.conn, []string{"foobar5000"}, nil)
Expect(len(errs)).To(BeNumerically(">", 0))
Expect(response.ExitCode).To(BeNumerically("==", 1)) // podman-remote would exit with 1
// Remove an image by name, validate image is removed and error is nil
inspectData, err := images.GetImage(bt.conn, busybox.shortName, nil)
Expect(err).ToNot(HaveOccurred())
response, errs = images.Remove(bt.conn, []string{busybox.shortName}, nil)
Expect(len(errs)).To(BeZero())
Expect(inspectData.ID).To(Equal(response.Deleted[0]))
inspectData, err = images.GetImage(bt.conn, busybox.shortName, nil)
code, _ := bindings.CheckResponseCode(err)
Expect(code).To(BeNumerically("==", http.StatusNotFound))
// Start a container with alpine image
var top string = "top"
_, err = bt.RunTopContainer(&top, nil)
Expect(err).ToNot(HaveOccurred())
// we should now have a container called "top" running
containerResponse, err := containers.Inspect(bt.conn, "top", nil)
Expect(err).ToNot(HaveOccurred())
Expect(containerResponse.Name).To(Equal("top"))
// try to remove the image "alpine". This should fail since we are not force
// deleting hence image cannot be deleted until the container is deleted.
response, errs = images.Remove(bt.conn, []string{alpine.shortName}, nil)
code, _ = bindings.CheckResponseCode(errs[0])
// FIXME FIXME FIXME: #12441: another invalid error
// FIXME FIXME FIXME: this time msg="Image used by SHA: ..."
Expect(code).To(BeNumerically("==", -1))
// Removing the image "alpine" where force = true
options := new(images.RemoveOptions).WithForce(true)
response, errs = images.Remove(bt.conn, []string{alpine.shortName}, options)
Expect(errs).To(Or(HaveLen(0), BeNil()))
// To be extra sure, check if the previously created container
// is gone as well.
_, err = containers.Inspect(bt.conn, "top", nil)
code, _ = bindings.CheckResponseCode(err)
Expect(code).To(BeNumerically("==", http.StatusNotFound))
// Now make sure both images are gone.
inspectData, err = images.GetImage(bt.conn, busybox.shortName, nil)
code, _ = bindings.CheckResponseCode(err)
Expect(code).To(BeNumerically("==", http.StatusNotFound))
inspectData, err = images.GetImage(bt.conn, alpine.shortName, nil)
code, _ = bindings.CheckResponseCode(err)
Expect(code).To(BeNumerically("==", http.StatusNotFound))
})
// Tests to validate the image tag command.
It("tag image", func() {
// Validates if invalid image name is given a bad response is encountered.
err = images.Tag(bt.conn, "dummy", "demo", alpine.shortName, nil)
Expect(err).ToNot(BeNil())
code, _ := bindings.CheckResponseCode(err)
Expect(code).To(BeNumerically("==", http.StatusNotFound))
// Validates if the image is tagged successfully.
err = images.Tag(bt.conn, alpine.shortName, "demo", alpine.shortName, nil)
Expect(err).ToNot(HaveOccurred())
// Validates if name updates when the image is retagged.
_, err := images.GetImage(bt.conn, "alpine:demo", nil)
Expect(err).ToNot(HaveOccurred())
})
// Test to validate the List images command.
It("List image", func() {
// Array to hold the list of images returned
imageSummary, err := images.List(bt.conn, nil)
// There Should be no errors in the response.
Expect(err).ToNot(HaveOccurred())
// Since in the begin context two images are created the
// list context should have only 2 images
Expect(len(imageSummary)).To(Equal(2))
// Adding one more image. There Should be no errors in the response.
// And the count should be three now.
bt.Pull("testimage:20200929")
imageSummary, err = images.List(bt.conn, nil)
Expect(err).ToNot(HaveOccurred())
Expect(len(imageSummary)).To(BeNumerically(">=", 2))
// Validate the image names.
var names []string
for _, i := range imageSummary {
names = append(names, i.RepoTags...)
}
Expect(names).To(ContainElement(alpine.name))
Expect(names).To(ContainElement(busybox.name))
// List images with a filter
filters := make(map[string][]string)
filters["reference"] = []string{alpine.name}
options := new(images.ListOptions).WithFilters(filters).WithAll(false)
filteredImages, err := images.List(bt.conn, options)
Expect(err).ToNot(HaveOccurred())
Expect(len(filteredImages)).To(BeNumerically("==", 1))
// List images with a bad filter
filters["name"] = []string{alpine.name}
options = new(images.ListOptions).WithFilters(filters)
_, err = images.List(bt.conn, options)
Expect(err).ToNot(BeNil())
code, _ := bindings.CheckResponseCode(err)
Expect(code).To(BeNumerically("==", http.StatusInternalServerError))
})
It("Image Exists", func() {
// exists on bogus image should be false, with no error
exists, err := images.Exists(bt.conn, "foobar", nil)
Expect(err).ToNot(HaveOccurred())
Expect(exists).To(BeFalse())
// exists with shortname should be true
exists, err = images.Exists(bt.conn, alpine.shortName, nil)
Expect(err).ToNot(HaveOccurred())
Expect(exists).To(BeTrue())
// exists with fqname should be true
exists, err = images.Exists(bt.conn, alpine.name, nil)
Expect(err).ToNot(HaveOccurred())
Expect(exists).To(BeTrue())
})
It("Load|Import Image", func() {
// load an image
_, errs := images.Remove(bt.conn, []string{alpine.name}, nil)
Expect(len(errs)).To(BeZero())
exists, err := images.Exists(bt.conn, alpine.name, nil)
Expect(err).ToNot(HaveOccurred())
Expect(exists).To(BeFalse())
f, err := os.Open(filepath.Join(ImageCacheDir, alpine.tarballName))
defer f.Close()
Expect(err).ToNot(HaveOccurred())
names, err := images.Load(bt.conn, f)
Expect(err).ToNot(HaveOccurred())
Expect(names.Names[0]).To(Equal(alpine.name))
exists, err = images.Exists(bt.conn, alpine.name, nil)
Expect(err).ToNot(HaveOccurred())
Expect(exists).To(BeTrue())
// load with a repo name
f, err = os.Open(filepath.Join(ImageCacheDir, alpine.tarballName))
Expect(err).ToNot(HaveOccurred())
_, errs = images.Remove(bt.conn, []string{alpine.name}, nil)
Expect(len(errs)).To(BeZero())
exists, err = images.Exists(bt.conn, alpine.name, nil)
Expect(err).ToNot(HaveOccurred())
Expect(exists).To(BeFalse())
names, err = images.Load(bt.conn, f)
Expect(err).ToNot(HaveOccurred())
Expect(names.Names[0]).To(Equal(alpine.name))
// load with a bad repo name should trigger a 500
f, err = os.Open(filepath.Join(ImageCacheDir, alpine.tarballName))
Expect(err).ToNot(HaveOccurred())
_, errs = images.Remove(bt.conn, []string{alpine.name}, nil)
Expect(len(errs)).To(BeZero())
exists, err = images.Exists(bt.conn, alpine.name, nil)
Expect(err).ToNot(HaveOccurred())
Expect(exists).To(BeFalse())
})
It("Export Image", func() {
// Export an image
exportPath := filepath.Join(bt.tempDirPath, alpine.tarballName)
w, err := os.Create(filepath.Join(bt.tempDirPath, alpine.tarballName))
defer w.Close()
Expect(err).ToNot(HaveOccurred())
err = images.Export(bt.conn, []string{alpine.name}, w, nil)
Expect(err).ToNot(HaveOccurred())
_, err = os.Stat(exportPath)
Expect(err).ToNot(HaveOccurred())
// TODO how do we verify that a format change worked?
})
It("Import Image", func() {
// load an image
_, errs := images.Remove(bt.conn, []string{alpine.name}, nil)
Expect(len(errs)).To(BeZero())
exists, err := images.Exists(bt.conn, alpine.name, nil)
Expect(err).ToNot(HaveOccurred())
Expect(exists).To(BeFalse())
f, err := os.Open(filepath.Join(ImageCacheDir, alpine.tarballName))
defer f.Close()
Expect(err).ToNot(HaveOccurred())
changes := []string{"CMD /bin/foobar"}
testMessage := "test_import"
options := new(images.ImportOptions).WithMessage(testMessage).WithChanges(changes).WithReference(alpine.name)
_, err = images.Import(bt.conn, f, options)
Expect(err).ToNot(HaveOccurred())
exists, err = images.Exists(bt.conn, alpine.name, nil)
Expect(err).ToNot(HaveOccurred())
Expect(exists).To(BeTrue())
data, err := images.GetImage(bt.conn, alpine.name, nil)
Expect(err).ToNot(HaveOccurred())
Expect(data.Comment).To(Equal(testMessage))
})
It("History Image", func() {
// a bogus name should return a 404
_, err := images.History(bt.conn, "foobar", nil)
Expect(err).To(Not(BeNil()))
code, _ := bindings.CheckResponseCode(err)
Expect(code).To(BeNumerically("==", http.StatusNotFound))
var foundID bool
data, err := images.GetImage(bt.conn, alpine.name, nil)
Expect(err).ToNot(HaveOccurred())
history, err := images.History(bt.conn, alpine.name, nil)
Expect(err).ToNot(HaveOccurred())
for _, i := range history {
if i.ID == data.ID {
foundID = true
break
}
}
Expect(foundID).To(BeTrue())
})
It("Search for an image", func() {
reports, err := images.Search(bt.conn, "alpine", nil)
Expect(err).ToNot(HaveOccurred())
Expect(len(reports)).To(BeNumerically(">", 1))
var foundAlpine bool
for _, i := range reports {
if i.Name == "docker.io/library/alpine" {
foundAlpine = true
break
}
}
Expect(foundAlpine).To(BeTrue())
// Search for alpine with a limit of 10
options := new(images.SearchOptions).WithLimit(10)
reports, err = images.Search(bt.conn, "docker.io/alpine", options)
Expect(err).ToNot(HaveOccurred())
Expect(len(reports)).To(BeNumerically("<=", 10))
filters := make(map[string][]string)
filters["stars"] = []string{"100"}
// Search for alpine with stars greater than 100
options = new(images.SearchOptions).WithFilters(filters)
reports, err = images.Search(bt.conn, "docker.io/alpine", options)
Expect(err).ToNot(HaveOccurred())
for _, i := range reports {
Expect(i.Stars).To(BeNumerically(">=", 100))
}
// Search with a fqdn
reports, err = images.Search(bt.conn, "quay.io/libpod/alpine_nginx", nil)
Expect(err).To(BeNil(), "Error in images.Search()")
Expect(len(reports)).To(BeNumerically(">=", 1))
})
It("Prune images", func() {
options := new(images.PruneOptions).WithAll(true)
results, err := images.Prune(bt.conn, options)
Expect(err).NotTo(HaveOccurred())
Expect(len(results)).To(BeNumerically(">", 0))
})
// TODO: we really need to extent to pull tests once we have a more sophisticated CI.
It("Image Pull", func() {
rawImage := "docker.io/library/busybox:latest"
pulledImages, err := images.Pull(bt.conn, rawImage, nil)
Expect(err).NotTo(HaveOccurred())
Expect(len(pulledImages)).To(Equal(1))
exists, err := images.Exists(bt.conn, rawImage, nil)
Expect(err).NotTo(HaveOccurred())
Expect(exists).To(BeTrue())
// Make sure the normalization AND the full-transport reference works.
_, err = images.Pull(bt.conn, "docker://"+rawImage, nil)
Expect(err).NotTo(HaveOccurred())
// The v2 endpoint only supports the docker transport. Let's see if that's really true.
_, err = images.Pull(bt.conn, "bogus-transport:bogus.com/image:reference", nil)
Expect(err).To(HaveOccurred())
})
It("Build no options", func() {
results, err := images.Build(bt.conn, []string{"fixture/Containerfile"}, entities.BuildOptions{})
Expect(err).ToNot(HaveOccurred())
Expect(*results).To(MatchFields(IgnoreMissing, Fields{
"ID": Not(BeEmpty()),
}))
})
})