mirror of
https://github.com/containers/podman.git
synced 2025-11-02 06:37:09 +08:00
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>
394 lines
14 KiB
Go
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()),
|
|
}))
|
|
})
|
|
})
|