summaryrefslogtreecommitdiff
path: root/pkg/bindings
diff options
context:
space:
mode:
authorBrent Baude <bbaude@redhat.com>2020-03-04 10:14:07 -0600
committerBrent Baude <bbaude@redhat.com>2020-03-16 10:03:55 -0500
commitabbbeacd68b4d4973a55a8d7263bd0a27f5c4e8e (patch)
treed0612ea3340bb61cafbf2dfd52897dcfec4adbc1 /pkg/bindings
parent9c7481dbd1e6ea7e20ee03c85d4710c42ba81c6e (diff)
downloadpodman-abbbeacd68b4d4973a55a8d7263bd0a27f5c4e8e.tar.gz
podman-abbbeacd68b4d4973a55a8d7263bd0a27f5c4e8e.tar.bz2
podman-abbbeacd68b4d4973a55a8d7263bd0a27f5c4e8e.zip
apiv2 addition of manifests
add endpoints for create, add, remove, inspect, and push. this allows manifests to be managed through the restful interfaces. also added go-bindings and tests Signed-off-by: Brent Baude <bbaude@redhat.com>
Diffstat (limited to 'pkg/bindings')
-rw-r--r--pkg/bindings/manifests/manifests.go126
-rw-r--r--pkg/bindings/test/manifests_test.go124
2 files changed, 250 insertions, 0 deletions
diff --git a/pkg/bindings/manifests/manifests.go b/pkg/bindings/manifests/manifests.go
new file mode 100644
index 000000000..a8d1e6ca3
--- /dev/null
+++ b/pkg/bindings/manifests/manifests.go
@@ -0,0 +1,126 @@
+package manifests
+
+import (
+ "context"
+ "errors"
+ "net/http"
+ "net/url"
+ "strconv"
+ "strings"
+
+ "github.com/containers/image/v5/manifest"
+ "github.com/containers/libpod/libpod/image"
+ "github.com/containers/libpod/pkg/api/handlers"
+ "github.com/containers/libpod/pkg/bindings"
+ jsoniter "github.com/json-iterator/go"
+)
+
+// Create creates a manifest for the given name. Optional images to be associated with
+// the new manifest can also be specified. The all boolean specifies to add all entries
+// of a list if the name provided is a manifest list. The ID of the new manifest list
+// is returned as a string.
+func Create(ctx context.Context, names, images []string, all *bool) (string, error) {
+ var idr handlers.IDResponse
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return "", err
+ }
+ if len(names) < 1 {
+ return "", errors.New("creating a manifest requires at least one name argument")
+ }
+ params := url.Values{}
+ if all != nil {
+ params.Set("all", strconv.FormatBool(*all))
+ }
+ for _, name := range names {
+ params.Add("name", name)
+ }
+ for _, i := range images {
+ params.Add("image", i)
+ }
+
+ response, err := conn.DoRequest(nil, http.MethodPost, "/manifests/create", params)
+ if err != nil {
+ return "", err
+ }
+ return idr.ID, response.Process(&idr)
+}
+
+// Inspect returns a manifest list for a given name.
+func Inspect(ctx context.Context, name string) (*manifest.Schema2List, error) {
+ var list manifest.Schema2List
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return nil, err
+ }
+ response, err := conn.DoRequest(nil, http.MethodGet, "/manifests/%s/json", nil, name)
+ if err != nil {
+ return nil, err
+ }
+ return &list, response.Process(&list)
+}
+
+// Add adds a manifest to a given manifest list. Additional options for the manifest
+// can also be specified. The ID of the new manifest list is returned as a string
+func Add(ctx context.Context, name string, options image.ManifestAddOpts) (string, error) {
+ var idr handlers.IDResponse
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return "", err
+ }
+ optionsString, err := jsoniter.MarshalToString(options)
+ if err != nil {
+ return "", err
+ }
+ stringReader := strings.NewReader(optionsString)
+ response, err := conn.DoRequest(stringReader, http.MethodPost, "/manifests/%s/add", nil, name)
+ if err != nil {
+ return "", err
+ }
+ return idr.ID, response.Process(&idr)
+}
+
+// Remove deletes a manifest entry from a manifest list. Both name and the digest to be
+// removed are mandatory inputs. The ID of the new manifest list is returned as a string.
+func Remove(ctx context.Context, name, digest string) (string, error) {
+ var idr handlers.IDResponse
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return "", err
+ }
+ params := url.Values{}
+ params.Set("digest", digest)
+ response, err := conn.DoRequest(nil, http.MethodDelete, "/manifests/%s", params, name)
+ if err != nil {
+ return "", err
+ }
+ return idr.ID, response.Process(&idr)
+}
+
+// Push takes a manifest list and pushes to a destination. If the destination is not specified,
+// the name will be used instead. If the optional all boolean is specified, all images specified
+// in the list will be pushed as well.
+func Push(ctx context.Context, name string, destination *string, all *bool) (string, error) {
+ var (
+ idr handlers.IDResponse
+ )
+ dest := name
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return "", err
+ }
+ params := url.Values{}
+ params.Set("image", name)
+ if destination != nil {
+ dest = name
+ }
+ params.Set("destination", dest)
+ if all != nil {
+ params.Set("all", strconv.FormatBool(*all))
+ }
+ response, err := conn.DoRequest(nil, http.MethodPost, "/manifests/%s/push", params, name)
+ if err != nil {
+ return "", err
+ }
+ return idr.ID, response.Process(&idr)
+}
diff --git a/pkg/bindings/test/manifests_test.go b/pkg/bindings/test/manifests_test.go
new file mode 100644
index 000000000..23c3d8194
--- /dev/null
+++ b/pkg/bindings/test/manifests_test.go
@@ -0,0 +1,124 @@
+package test_bindings
+
+import (
+ "net/http"
+ "time"
+
+ "github.com/containers/libpod/libpod/image"
+ "github.com/containers/libpod/pkg/bindings"
+ "github.com/containers/libpod/pkg/bindings/images"
+ "github.com/containers/libpod/pkg/bindings/manifests"
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+ "github.com/onsi/gomega/gexec"
+)
+
+var _ = Describe("Podman containers ", func() {
+ var (
+ bt *bindingTest
+ s *gexec.Session
+ )
+
+ BeforeEach(func() {
+ bt = newBindingTest()
+ bt.RestoreImagesFromCache()
+ s = bt.startAPIService()
+ time.Sleep(1 * time.Second)
+ err := bt.NewConnection()
+ Expect(err).To(BeNil())
+ })
+
+ AfterEach(func() {
+ s.Kill()
+ bt.cleanup()
+ })
+
+ It("create manifest", func() {
+ // create manifest list without images
+ id, err := manifests.Create(bt.conn, []string{"quay.io/libpod/foobar:latest"}, []string{}, nil)
+ Expect(err).To(BeNil())
+ list, err := manifests.Inspect(bt.conn, id)
+ Expect(err).To(BeNil())
+ Expect(len(list.Manifests)).To(BeZero())
+
+ // creating a duplicate should fail as a 500
+ _, err = manifests.Create(bt.conn, []string{"quay.io/libpod/foobar:latest"}, []string{}, nil)
+ Expect(err).ToNot(BeNil())
+ code, _ := bindings.CheckResponseCode(err)
+ Expect(code).To(BeNumerically("==", http.StatusInternalServerError))
+
+ _, err = images.Remove(bt.conn, id, nil)
+ Expect(err).To(BeNil())
+
+ // create manifest list with images
+ id, err = manifests.Create(bt.conn, []string{"quay.io/libpod/foobar:latest"}, []string{alpine.name}, nil)
+ Expect(err).To(BeNil())
+ list, err = manifests.Inspect(bt.conn, id)
+ Expect(err).To(BeNil())
+ Expect(len(list.Manifests)).To(BeNumerically("==", 1))
+ })
+
+ It("inspect bogus manifest", func() {
+ _, err := manifests.Inspect(bt.conn, "larry")
+ Expect(err).ToNot(BeNil())
+ code, _ := bindings.CheckResponseCode(err)
+ Expect(code).To(BeNumerically("==", http.StatusNotFound))
+ })
+
+ It("add manifest", func() {
+ // add to bogus should 404
+ _, err := manifests.Add(bt.conn, "foobar", image.ManifestAddOpts{})
+ Expect(err).ToNot(BeNil())
+ code, _ := bindings.CheckResponseCode(err)
+ Expect(code).To(BeNumerically("==", http.StatusNotFound))
+
+ id, err := manifests.Create(bt.conn, []string{"quay.io/libpod/foobar:latest"}, []string{}, nil)
+ Expect(err).To(BeNil())
+ opts := image.ManifestAddOpts{Images: []string{alpine.name}}
+ _, err = manifests.Add(bt.conn, id, opts)
+ Expect(err).To(BeNil())
+ list, err := manifests.Inspect(bt.conn, id)
+ Expect(err).To(BeNil())
+ Expect(len(list.Manifests)).To(BeNumerically("==", 1))
+
+ // add bogus name to existing list should fail
+ opts.Images = []string{"larry"}
+ _, err = manifests.Add(bt.conn, id, opts)
+ Expect(err).ToNot(BeNil())
+ code, _ = bindings.CheckResponseCode(err)
+ Expect(code).To(BeNumerically("==", http.StatusInternalServerError))
+ })
+
+ It("remove manifest", func() {
+ // removal on bogus manifest list should be 404
+ _, err := manifests.Remove(bt.conn, "larry", "1234")
+ Expect(err).ToNot(BeNil())
+ code, _ := bindings.CheckResponseCode(err)
+ Expect(code).To(BeNumerically("==", http.StatusNotFound))
+
+ id, err := manifests.Create(bt.conn, []string{"quay.io/libpod/foobar:latest"}, []string{alpine.name}, nil)
+ Expect(err).To(BeNil())
+ data, err := manifests.Inspect(bt.conn, id)
+ Expect(err).To(BeNil())
+ Expect(len(data.Manifests)).To(BeNumerically("==", 1))
+
+ // removal on a good manifest list with a bad digest should be 400
+ _, err = manifests.Remove(bt.conn, id, "!234")
+ Expect(err).ToNot(BeNil())
+ code, _ = bindings.CheckResponseCode(err)
+ Expect(code).To(BeNumerically("==", http.StatusBadRequest))
+
+ digest := data.Manifests[0].Digest.String()
+ _, err = manifests.Remove(bt.conn, id, digest)
+ Expect(err).To(BeNil())
+
+ // removal on good manifest with good digest should work
+ data, err = manifests.Inspect(bt.conn, id)
+ Expect(err).To(BeNil())
+ Expect(len(data.Manifests)).To(BeZero())
+ })
+
+ It("push manifest", func() {
+ Skip("TODO")
+ })
+})