summaryrefslogtreecommitdiff
path: root/pkg/bindings
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/bindings')
-rw-r--r--pkg/bindings/bindings.go9
-rw-r--r--pkg/bindings/connection.go311
-rw-r--r--pkg/bindings/containers.go139
-rw-r--r--pkg/bindings/containers/commit.go49
-rw-r--r--pkg/bindings/containers/containers.go298
-rw-r--r--pkg/bindings/containers/create.go30
-rw-r--r--pkg/bindings/containers/exec.go71
-rw-r--r--pkg/bindings/containers/healthcheck.go26
-rw-r--r--pkg/bindings/containers/logs.go116
-rw-r--r--pkg/bindings/containers/mount.go53
-rw-r--r--pkg/bindings/containers/types.go26
-rw-r--r--pkg/bindings/errors.go14
-rw-r--r--pkg/bindings/generate.go4
-rw-r--r--pkg/bindings/generate/generate.go4
-rw-r--r--pkg/bindings/healthcheck.go19
-rw-r--r--pkg/bindings/images.go111
-rw-r--r--pkg/bindings/images/images.go231
-rw-r--r--pkg/bindings/images/search.go41
-rw-r--r--pkg/bindings/manifests/manifests.go126
-rw-r--r--pkg/bindings/mount.go26
-rw-r--r--pkg/bindings/network.go37
-rw-r--r--pkg/bindings/network/network.go50
-rw-r--r--pkg/bindings/play.go3
-rw-r--r--pkg/bindings/play/play.go7
-rw-r--r--pkg/bindings/pods.go129
-rw-r--r--pkg/bindings/pods/pods.go233
-rw-r--r--pkg/bindings/search.go39
-rw-r--r--pkg/bindings/system/system.go61
-rw-r--r--pkg/bindings/test/common_test.go271
-rw-r--r--pkg/bindings/test/containers_test.go413
-rw-r--r--pkg/bindings/test/create_test.go50
-rw-r--r--pkg/bindings/test/exec_test.go77
-rw-r--r--pkg/bindings/test/images_test.go356
-rw-r--r--pkg/bindings/test/manifests_test.go124
-rw-r--r--pkg/bindings/test/pods_test.go322
-rw-r--r--pkg/bindings/test/system_test.go51
-rw-r--r--pkg/bindings/test/test_suite_test.go13
-rw-r--r--pkg/bindings/test/volumes_test.go173
-rw-r--r--pkg/bindings/volumes.go60
-rw-r--r--pkg/bindings/volumes/volumes.go109
40 files changed, 3681 insertions, 601 deletions
diff --git a/pkg/bindings/bindings.go b/pkg/bindings/bindings.go
index e83c4a5e1..4b07847d1 100644
--- a/pkg/bindings/bindings.go
+++ b/pkg/bindings/bindings.go
@@ -7,3 +7,12 @@
// is established, users can then manage the Podman container runtime.
package bindings
+
+var (
+ // PTrue is a convenience variable that can be used in bindings where
+ // a pointer to a bool (optional parameter) is required.
+ PTrue bool = true
+ // PFalse is a convenience variable that can be used in bindings where
+ // a pointer to a bool (optional parameter) is required.
+ PFalse bool = false
+)
diff --git a/pkg/bindings/connection.go b/pkg/bindings/connection.go
index 551a63c62..4fe4dd72d 100644
--- a/pkg/bindings/connection.go
+++ b/pkg/bindings/connection.go
@@ -1,14 +1,34 @@
package bindings
import (
+ "bufio"
+ "context"
"fmt"
"io"
+ "io/ioutil"
+ "net"
"net/http"
+ "net/url"
+ "os"
+ "path/filepath"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/containers/libpod/pkg/api/handlers"
+ jsoniter "github.com/json-iterator/go"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+ "golang.org/x/crypto/ssh"
+ "k8s.io/client-go/util/homedir"
)
-const (
- defaultConnection string = "http://localhost:8080/v1.24/libpod"
- pingConnection string = "http://localhost:8080/_ping"
+var (
+ basePath = &url.URL{
+ Scheme: "http",
+ Host: "d",
+ Path: "/v" + handlers.MinimalApiVersion + "/libpod",
+ }
)
type APIResponse struct {
@@ -17,46 +37,285 @@ type APIResponse struct {
}
type Connection struct {
- url string
+ _url *url.URL
client *http.Client
}
-func NewConnection(url string) (Connection, error) {
- if len(url) < 1 {
- url = defaultConnection
+type valueKey string
+
+const (
+ clientKey = valueKey("client")
+)
+
+// GetClient from context build by NewConnection()
+func GetClient(ctx context.Context) (*Connection, error) {
+ c, ok := ctx.Value(clientKey).(*Connection)
+ if !ok {
+ return nil, errors.Errorf("ClientKey not set in context")
+ }
+ return c, nil
+}
+
+// JoinURL elements with '/'
+func JoinURL(elements ...string) string {
+ return strings.Join(elements, "/")
+}
+
+// NewConnection takes a URI as a string and returns a context with the
+// Connection embedded as a value. This context needs to be passed to each
+// endpoint to work correctly.
+//
+// A valid URI connection should be scheme://
+// For example tcp://localhost:<port>
+// or unix:///run/podman/podman.sock
+// or ssh://<user>@<host>[:port]/run/podman/podman.sock?secure=True
+func NewConnection(ctx context.Context, uri string, identity ...string) (context.Context, error) {
+ var (
+ err error
+ secure bool
+ )
+ if v, found := os.LookupEnv("PODMAN_HOST"); found {
+ uri = v
+ }
+
+ if v, found := os.LookupEnv("PODMAN_SSHKEY"); found {
+ identity = []string{v}
+ }
+
+ _url, err := url.Parse(uri)
+ if err != nil {
+ return nil, errors.Wrapf(err, "Value of PODMAN_HOST is not a valid url: %s", uri)
}
- newConn := Connection{
- url: url,
- client: &http.Client{},
+
+ // Now we setup the http client to use the connection above
+ var client *http.Client
+ switch _url.Scheme {
+ case "ssh":
+ secure, err = strconv.ParseBool(_url.Query().Get("secure"))
+ if err != nil {
+ secure = false
+ }
+ client, err = sshClient(_url, identity[0], secure)
+ case "unix":
+ if !strings.HasPrefix(uri, "unix:///") {
+ // autofix unix://path_element vs unix:///path_element
+ _url.Path = JoinURL(_url.Host, _url.Path)
+ _url.Host = ""
+ }
+ client, err = unixClient(_url)
+ case "tcp":
+ if !strings.HasPrefix(uri, "tcp://") {
+ return nil, errors.New("tcp URIs should begin with tcp://")
+ }
+ client, err = tcpClient(_url)
+ default:
+ return nil, errors.Errorf("'%s' is not a supported schema", _url.Scheme)
}
- response, err := http.Get(pingConnection)
if err != nil {
- return newConn, err
+ return nil, errors.Wrapf(err, "Failed to create %sClient", _url.Scheme)
}
- if err := response.Body.Close(); err != nil {
- return newConn, err
+
+ ctx = context.WithValue(ctx, clientKey, &Connection{_url, client})
+ if err := pingNewConnection(ctx); err != nil {
+ return nil, err
}
- return newConn, err
+ return ctx, nil
+}
+
+func tcpClient(_url *url.URL) (*http.Client, error) {
+ return &http.Client{
+ Transport: &http.Transport{
+ DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
+ return net.Dial("tcp", _url.Path)
+ },
+ DisableCompression: true,
+ },
+ }, nil
}
-func (c Connection) makeEndpoint(u string) string {
- return fmt.Sprintf("%s%s", defaultConnection, u)
+// pingNewConnection pings to make sure the RESTFUL service is up
+// and running. it should only be used where initializing a connection
+func pingNewConnection(ctx context.Context) error {
+ client, err := GetClient(ctx)
+ if err != nil {
+ return err
+ }
+ // the ping endpoint sits at / in this case
+ response, err := client.DoRequest(nil, http.MethodGet, "../../../_ping", nil)
+ if err != nil {
+ return err
+ }
+ if response.StatusCode == http.StatusOK {
+ return nil
+ }
+ return errors.Errorf("ping response was %q", response.StatusCode)
}
-func (c Connection) newRequest(httpMethod, endpoint string, httpBody io.Reader, params map[string]string) (*APIResponse, error) {
- e := c.makeEndpoint(endpoint)
+func sshClient(_url *url.URL, identity string, secure bool) (*http.Client, error) {
+ auth, err := publicKey(identity)
+ if err != nil {
+ return nil, errors.Wrapf(err, "Failed to parse identity %s: %v\n", _url.String(), identity)
+ }
+
+ callback := ssh.InsecureIgnoreHostKey()
+ if secure {
+ key := hostKey(_url.Hostname())
+ if key != nil {
+ callback = ssh.FixedHostKey(key)
+ }
+ }
+
+ port := _url.Port()
+ if port == "" {
+ port = "22"
+ }
+
+ bastion, err := ssh.Dial("tcp",
+ net.JoinHostPort(_url.Hostname(), port),
+ &ssh.ClientConfig{
+ User: _url.User.Username(),
+ Auth: []ssh.AuthMethod{auth},
+ HostKeyCallback: callback,
+ HostKeyAlgorithms: []string{
+ ssh.KeyAlgoRSA,
+ ssh.KeyAlgoDSA,
+ ssh.KeyAlgoECDSA256,
+ ssh.KeyAlgoECDSA384,
+ ssh.KeyAlgoECDSA521,
+ ssh.KeyAlgoED25519,
+ },
+ Timeout: 5 * time.Second,
+ },
+ )
+ if err != nil {
+ return nil, errors.Wrapf(err, "Connection to bastion host (%s) failed.", _url.String())
+ }
+ return &http.Client{
+ Transport: &http.Transport{
+ DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
+ return bastion.Dial("unix", _url.Path)
+ },
+ }}, nil
+}
+
+func unixClient(_url *url.URL) (*http.Client, error) {
+ return &http.Client{
+ Transport: &http.Transport{
+ DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
+ d := net.Dialer{}
+ return d.DialContext(ctx, "unix", _url.Path)
+ },
+ DisableCompression: true,
+ },
+ }, nil
+}
+
+// DoRequest assembles the http request and returns the response
+func (c *Connection) DoRequest(httpBody io.Reader, httpMethod, endpoint string, queryParams url.Values, pathValues ...string) (*APIResponse, error) {
+ var (
+ err error
+ response *http.Response
+ )
+ safePathValues := make([]interface{}, len(pathValues))
+ // Make sure path values are http url safe
+ for i, pv := range pathValues {
+ safePathValues[i] = url.PathEscape(pv)
+ }
+ // Lets eventually use URL for this which might lead to safer
+ // usage
+ safeEndpoint := fmt.Sprintf(endpoint, safePathValues...)
+ e := basePath.String() + safeEndpoint
req, err := http.NewRequest(httpMethod, e, httpBody)
if err != nil {
return nil, err
}
- if len(params) > 0 {
- // if more desirable we could use url to form the encoded endpoint with params
- r := req.URL.Query()
- for k, v := range params {
- r.Add(k, v)
+ if len(queryParams) > 0 {
+ req.URL.RawQuery = queryParams.Encode()
+ }
+ // Give the Do three chances in the case of a comm/service hiccup
+ for i := 0; i < 3; i++ {
+ response, err = c.client.Do(req) // nolint
+ if err == nil {
+ break
}
- req.URL.RawQuery = r.Encode()
+ time.Sleep(time.Duration(i*100) * time.Millisecond)
}
- response, err := c.client.Do(req) // nolint
return &APIResponse{response, req}, err
}
+
+// FiltersToString converts our typical filter format of a
+// map[string][]string to a query/html safe string.
+func FiltersToString(filters map[string][]string) (string, error) {
+ lowerCaseKeys := make(map[string][]string)
+ for k, v := range filters {
+ lowerCaseKeys[strings.ToLower(k)] = v
+ }
+ return jsoniter.MarshalToString(lowerCaseKeys)
+}
+
+// IsInformation returns true if the response code is 1xx
+func (h *APIResponse) IsInformational() bool {
+ return h.Response.StatusCode/100 == 1
+}
+
+// IsSuccess returns true if the response code is 2xx
+func (h *APIResponse) IsSuccess() bool {
+ return h.Response.StatusCode/100 == 2
+}
+
+// IsRedirection returns true if the response code is 3xx
+func (h *APIResponse) IsRedirection() bool {
+ return h.Response.StatusCode/100 == 3
+}
+
+// IsClientError returns true if the response code is 4xx
+func (h *APIResponse) IsClientError() bool {
+ return h.Response.StatusCode/100 == 4
+}
+
+// IsServerError returns true if the response code is 5xx
+func (h *APIResponse) IsServerError() bool {
+ return h.Response.StatusCode/100 == 5
+}
+
+func publicKey(path string) (ssh.AuthMethod, error) {
+ key, err := ioutil.ReadFile(path)
+ if err != nil {
+ return nil, err
+ }
+
+ signer, err := ssh.ParsePrivateKey(key)
+ if err != nil {
+ return nil, err
+ }
+
+ return ssh.PublicKeys(signer), nil
+}
+
+func hostKey(host string) ssh.PublicKey {
+ // parse OpenSSH known_hosts file
+ // ssh or use ssh-keyscan to get initial key
+ known_hosts := filepath.Join(homedir.HomeDir(), ".ssh", "known_hosts")
+ fd, err := os.Open(known_hosts)
+ if err != nil {
+ logrus.Error(err)
+ return nil
+ }
+
+ scanner := bufio.NewScanner(fd)
+ for scanner.Scan() {
+ _, hosts, key, _, _, err := ssh.ParseKnownHosts(scanner.Bytes())
+ if err != nil {
+ logrus.Errorf("Failed to parse known_hosts: %s", scanner.Text())
+ continue
+ }
+
+ for _, h := range hosts {
+ if h == host {
+ return key
+ }
+ }
+ }
+
+ return nil
+}
diff --git a/pkg/bindings/containers.go b/pkg/bindings/containers.go
deleted file mode 100644
index 057580088..000000000
--- a/pkg/bindings/containers.go
+++ /dev/null
@@ -1,139 +0,0 @@
-package bindings
-
-import (
- "fmt"
- "net/http"
- "strconv"
-
- "github.com/containers/libpod/cmd/podman/shared"
- "github.com/containers/libpod/libpod"
-)
-
-func (c Connection) ListContainers(filter []string, last int, size, sync bool) ([]shared.PsContainerOutput, error) { // nolint:typecheck
- images := []shared.PsContainerOutput{}
- params := make(map[string]string)
- params["last"] = strconv.Itoa(last)
- params["size"] = strconv.FormatBool(size)
- params["sync"] = strconv.FormatBool(sync)
- response, err := c.newRequest(http.MethodGet, "/containers/json", nil, params)
- if err != nil {
- return images, err
- }
- return images, response.Process(nil)
-}
-
-func (c Connection) PruneContainers() ([]string, error) {
- var (
- pruned []string
- )
- response, err := c.newRequest(http.MethodPost, "/containers/prune", nil, nil)
- if err != nil {
- return pruned, err
- }
- return pruned, response.Process(nil)
-}
-
-func (c Connection) RemoveContainer(nameOrID string, force, volumes bool) error {
- params := make(map[string]string)
- params["force"] = strconv.FormatBool(force)
- params["vols"] = strconv.FormatBool(volumes)
- response, err := c.newRequest(http.MethodDelete, fmt.Sprintf("/containers/%s", nameOrID), nil, params)
- if err != nil {
- return err
- }
- return response.Process(nil)
-}
-
-func (c Connection) InspectContainer(nameOrID string, size bool) (*libpod.InspectContainerData, error) {
- params := make(map[string]string)
- params["size"] = strconv.FormatBool(size)
- response, err := c.newRequest(http.MethodGet, fmt.Sprintf("/containers/%s/json", nameOrID), nil, params)
- if err != nil {
- return nil, err
- }
- inspect := libpod.InspectContainerData{}
- return &inspect, response.Process(&inspect)
-}
-
-func (c Connection) KillContainer(nameOrID string, signal int) error {
- params := make(map[string]string)
- params["signal"] = strconv.Itoa(signal)
- response, err := c.newRequest(http.MethodPost, fmt.Sprintf("/containers/%s/kill", nameOrID), nil, params)
- if err != nil {
- return err
- }
- return response.Process(nil)
-
-}
-func (c Connection) ContainerLogs() {}
-func (c Connection) PauseContainer(nameOrID string) error {
- response, err := c.newRequest(http.MethodPost, fmt.Sprintf("/containers/%s/pause", nameOrID), nil, nil)
- if err != nil {
- return err
- }
- return response.Process(nil)
-}
-
-func (c Connection) RestartContainer(nameOrID string, timeout int) error {
- // TODO how do we distinguish between an actual zero value and not wanting to change the timeout value
- params := make(map[string]string)
- params["timeout"] = strconv.Itoa(timeout)
- response, err := c.newRequest(http.MethodPost, fmt.Sprintf("/containers/%s/restart", nameOrID), nil, params)
- if err != nil {
- return err
- }
- return response.Process(nil)
-}
-
-func (c Connection) StartContainer(nameOrID, detachKeys string) error {
- params := make(map[string]string)
- if len(detachKeys) > 0 {
- params["detachKeys"] = detachKeys
- }
- response, err := c.newRequest(http.MethodPost, fmt.Sprintf("/containers/%s/start", nameOrID), nil, params)
- if err != nil {
- return err
- }
- return response.Process(nil)
-}
-
-func (c Connection) ContainerStats() {}
-func (c Connection) ContainerTop() {}
-
-func (c Connection) UnpauseContainer(nameOrID string) error {
- response, err := c.newRequest(http.MethodPost, fmt.Sprintf("/containers/%s/unpause", nameOrID), nil, nil)
- if err != nil {
- return err
- }
- return response.Process(nil)
-}
-
-func (c Connection) WaitContainer(nameOrID string) error {
- // TODO when returns are ironed out, we can should use the newRequest approach
- _, err := http.Post(c.makeEndpoint(fmt.Sprintf("containers/%s/wait", nameOrID)), "application/json", nil) // nolint
- return err
-}
-
-func (c Connection) ContainerExists(nameOrID string) (bool, error) {
- response, err := http.Get(c.makeEndpoint(fmt.Sprintf("/containers/%s/exists", nameOrID))) // nolint
- defer closeResponseBody(response)
- if err != nil {
- return false, err
- }
- if response.StatusCode == http.StatusOK {
- return true, nil
- }
- return false, nil
-}
-
-func (c Connection) StopContainer(nameOrID string, timeout *int) error {
- params := make(map[string]string)
- if timeout != nil {
- params["t"] = strconv.Itoa(*timeout)
- }
- response, err := c.newRequest(http.MethodPost, fmt.Sprintf("/containers/%s/stop", nameOrID), nil, params)
- if err != nil {
- return err
- }
- return response.Process(nil)
-}
diff --git a/pkg/bindings/containers/commit.go b/pkg/bindings/containers/commit.go
new file mode 100644
index 000000000..12c25f842
--- /dev/null
+++ b/pkg/bindings/containers/commit.go
@@ -0,0 +1,49 @@
+package containers
+
+import (
+ "context"
+ "net/http"
+ "net/url"
+ "strconv"
+
+ "github.com/containers/libpod/pkg/api/handlers"
+ "github.com/containers/libpod/pkg/bindings"
+)
+
+// Commit creates a container image from a container. The container is defined by nameOrId. Use
+// the CommitOptions for finer grain control on characteristics of the resulting image.
+func Commit(ctx context.Context, nameOrId string, options CommitOptions) (handlers.IDResponse, error) {
+ id := handlers.IDResponse{}
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return id, err
+ }
+ params := url.Values{}
+ params.Set("container", nameOrId)
+ if options.Author != nil {
+ params.Set("author", *options.Author)
+ }
+ for _, change := range options.Changes {
+ params.Set("changes", change)
+ }
+ if options.Comment != nil {
+ params.Set("comment", *options.Comment)
+ }
+ if options.Format != nil {
+ params.Set("format", *options.Format)
+ }
+ if options.Pause != nil {
+ params.Set("pause", strconv.FormatBool(*options.Pause))
+ }
+ if options.Repo != nil {
+ params.Set("repo", *options.Repo)
+ }
+ if options.Tag != nil {
+ params.Set("tag", *options.Tag)
+ }
+ response, err := conn.DoRequest(nil, http.MethodPost, "/commit", params)
+ if err != nil {
+ return id, err
+ }
+ return id, response.Process(&id)
+}
diff --git a/pkg/bindings/containers/containers.go b/pkg/bindings/containers/containers.go
new file mode 100644
index 000000000..bad1294f4
--- /dev/null
+++ b/pkg/bindings/containers/containers.go
@@ -0,0 +1,298 @@
+package containers
+
+import (
+ "context"
+ "net/http"
+ "net/url"
+ "strconv"
+ "strings"
+
+ "github.com/containers/libpod/libpod/define"
+ "github.com/containers/libpod/pkg/api/handlers"
+ lpapiv2 "github.com/containers/libpod/pkg/api/handlers/libpod"
+ "github.com/containers/libpod/pkg/bindings"
+)
+
+// List obtains a list of containers in local storage. All parameters to this method are optional.
+// The filters are used to determine which containers are listed. The last parameter indicates to only return
+// the most recent number of containers. The pod and size booleans indicate that pod information and rootfs
+// size information should also be included. Finally, the sync bool synchronizes the OCI runtime and
+// container state.
+func List(ctx context.Context, filters map[string][]string, all *bool, last *int, pod, size, sync *bool) ([]lpapiv2.ListContainer, error) { // nolint:typecheck
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return nil, err
+ }
+ var containers []lpapiv2.ListContainer
+ params := url.Values{}
+ if all != nil {
+ params.Set("all", strconv.FormatBool(*all))
+ }
+ if last != nil {
+ params.Set("last", strconv.Itoa(*last))
+ }
+ if pod != nil {
+ params.Set("pod", strconv.FormatBool(*pod))
+ }
+ if size != nil {
+ params.Set("size", strconv.FormatBool(*size))
+ }
+ if sync != nil {
+ params.Set("sync", strconv.FormatBool(*sync))
+ }
+ if filters != nil {
+ filterString, err := bindings.FiltersToString(filters)
+ if err != nil {
+ return nil, err
+ }
+ params.Set("filters", filterString)
+ }
+ response, err := conn.DoRequest(nil, http.MethodGet, "/containers/json", params)
+ if err != nil {
+ return containers, err
+ }
+ return containers, response.Process(&containers)
+}
+
+// Prune removes stopped and exited containers from local storage. The optional filters can be
+// used for more granular selection of containers. The main error returned indicates if there were runtime
+// errors like finding containers. Errors specific to the removal of a container are in the PruneContainerResponse
+// structure.
+func Prune(ctx context.Context, filters map[string][]string) ([]string, error) {
+ var (
+ pruneResponse []string
+ )
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return nil, err
+ }
+ params := url.Values{}
+ if filters != nil {
+ filterString, err := bindings.FiltersToString(filters)
+ if err != nil {
+ return nil, err
+ }
+ params.Set("filters", filterString)
+ }
+ response, err := conn.DoRequest(nil, http.MethodPost, "/containers/prune", params)
+ if err != nil {
+ return pruneResponse, err
+ }
+ return pruneResponse, response.Process(pruneResponse)
+}
+
+// Remove removes a container from local storage. The force bool designates
+// that the container should be removed forcibly (example, even it is running). The volumes
+// bool dictates that a container's volumes should also be removed.
+func Remove(ctx context.Context, nameOrID string, force, volumes *bool) error {
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return err
+ }
+ params := url.Values{}
+ if force != nil {
+ params.Set("force", strconv.FormatBool(*force))
+ }
+ if volumes != nil {
+ params.Set("vols", strconv.FormatBool(*volumes))
+ }
+ response, err := conn.DoRequest(nil, http.MethodDelete, "/containers/%s", params, nameOrID)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
+
+// Inspect returns low level information about a Container. The nameOrID can be a container name
+// or a partial/full ID. The size bool determines whether the size of the container's root filesystem
+// should be calculated. Calculating the size of a container requires extra work from the filesystem and
+// is therefore slower.
+func Inspect(ctx context.Context, nameOrID string, size *bool) (*define.InspectContainerData, error) {
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return nil, err
+ }
+ params := url.Values{}
+ if size != nil {
+ params.Set("size", strconv.FormatBool(*size))
+ }
+ response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/json", params, nameOrID)
+ if err != nil {
+ return nil, err
+ }
+ inspect := define.InspectContainerData{}
+ return &inspect, response.Process(&inspect)
+}
+
+// Kill sends a given signal to a given container. The signal should be the string
+// representation of a signal like 'SIGKILL'. The nameOrID can be a container name
+// or a partial/full ID
+func Kill(ctx context.Context, nameOrID string, sig string) error {
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return err
+ }
+ params := url.Values{}
+ params.Set("signal", sig)
+ response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/kill", params, nameOrID)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+
+}
+
+// Pause pauses a given container. The nameOrID can be a container name
+// or a partial/full ID.
+func Pause(ctx context.Context, nameOrID string) error {
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return err
+ }
+ response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/pause", nil, nameOrID)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
+
+// Restart restarts a running container. The nameOrID can be a container name
+// or a partial/full ID. The optional timeout specifies the number of seconds to wait
+// for the running container to stop before killing it.
+func Restart(ctx context.Context, nameOrID string, timeout *int) error {
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return err
+ }
+ params := url.Values{}
+ if timeout != nil {
+ params.Set("t", strconv.Itoa(*timeout))
+ }
+ response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/restart", params, nameOrID)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
+
+// Start starts a non-running container.The nameOrID can be a container name
+// or a partial/full ID. The optional parameter for detach keys are to override the default
+// detach key sequence.
+func Start(ctx context.Context, nameOrID string, detachKeys *string) error {
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return err
+ }
+ params := url.Values{}
+ if detachKeys != nil {
+ params.Set("detachKeys", *detachKeys)
+ }
+ response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/start", params, nameOrID)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
+
+func Stats() {}
+
+// Top gathers statistics about the running processes in a container. The nameOrID can be a container name
+// or a partial/full ID. The descriptors allow for specifying which data to collect from the process.
+func Top(ctx context.Context, nameOrID string, descriptors []string) ([]string, error) {
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return nil, err
+ }
+ params := url.Values{}
+
+ if len(descriptors) > 0 {
+ // flatten the slice into one string
+ params.Set("ps_args", strings.Join(descriptors, ","))
+ }
+ response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/top", params, nameOrID)
+ if err != nil {
+ return nil, err
+ }
+
+ body := handlers.ContainerTopOKBody{}
+ if err = response.Process(&body); err != nil {
+ return nil, err
+ }
+
+ // handlers.ContainerTopOKBody{} returns a slice of slices where each cell in the top table is an item.
+ // In libpod land, we're just using a slice with cells being split by tabs, which allows for an idiomatic
+ // usage of the tabwriter.
+ topOutput := []string{strings.Join(body.Titles, "\t")}
+ for _, out := range body.Processes {
+ topOutput = append(topOutput, strings.Join(out, "\t"))
+ }
+
+ return topOutput, err
+}
+
+// Unpause resumes the given paused container. The nameOrID can be a container name
+// or a partial/full ID.
+func Unpause(ctx context.Context, nameOrID string) error {
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return err
+ }
+ response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/unpause", nil, nameOrID)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
+
+// Wait blocks until the given container reaches a condition. If not provided, the condition will
+// default to stopped. If the condition is stopped, an exit code for the container will be provided. The
+// nameOrID can be a container name or a partial/full ID.
+func Wait(ctx context.Context, nameOrID string, condition *define.ContainerStatus) (int32, error) { //nolint
+ var exitCode int32
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return exitCode, err
+ }
+ params := url.Values{}
+ if condition != nil {
+ params.Set("condition", condition.String())
+ }
+ response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/wait", params, nameOrID)
+ if err != nil {
+ return exitCode, err
+ }
+ return exitCode, response.Process(&exitCode)
+}
+
+// Exists is a quick, light-weight way to determine if a given container
+// exists in local storage. The nameOrID can be a container name
+// or a partial/full ID.
+func Exists(ctx context.Context, nameOrID string) (bool, error) {
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return false, err
+ }
+ response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/exists", nil, nameOrID)
+ if err != nil {
+ return false, err
+ }
+ return response.IsSuccess(), nil
+}
+
+// Stop stops a running container. The timeout is optional. The nameOrID can be a container name
+// or a partial/full ID
+func Stop(ctx context.Context, nameOrID string, timeout *uint) error {
+ params := url.Values{}
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return err
+ }
+ if timeout != nil {
+ params.Set("t", strconv.Itoa(int(*timeout)))
+ }
+ response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/stop", params, nameOrID)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
diff --git a/pkg/bindings/containers/create.go b/pkg/bindings/containers/create.go
new file mode 100644
index 000000000..495f9db49
--- /dev/null
+++ b/pkg/bindings/containers/create.go
@@ -0,0 +1,30 @@
+package containers
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/containers/libpod/pkg/bindings"
+ "github.com/containers/libpod/pkg/specgen"
+ jsoniter "github.com/json-iterator/go"
+)
+
+func CreateWithSpec(ctx context.Context, s *specgen.SpecGenerator) (utils.ContainerCreateResponse, error) {
+ var ccr utils.ContainerCreateResponse
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return ccr, err
+ }
+ specgenString, err := jsoniter.MarshalToString(s)
+ if err != nil {
+ return ccr, err
+ }
+ stringReader := strings.NewReader(specgenString)
+ response, err := conn.DoRequest(stringReader, http.MethodPost, "/containers/create", nil)
+ if err != nil {
+ return ccr, err
+ }
+ return ccr, response.Process(&ccr)
+}
diff --git a/pkg/bindings/containers/exec.go b/pkg/bindings/containers/exec.go
new file mode 100644
index 000000000..48f9ed697
--- /dev/null
+++ b/pkg/bindings/containers/exec.go
@@ -0,0 +1,71 @@
+package containers
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/containers/libpod/libpod/define"
+ "github.com/containers/libpod/pkg/api/handlers"
+ "github.com/containers/libpod/pkg/bindings"
+ jsoniter "github.com/json-iterator/go"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+)
+
+var json = jsoniter.ConfigCompatibleWithStandardLibrary
+
+// ExecCreate creates a new exec session in an existing container.
+// The exec session will not be started; that is done with ExecStart.
+// Returns ID of new exec session, or an error if one occurred.
+func ExecCreate(ctx context.Context, nameOrID string, config *handlers.ExecCreateConfig) (string, error) {
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return "", err
+ }
+
+ if config == nil {
+ return "", errors.Errorf("must provide a configuration for exec session")
+ }
+
+ requestJSON, err := json.Marshal(config)
+ if err != nil {
+ return "", errors.Wrapf(err, "error marshalling exec config to JSON")
+ }
+ jsonReader := strings.NewReader(string(requestJSON))
+
+ resp, err := conn.DoRequest(jsonReader, http.MethodPost, "/containers/%s/exec", nil, nameOrID)
+ if err != nil {
+ return "", err
+ }
+
+ respStruct := new(handlers.ExecCreateResponse)
+ if err := resp.Process(respStruct); err != nil {
+ return "", err
+ }
+
+ return respStruct.ID, nil
+}
+
+// ExecInspect inspects an existing exec session, returning detailed information
+// about it.
+func ExecInspect(ctx context.Context, sessionID string) (*define.InspectExecSession, error) {
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return nil, err
+ }
+
+ logrus.Debugf("Inspecting session ID %q", sessionID)
+
+ resp, err := conn.DoRequest(nil, http.MethodGet, "/exec/%s/json", nil, sessionID)
+ if err != nil {
+ return nil, err
+ }
+
+ respStruct := new(define.InspectExecSession)
+ if err := resp.Process(respStruct); err != nil {
+ return nil, err
+ }
+
+ return respStruct, nil
+}
diff --git a/pkg/bindings/containers/healthcheck.go b/pkg/bindings/containers/healthcheck.go
new file mode 100644
index 000000000..2b783ac73
--- /dev/null
+++ b/pkg/bindings/containers/healthcheck.go
@@ -0,0 +1,26 @@
+package containers
+
+import (
+ "context"
+ "net/http"
+
+ "github.com/containers/libpod/libpod/define"
+ "github.com/containers/libpod/pkg/bindings"
+)
+
+// RunHealthCheck executes the container's healthcheck and returns the health status of the
+// container.
+func RunHealthCheck(ctx context.Context, nameOrID string) (*define.HealthCheckResults, error) {
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return nil, err
+ }
+ var (
+ status define.HealthCheckResults
+ )
+ response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/healthcheck", nil, nameOrID)
+ if err != nil {
+ return nil, err
+ }
+ return &status, response.Process(&status)
+}
diff --git a/pkg/bindings/containers/logs.go b/pkg/bindings/containers/logs.go
new file mode 100644
index 000000000..b7ecb3c7e
--- /dev/null
+++ b/pkg/bindings/containers/logs.go
@@ -0,0 +1,116 @@
+package containers
+
+import (
+ "context"
+ "encoding/binary"
+ "io"
+ "net/http"
+ "net/url"
+ "strconv"
+
+ "github.com/containers/libpod/pkg/bindings"
+ "github.com/pkg/errors"
+)
+
+// Logs obtains a container's logs given the options provided. The logs are then sent to the
+// stdout|stderr channels as strings.
+func Logs(ctx context.Context, nameOrID string, opts LogOptions, stdoutChan, stderrChan chan string) error {
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return err
+ }
+ params := url.Values{}
+ if opts.Follow != nil {
+ params.Set("follow", strconv.FormatBool(*opts.Follow))
+ }
+ if opts.Since != nil {
+ params.Set("since", *opts.Since)
+ }
+ if opts.Stderr != nil {
+ params.Set("stderr", strconv.FormatBool(*opts.Stderr))
+ }
+ if opts.Stdout != nil {
+ params.Set("stdout", strconv.FormatBool(*opts.Stdout))
+ }
+ if opts.Tail != nil {
+ params.Set("tail", *opts.Tail)
+ }
+ if opts.Timestamps != nil {
+ params.Set("timestamps", strconv.FormatBool(*opts.Timestamps))
+ }
+ if opts.Until != nil {
+ params.Set("until", *opts.Until)
+ }
+ // The API requires either stdout|stderr be used. If neither are specified, we specify stdout
+ if opts.Stdout == nil && opts.Stderr == nil {
+ params.Set("stdout", strconv.FormatBool(true))
+ }
+ response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/logs", params, nameOrID)
+ if err != nil {
+ return err
+ }
+
+ // read 8 bytes
+ // first byte determines stderr=2|stdout=1
+ // bytes 4-7 len(msg) in uint32
+ for {
+ stream, msgSize, err := readHeader(response.Body)
+ if err != nil {
+ // In case the server side closes up shop because !follow
+ if err == io.EOF {
+ break
+ }
+ return errors.Wrap(err, "unable to read log header")
+ }
+ msg, err := readMsg(response.Body, msgSize)
+ if err != nil {
+ return errors.Wrap(err, "unable to read log message")
+ }
+ if stream == 1 {
+ stdoutChan <- msg
+ } else {
+ stderrChan <- msg
+ }
+ }
+ return nil
+}
+
+func readMsg(r io.Reader, msgSize int) (string, error) {
+ var msg []byte
+ size := msgSize
+ for {
+ b := make([]byte, size)
+ _, err := r.Read(b)
+ if err != nil {
+ return "", err
+ }
+ msg = append(msg, b...)
+ if len(msg) == msgSize {
+ break
+ }
+ size = msgSize - len(msg)
+ }
+ return string(msg), nil
+}
+
+func readHeader(r io.Reader) (byte, int, error) {
+ var (
+ header []byte
+ size = 8
+ )
+ for {
+ b := make([]byte, size)
+ _, err := r.Read(b)
+ if err != nil {
+ return 0, 0, err
+ }
+ header = append(header, b...)
+ if len(header) == 8 {
+ break
+ }
+ size = 8 - len(header)
+ }
+ stream := header[0]
+ msgSize := int(binary.BigEndian.Uint32(header[4:]) - 8)
+ return stream, msgSize, nil
+}
diff --git a/pkg/bindings/containers/mount.go b/pkg/bindings/containers/mount.go
new file mode 100644
index 000000000..e0627d9a3
--- /dev/null
+++ b/pkg/bindings/containers/mount.go
@@ -0,0 +1,53 @@
+package containers
+
+import (
+ "context"
+ "net/http"
+
+ "github.com/containers/libpod/pkg/bindings"
+)
+
+// Mount mounts an existing container to the filesystem. It returns the path
+// of the mounted container in string format.
+func Mount(ctx context.Context, nameOrID string) (string, error) {
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return "", err
+ }
+ var (
+ path string
+ )
+ response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/mount", nil, nameOrID)
+ if err != nil {
+ return path, err
+ }
+ return path, response.Process(&path)
+}
+
+// Unmount unmounts a container from the filesystem. The container must not be running
+// or the unmount will fail.
+func Unmount(ctx context.Context, nameOrID string) error {
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return err
+ }
+ response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/unmount", nil, nameOrID)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
+
+// GetMountedContainerPaths returns a map of mounted containers and their mount locations.
+func GetMountedContainerPaths(ctx context.Context) (map[string]string, error) {
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return nil, err
+ }
+ mounts := make(map[string]string)
+ response, err := conn.DoRequest(nil, http.MethodGet, "/containers/showmounted", nil)
+ if err != nil {
+ return mounts, err
+ }
+ return mounts, response.Process(&mounts)
+}
diff --git a/pkg/bindings/containers/types.go b/pkg/bindings/containers/types.go
new file mode 100644
index 000000000..31daaf565
--- /dev/null
+++ b/pkg/bindings/containers/types.go
@@ -0,0 +1,26 @@
+package containers
+
+// LogOptions describe finer control of log content or
+// how the content is formatted.
+type LogOptions struct {
+ Follow *bool
+ Since *string
+ Stderr *bool
+ Stdout *bool
+ Tail *string
+ Timestamps *bool
+ Until *string
+}
+
+// CommitOptions describe details about the resulting commited
+// image as defined by repo and tag. None of these options
+// are required.
+type CommitOptions struct {
+ Author *string
+ Changes []string
+ Comment *string
+ Format *string
+ Pause *bool
+ Repo *string
+ Tag *string
+}
diff --git a/pkg/bindings/errors.go b/pkg/bindings/errors.go
index 9a02925a3..5fa711199 100644
--- a/pkg/bindings/errors.go
+++ b/pkg/bindings/errors.go
@@ -3,11 +3,9 @@ package bindings
import (
"encoding/json"
"io/ioutil"
- "net/http"
"github.com/containers/libpod/pkg/api/handlers/utils"
"github.com/pkg/errors"
- "github.com/sirupsen/logrus"
)
var (
@@ -27,7 +25,7 @@ func (a APIResponse) Process(unmarshalInto interface{}) error {
if err != nil {
return errors.Wrap(err, "unable to process API response")
}
- if a.Response.StatusCode == http.StatusOK {
+ if a.IsSuccess() || a.IsRedirection() {
if unmarshalInto != nil {
return json.Unmarshal(data, unmarshalInto)
}
@@ -37,10 +35,10 @@ func (a APIResponse) Process(unmarshalInto interface{}) error {
return handleError(data)
}
-func closeResponseBody(r *http.Response) {
- if r != nil {
- if err := r.Body.Close(); err != nil {
- logrus.Error(errors.Wrap(err, "unable to close response body"))
- }
+func CheckResponseCode(inError error) (int, error) {
+ e, ok := inError.(utils.ErrorModel)
+ if !ok {
+ return -1, errors.New("error is not type ErrorModel")
}
+ return e.Code(), nil
}
diff --git a/pkg/bindings/generate.go b/pkg/bindings/generate.go
deleted file mode 100644
index 534909062..000000000
--- a/pkg/bindings/generate.go
+++ /dev/null
@@ -1,4 +0,0 @@
-package bindings
-
-func (c Connection) GenerateKube() {}
-func (c Connection) GenerateSystemd() {}
diff --git a/pkg/bindings/generate/generate.go b/pkg/bindings/generate/generate.go
new file mode 100644
index 000000000..2916754b8
--- /dev/null
+++ b/pkg/bindings/generate/generate.go
@@ -0,0 +1,4 @@
+package generate
+
+func GenerateKube() {}
+func GenerateSystemd() {}
diff --git a/pkg/bindings/healthcheck.go b/pkg/bindings/healthcheck.go
deleted file mode 100644
index 32515e332..000000000
--- a/pkg/bindings/healthcheck.go
+++ /dev/null
@@ -1,19 +0,0 @@
-package bindings
-
-import (
- "fmt"
- "net/http"
-
- "github.com/containers/libpod/libpod"
-)
-
-func (c Connection) RunHealthCheck(nameOrID string) (*libpod.HealthCheckStatus, error) {
- var (
- status libpod.HealthCheckStatus
- )
- response, err := c.newRequest(http.MethodGet, fmt.Sprintf("/containers/%s/runhealthcheck", nameOrID), nil, nil)
- if err != nil {
- return nil, err
- }
- return &status, response.Process(&status)
-}
diff --git a/pkg/bindings/images.go b/pkg/bindings/images.go
deleted file mode 100644
index 3abc8c372..000000000
--- a/pkg/bindings/images.go
+++ /dev/null
@@ -1,111 +0,0 @@
-package bindings
-
-import (
- "fmt"
- "io"
- "net/http"
- "strconv"
-
- "github.com/containers/libpod/pkg/api/handlers"
- "github.com/containers/libpod/pkg/inspect"
-)
-
-func (c Connection) ImageExists(nameOrID string) (bool, error) {
- response, err := http.Get(c.makeEndpoint(fmt.Sprintf("/images/%s/exists", nameOrID))) // nolint
- defer closeResponseBody(response)
- if err != nil {
- return false, err
- }
- if response.StatusCode == http.StatusOK {
- return true, nil
- }
- return false, nil
-}
-
-func (c Connection) ListImages() ([]handlers.ImageSummary, error) {
- imageSummary := []handlers.ImageSummary{}
- response, err := c.newRequest(http.MethodGet, "/images/json", nil, nil)
- if err != nil {
- return imageSummary, err
- }
- return imageSummary, response.Process(&imageSummary)
-}
-
-func (c Connection) GetImage(nameOrID string) (*inspect.ImageData, error) {
- inspectedData := inspect.ImageData{}
- response, err := c.newRequest(http.MethodGet, fmt.Sprintf("/images/%s/json", nameOrID), nil, nil)
- if err != nil {
- return &inspectedData, err
- }
- return &inspectedData, response.Process(&inspectedData)
-}
-
-func (c Connection) ImageTree(nameOrId string) error {
- return ErrNotImplemented
-}
-
-func (c Connection) ImageHistory(nameOrID string) ([]handlers.HistoryResponse, error) {
- history := []handlers.HistoryResponse{}
- response, err := c.newRequest(http.MethodGet, fmt.Sprintf("/images/%s/history", nameOrID), nil, nil)
- if err != nil {
- return history, err
- }
- return history, response.Process(&history)
-}
-
-func (c Connection) LoadImage(r io.Reader) error {
- // TODO this still needs error handling added
- _, err := http.Post(c.makeEndpoint("/images/loads"), "application/json", r) //nolint
- return err
-}
-
-func (c Connection) RemoveImage(nameOrID string, force bool) ([]map[string]string, error) {
- deletes := []map[string]string{}
- params := make(map[string]string)
- params["force"] = strconv.FormatBool(force)
- response, err := c.newRequest(http.MethodDelete, fmt.Sprintf("/images/%s", nameOrID), nil, params)
- if err != nil {
- return nil, err
- }
- return deletes, response.Process(&deletes)
-}
-
-func (c Connection) ExportImage(nameOrID string, w io.Writer, format string, compress bool) error {
- params := make(map[string]string)
- params["format"] = format
- params["compress"] = strconv.FormatBool(compress)
- response, err := c.newRequest(http.MethodGet, fmt.Sprintf("/images/%s/get", nameOrID), nil, params)
- if err != nil {
- return err
- }
- if err := response.Process(nil); err != nil {
- return err
- }
- _, err = io.Copy(w, response.Body)
- return err
-}
-
-func (c Connection) PruneImages(all bool, filters []string) ([]string, error) {
- var (
- deleted []string
- )
- params := make(map[string]string)
- // FIXME How do we do []strings?
- //params["filters"] = format
- response, err := c.newRequest(http.MethodPost, "/images/prune", nil, params)
- if err != nil {
- return deleted, err
- }
- return deleted, response.Process(nil)
-}
-
-func (c Connection) TagImage(nameOrID string) error {
- var ()
- response, err := c.newRequest(http.MethodPost, fmt.Sprintf("/images/%s/tag", nameOrID), nil, nil)
- if err != nil {
- return err
- }
- return response.Process(nil)
-}
-
-func (c Connection) BuildImage(nameOrId string) {}
diff --git a/pkg/bindings/images/images.go b/pkg/bindings/images/images.go
new file mode 100644
index 000000000..5e3af7a60
--- /dev/null
+++ b/pkg/bindings/images/images.go
@@ -0,0 +1,231 @@
+package images
+
+import (
+ "context"
+ "errors"
+ "io"
+ "net/http"
+ "net/url"
+ "strconv"
+
+ "github.com/containers/libpod/pkg/api/handlers"
+ "github.com/containers/libpod/pkg/bindings"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/containers/libpod/pkg/inspect"
+)
+
+// Exists a lightweight way to determine if an image exists in local storage. It returns a
+// boolean response.
+func Exists(ctx context.Context, nameOrID string) (bool, error) {
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return false, err
+ }
+ response, err := conn.DoRequest(nil, http.MethodGet, "/images/%s/exists", nil, nameOrID)
+ if err != nil {
+ return false, err
+ }
+ return response.IsSuccess(), nil
+}
+
+// List returns a list of images in local storage. The all boolean and filters parameters are optional
+// ways to alter the image query.
+func List(ctx context.Context, all *bool, filters map[string][]string) ([]*entities.ImageSummary, error) {
+ var imageSummary []*entities.ImageSummary
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return nil, err
+ }
+ params := url.Values{}
+ if all != nil {
+ params.Set("all", strconv.FormatBool(*all))
+ }
+ if filters != nil {
+ strFilters, err := bindings.FiltersToString(filters)
+ if err != nil {
+ return nil, err
+ }
+ params.Set("filters", strFilters)
+ }
+ response, err := conn.DoRequest(nil, http.MethodGet, "/images/json", params)
+ if err != nil {
+ return imageSummary, err
+ }
+ return imageSummary, response.Process(&imageSummary)
+}
+
+// Get performs an image inspect. To have the on-disk size of the image calculated, you can
+// use the optional size parameter.
+func GetImage(ctx context.Context, nameOrID string, size *bool) (*inspect.ImageData, error) {
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return nil, err
+ }
+ params := url.Values{}
+ if size != nil {
+ params.Set("size", strconv.FormatBool(*size))
+ }
+ inspectedData := inspect.ImageData{}
+ response, err := conn.DoRequest(nil, http.MethodGet, "/images/%s/json", params, nameOrID)
+ if err != nil {
+ return &inspectedData, err
+ }
+ return &inspectedData, response.Process(&inspectedData)
+}
+
+func ImageTree(ctx context.Context, nameOrId string) error {
+ return bindings.ErrNotImplemented
+}
+
+// History returns the parent layers of an image.
+func History(ctx context.Context, nameOrID string) ([]*handlers.HistoryResponse, error) {
+ var history []*handlers.HistoryResponse
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return nil, err
+ }
+ response, err := conn.DoRequest(nil, http.MethodGet, "/images/%s/history", nil, nameOrID)
+ if err != nil {
+ return history, err
+ }
+ return history, response.Process(&history)
+}
+
+func Load(ctx context.Context, r io.Reader, name *string) (string, error) {
+ var id handlers.IDResponse
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return "", err
+ }
+ params := url.Values{}
+ if name != nil {
+ params.Set("reference", *name)
+ }
+ response, err := conn.DoRequest(r, http.MethodPost, "/images/load", params)
+ if err != nil {
+ return "", err
+ }
+ return id.ID, response.Process(&id)
+}
+
+// Remove deletes an image from local storage. The optional force parameter will forcibly remove
+// the image by removing all all containers, including those that are Running, first.
+func Remove(ctx context.Context, nameOrID string, force *bool) ([]map[string]string, error) {
+ var deletes []map[string]string
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return nil, err
+ }
+ params := url.Values{}
+ if force != nil {
+ params.Set("force", strconv.FormatBool(*force))
+ }
+ response, err := conn.DoRequest(nil, http.MethodDelete, "/images/%s", params, nameOrID)
+ if err != nil {
+ return nil, err
+ }
+ return deletes, response.Process(&deletes)
+}
+
+// Export saves an image from local storage as a tarball or image archive. The optional format
+// parameter is used to change the format of the output.
+func Export(ctx context.Context, nameOrID string, w io.Writer, format *string, compress *bool) error {
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return err
+ }
+ params := url.Values{}
+ if format != nil {
+ params.Set("format", *format)
+ }
+ if compress != nil {
+ params.Set("compress", strconv.FormatBool(*compress))
+ }
+ response, err := conn.DoRequest(nil, http.MethodGet, "/images/%s/get", params, nameOrID)
+ if err != nil {
+ return err
+ }
+ if err := response.Process(nil); err != nil {
+ return err
+ }
+ _, err = io.Copy(w, response.Body)
+ return err
+}
+
+// Prune removes unused images from local storage. The optional filters can be used to further
+// define which images should be pruned.
+func Prune(ctx context.Context, all *bool, filters map[string][]string) ([]string, error) {
+ var (
+ deleted []string
+ )
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return nil, err
+ }
+ params := url.Values{}
+ if all != nil {
+ params.Set("all", strconv.FormatBool(*all))
+ }
+ if filters != nil {
+ stringFilter, err := bindings.FiltersToString(filters)
+ if err != nil {
+ return nil, err
+ }
+ params.Set("filters", stringFilter)
+ }
+ response, err := conn.DoRequest(nil, http.MethodPost, "/images/prune", params)
+ if err != nil {
+ return deleted, err
+ }
+ return deleted, response.Process(&deleted)
+}
+
+// Tag adds an additional name to locally-stored image. Both the tag and repo parameters are required.
+func Tag(ctx context.Context, nameOrID, tag, repo string) error {
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return err
+ }
+ params := url.Values{}
+ params.Set("tag", tag)
+ params.Set("repo", repo)
+ response, err := conn.DoRequest(nil, http.MethodPost, "/images/%s/tag", params, nameOrID)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
+
+func Build(nameOrId string) {}
+
+// Imports adds the given image to the local image store. This can be done by file and the given reader
+// or via the url parameter. Additional metadata can be associated with the image by using the changes and
+// message parameters. The image can also be tagged given a reference. One of url OR r must be provided.
+func Import(ctx context.Context, changes []string, message, reference, u *string, r io.Reader) (string, error) {
+ var id handlers.IDResponse
+ if r != nil && u != nil {
+ return "", errors.New("url and r parameters cannot be used together")
+ }
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return "", err
+ }
+ params := url.Values{}
+ for _, change := range changes {
+ params.Add("changes", change)
+ }
+ if message != nil {
+ params.Set("message", *message)
+ }
+ if reference != nil {
+ params.Set("reference", *reference)
+ }
+ if u != nil {
+ params.Set("url", *u)
+ }
+ response, err := conn.DoRequest(r, http.MethodPost, "/images/import", params)
+ if err != nil {
+ return "", err
+ }
+ return id.ID, response.Process(&id)
+}
diff --git a/pkg/bindings/images/search.go b/pkg/bindings/images/search.go
new file mode 100644
index 000000000..183ff3d77
--- /dev/null
+++ b/pkg/bindings/images/search.go
@@ -0,0 +1,41 @@
+package images
+
+import (
+ "context"
+ "net/http"
+ "net/url"
+ "strconv"
+
+ "github.com/containers/libpod/libpod/image"
+ "github.com/containers/libpod/pkg/bindings"
+)
+
+// Search looks for the given image (term) in container image registries. The optional limit parameter sets
+// a maximum number of results returned. The optional filters parameter allow for more specific image
+// searches.
+func Search(ctx context.Context, term string, limit *int, filters map[string][]string) ([]image.SearchResult, error) {
+ var (
+ searchResults []image.SearchResult
+ )
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return nil, err
+ }
+ params := url.Values{}
+ params.Set("term", term)
+ if limit != nil {
+ params.Set("limit", strconv.Itoa(*limit))
+ }
+ if filters != nil {
+ stringFilter, err := bindings.FiltersToString(filters)
+ if err != nil {
+ return nil, err
+ }
+ params.Set("filters", stringFilter)
+ }
+ response, err := conn.DoRequest(nil, http.MethodGet, "/images/search", params)
+ if err != nil {
+ return searchResults, nil
+ }
+ return searchResults, response.Process(&searchResults)
+}
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/mount.go b/pkg/bindings/mount.go
deleted file mode 100644
index 2e3d6d7f6..000000000
--- a/pkg/bindings/mount.go
+++ /dev/null
@@ -1,26 +0,0 @@
-package bindings
-
-import (
- "fmt"
- "net/http"
-)
-
-func (c Connection) MountContainer(nameOrID string) (string, error) {
- var (
- path string
- )
- response, err := c.newRequest(http.MethodPost, fmt.Sprintf("/containers/%s/mount", nameOrID), nil, nil)
- if err != nil {
- return path, err
- }
- return path, response.Process(&path)
-}
-
-func (c Connection) GetMountedContainerPaths() (map[string]string, error) {
- mounts := make(map[string]string)
- response, err := c.newRequest(http.MethodGet, "/containers/showmounted", nil, nil)
- if err != nil {
- return mounts, err
- }
- return mounts, response.Process(&mounts)
-}
diff --git a/pkg/bindings/network.go b/pkg/bindings/network.go
deleted file mode 100644
index 383615e5d..000000000
--- a/pkg/bindings/network.go
+++ /dev/null
@@ -1,37 +0,0 @@
-package bindings
-
-import (
- "fmt"
- "net/http"
-
- "github.com/containernetworking/cni/libcni"
-)
-
-func (c Connection) CreateNetwork() {}
-func (c Connection) InspectNetwork(nameOrID string) (map[string]interface{}, error) {
- n := make(map[string]interface{})
- response, err := c.newRequest(http.MethodGet, fmt.Sprintf("/networks/%s/json", nameOrID), nil, nil)
- if err != nil {
- return n, err
- }
- return n, response.Process(&n)
-}
-
-func (c Connection) RemoveNetwork(nameOrID string) error {
- response, err := c.newRequest(http.MethodDelete, fmt.Sprintf("/networks/%s", nameOrID), nil, nil)
- if err != nil {
- return err
- }
- return response.Process(nil)
-}
-
-func (c Connection) ListNetworks() ([]*libcni.NetworkConfigList, error) {
- var (
- netList []*libcni.NetworkConfigList
- )
- response, err := c.newRequest(http.MethodGet, "/networks/json", nil, nil)
- if err != nil {
- return netList, err
- }
- return netList, response.Process(&netList)
-}
diff --git a/pkg/bindings/network/network.go b/pkg/bindings/network/network.go
new file mode 100644
index 000000000..c95b22953
--- /dev/null
+++ b/pkg/bindings/network/network.go
@@ -0,0 +1,50 @@
+package network
+
+import (
+ "context"
+ "net/http"
+
+ "github.com/containernetworking/cni/libcni"
+ "github.com/containers/libpod/pkg/bindings"
+)
+
+func Create() {}
+func Inspect(ctx context.Context, nameOrID string) (map[string]interface{}, error) {
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return nil, err
+ }
+ n := make(map[string]interface{})
+ response, err := conn.DoRequest(nil, http.MethodGet, "/networks/%s/json", nil, nameOrID)
+ if err != nil {
+ return n, err
+ }
+ return n, response.Process(&n)
+}
+
+func Remove(ctx context.Context, nameOrID string) error {
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return err
+ }
+ response, err := conn.DoRequest(nil, http.MethodDelete, "/networks/%s", nil, nameOrID)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
+
+func List(ctx context.Context) ([]*libcni.NetworkConfigList, error) {
+ var (
+ netList []*libcni.NetworkConfigList
+ )
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return nil, err
+ }
+ response, err := conn.DoRequest(nil, http.MethodGet, "/networks/json", nil)
+ if err != nil {
+ return netList, err
+ }
+ return netList, response.Process(&netList)
+}
diff --git a/pkg/bindings/play.go b/pkg/bindings/play.go
deleted file mode 100644
index a9dee82b1..000000000
--- a/pkg/bindings/play.go
+++ /dev/null
@@ -1,3 +0,0 @@
-package bindings
-
-func (c Connection) PlayKube() {}
diff --git a/pkg/bindings/play/play.go b/pkg/bindings/play/play.go
new file mode 100644
index 000000000..a6f03cad2
--- /dev/null
+++ b/pkg/bindings/play/play.go
@@ -0,0 +1,7 @@
+package play
+
+import "github.com/containers/libpod/pkg/bindings"
+
+func PlayKube() error {
+ return bindings.ErrNotImplemented
+}
diff --git a/pkg/bindings/pods.go b/pkg/bindings/pods.go
deleted file mode 100644
index 704d71477..000000000
--- a/pkg/bindings/pods.go
+++ /dev/null
@@ -1,129 +0,0 @@
-package bindings
-
-import (
- "fmt"
- "net/http"
- "strconv"
-
- "github.com/containers/libpod/libpod"
-)
-
-func (c Connection) CreatePod() error {
- // TODO
- return ErrNotImplemented
-}
-
-func (c Connection) PodExists(nameOrID string) (bool, error) {
- response, err := http.Get(c.makeEndpoint(fmt.Sprintf("/pods/%s/exists", nameOrID))) // nolint
- defer closeResponseBody(response)
- if err != nil {
- return false, err
- }
- return response.StatusCode == http.StatusOK, err
-}
-
-func (c Connection) InspectPod(nameOrID string) (*libpod.PodInspect, error) {
- inspect := libpod.PodInspect{}
- response, err := c.newRequest(http.MethodGet, fmt.Sprintf("/pods/%s/json", nameOrID), nil, nil)
- if err != nil {
- return &inspect, err
- }
- return &inspect, response.Process(&inspect)
-}
-
-func (c Connection) KillPod(nameOrID string, signal int) error {
- params := make(map[string]string)
- params["signal"] = strconv.Itoa(signal)
- response, err := c.newRequest(http.MethodPost, fmt.Sprintf("/pods/%s/kill", nameOrID), nil, params)
- if err != nil {
- return err
- }
- return response.Process(nil)
-}
-
-func (c Connection) PausePod(nameOrID string) error {
- response, err := c.newRequest(http.MethodPost, fmt.Sprintf("/pods/%s/pause", nameOrID), nil, nil)
- if err != nil {
- return err
- }
- return response.Process(nil)
-}
-
-func (c Connection) PrunePods(force bool) error {
- params := make(map[string]string)
- params["force"] = strconv.FormatBool(force)
- response, err := c.newRequest(http.MethodPost, "/pods/prune", nil, params)
- if err != nil {
- return err
- }
- return response.Process(nil)
-}
-
-func (c Connection) ListPods(filters []string) (*[]libpod.PodInspect, error) {
- var (
- inspect []libpod.PodInspect
- )
- params := make(map[string]string)
- // TODO I dont remember how to do this for []string{}
- // FIXME
- //params["filters"] = strconv.FormatBool(force)
- response, err := c.newRequest(http.MethodPost, "/pods/json", nil, params)
- if err != nil {
- return &inspect, err
- }
- return &inspect, response.Process(&inspect)
-}
-
-func (c Connection) RestartPod(nameOrID string) error {
- response, err := c.newRequest(http.MethodPost, fmt.Sprintf("/pods/%s/restart", nameOrID), nil, nil)
- if err != nil {
- return err
- }
- return response.Process(nil)
-}
-
-func (c Connection) RemovePod(nameOrID string, force bool) error {
- params := make(map[string]string)
- params["force"] = strconv.FormatBool(force)
- response, err := c.newRequest(http.MethodDelete, fmt.Sprintf("/pods/%s", nameOrID), nil, params)
- if err != nil {
- return err
- }
- return response.Process(nil)
-}
-
-func (c Connection) StartPod(nameOrID string) error {
- response, err := c.newRequest(http.MethodDelete, fmt.Sprintf("/pods/%s/start", nameOrID), nil, nil)
- if err != nil {
- return err
- }
- return response.Process(nil)
-}
-
-func (c Connection) PodStats() error {
- // TODO
- return ErrNotImplemented
-}
-
-func (c Connection) StopPod(nameOrID string, timeout int) error {
- params := make(map[string]string)
- params["t"] = strconv.Itoa(timeout)
- response, err := c.newRequest(http.MethodPost, fmt.Sprintf("/pods/%s/stop", nameOrID), nil, params)
- if err != nil {
- return err
- }
- return response.Process(nil)
-}
-
-func (c Connection) PodTop() error {
- // TODO
- return ErrNotImplemented // nolint:typecheck
-}
-
-func (c Connection) UnpausePod(nameOrID string) error {
- response, err := c.newRequest(http.MethodPost, fmt.Sprintf("/pods/%s/unpause", nameOrID), nil, nil)
- if err != nil {
- return err
- }
- return response.Process(nil)
-}
diff --git a/pkg/bindings/pods/pods.go b/pkg/bindings/pods/pods.go
new file mode 100644
index 000000000..bb0abebc4
--- /dev/null
+++ b/pkg/bindings/pods/pods.go
@@ -0,0 +1,233 @@
+package pods
+
+import (
+ "context"
+ "net/http"
+ "net/url"
+ "strconv"
+ "strings"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/pkg/bindings"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/containers/libpod/pkg/specgen"
+ jsoniter "github.com/json-iterator/go"
+)
+
+func CreatePodFromSpec(ctx context.Context, s *specgen.PodSpecGenerator) (*entities.PodCreateReport, error) {
+ var (
+ pcr entities.PodCreateReport
+ )
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return nil, err
+ }
+ specgenString, err := jsoniter.MarshalToString(s)
+ if err != nil {
+ return nil, err
+ }
+ stringReader := strings.NewReader(specgenString)
+ response, err := conn.DoRequest(stringReader, http.MethodPost, "/pods/create", nil)
+ if err != nil {
+ return nil, err
+ }
+ return &pcr, response.Process(&pcr)
+}
+
+// Exists is a lightweight method to determine if a pod exists in local storage
+func Exists(ctx context.Context, nameOrID string) (bool, error) {
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return false, err
+ }
+ response, err := conn.DoRequest(nil, http.MethodGet, "/pods/%s/exists", nil, nameOrID)
+ if err != nil {
+ return false, err
+ }
+ return response.IsSuccess(), nil
+}
+
+// Inspect returns low-level information about the given pod.
+func Inspect(ctx context.Context, nameOrID string) (*libpod.PodInspect, error) {
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return nil, err
+ }
+ inspect := libpod.PodInspect{}
+ response, err := conn.DoRequest(nil, http.MethodGet, "/pods/%s/json", nil, nameOrID)
+ if err != nil {
+ return &inspect, err
+ }
+ return &inspect, response.Process(&inspect)
+}
+
+// Kill sends a SIGTERM to all the containers in a pod. The optional signal parameter
+// can be used to override SIGTERM.
+func Kill(ctx context.Context, nameOrID string, signal *string) (*entities.PodKillReport, error) {
+ var (
+ report entities.PodKillReport
+ )
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return nil, err
+ }
+ params := url.Values{}
+ if signal != nil {
+ params.Set("signal", *signal)
+ }
+ response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/kill", params, nameOrID)
+ if err != nil {
+ return nil, err
+ }
+ return &report, response.Process(&report)
+}
+
+// Pause pauses all running containers in a given pod.
+func Pause(ctx context.Context, nameOrID string) (*entities.PodPauseReport, error) {
+ var report entities.PodPauseReport
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return nil, err
+ }
+ response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/pause", nil, nameOrID)
+ if err != nil {
+ return nil, err
+ }
+ return &report, response.Process(&report)
+}
+
+// Prune removes all non-running pods in local storage.
+func Prune(ctx context.Context) error {
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return err
+ }
+ response, err := conn.DoRequest(nil, http.MethodPost, "/pods/prune", nil)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
+
+// List returns all pods in local storage. The optional filters parameter can
+// be used to refine which pods should be listed.
+func List(ctx context.Context, filters map[string][]string) ([]*entities.ListPodsReport, error) {
+ var (
+ podsReports []*entities.ListPodsReport
+ )
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return nil, err
+ }
+ params := url.Values{}
+ if filters != nil {
+ stringFilter, err := bindings.FiltersToString(filters)
+ if err != nil {
+ return nil, err
+ }
+ params.Set("filters", stringFilter)
+ }
+ response, err := conn.DoRequest(nil, http.MethodGet, "/pods/json", params)
+ if err != nil {
+ return podsReports, err
+ }
+ return podsReports, response.Process(&podsReports)
+}
+
+// Restart restarts all containers in a pod.
+func Restart(ctx context.Context, nameOrID string) (*entities.PodRestartReport, error) {
+ var report entities.PodRestartReport
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return nil, err
+ }
+ response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/restart", nil, nameOrID)
+ if err != nil {
+ return nil, err
+ }
+ return &report, response.Process(&report)
+}
+
+// Remove deletes a Pod from from local storage. The optional force parameter denotes
+// that the Pod can be removed even if in a running state.
+func Remove(ctx context.Context, nameOrID string, force *bool) (*entities.PodRmReport, error) {
+ var report entities.PodRmReport
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return nil, err
+ }
+ params := url.Values{}
+ if force != nil {
+ params.Set("force", strconv.FormatBool(*force))
+ }
+ response, err := conn.DoRequest(nil, http.MethodDelete, "/pods/%s", params, nameOrID)
+ if err != nil {
+ return nil, err
+ }
+ return &report, response.Process(&report)
+}
+
+// Start starts all containers in a pod.
+func Start(ctx context.Context, nameOrID string) (*entities.PodStartReport, error) {
+ var report entities.PodStartReport
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return nil, err
+ }
+ response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/start", nil, nameOrID)
+ if err != nil {
+ return nil, err
+ }
+ if response.StatusCode == http.StatusNotModified {
+ report.Id = nameOrID
+ return &report, nil
+ }
+ return &report, response.Process(&report)
+}
+
+func Stats() error {
+ // TODO
+ return bindings.ErrNotImplemented
+}
+
+// Stop stops all containers in a Pod. The optional timeout parameter can be
+// used to override the timeout before the container is killed.
+func Stop(ctx context.Context, nameOrID string, timeout *int) (*entities.PodStopReport, error) {
+ var report entities.PodStopReport
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return nil, err
+ }
+ params := url.Values{}
+ if timeout != nil {
+ params.Set("t", strconv.Itoa(*timeout))
+ }
+ response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/stop", params, nameOrID)
+ if err != nil {
+ return nil, err
+ }
+ if response.StatusCode == http.StatusNotModified {
+ report.Id = nameOrID
+ return &report, nil
+ }
+ return &report, response.Process(&report)
+}
+
+func Top() error {
+ // TODO
+ return bindings.ErrNotImplemented // nolint:typecheck
+}
+
+// Unpause unpauses all paused containers in a Pod.
+func Unpause(ctx context.Context, nameOrID string) (*entities.PodUnpauseReport, error) {
+ var report entities.PodUnpauseReport
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return nil, err
+ }
+ response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/unpause", nil, nameOrID)
+ if err != nil {
+ return nil, err
+ }
+ return &report, response.Process(&report)
+}
diff --git a/pkg/bindings/search.go b/pkg/bindings/search.go
deleted file mode 100644
index 0f462357c..000000000
--- a/pkg/bindings/search.go
+++ /dev/null
@@ -1,39 +0,0 @@
-package bindings
-
-import (
- "net/http"
- "strconv"
-
- "github.com/containers/libpod/libpod/image"
-)
-
-type ImageSearchFilters struct {
- Automated bool `json:"automated"`
- Official bool `json:"official"`
- Stars int `json:"stars"`
-}
-
-// TODO This method can be concluded when we determine how we want the filters to work on the
-// API end
-func (i *ImageSearchFilters) ToMapJSON() string {
- return ""
-}
-
-func (c Connection) SearchImages(term string, limit int, filters *ImageSearchFilters) ([]image.SearchResult, error) {
- var (
- searchResults []image.SearchResult
- )
- params := make(map[string]string)
- params["term"] = term
- if limit > 0 {
- params["limit"] = strconv.Itoa(limit)
- }
- if filters != nil {
- params["filters"] = filters.ToMapJSON()
- }
- response, err := c.newRequest(http.MethodGet, "/images/search", nil, params)
- if err != nil {
- return searchResults, nil
- }
- return searchResults, response.Process(&searchResults)
-}
diff --git a/pkg/bindings/system/system.go b/pkg/bindings/system/system.go
new file mode 100644
index 000000000..fce8bbb8e
--- /dev/null
+++ b/pkg/bindings/system/system.go
@@ -0,0 +1,61 @@
+package system
+
+import (
+ "context"
+ "encoding/json"
+ "io"
+ "net/http"
+ "net/url"
+
+ "github.com/containers/libpod/pkg/api/handlers"
+ "github.com/containers/libpod/pkg/bindings"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+)
+
+// Events allows you to monitor libdpod related events like container creation and
+// removal. The events are then passed to the eventChan provided. The optional cancelChan
+// can be used to cancel the read of events and close down the HTTP connection.
+func Events(ctx context.Context, eventChan chan (handlers.Event), cancelChan chan bool, since, until *string, filters map[string][]string) error {
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return err
+ }
+ params := url.Values{}
+ if since != nil {
+ params.Set("since", *since)
+ }
+ if until != nil {
+ params.Set("until", *until)
+ }
+ if filters != nil {
+ filterString, err := bindings.FiltersToString(filters)
+ if err != nil {
+ return errors.Wrap(err, "invalid filters")
+ }
+ params.Set("filters", filterString)
+ }
+ response, err := conn.DoRequest(nil, http.MethodGet, "/events", params)
+ if err != nil {
+ return err
+ }
+ if cancelChan != nil {
+ go func() {
+ <-cancelChan
+ err = response.Body.Close()
+ logrus.Error(errors.Wrap(err, "unable to close event response body"))
+ }()
+ }
+ dec := json.NewDecoder(response.Body)
+ for {
+ e := handlers.Event{}
+ if err := dec.Decode(&e); err != nil {
+ if err == io.EOF {
+ break
+ }
+ return errors.Wrap(err, "unable to decode event response")
+ }
+ eventChan <- e
+ }
+ return nil
+}
diff --git a/pkg/bindings/test/common_test.go b/pkg/bindings/test/common_test.go
new file mode 100644
index 000000000..6b8d6788c
--- /dev/null
+++ b/pkg/bindings/test/common_test.go
@@ -0,0 +1,271 @@
+package test_bindings
+
+import (
+ "context"
+ "fmt"
+ "github.com/containers/libpod/libpod/define"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strings"
+
+ . "github.com/containers/libpod/pkg/bindings"
+ "github.com/containers/libpod/pkg/bindings/containers"
+ "github.com/containers/libpod/pkg/specgen"
+ "github.com/onsi/ginkgo"
+ "github.com/onsi/gomega/gexec"
+ "github.com/pkg/errors"
+)
+
+type testImage struct {
+ name string
+ shortName string
+ tarballName string
+}
+
+const (
+ devPodmanBinaryLocation string = "../../../bin/podman"
+ defaultPodmanBinaryLocation string = "/usr/bin/podman"
+)
+
+func getPodmanBinary() string {
+ _, err := os.Stat(devPodmanBinaryLocation)
+ if os.IsNotExist(err) {
+ return defaultPodmanBinaryLocation
+ }
+ return devPodmanBinaryLocation
+}
+
+var (
+ ImageCacheDir = "/tmp/podman/imagecachedir"
+ LockTmpDir string
+ alpine = testImage{
+ name: "docker.io/library/alpine:latest",
+ shortName: "alpine",
+ tarballName: "alpine.tar",
+ }
+ busybox = testImage{
+ name: "docker.io/library/busybox:latest",
+ shortName: "busybox",
+ tarballName: "busybox.tar",
+ }
+ CACHE_IMAGES = []testImage{alpine, busybox}
+)
+
+type bindingTest struct {
+ artifactDirPath string
+ imageCacheDir string
+ sock string
+ tempDirPath string
+ runRoot string
+ crioRoot string
+ conn context.Context
+}
+
+func (b *bindingTest) NewConnection() error {
+ connText, err := NewConnection(context.Background(), b.sock)
+ if err != nil {
+ return err
+ }
+ b.conn = connText
+ return nil
+}
+
+func (b *bindingTest) runPodman(command []string) *gexec.Session {
+ var cmd []string
+ podmanBinary := getPodmanBinary()
+ val, ok := os.LookupEnv("PODMAN_BINARY")
+ if ok {
+ podmanBinary = val
+ }
+ val, ok = os.LookupEnv("CGROUP_MANAGER")
+ if ok {
+ cmd = append(cmd, "--cgroup-manager", val)
+ }
+ val, ok = os.LookupEnv("CNI_CONFIG_DIR")
+ if ok {
+ cmd = append(cmd, "--cni-config-dir", val)
+ }
+ val, ok = os.LookupEnv("CONMON")
+ if ok {
+ cmd = append(cmd, "--conmon", val)
+ }
+ val, ok = os.LookupEnv("ROOT")
+ if ok {
+ cmd = append(cmd, "--root", val)
+ } else {
+ cmd = append(cmd, "--root", b.crioRoot)
+ }
+ val, ok = os.LookupEnv("OCI_RUNTIME")
+ if ok {
+ cmd = append(cmd, "--runtime", val)
+ }
+ val, ok = os.LookupEnv("RUNROOT")
+ if ok {
+ cmd = append(cmd, "--runroot", val)
+ } else {
+ cmd = append(cmd, "--runroot", b.runRoot)
+ }
+ val, ok = os.LookupEnv("TEMPDIR")
+ if ok {
+ cmd = append(cmd, "--tmpdir", val)
+ } else {
+ cmd = append(cmd, "--tmpdir", b.tempDirPath)
+ }
+ val, ok = os.LookupEnv("STORAGE_DRIVER")
+ if ok {
+ cmd = append(cmd, "--storage-driver", val)
+ }
+ val, ok = os.LookupEnv("STORAGE_OPTIONS")
+ if ok {
+ cmd = append(cmd, "--storage", val)
+ }
+ cmd = append(cmd, command...)
+ c := exec.Command(podmanBinary, cmd...)
+ fmt.Printf("Running: %s %s\n", podmanBinary, strings.Join(cmd, " "))
+ session, err := gexec.Start(c, ginkgo.GinkgoWriter, ginkgo.GinkgoWriter)
+ if err != nil {
+ panic(errors.Errorf("unable to run podman command: %q", cmd))
+ }
+ return session
+}
+
+func newBindingTest() *bindingTest {
+ tmpPath, _ := createTempDirInTempDir()
+ b := bindingTest{
+ crioRoot: filepath.Join(tmpPath, "crio"),
+ runRoot: filepath.Join(tmpPath, "run"),
+ artifactDirPath: "",
+ imageCacheDir: "",
+ sock: fmt.Sprintf("unix://%s", filepath.Join(tmpPath, "api.sock")),
+ tempDirPath: tmpPath,
+ }
+ return &b
+}
+
+// createTempDirinTempDir create a temp dir with prefix podman_test
+func createTempDirInTempDir() (string, error) {
+ return ioutil.TempDir("", "libpod_api")
+}
+
+func (b *bindingTest) startAPIService() *gexec.Session {
+ var (
+ cmd []string
+ )
+ cmd = append(cmd, "--log-level=debug", "--events-backend=file", "system", "service", "--timeout=0", b.sock)
+ return b.runPodman(cmd)
+}
+
+func (b *bindingTest) cleanup() {
+ s := b.runPodman([]string{"stop", "-a", "-t", "0"})
+ s.Wait(45)
+ if err := os.RemoveAll(b.tempDirPath); err != nil {
+ fmt.Println(err)
+ }
+}
+
+// Pull is a helper function to pull in images
+func (b *bindingTest) Pull(name string) {
+ p := b.runPodman([]string{"pull", name})
+ p.Wait(45)
+}
+
+func (b *bindingTest) Save(i testImage) {
+ p := b.runPodman([]string{"save", "-o", filepath.Join(ImageCacheDir, i.tarballName), i.name})
+ p.Wait(45)
+}
+
+func (b *bindingTest) RestoreImagesFromCache() {
+ for _, i := range CACHE_IMAGES {
+ b.restoreImageFromCache(i)
+ }
+}
+func (b *bindingTest) restoreImageFromCache(i testImage) {
+ p := b.runPodman([]string{"load", "-i", filepath.Join(ImageCacheDir, i.tarballName), i.name})
+ p.Wait(45)
+}
+
+// Run a container within or without a pod
+// and add or append the alpine image to it
+func (b *bindingTest) RunTopContainer(containerName *string, insidePod *bool, podName *string) (string, error) {
+ s := specgen.NewSpecGenerator(alpine.name)
+ s.Terminal = false
+ s.Command = []string{"top"}
+ if containerName != nil {
+ s.Name = *containerName
+ }
+ if insidePod != nil && podName != nil {
+ s.Pod = *podName
+ }
+ ctr, err := containers.CreateWithSpec(b.conn, s)
+ if err != nil {
+ return "", nil
+ }
+ err = containers.Start(b.conn, ctr.ID, nil)
+ if err != nil {
+ return "", err
+ }
+ wait := define.ContainerStateRunning
+ _, err = containers.Wait(b.conn, ctr.ID, &wait)
+ return ctr.ID, err
+}
+
+// This method creates a pod with the given pod name.
+// Podname is an optional parameter
+func (b *bindingTest) Podcreate(name *string) {
+ if name != nil {
+ podname := *name
+ b.runPodman([]string{"pod", "create", "--name", podname}).Wait(45)
+ } else {
+ b.runPodman([]string{"pod", "create"}).Wait(45)
+ }
+}
+
+// StringInSlice returns a boolean based on whether a given
+// string is in a given slice
+func StringInSlice(s string, sl []string) bool {
+ for _, val := range sl {
+ if s == val {
+ return true
+ }
+ }
+ return false
+}
+
+var _ = ginkgo.SynchronizedBeforeSuite(func() []byte {
+ // make cache dir
+ if err := os.MkdirAll(ImageCacheDir, 0777); err != nil {
+ fmt.Printf("%q\n", err)
+ os.Exit(1)
+ }
+
+ // If running localized tests, the cache dir is created and populated. if the
+ // tests are remote, this is a no-op
+ createCache()
+ path, err := ioutil.TempDir("", "libpodlock")
+ if err != nil {
+ fmt.Println(err)
+ os.Exit(1)
+ }
+ return []byte(path)
+}, func(data []byte) {
+ LockTmpDir = string(data)
+})
+
+func createCache() {
+ b := newBindingTest()
+ for _, i := range CACHE_IMAGES {
+ _, err := os.Stat(filepath.Join(ImageCacheDir, i.tarballName))
+ if os.IsNotExist(err) {
+ // pull the image
+ b.Pull(i.name)
+ b.Save(i)
+ }
+ }
+ b.cleanup()
+}
+
+func isStopped(state string) bool {
+ return state == "exited" || state == "stopped"
+}
diff --git a/pkg/bindings/test/containers_test.go b/pkg/bindings/test/containers_test.go
new file mode 100644
index 000000000..9dd9cb707
--- /dev/null
+++ b/pkg/bindings/test/containers_test.go
@@ -0,0 +1,413 @@
+package test_bindings
+
+import (
+ "net/http"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/containers/libpod/libpod/define"
+ "github.com/containers/libpod/pkg/bindings"
+ "github.com/containers/libpod/pkg/bindings/containers"
+ "github.com/containers/libpod/pkg/specgen"
+ "github.com/containers/libpod/test/utils"
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+ "github.com/onsi/gomega/gexec"
+)
+
+var _ = Describe("Podman containers ", func() {
+ var (
+ bt *bindingTest
+ s *gexec.Session
+ err error
+ )
+
+ 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("podman pause a bogus container", func() {
+ // Pausing bogus container should return 404
+ err = containers.Pause(bt.conn, "foobar")
+ Expect(err).ToNot(BeNil())
+ code, _ := bindings.CheckResponseCode(err)
+ Expect(code).To(BeNumerically("==", http.StatusNotFound))
+ })
+
+ It("podman unpause a bogus container", func() {
+ // Unpausing bogus container should return 404
+ err = containers.Unpause(bt.conn, "foobar")
+ Expect(err).ToNot(BeNil())
+ code, _ := bindings.CheckResponseCode(err)
+ Expect(code).To(BeNumerically("==", http.StatusNotFound))
+ })
+
+ It("podman pause a running container by name", func() {
+ // Pausing by name should work
+ var name = "top"
+ _, err := bt.RunTopContainer(&name, &bindings.PFalse, nil)
+ Expect(err).To(BeNil())
+ err = containers.Pause(bt.conn, name)
+ Expect(err).To(BeNil())
+
+ // Ensure container is paused
+ data, err := containers.Inspect(bt.conn, name, nil)
+ Expect(err).To(BeNil())
+ Expect(data.State.Status).To(Equal("paused"))
+ })
+
+ It("podman pause a running container by id", func() {
+ // Pausing by id should work
+ var name = "top"
+ cid, err := bt.RunTopContainer(&name, &bindings.PFalse, nil)
+ Expect(err).To(BeNil())
+ err = containers.Pause(bt.conn, cid)
+ Expect(err).To(BeNil())
+
+ // Ensure container is paused
+ data, err := containers.Inspect(bt.conn, cid, nil)
+ Expect(err).To(BeNil())
+ Expect(data.State.Status).To(Equal("paused"))
+ })
+
+ It("podman unpause a running container by name", func() {
+ // Unpausing by name should work
+ var name = "top"
+ _, err := bt.RunTopContainer(&name, &bindings.PFalse, nil)
+ Expect(err).To(BeNil())
+ err = containers.Pause(bt.conn, name)
+ Expect(err).To(BeNil())
+ err = containers.Unpause(bt.conn, name)
+ Expect(err).To(BeNil())
+
+ // Ensure container is unpaused
+ data, err := containers.Inspect(bt.conn, name, nil)
+ Expect(err).To(BeNil())
+ Expect(data.State.Status).To(Equal("running"))
+ })
+
+ It("podman unpause a running container by ID", func() {
+ // Unpausing by ID should work
+ var name = "top"
+ _, err := bt.RunTopContainer(&name, &bindings.PFalse, nil)
+ Expect(err).To(BeNil())
+ // Pause by name
+ err = containers.Pause(bt.conn, name)
+ //paused := "paused"
+ //_, err = containers.Wait(bt.conn, cid, &paused)
+ //Expect(err).To(BeNil())
+ err = containers.Unpause(bt.conn, name)
+ Expect(err).To(BeNil())
+
+ // Ensure container is unpaused
+ data, err := containers.Inspect(bt.conn, name, nil)
+ Expect(err).To(BeNil())
+ Expect(data.State.Status).To(Equal("running"))
+ })
+
+ It("podman pause a paused container by name", func() {
+ // Pausing a paused container by name should fail
+ var name = "top"
+ _, err := bt.RunTopContainer(&name, &bindings.PFalse, nil)
+ Expect(err).To(BeNil())
+ err = containers.Pause(bt.conn, name)
+ Expect(err).To(BeNil())
+ err = containers.Pause(bt.conn, name)
+ Expect(err).ToNot(BeNil())
+ code, _ := bindings.CheckResponseCode(err)
+ Expect(code).To(BeNumerically("==", http.StatusInternalServerError))
+ })
+
+ It("podman pause a paused container by id", func() {
+ // Pausing a paused container by id should fail
+ var name = "top"
+ cid, err := bt.RunTopContainer(&name, &bindings.PFalse, nil)
+ Expect(err).To(BeNil())
+ err = containers.Pause(bt.conn, cid)
+ Expect(err).To(BeNil())
+ err = containers.Pause(bt.conn, cid)
+ Expect(err).ToNot(BeNil())
+ code, _ := bindings.CheckResponseCode(err)
+ Expect(code).To(BeNumerically("==", http.StatusInternalServerError))
+ })
+
+ It("podman pause a stopped container by name", func() {
+ // Pausing a stopped container by name should fail
+ var name = "top"
+ _, err := bt.RunTopContainer(&name, &bindings.PFalse, nil)
+ Expect(err).To(BeNil())
+ err = containers.Stop(bt.conn, name, nil)
+ Expect(err).To(BeNil())
+ err = containers.Pause(bt.conn, name)
+ Expect(err).ToNot(BeNil())
+ code, _ := bindings.CheckResponseCode(err)
+ Expect(code).To(BeNumerically("==", http.StatusInternalServerError))
+ })
+
+ It("podman pause a stopped container by id", func() {
+ // Pausing a stopped container by id should fail
+ var name = "top"
+ cid, err := bt.RunTopContainer(&name, &bindings.PFalse, nil)
+ Expect(err).To(BeNil())
+ err = containers.Stop(bt.conn, cid, nil)
+ Expect(err).To(BeNil())
+ err = containers.Pause(bt.conn, cid)
+ Expect(err).ToNot(BeNil())
+ code, _ := bindings.CheckResponseCode(err)
+ Expect(code).To(BeNumerically("==", http.StatusInternalServerError))
+ })
+
+ It("podman remove a paused container by id without force", func() {
+ // Removing a paused container without force should fail
+ var name = "top"
+ cid, err := bt.RunTopContainer(&name, &bindings.PFalse, nil)
+ Expect(err).To(BeNil())
+ err = containers.Pause(bt.conn, cid)
+ Expect(err).To(BeNil())
+ err = containers.Remove(bt.conn, cid, &bindings.PFalse, &bindings.PFalse)
+ Expect(err).ToNot(BeNil())
+ code, _ := bindings.CheckResponseCode(err)
+ Expect(code).To(BeNumerically("==", http.StatusInternalServerError))
+ })
+
+ It("podman remove a paused container by id with force", func() {
+ // FIXME: Skip on F31 and later
+ host := utils.GetHostDistributionInfo()
+ osVer, err := strconv.Atoi(host.Version)
+ Expect(err).To(BeNil())
+ if host.Distribution == "fedora" && osVer >= 31 {
+ Skip("FIXME: https://github.com/containers/libpod/issues/5325")
+ }
+
+ // Removing a paused container with force should work
+ var name = "top"
+ cid, err := bt.RunTopContainer(&name, &bindings.PFalse, nil)
+ Expect(err).To(BeNil())
+ err = containers.Pause(bt.conn, cid)
+ Expect(err).To(BeNil())
+ err = containers.Remove(bt.conn, cid, &bindings.PTrue, &bindings.PFalse)
+ Expect(err).To(BeNil())
+ })
+
+ It("podman stop a paused container by name", func() {
+ // Stopping a paused container by name should fail
+ var name = "top"
+ _, err := bt.RunTopContainer(&name, &bindings.PFalse, nil)
+ Expect(err).To(BeNil())
+ err = containers.Pause(bt.conn, name)
+ Expect(err).To(BeNil())
+ err = containers.Stop(bt.conn, name, nil)
+ Expect(err).ToNot(BeNil())
+ code, _ := bindings.CheckResponseCode(err)
+ Expect(code).To(BeNumerically("==", http.StatusInternalServerError))
+ })
+
+ It("podman stop a paused container by id", func() {
+ // Stopping a paused container by id should fail
+ var name = "top"
+ cid, err := bt.RunTopContainer(&name, &bindings.PFalse, nil)
+ Expect(err).To(BeNil())
+ err = containers.Pause(bt.conn, cid)
+ Expect(err).To(BeNil())
+ err = containers.Stop(bt.conn, cid, nil)
+ Expect(err).ToNot(BeNil())
+ code, _ := bindings.CheckResponseCode(err)
+ Expect(code).To(BeNumerically("==", http.StatusInternalServerError))
+ })
+
+ It("podman stop a running container by name", func() {
+ // Stopping a running container by name should work
+ var name = "top"
+ _, err := bt.RunTopContainer(&name, &bindings.PFalse, nil)
+ Expect(err).To(BeNil())
+ err = containers.Stop(bt.conn, name, nil)
+ Expect(err).To(BeNil())
+
+ // Ensure container is stopped
+ data, err := containers.Inspect(bt.conn, name, nil)
+ Expect(err).To(BeNil())
+ Expect(isStopped(data.State.Status)).To(BeTrue())
+ })
+
+ It("podman stop a running container by ID", func() {
+ // Stopping a running container by ID should work
+ var name = "top"
+ cid, err := bt.RunTopContainer(&name, &bindings.PFalse, nil)
+ Expect(err).To(BeNil())
+ err = containers.Stop(bt.conn, cid, nil)
+ Expect(err).To(BeNil())
+
+ // Ensure container is stopped
+ data, err := containers.Inspect(bt.conn, name, nil)
+ Expect(err).To(BeNil())
+ Expect(isStopped(data.State.Status)).To(BeTrue())
+ })
+
+ It("podman wait no condition", func() {
+ var (
+ name = "top"
+ exitCode int32 = -1
+ )
+ _, err := containers.Wait(bt.conn, "foobar", nil)
+ Expect(err).ToNot(BeNil())
+ code, _ := bindings.CheckResponseCode(err)
+ Expect(code).To(BeNumerically("==", http.StatusNotFound))
+
+ errChan := make(chan error)
+ _, err = bt.RunTopContainer(&name, nil, nil)
+ Expect(err).To(BeNil())
+ go func() {
+ exitCode, err = containers.Wait(bt.conn, name, nil)
+ errChan <- err
+ close(errChan)
+ }()
+ err = containers.Stop(bt.conn, name, nil)
+ Expect(err).To(BeNil())
+ wait := <-errChan
+ Expect(wait).To(BeNil())
+ Expect(exitCode).To(BeNumerically("==", 143))
+ })
+
+ It("podman wait to pause|unpause condition", func() {
+ var (
+ name = "top"
+ exitCode int32 = -1
+ pause = define.ContainerStatePaused
+ running = define.ContainerStateRunning
+ )
+ errChan := make(chan error)
+ _, err := bt.RunTopContainer(&name, nil, nil)
+ Expect(err).To(BeNil())
+ go func() {
+ exitCode, err = containers.Wait(bt.conn, name, &pause)
+ errChan <- err
+ close(errChan)
+ }()
+ err = containers.Pause(bt.conn, name)
+ Expect(err).To(BeNil())
+ wait := <-errChan
+ Expect(wait).To(BeNil())
+ Expect(exitCode).To(BeNumerically("==", -1))
+
+ errChan = make(chan error)
+ go func() {
+ _, waitErr := containers.Wait(bt.conn, name, &running)
+ errChan <- waitErr
+ close(errChan)
+ }()
+ err = containers.Unpause(bt.conn, name)
+ Expect(err).To(BeNil())
+ unPausewait := <-errChan
+ Expect(unPausewait).To(BeNil())
+ Expect(exitCode).To(BeNumerically("==", -1))
+ })
+
+ It("run healthcheck", func() {
+ bt.runPodman([]string{"run", "-d", "--name", "hc", "--health-interval", "disable", "--health-retries", "2", "--health-cmd", "ls / || exit 1", alpine.name, "top"})
+
+ // bogus name should result in 404
+ _, err := containers.RunHealthCheck(bt.conn, "foobar")
+ Expect(err).ToNot(BeNil())
+ code, _ := bindings.CheckResponseCode(err)
+ Expect(code).To(BeNumerically("==", http.StatusNotFound))
+
+ // a container that has no healthcheck should be a 409
+ var name = "top"
+ bt.RunTopContainer(&name, &bindings.PFalse, nil)
+ _, err = containers.RunHealthCheck(bt.conn, name)
+ Expect(err).ToNot(BeNil())
+ code, _ = bindings.CheckResponseCode(err)
+ Expect(code).To(BeNumerically("==", http.StatusConflict))
+
+ // TODO for the life of me, i cannot get this to work. maybe another set
+ // of eyes will
+ // successful healthcheck
+ //status := "healthy"
+ //for i:=0; i < 10; i++ {
+ // result, err := containers.RunHealthCheck(connText, "hc")
+ // Expect(err).To(BeNil())
+ // if result.Status != "healthy" {
+ // fmt.Println("Healthcheck container still starting, retrying in 1 second")
+ // time.Sleep(1 * time.Second)
+ // continue
+ // }
+ // status = result.Status
+ // break
+ //}
+ //Expect(status).To(Equal("healthy"))
+
+ // TODO enable this when wait is working
+ // healthcheck on a stopped container should be a 409
+ //err = containers.Stop(connText, "hc", nil)
+ //Expect(err).To(BeNil())
+ //_, err = containers.Wait(connText, "hc")
+ //Expect(err).To(BeNil())
+ //_, err = containers.RunHealthCheck(connText, "hc")
+ //code, _ = bindings.CheckResponseCode(err)
+ //Expect(code).To(BeNumerically("==", http.StatusConflict))
+ })
+
+ It("logging", func() {
+ stdoutChan := make(chan string, 10)
+ s := specgen.NewSpecGenerator(alpine.name)
+ s.Terminal = true
+ s.Command = []string{"date", "-R"}
+ r, err := containers.CreateWithSpec(bt.conn, s)
+ Expect(err).To(BeNil())
+ err = containers.Start(bt.conn, r.ID, nil)
+ Expect(err).To(BeNil())
+
+ _, err = containers.Wait(bt.conn, r.ID, nil)
+ Expect(err).To(BeNil())
+
+ opts := containers.LogOptions{Stdout: &bindings.PTrue, Follow: &bindings.PTrue}
+ go func() {
+ containers.Logs(bt.conn, r.ID, opts, stdoutChan, nil)
+ }()
+ o := <-stdoutChan
+ o = strings.ReplaceAll(o, "\r", "")
+ _, err = time.Parse(time.RFC1123Z, o)
+ Expect(err).To(BeNil())
+ })
+
+ It("podman top", func() {
+ var name = "top"
+ cid, err := bt.RunTopContainer(&name, &bindings.PFalse, nil)
+ Expect(err).To(BeNil())
+
+ // By name
+ output, err := containers.Top(bt.conn, name, nil)
+ Expect(err).To(BeNil())
+
+ // By id
+ output, err = containers.Top(bt.conn, cid, nil)
+ Expect(err).To(BeNil())
+
+ // With descriptors
+ output, err = containers.Top(bt.conn, cid, []string{"user,pid,hpid"})
+ Expect(err).To(BeNil())
+ header := strings.Split(output[0], "\t")
+ for _, d := range []string{"USER", "PID", "HPID"} {
+ Expect(d).To(BeElementOf(header))
+ }
+
+ // With bogus ID
+ _, err = containers.Top(bt.conn, "IdoNotExist", nil)
+ Expect(err).ToNot(BeNil())
+
+ // With bogus descriptors
+ _, err = containers.Top(bt.conn, cid, []string{"Me,Neither"})
+ Expect(err).To(BeNil())
+ })
+})
diff --git a/pkg/bindings/test/create_test.go b/pkg/bindings/test/create_test.go
new file mode 100644
index 000000000..f83a9b14d
--- /dev/null
+++ b/pkg/bindings/test/create_test.go
@@ -0,0 +1,50 @@
+package test_bindings
+
+import (
+ "time"
+
+ "github.com/containers/libpod/pkg/bindings/containers"
+ "github.com/containers/libpod/pkg/specgen"
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+ "github.com/onsi/gomega/gexec"
+)
+
+var _ = Describe("Create 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 a container running top", func() {
+ s := specgen.NewSpecGenerator(alpine.name)
+ s.Command = []string{"top"}
+ s.Terminal = true
+ s.Name = "top"
+ ctr, err := containers.CreateWithSpec(bt.conn, s)
+ Expect(err).To(BeNil())
+ data, err := containers.Inspect(bt.conn, ctr.ID, nil)
+ Expect(err).To(BeNil())
+ Expect(data.Name).To(Equal("top"))
+ err = containers.Start(bt.conn, ctr.ID, nil)
+ Expect(err).To(BeNil())
+ data, err = containers.Inspect(bt.conn, ctr.ID, nil)
+ Expect(err).To(BeNil())
+ Expect(data.State.Status).To(Equal("running"))
+ })
+
+})
diff --git a/pkg/bindings/test/exec_test.go b/pkg/bindings/test/exec_test.go
new file mode 100644
index 000000000..1ef2197b6
--- /dev/null
+++ b/pkg/bindings/test/exec_test.go
@@ -0,0 +1,77 @@
+package test_bindings
+
+import (
+ "time"
+
+ "github.com/containers/libpod/pkg/api/handlers"
+ "github.com/containers/libpod/pkg/bindings"
+ "github.com/containers/libpod/pkg/bindings/containers"
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+ "github.com/onsi/gomega/gexec"
+)
+
+var _ = Describe("Podman containers exec", 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("Podman exec create makes an exec session", func() {
+ name := "testCtr"
+ cid, err := bt.RunTopContainer(&name, &bindings.PFalse, nil)
+ Expect(err).To(BeNil())
+
+ execConfig := new(handlers.ExecCreateConfig)
+ execConfig.Cmd = []string{"echo", "hello world"}
+
+ sessionID, err := containers.ExecCreate(bt.conn, name, execConfig)
+ Expect(err).To(BeNil())
+ Expect(sessionID).To(Not(Equal("")))
+
+ inspectOut, err := containers.ExecInspect(bt.conn, sessionID)
+ Expect(err).To(BeNil())
+ Expect(inspectOut.ContainerID).To(Equal(cid))
+ Expect(inspectOut.ProcessConfig.Entrypoint).To(Equal("echo"))
+ Expect(len(inspectOut.ProcessConfig.Arguments)).To(Equal(1))
+ Expect(inspectOut.ProcessConfig.Arguments[0]).To(Equal("hello world"))
+ })
+
+ It("Podman exec create with bad command fails", func() {
+ name := "testCtr"
+ _, err := bt.RunTopContainer(&name, &bindings.PFalse, nil)
+ Expect(err).To(BeNil())
+
+ execConfig := new(handlers.ExecCreateConfig)
+
+ _, err = containers.ExecCreate(bt.conn, name, execConfig)
+ Expect(err).To(Not(BeNil()))
+ })
+
+ It("Podman exec create with invalid container fails", func() {
+ execConfig := new(handlers.ExecCreateConfig)
+ execConfig.Cmd = []string{"echo", "hello world"}
+
+ _, err := containers.ExecCreate(bt.conn, "doesnotexist", execConfig)
+ Expect(err).To(Not(BeNil()))
+ })
+
+ It("Podman exec inspect on invalid session fails", func() {
+ _, err := containers.ExecInspect(bt.conn, "0000000000000000000000000000000000000000000000000000000000000000")
+ Expect(err).To(Not(BeNil()))
+ })
+})
diff --git a/pkg/bindings/test/images_test.go b/pkg/bindings/test/images_test.go
new file mode 100644
index 000000000..13b6086c3
--- /dev/null
+++ b/pkg/bindings/test/images_test.go
@@ -0,0 +1,356 @@
+package test_bindings
+
+import (
+ "net/http"
+ "os"
+ "path/filepath"
+ "time"
+
+ "github.com/containers/libpod/pkg/bindings"
+ "github.com/containers/libpod/pkg/bindings/containers"
+ "github.com/containers/libpod/pkg/bindings/images"
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+ "github.com/onsi/gomega/gexec"
+)
+
+var _ = Describe("Podman images", func() {
+ var (
+ // tempdir string
+ // err error
+ // podmanTest *PodmanTestIntegration
+ bt *bindingTest
+ s *gexec.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).To(BeNil())
+ })
+
+ 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).To(BeNil())
+
+ // Inspect with full ID
+ _, err = images.GetImage(bt.conn, data.ID, nil)
+ Expect(err).To(BeNil())
+
+ // Inspect with partial ID
+ _, err = images.GetImage(bt.conn, data.ID[0:12], nil)
+ Expect(err).To(BeNil())
+
+ // Inspect by long name
+ _, err = images.GetImage(bt.conn, alpine.name, nil)
+ Expect(err).To(BeNil())
+ // TODO it looks like the images API alwaays returns size regardless
+ // of bool or not. What should we do ?
+ // Expect(data.Size).To(BeZero())
+
+ // Enabling the size parameter should result in size being populated
+ data, err = images.GetImage(bt.conn, alpine.name, &bindings.PTrue)
+ Expect(err).To(BeNil())
+ Expect(data.Size).To(BeNumerically(">", 0))
+ })
+
+ // Test to validate the remove image api
+ It("remove image", func() {
+ // Remove invalid image should be a 404
+ _, err = images.Remove(bt.conn, "foobar5000", &bindings.PFalse)
+ Expect(err).ToNot(BeNil())
+ code, _ := bindings.CheckResponseCode(err)
+ Expect(code).To(BeNumerically("==", http.StatusNotFound))
+
+ // Remove an image by name, validate image is removed and error is nil
+ inspectData, err := images.GetImage(bt.conn, busybox.shortName, nil)
+ Expect(err).To(BeNil())
+ response, err := images.Remove(bt.conn, busybox.shortName, nil)
+ Expect(err).To(BeNil())
+ Expect(inspectData.ID).To(Equal(response[0]["Deleted"]))
+ 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, &bindings.PFalse, nil)
+ Expect(err).To(BeNil())
+ // we should now have a container called "top" running
+ containerResponse, err := containers.Inspect(bt.conn, "top", &bindings.PFalse)
+ Expect(err).To(BeNil())
+ 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, err = images.Remove(bt.conn, alpine.shortName, &bindings.PFalse)
+ code, _ = bindings.CheckResponseCode(err)
+ Expect(code).To(BeNumerically("==", http.StatusInternalServerError))
+
+ // Removing the image "alpine" where force = true
+ response, err = images.Remove(bt.conn, alpine.shortName, &bindings.PTrue)
+ Expect(err).To(BeNil())
+
+ // Checking if both the images are gone as well as the container is deleted
+ 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))
+
+ _, err = containers.Inspect(bt.conn, "top", &bindings.PFalse)
+ 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)
+ 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)
+ Expect(err).To(BeNil())
+
+ // Validates if name updates when the image is retagged.
+ _, err := images.GetImage(bt.conn, "alpine:demo", nil)
+ Expect(err).To(BeNil())
+
+ })
+
+ // 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, nil)
+ // There Should be no errors in the response.
+ Expect(err).To(BeNil())
+ // 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("busybox:glibc")
+ imageSummary, err = images.List(bt.conn, nil, nil)
+ Expect(err).To(BeNil())
+ Expect(len(imageSummary)).To(Equal(3))
+
+ // Validate the image names.
+ var names []string
+ for _, i := range imageSummary {
+ names = append(names, i.RepoTags...)
+ }
+ Expect(StringInSlice(alpine.name, names)).To(BeTrue())
+ Expect(StringInSlice(busybox.name, names)).To(BeTrue())
+
+ // List images with a filter
+ filters := make(map[string][]string)
+ filters["reference"] = []string{alpine.name}
+ filteredImages, err := images.List(bt.conn, &bindings.PFalse, filters)
+ Expect(err).To(BeNil())
+ Expect(len(filteredImages)).To(BeNumerically("==", 1))
+
+ // List images with a bad filter
+ filters["name"] = []string{alpine.name}
+ _, err = images.List(bt.conn, &bindings.PFalse, filters)
+ 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")
+ Expect(err).To(BeNil())
+ Expect(exists).To(BeFalse())
+
+ // exists with shortname should be true
+ exists, err = images.Exists(bt.conn, alpine.shortName)
+ Expect(err).To(BeNil())
+ Expect(exists).To(BeTrue())
+
+ // exists with fqname should be true
+ exists, err = images.Exists(bt.conn, alpine.name)
+ Expect(err).To(BeNil())
+ Expect(exists).To(BeTrue())
+ })
+
+ It("Load|Import Image", func() {
+ // load an image
+ _, err := images.Remove(bt.conn, alpine.name, nil)
+ Expect(err).To(BeNil())
+ exists, err := images.Exists(bt.conn, alpine.name)
+ Expect(err).To(BeNil())
+ Expect(exists).To(BeFalse())
+ f, err := os.Open(filepath.Join(ImageCacheDir, alpine.tarballName))
+ defer f.Close()
+ Expect(err).To(BeNil())
+ names, err := images.Load(bt.conn, f, nil)
+ Expect(err).To(BeNil())
+ Expect(names).To(Equal(alpine.name))
+ exists, err = images.Exists(bt.conn, alpine.name)
+ Expect(err).To(BeNil())
+ Expect(exists).To(BeTrue())
+
+ // load with a repo name
+ f, err = os.Open(filepath.Join(ImageCacheDir, alpine.tarballName))
+ Expect(err).To(BeNil())
+ _, err = images.Remove(bt.conn, alpine.name, nil)
+ Expect(err).To(BeNil())
+ exists, err = images.Exists(bt.conn, alpine.name)
+ Expect(err).To(BeNil())
+ Expect(exists).To(BeFalse())
+ newName := "quay.io/newname:fizzle"
+ names, err = images.Load(bt.conn, f, &newName)
+ Expect(err).To(BeNil())
+ Expect(names).To(Equal(alpine.name))
+ exists, err = images.Exists(bt.conn, newName)
+ Expect(err).To(BeNil())
+ Expect(exists).To(BeTrue())
+
+ // load with a bad repo name should trigger a 500
+ f, err = os.Open(filepath.Join(ImageCacheDir, alpine.tarballName))
+ Expect(err).To(BeNil())
+ _, err = images.Remove(bt.conn, alpine.name, nil)
+ Expect(err).To(BeNil())
+ exists, err = images.Exists(bt.conn, alpine.name)
+ Expect(err).To(BeNil())
+ Expect(exists).To(BeFalse())
+ badName := "quay.io/newName:fizzle"
+ _, err = images.Load(bt.conn, f, &badName)
+ Expect(err).ToNot(BeNil())
+ code, _ := bindings.CheckResponseCode(err)
+ Expect(code).To(BeNumerically("==", http.StatusInternalServerError))
+ })
+
+ 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).To(BeNil())
+ err = images.Export(bt.conn, alpine.name, w, nil, nil)
+ Expect(err).To(BeNil())
+ _, err = os.Stat(exportPath)
+ Expect(err).To(BeNil())
+
+ // TODO how do we verify that a format change worked?
+ })
+
+ It("Import Image", func() {
+ // load an image
+ _, err = images.Remove(bt.conn, alpine.name, nil)
+ Expect(err).To(BeNil())
+ exists, err := images.Exists(bt.conn, alpine.name)
+ Expect(err).To(BeNil())
+ Expect(exists).To(BeFalse())
+ f, err := os.Open(filepath.Join(ImageCacheDir, alpine.tarballName))
+ defer f.Close()
+ Expect(err).To(BeNil())
+ changes := []string{"CMD /bin/foobar"}
+ testMessage := "test_import"
+ _, err = images.Import(bt.conn, changes, &testMessage, &alpine.name, nil, f)
+ Expect(err).To(BeNil())
+ exists, err = images.Exists(bt.conn, alpine.name)
+ Expect(err).To(BeNil())
+ Expect(exists).To(BeTrue())
+ data, err := images.GetImage(bt.conn, alpine.name, nil)
+ Expect(err).To(BeNil())
+ Expect(data.Comment).To(Equal(testMessage))
+
+ })
+
+ It("History Image", func() {
+ // a bogus name should return a 404
+ _, err := images.History(bt.conn, "foobar")
+ 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).To(BeNil())
+ history, err := images.History(bt.conn, alpine.name)
+ Expect(err).To(BeNil())
+ for _, i := range history {
+ if i.ID == data.ID {
+ foundID = true
+ break
+ }
+ }
+ Expect(foundID).To(BeTrue())
+ })
+
+ It("Search for an image", func() {
+ imgs, err := images.Search(bt.conn, "alpine", nil, nil)
+ Expect(err).To(BeNil())
+ Expect(len(imgs)).To(BeNumerically(">", 1))
+ var foundAlpine bool
+ for _, i := range imgs {
+ if i.Name == "docker.io/library/alpine" {
+ foundAlpine = true
+ break
+ }
+ }
+ Expect(foundAlpine).To(BeTrue())
+
+ // Search for alpine with a limit of 10
+ ten := 10
+ imgs, err = images.Search(bt.conn, "docker.io/alpine", &ten, nil)
+ Expect(err).To(BeNil())
+ Expect(len(imgs)).To(BeNumerically("<=", 10))
+
+ // Search for alpine with stars greater than 100
+ filters := make(map[string][]string)
+ filters["stars"] = []string{"100"}
+ imgs, err = images.Search(bt.conn, "docker.io/alpine", nil, filters)
+ Expect(err).To(BeNil())
+ for _, i := range imgs {
+ Expect(i.Stars).To(BeNumerically(">=", 100))
+ }
+
+ // Search with a fqdn
+ imgs, err = images.Search(bt.conn, "quay.io/libpod/alpine_nginx", nil, nil)
+ Expect(len(imgs)).To(BeNumerically(">=", 1))
+ })
+
+ It("Prune images", func() {
+ trueBoxed := true
+ results, err := images.Prune(bt.conn, &trueBoxed, nil)
+ Expect(err).NotTo(HaveOccurred())
+ Expect(len(results)).To(BeNumerically(">", 0))
+ Expect(results).To(ContainElement("docker.io/library/alpine:latest"))
+ })
+
+})
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")
+ })
+})
diff --git a/pkg/bindings/test/pods_test.go b/pkg/bindings/test/pods_test.go
new file mode 100644
index 000000000..0f786e341
--- /dev/null
+++ b/pkg/bindings/test/pods_test.go
@@ -0,0 +1,322 @@
+package test_bindings
+
+import (
+ "net/http"
+ "time"
+
+ "github.com/containers/libpod/libpod/define"
+ "github.com/containers/libpod/pkg/bindings"
+ "github.com/containers/libpod/pkg/bindings/pods"
+ "github.com/containers/libpod/pkg/specgen"
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+ "github.com/onsi/gomega/gexec"
+)
+
+var _ = Describe("Podman pods", func() {
+ var (
+ bt *bindingTest
+ s *gexec.Session
+ newpod string
+ err error
+ )
+
+ BeforeEach(func() {
+ bt = newBindingTest()
+ newpod = "newpod"
+ bt.RestoreImagesFromCache()
+ bt.Podcreate(&newpod)
+ s = bt.startAPIService()
+ time.Sleep(1 * time.Second)
+ err := bt.NewConnection()
+ Expect(err).To(BeNil())
+ })
+
+ AfterEach(func() {
+ s.Kill()
+ bt.cleanup()
+ })
+
+ It("inspect pod", func() {
+ //Inspect an invalid pod name
+ _, err := pods.Inspect(bt.conn, "dummyname")
+ Expect(err).ToNot(BeNil())
+ code, _ := bindings.CheckResponseCode(err)
+ Expect(code).To(BeNumerically("==", http.StatusNotFound))
+
+ //Inspect an valid pod name
+ response, err := pods.Inspect(bt.conn, newpod)
+ Expect(err).To(BeNil())
+ Expect(response.Config.Name).To(Equal(newpod))
+ })
+
+ // Test validates the list all api returns
+ It("list pod", func() {
+ //List all the pods in the current instance
+ podSummary, err := pods.List(bt.conn, nil)
+ Expect(err).To(BeNil())
+ Expect(len(podSummary)).To(Equal(1))
+ // Adding an alpine container to the existing pod
+ _, err = bt.RunTopContainer(nil, &bindings.PTrue, &newpod)
+ Expect(err).To(BeNil())
+ podSummary, err = pods.List(bt.conn, nil)
+ // Verify no errors.
+ Expect(err).To(BeNil())
+ // Verify number of containers in the pod.
+ Expect(len(podSummary[0].Containers)).To(Equal(2))
+
+ // Add multiple pods and verify them by name and size.
+ var newpod2 string = "newpod2"
+ bt.Podcreate(&newpod2)
+ podSummary, err = pods.List(bt.conn, nil)
+ Expect(len(podSummary)).To(Equal(2))
+ var names []string
+ for _, i := range podSummary {
+ names = append(names, i.Name)
+ }
+ Expect(StringInSlice(newpod, names)).To(BeTrue())
+ Expect(StringInSlice("newpod2", names)).To(BeTrue())
+ })
+
+ // The test validates the list pod endpoint with passing filters as the params.
+ It("List pods with filters", func() {
+ newpod2 := "newpod2"
+ bt.Podcreate(&newpod2)
+ _, err = bt.RunTopContainer(nil, &bindings.PTrue, &newpod)
+ Expect(err).To(BeNil())
+
+ // Expected err with invalid filter params
+ filters := make(map[string][]string)
+ filters["dummy"] = []string{"dummy"}
+ filteredPods, err := pods.List(bt.conn, filters)
+ Expect(err).ToNot(BeNil())
+ code, _ := bindings.CheckResponseCode(err)
+ Expect(code).To(BeNumerically("==", http.StatusInternalServerError))
+
+ // Expected empty response with invalid filters
+ filters = make(map[string][]string)
+ filters["name"] = []string{"dummy"}
+ filteredPods, err = pods.List(bt.conn, filters)
+ Expect(err).To(BeNil())
+ Expect(len(filteredPods)).To(BeNumerically("==", 0))
+
+ // Validate list pod with name filter
+ filters = make(map[string][]string)
+ filters["name"] = []string{newpod2}
+ filteredPods, err = pods.List(bt.conn, filters)
+ Expect(err).To(BeNil())
+ Expect(len(filteredPods)).To(BeNumerically("==", 1))
+ var names []string
+ for _, i := range filteredPods {
+ names = append(names, i.Name)
+ }
+ Expect(StringInSlice("newpod2", names)).To(BeTrue())
+
+ // Validate list pod with id filter
+ filters = make(map[string][]string)
+ response, err := pods.Inspect(bt.conn, newpod)
+ Expect(err).To(BeNil())
+ id := response.Config.ID
+ filters["id"] = []string{id}
+ filteredPods, err = pods.List(bt.conn, filters)
+ Expect(err).To(BeNil())
+ Expect(len(filteredPods)).To(BeNumerically("==", 1))
+ names = names[:0]
+ for _, i := range filteredPods {
+ names = append(names, i.Name)
+ }
+ Expect(StringInSlice("newpod", names)).To(BeTrue())
+
+ // Using multiple filters
+ filters["name"] = []string{newpod}
+ filteredPods, err = pods.List(bt.conn, filters)
+ Expect(err).To(BeNil())
+ Expect(len(filteredPods)).To(BeNumerically("==", 1))
+ names = names[:0]
+ for _, i := range filteredPods {
+ names = append(names, i.Name)
+ }
+ Expect(StringInSlice("newpod", names)).To(BeTrue())
+ })
+
+ // The test validates if the exists responds
+ It("exists pod", func() {
+ response, err := pods.Exists(bt.conn, "dummyName")
+ Expect(err).To(BeNil())
+ Expect(response).To(BeFalse())
+
+ // Should exit with no error and response should be true
+ response, err = pods.Exists(bt.conn, "newpod")
+ Expect(err).To(BeNil())
+ Expect(response).To(BeTrue())
+ })
+
+ // This test validates if All running containers within
+ // each specified pod are paused and unpaused
+ It("pause upause pod", func() {
+ // TODO fix this
+ Skip("Pod behavior is jacked right now.")
+ // Pause invalid container
+ _, err := pods.Pause(bt.conn, "dummyName")
+ Expect(err).ToNot(BeNil())
+ code, _ := bindings.CheckResponseCode(err)
+ Expect(code).To(BeNumerically("==", http.StatusNotFound))
+
+ // Adding an alpine container to the existing pod
+ _, err = bt.RunTopContainer(nil, &bindings.PTrue, &newpod)
+ Expect(err).To(BeNil())
+
+ // Binding needs to be modified to inspect the pod state.
+ // Since we don't have a pod state we inspect the states of the containers within the pod.
+ // Pause a valid container
+ _, err = pods.Pause(bt.conn, newpod)
+ Expect(err).To(BeNil())
+ response, err := pods.Inspect(bt.conn, newpod)
+ Expect(err).To(BeNil())
+ Expect(response.State.Status).To(Equal(define.PodStatePaused))
+ for _, i := range response.Containers {
+ Expect(define.StringToContainerStatus(i.State)).
+ To(Equal(define.ContainerStatePaused))
+ }
+
+ // Unpause a valid container
+ _, err = pods.Unpause(bt.conn, newpod)
+ Expect(err).To(BeNil())
+ response, err = pods.Inspect(bt.conn, newpod)
+ Expect(err).To(BeNil())
+ Expect(response.State.Status).To(Equal(define.PodStateRunning))
+ for _, i := range response.Containers {
+ Expect(define.StringToContainerStatus(i.State)).
+ To(Equal(define.ContainerStateRunning))
+ }
+ })
+
+ It("start stop restart pod", func() {
+ // Start an invalid pod
+ _, err = pods.Start(bt.conn, "dummyName")
+ Expect(err).ToNot(BeNil())
+ code, _ := bindings.CheckResponseCode(err)
+ Expect(code).To(BeNumerically("==", http.StatusNotFound))
+
+ // Stop an invalid pod
+ _, err = pods.Stop(bt.conn, "dummyName", nil)
+ Expect(err).ToNot(BeNil())
+ code, _ = bindings.CheckResponseCode(err)
+ Expect(code).To(BeNumerically("==", http.StatusNotFound))
+
+ // Restart an invalid pod
+ _, err = pods.Restart(bt.conn, "dummyName")
+ Expect(err).ToNot(BeNil())
+ code, _ = bindings.CheckResponseCode(err)
+ Expect(code).To(BeNumerically("==", http.StatusNotFound))
+
+ // Start a valid pod and inspect status of each container
+ _, err = pods.Start(bt.conn, newpod)
+ Expect(err).To(BeNil())
+
+ response, err := pods.Inspect(bt.conn, newpod)
+ Expect(err).To(BeNil())
+ Expect(response.State.Status).To(Equal(define.PodStateRunning))
+ for _, i := range response.Containers {
+ Expect(define.StringToContainerStatus(i.State)).
+ To(Equal(define.ContainerStateRunning))
+ }
+
+ // Start an already running pod
+ _, err = pods.Start(bt.conn, newpod)
+ Expect(err).To(BeNil())
+
+ // Stop the running pods
+ _, err = pods.Stop(bt.conn, newpod, nil)
+ Expect(err).To(BeNil())
+ response, _ = pods.Inspect(bt.conn, newpod)
+ Expect(response.State.Status).To(Equal(define.PodStateExited))
+ for _, i := range response.Containers {
+ Expect(define.StringToContainerStatus(i.State)).
+ To(Equal(define.ContainerStateStopped))
+ }
+
+ // Stop an already stopped pod
+ _, err = pods.Stop(bt.conn, newpod, nil)
+ Expect(err).To(BeNil())
+
+ _, err = pods.Restart(bt.conn, newpod)
+ Expect(err).To(BeNil())
+ response, _ = pods.Inspect(bt.conn, newpod)
+ Expect(response.State.Status).To(Equal(define.PodStateRunning))
+ for _, i := range response.Containers {
+ Expect(define.StringToContainerStatus(i.State)).
+ To(Equal(define.ContainerStateRunning))
+ }
+ })
+
+ // Test to validate all the pods in the stopped/exited state are pruned successfully.
+ It("prune pod", func() {
+ // Add a new pod
+ var newpod2 string = "newpod2"
+ bt.Podcreate(&newpod2)
+ // No pods pruned since no pod in exited state
+ err = pods.Prune(bt.conn)
+ Expect(err).To(BeNil())
+ podSummary, err := pods.List(bt.conn, nil)
+ Expect(err).To(BeNil())
+ Expect(len(podSummary)).To(Equal(2))
+
+ // Prune only one pod which is in exited state.
+ // Start then stop a pod.
+ // pod moves to exited state one pod should be pruned now.
+ _, err = pods.Start(bt.conn, newpod)
+ Expect(err).To(BeNil())
+ _, err = pods.Stop(bt.conn, newpod, nil)
+ Expect(err).To(BeNil())
+ response, err := pods.Inspect(bt.conn, newpod)
+ Expect(err).To(BeNil())
+ Expect(response.State.Status).To(Equal(define.PodStateExited))
+ err = pods.Prune(bt.conn)
+ Expect(err).To(BeNil())
+ podSummary, err = pods.List(bt.conn, nil)
+ Expect(err).To(BeNil())
+ Expect(len(podSummary)).To(Equal(1))
+
+ // Test prune all pods in exited state.
+ bt.Podcreate(&newpod)
+ _, err = pods.Start(bt.conn, newpod)
+ Expect(err).To(BeNil())
+ _, err = pods.Start(bt.conn, newpod2)
+ Expect(err).To(BeNil())
+ _, err = pods.Stop(bt.conn, newpod, nil)
+ Expect(err).To(BeNil())
+ response, err = pods.Inspect(bt.conn, newpod)
+ Expect(err).To(BeNil())
+ Expect(response.State.Status).To(Equal(define.PodStateExited))
+ for _, i := range response.Containers {
+ Expect(define.StringToContainerStatus(i.State)).
+ To(Equal(define.ContainerStateStopped))
+ }
+ _, err = pods.Stop(bt.conn, newpod2, nil)
+ Expect(err).To(BeNil())
+ response, err = pods.Inspect(bt.conn, newpod2)
+ Expect(err).To(BeNil())
+ Expect(response.State.Status).To(Equal(define.PodStateExited))
+ for _, i := range response.Containers {
+ Expect(define.StringToContainerStatus(i.State)).
+ To(Equal(define.ContainerStateStopped))
+ }
+ err = pods.Prune(bt.conn)
+ Expect(err).To(BeNil())
+ podSummary, err = pods.List(bt.conn, nil)
+ Expect(err).To(BeNil())
+ Expect(len(podSummary)).To(Equal(0))
+ })
+
+ It("simple create pod", func() {
+ ps := specgen.PodSpecGenerator{}
+ ps.Name = "foobar"
+ _, err := pods.CreatePodFromSpec(bt.conn, &ps)
+ Expect(err).To(BeNil())
+
+ exists, err := pods.Exists(bt.conn, "foobar")
+ Expect(err).To(BeNil())
+ Expect(exists).To(BeTrue())
+ })
+})
diff --git a/pkg/bindings/test/system_test.go b/pkg/bindings/test/system_test.go
new file mode 100644
index 000000000..3abc26b34
--- /dev/null
+++ b/pkg/bindings/test/system_test.go
@@ -0,0 +1,51 @@
+package test_bindings
+
+import (
+ "time"
+
+ "github.com/containers/libpod/pkg/api/handlers"
+ "github.com/containers/libpod/pkg/bindings/system"
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+ "github.com/onsi/gomega/gexec"
+)
+
+var _ = Describe("Podman system", 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("podman events", func() {
+ eChan := make(chan handlers.Event, 1)
+ var messages []handlers.Event
+ cancelChan := make(chan bool, 1)
+ go func() {
+ for e := range eChan {
+ messages = append(messages, e)
+ }
+ }()
+ go func() {
+ system.Events(bt.conn, eChan, cancelChan, nil, nil, nil)
+ }()
+
+ _, err := bt.RunTopContainer(nil, nil, nil)
+ Expect(err).To(BeNil())
+ cancelChan <- true
+ Expect(len(messages)).To(BeNumerically("==", 3))
+ })
+})
diff --git a/pkg/bindings/test/test_suite_test.go b/pkg/bindings/test/test_suite_test.go
new file mode 100644
index 000000000..dc2b49b88
--- /dev/null
+++ b/pkg/bindings/test/test_suite_test.go
@@ -0,0 +1,13 @@
+package test_bindings_test
+
+import (
+ "testing"
+
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+)
+
+func TestTest(t *testing.T) {
+ RegisterFailHandler(Fail)
+ RunSpecs(t, "Test Suite")
+}
diff --git a/pkg/bindings/test/volumes_test.go b/pkg/bindings/test/volumes_test.go
new file mode 100644
index 000000000..59fe48f22
--- /dev/null
+++ b/pkg/bindings/test/volumes_test.go
@@ -0,0 +1,173 @@
+package test_bindings
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "time"
+
+ "github.com/containers/libpod/pkg/bindings"
+ "github.com/containers/libpod/pkg/bindings/containers"
+ "github.com/containers/libpod/pkg/bindings/volumes"
+ "github.com/containers/libpod/pkg/domain/entities"
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+ "github.com/onsi/gomega/gexec"
+)
+
+var _ = Describe("Podman volumes", func() {
+ var (
+ //tempdir string
+ //err error
+ //podmanTest *PodmanTestIntegration
+ bt *bindingTest
+ s *gexec.Session
+ connText context.Context
+ 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)
+ connText, err = bindings.NewConnection(context.Background(), bt.sock)
+ Expect(err).To(BeNil())
+ })
+
+ AfterEach(func() {
+ //podmanTest.Cleanup()
+ //f := CurrentGinkgoTestDescription()
+ //processTestResult(f)
+ s.Kill()
+ bt.cleanup()
+ })
+
+ It("create volume", func() {
+ // create a volume with blank config should work
+ _, err := volumes.Create(connText, entities.VolumeCreateOptions{})
+ Expect(err).To(BeNil())
+
+ vcc := entities.VolumeCreateOptions{
+ Name: "foobar",
+ Label: nil,
+ Options: nil,
+ }
+ vol, err := volumes.Create(connText, vcc)
+ Expect(err).To(BeNil())
+ Expect(vol.Name).To(Equal("foobar"))
+
+ // create volume with same name should 500
+ _, err = volumes.Create(connText, vcc)
+ Expect(err).ToNot(BeNil())
+ code, _ := bindings.CheckResponseCode(err)
+ Expect(code).To(BeNumerically("==", http.StatusInternalServerError))
+ })
+
+ It("inspect volume", func() {
+ vol, err := volumes.Create(connText, entities.VolumeCreateOptions{})
+ Expect(err).To(BeNil())
+ data, err := volumes.Inspect(connText, vol.Name)
+ Expect(err).To(BeNil())
+ Expect(data.Name).To(Equal(vol.Name))
+ })
+
+ It("remove volume", func() {
+ // removing a bogus volume should result in 404
+ err := volumes.Remove(connText, "foobar", nil)
+ code, _ := bindings.CheckResponseCode(err)
+ Expect(code).To(BeNumerically("==", http.StatusNotFound))
+
+ // Removing an unused volume should work
+ vol, err := volumes.Create(connText, entities.VolumeCreateOptions{})
+ Expect(err).To(BeNil())
+ err = volumes.Remove(connText, vol.Name, nil)
+ Expect(err).To(BeNil())
+
+ // Removing a volume that is being used without force should be 409
+ vol, err = volumes.Create(connText, entities.VolumeCreateOptions{})
+ Expect(err).To(BeNil())
+ session := bt.runPodman([]string{"run", "-dt", "-v", fmt.Sprintf("%s:/foobar", vol.Name), "--name", "vtest", alpine.name, "top"})
+ session.Wait(45)
+ err = volumes.Remove(connText, vol.Name, nil)
+ Expect(err).ToNot(BeNil())
+ code, _ = bindings.CheckResponseCode(err)
+ Expect(code).To(BeNumerically("==", http.StatusConflict))
+
+ // Removing with a volume in use with force should work with a stopped container
+ zero := uint(0)
+ err = containers.Stop(connText, "vtest", &zero)
+ Expect(err).To(BeNil())
+ err = volumes.Remove(connText, vol.Name, &bindings.PTrue)
+ Expect(err).To(BeNil())
+ })
+
+ It("list volumes", func() {
+ // no volumes should be ok
+ vols, err := volumes.List(connText, nil)
+ Expect(err).To(BeNil())
+ Expect(len(vols)).To(BeZero())
+
+ // create a bunch of named volumes and make verify with list
+ volNames := []string{"homer", "bart", "lisa", "maggie", "marge"}
+ for i := 0; i < 5; i++ {
+ _, err = volumes.Create(connText, entities.VolumeCreateOptions{Name: volNames[i]})
+ Expect(err).To(BeNil())
+ }
+ vols, err = volumes.List(connText, nil)
+ Expect(err).To(BeNil())
+ Expect(len(vols)).To(BeNumerically("==", 5))
+ for _, v := range vols {
+ Expect(StringInSlice(v.Name, volNames)).To(BeTrue())
+ }
+
+ // list with bad filter should be 500
+ filters := make(map[string][]string)
+ filters["foobar"] = []string{"1234"}
+ _, err = volumes.List(connText, filters)
+ Expect(err).ToNot(BeNil())
+ code, _ := bindings.CheckResponseCode(err)
+ Expect(code).To(BeNumerically("==", http.StatusInternalServerError))
+
+ filters = make(map[string][]string)
+ filters["name"] = []string{"homer"}
+ vols, err = volumes.List(connText, filters)
+ Expect(err).To(BeNil())
+ Expect(len(vols)).To(BeNumerically("==", 1))
+ Expect(vols[0].Name).To(Equal("homer"))
+ })
+
+ // TODO we need to add filtering to tests
+ It("prune unused volume", func() {
+ // Pruning when no volumes present should be ok
+ _, err := volumes.Prune(connText)
+ Expect(err).To(BeNil())
+
+ // Removing an unused volume should work
+ _, err = volumes.Create(connText, entities.VolumeCreateOptions{})
+ Expect(err).To(BeNil())
+ vols, err := volumes.Prune(connText)
+ Expect(err).To(BeNil())
+ Expect(len(vols)).To(BeNumerically("==", 1))
+
+ _, err = volumes.Create(connText, entities.VolumeCreateOptions{Name: "homer"})
+ Expect(err).To(BeNil())
+ _, err = volumes.Create(connText, entities.VolumeCreateOptions{})
+ Expect(err).To(BeNil())
+ session := bt.runPodman([]string{"run", "-dt", "-v", fmt.Sprintf("%s:/homer", "homer"), "--name", "vtest", alpine.name, "top"})
+ session.Wait(45)
+ vols, err = volumes.Prune(connText)
+ Expect(err).To(BeNil())
+ Expect(len(vols)).To(BeNumerically("==", 1))
+ _, err = volumes.Inspect(connText, "homer")
+ Expect(err).To(BeNil())
+ })
+
+})
diff --git a/pkg/bindings/volumes.go b/pkg/bindings/volumes.go
deleted file mode 100644
index 219f924e7..000000000
--- a/pkg/bindings/volumes.go
+++ /dev/null
@@ -1,60 +0,0 @@
-package bindings
-
-import (
- "fmt"
- "net/http"
- "strconv"
-
- "github.com/containers/libpod/libpod"
- "github.com/containers/libpod/pkg/api/handlers"
-)
-
-func (c Connection) CreateVolume(config handlers.VolumeCreateConfig) (string, error) {
- var (
- volumeID string
- )
- response, err := c.newRequest(http.MethodPost, "/volumes/create", nil, nil)
- if err != nil {
- return volumeID, err
- }
- return volumeID, response.Process(&volumeID)
-}
-
-func (c Connection) InspectVolume(nameOrID string) (*libpod.InspectVolumeData, error) {
- var (
- inspect libpod.InspectVolumeData
- )
- response, err := c.newRequest(http.MethodPost, fmt.Sprintf("/volumes/%s/json", nameOrID), nil, nil)
- if err != nil {
- return &inspect, err
- }
- return &inspect, response.Process(&inspect)
-}
-
-func (c Connection) ListVolumes() error {
- // TODO
- // The API side of things for this one does a lot in main and therefore
- // is not implemented yet.
- return ErrNotImplemented // nolint:typecheck
-}
-
-func (c Connection) PruneVolumes() ([]string, error) {
- var (
- pruned []string
- )
- response, err := c.newRequest(http.MethodPost, "/volumes/prune", nil, nil)
- if err != nil {
- return pruned, err
- }
- return pruned, response.Process(&pruned)
-}
-
-func (c Connection) RemoveVolume(nameOrID string, force bool) error {
- params := make(map[string]string)
- params["force"] = strconv.FormatBool(force)
- response, err := c.newRequest(http.MethodPost, "/volumes/prune", nil, params)
- if err != nil {
- return err
- }
- return response.Process(nil)
-}
diff --git a/pkg/bindings/volumes/volumes.go b/pkg/bindings/volumes/volumes.go
new file mode 100644
index 000000000..cef9246cb
--- /dev/null
+++ b/pkg/bindings/volumes/volumes.go
@@ -0,0 +1,109 @@
+package volumes
+
+import (
+ "context"
+ "net/http"
+ "net/url"
+ "strconv"
+ "strings"
+
+ "github.com/containers/libpod/pkg/bindings"
+ "github.com/containers/libpod/pkg/domain/entities"
+ jsoniter "github.com/json-iterator/go"
+)
+
+// Create creates a volume given its configuration.
+func Create(ctx context.Context, config entities.VolumeCreateOptions) (*entities.VolumeConfigResponse, error) {
+ var (
+ v entities.VolumeConfigResponse
+ )
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return nil, err
+ }
+ createString, err := jsoniter.MarshalToString(config)
+ if err != nil {
+ return nil, err
+ }
+ stringReader := strings.NewReader(createString)
+ response, err := conn.DoRequest(stringReader, http.MethodPost, "/volumes/create", nil)
+ if err != nil {
+ return nil, err
+ }
+ return &v, response.Process(&v)
+}
+
+// Inspect returns low-level information about a volume.
+func Inspect(ctx context.Context, nameOrID string) (*entities.VolumeConfigResponse, error) {
+ var (
+ inspect entities.VolumeConfigResponse
+ )
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return nil, err
+ }
+ response, err := conn.DoRequest(nil, http.MethodGet, "/volumes/%s/json", nil, nameOrID)
+ if err != nil {
+ return &inspect, err
+ }
+ return &inspect, response.Process(&inspect)
+}
+
+// List returns the configurations for existing volumes in the form of a slice. Optionally, filters
+// can be used to refine the list of volumes.
+func List(ctx context.Context, filters map[string][]string) ([]*entities.VolumeListReport, error) {
+ var (
+ vols []*entities.VolumeListReport
+ )
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return nil, err
+ }
+ params := url.Values{}
+ if len(filters) > 0 {
+ strFilters, err := bindings.FiltersToString(filters)
+ if err != nil {
+ return nil, err
+ }
+ params.Set("filters", strFilters)
+ }
+ response, err := conn.DoRequest(nil, http.MethodGet, "/volumes/json", params)
+ if err != nil {
+ return vols, err
+ }
+ return vols, response.Process(&vols)
+}
+
+// Prune removes unused volumes from the local filesystem.
+func Prune(ctx context.Context) ([]*entities.VolumePruneReport, error) {
+ var (
+ pruned []*entities.VolumePruneReport
+ )
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return nil, err
+ }
+ response, err := conn.DoRequest(nil, http.MethodPost, "/volumes/prune", nil)
+ if err != nil {
+ return nil, err
+ }
+ return pruned, response.Process(&pruned)
+}
+
+// Remove deletes the given volume from storage. The optional force parameter
+// is used to remove a volume even if it is being used by a container.
+func Remove(ctx context.Context, nameOrID string, force *bool) error {
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return err
+ }
+ params := url.Values{}
+ if force != nil {
+ params.Set("force", strconv.FormatBool(*force))
+ }
+ response, err := conn.DoRequest(nil, http.MethodDelete, "/volumes/%s", params, nameOrID)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}