diff options
Diffstat (limited to 'pkg/bindings')
-rw-r--r-- | pkg/bindings/connection.go | 234 | ||||
-rw-r--r-- | pkg/bindings/containers/containers.go | 24 | ||||
-rw-r--r-- | pkg/bindings/containers/create.go | 2 | ||||
-rw-r--r-- | pkg/bindings/containers/healthcheck.go | 2 | ||||
-rw-r--r-- | pkg/bindings/containers/mount.go | 6 | ||||
-rw-r--r-- | pkg/bindings/images/images.go | 18 | ||||
-rw-r--r-- | pkg/bindings/images/search.go | 2 | ||||
-rw-r--r-- | pkg/bindings/network/network.go | 6 | ||||
-rw-r--r-- | pkg/bindings/pods/pods.go | 34 | ||||
-rw-r--r-- | pkg/bindings/test/common_test.go | 30 | ||||
-rw-r--r-- | pkg/bindings/test/images_test.go | 4 | ||||
-rw-r--r-- | pkg/bindings/test/pods_test.go | 202 | ||||
-rw-r--r-- | pkg/bindings/volumes/volumes.go | 8 |
13 files changed, 459 insertions, 113 deletions
diff --git a/pkg/bindings/connection.go b/pkg/bindings/connection.go index f270060a6..75f1fc6a5 100644 --- a/pkg/bindings/connection.go +++ b/pkg/bindings/connection.go @@ -1,22 +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" ) var ( - defaultConnectionPath string = filepath.Join(fmt.Sprintf("v%s", handlers.MinimalApiVersion), "libpod") + basePath = &url.URL{ + Scheme: "http", + Host: "d", + Path: "/v" + handlers.MinimalApiVersion + "/libpod", + } ) type APIResponse struct { @@ -25,9 +37,28 @@ type APIResponse struct { } type Connection struct { - scheme string - address string - client *http.Client + _url *url.URL + client *http.Client +} + +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 @@ -36,46 +67,81 @@ type Connection struct { // // A valid URI connection should be scheme:// // For example tcp://localhost:<port> -// or unix://run/podman/podman.sock -func NewConnection(uri string) (context.Context, error) { - u, err := url.Parse(uri) - if err != nil { - return nil, err - } - // TODO once ssh is implemented, remove this block and - // add it to the conditional beneath it - if u.Scheme == "ssh" { - return nil, ErrNotImplemented +// 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 u.Scheme != "tcp" && u.Scheme != "unix" { - return nil, errors.Errorf("%s is not a support schema", u.Scheme) + + if v, found := os.LookupEnv("PODMAN_SSHKEY"); found { + identity = []string{v} } - if u.Scheme == "tcp" && !strings.HasPrefix(uri, "tcp://") { - return nil, errors.New("tcp URIs should begin with tcp://") + _url, err := url.Parse(uri) + if err != nil { + return nil, errors.Wrapf(err, "Value of PODMAN_HOST is not a valid url: %s", uri) } - address := u.Path - if u.Scheme == "tcp" { - address = u.Host + // 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 support schema", _url.Scheme) + } + if err != nil { + return nil, errors.Wrapf(err, "Failed to create %sClient", _url.Scheme) } - newConn := newConnection(u.Scheme, address) - ctx := context.WithValue(context.Background(), "conn", &newConn) + + ctx = context.WithValue(ctx, clientKey, &Connection{_url, client}) if err := pingNewConnection(ctx); err != nil { return nil, 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 +} + // 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 { - conn, err := GetConnectionFromContext(ctx) + client, err := GetClient(ctx) if err != nil { return err } // the ping endpoint sits at / in this case - response, err := conn.DoRequest(nil, http.MethodGet, "../../../_ping", nil) + response, err := client.DoRequest(nil, http.MethodGet, "../../../_ping", nil) if err != nil { return err } @@ -85,26 +151,58 @@ func pingNewConnection(ctx context.Context) error { return errors.Errorf("ping response was %q", response.StatusCode) } -// newConnection takes a scheme and address and creates a connection from it -func newConnection(scheme, address string) Connection { - client := http.Client{ - Transport: &http.Transport{ - DialContext: func(_ context.Context, _, _ string) (net.Conn, error) { - return net.Dial(scheme, address) +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) + } + } + + bastion, err := ssh.Dial("tcp", + net.JoinHostPort(_url.Hostname(), _url.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()) } - newConn := Connection{ - client: &client, - address: address, - scheme: scheme, - } - return newConn + return &http.Client{ + Transport: &http.Transport{ + DialContext: func(_ context.Context, _, _ string) (net.Conn, error) { + return bastion.Dial("unix", _url.Path) + }, + }}, nil } -func (c *Connection) makeEndpoint(u string) string { - // The d character in the url is discarded and is meaningless - return fmt.Sprintf("http://d/%s%s", defaultConnectionPath, u) +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 @@ -121,7 +219,7 @@ func (c *Connection) DoRequest(httpBody io.Reader, httpMethod, endpoint string, // Lets eventually use URL for this which might lead to safer // usage safeEndpoint := fmt.Sprintf(endpoint, safePathValues...) - e := c.makeEndpoint(safeEndpoint) + e := basePath.String() + safeEndpoint req, err := http.NewRequest(httpMethod, e, httpBody) if err != nil { return nil, err @@ -140,21 +238,11 @@ func (c *Connection) DoRequest(httpBody io.Reader, httpMethod, endpoint string, if err == nil { break } + time.Sleep(time.Duration(i*100) * time.Millisecond) } return &APIResponse{response, req}, err } -// GetConnectionFromContext returns a bindings connection from the context -// being passed into each method. -func GetConnectionFromContext(ctx context.Context) (*Connection, error) { - c := ctx.Value("conn") - if c == nil { - return nil, errors.New("unable to get connection from context") - } - conn := c.(*Connection) - return conn, nil -} - // 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) { @@ -189,3 +277,45 @@ func (h *APIResponse) IsClientError() bool { 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/containers.go b/pkg/bindings/containers/containers.go index 04f7f8802..a437e9a9b 100644 --- a/pkg/bindings/containers/containers.go +++ b/pkg/bindings/containers/containers.go @@ -16,7 +16,7 @@ import ( // 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.GetConnectionFromContext(ctx) + conn, err := bindings.GetClient(ctx) if err != nil { return nil, err } @@ -59,7 +59,7 @@ func Prune(ctx context.Context, filters map[string][]string) ([]string, error) { var ( pruneResponse []string ) - conn, err := bindings.GetConnectionFromContext(ctx) + conn, err := bindings.GetClient(ctx) if err != nil { return nil, err } @@ -82,7 +82,7 @@ func Prune(ctx context.Context, filters map[string][]string) ([]string, error) { // 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.GetConnectionFromContext(ctx) + conn, err := bindings.GetClient(ctx) if err != nil { return err } @@ -105,7 +105,7 @@ func Remove(ctx context.Context, nameOrID string, force, volumes *bool) error { // 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) (*libpod.InspectContainerData, error) { - conn, err := bindings.GetConnectionFromContext(ctx) + conn, err := bindings.GetClient(ctx) if err != nil { return nil, err } @@ -125,7 +125,7 @@ func Inspect(ctx context.Context, nameOrID string, size *bool) (*libpod.InspectC // 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, signal string) error { - conn, err := bindings.GetConnectionFromContext(ctx) + conn, err := bindings.GetClient(ctx) if err != nil { return err } @@ -143,7 +143,7 @@ func Logs() {} // 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.GetConnectionFromContext(ctx) + conn, err := bindings.GetClient(ctx) if err != nil { return err } @@ -158,7 +158,7 @@ func Pause(ctx context.Context, nameOrID string) error { // 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.GetConnectionFromContext(ctx) + conn, err := bindings.GetClient(ctx) if err != nil { return err } @@ -177,7 +177,7 @@ func Restart(ctx context.Context, nameOrID string, timeout *int) error { // 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.GetConnectionFromContext(ctx) + conn, err := bindings.GetClient(ctx) if err != nil { return err } @@ -198,7 +198,7 @@ func Top() {} // 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.GetConnectionFromContext(ctx) + conn, err := bindings.GetClient(ctx) if err != nil { return err } @@ -213,7 +213,7 @@ func Unpause(ctx context.Context, nameOrID string) error { // or a partial/full ID. func Wait(ctx context.Context, nameOrID string) (int32, error) { var exitCode int32 - conn, err := bindings.GetConnectionFromContext(ctx) + conn, err := bindings.GetClient(ctx) if err != nil { return exitCode, err } @@ -228,7 +228,7 @@ func Wait(ctx context.Context, nameOrID string) (int32, error) { // 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.GetConnectionFromContext(ctx) + conn, err := bindings.GetClient(ctx) if err != nil { return false, err } @@ -243,7 +243,7 @@ func Exists(ctx context.Context, nameOrID string) (bool, error) { // or a partial/full ID func Stop(ctx context.Context, nameOrID string, timeout *int) error { params := make(map[string]string) - conn, err := bindings.GetConnectionFromContext(ctx) + conn, err := bindings.GetClient(ctx) if err != nil { return err } diff --git a/pkg/bindings/containers/create.go b/pkg/bindings/containers/create.go index 18b32335b..2943cb522 100644 --- a/pkg/bindings/containers/create.go +++ b/pkg/bindings/containers/create.go @@ -13,7 +13,7 @@ import ( func CreateWithSpec(ctx context.Context, s specgen.SpecGenerator) (utils.ContainerCreateResponse, error) { var ccr utils.ContainerCreateResponse - conn, err := bindings.GetConnectionFromContext(ctx) + conn, err := bindings.GetClient(ctx) if err != nil { return ccr, err } diff --git a/pkg/bindings/containers/healthcheck.go b/pkg/bindings/containers/healthcheck.go index 9ed7f858d..dc607c1b3 100644 --- a/pkg/bindings/containers/healthcheck.go +++ b/pkg/bindings/containers/healthcheck.go @@ -11,7 +11,7 @@ import ( // RunHealthCheck executes the container's healthcheck and returns the health status of the // container. func RunHealthCheck(ctx context.Context, nameOrID string) (*libpod.HealthCheckStatus, error) { - conn, err := bindings.GetConnectionFromContext(ctx) + conn, err := bindings.GetClient(ctx) if err != nil { return nil, err } diff --git a/pkg/bindings/containers/mount.go b/pkg/bindings/containers/mount.go index d68dee981..e0627d9a3 100644 --- a/pkg/bindings/containers/mount.go +++ b/pkg/bindings/containers/mount.go @@ -10,7 +10,7 @@ import ( // 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.GetConnectionFromContext(ctx) + conn, err := bindings.GetClient(ctx) if err != nil { return "", err } @@ -27,7 +27,7 @@ func Mount(ctx context.Context, nameOrID string) (string, error) { // 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.GetConnectionFromContext(ctx) + conn, err := bindings.GetClient(ctx) if err != nil { return err } @@ -40,7 +40,7 @@ func Unmount(ctx context.Context, nameOrID string) error { // GetMountedContainerPaths returns a map of mounted containers and their mount locations. func GetMountedContainerPaths(ctx context.Context) (map[string]string, error) { - conn, err := bindings.GetConnectionFromContext(ctx) + conn, err := bindings.GetClient(ctx) if err != nil { return nil, err } diff --git a/pkg/bindings/images/images.go b/pkg/bindings/images/images.go index b19482943..271d58952 100644 --- a/pkg/bindings/images/images.go +++ b/pkg/bindings/images/images.go @@ -14,7 +14,7 @@ import ( // 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.GetConnectionFromContext(ctx) + conn, err := bindings.GetClient(ctx) if err != nil { return false, err } @@ -29,7 +29,7 @@ func Exists(ctx context.Context, nameOrID string) (bool, error) { // ways to alter the image query. func List(ctx context.Context, all *bool, filters map[string][]string) ([]*handlers.ImageSummary, error) { var imageSummary []*handlers.ImageSummary - conn, err := bindings.GetConnectionFromContext(ctx) + conn, err := bindings.GetClient(ctx) if err != nil { return nil, err } @@ -54,7 +54,7 @@ func List(ctx context.Context, all *bool, filters map[string][]string) ([]*handl // 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.GetConnectionFromContext(ctx) + conn, err := bindings.GetClient(ctx) if err != nil { return nil, err } @@ -77,7 +77,7 @@ func ImageTree(ctx context.Context, nameOrId string) error { // 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.GetConnectionFromContext(ctx) + conn, err := bindings.GetClient(ctx) if err != nil { return nil, err } @@ -89,7 +89,7 @@ func History(ctx context.Context, nameOrID string) ([]*handlers.HistoryResponse, } func Load(ctx context.Context, r io.Reader) error { - conn, err := bindings.GetConnectionFromContext(ctx) + conn, err := bindings.GetClient(ctx) if err != nil { return err } @@ -103,7 +103,7 @@ func Load(ctx context.Context, r io.Reader) error { // 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.GetConnectionFromContext(ctx) + conn, err := bindings.GetClient(ctx) if err != nil { return nil, err } @@ -121,7 +121,7 @@ func Remove(ctx context.Context, nameOrID string, force *bool) ([]map[string]str // 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.GetConnectionFromContext(ctx) + conn, err := bindings.GetClient(ctx) if err != nil { return err } @@ -149,7 +149,7 @@ func Prune(ctx context.Context, filters map[string][]string) ([]string, error) { var ( deleted []string ) - conn, err := bindings.GetConnectionFromContext(ctx) + conn, err := bindings.GetClient(ctx) if err != nil { return nil, err } @@ -170,7 +170,7 @@ func Prune(ctx context.Context, filters map[string][]string) ([]string, error) { // 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.GetConnectionFromContext(ctx) + conn, err := bindings.GetClient(ctx) if err != nil { return err } diff --git a/pkg/bindings/images/search.go b/pkg/bindings/images/search.go index 58b25425b..dca1b0e63 100644 --- a/pkg/bindings/images/search.go +++ b/pkg/bindings/images/search.go @@ -16,7 +16,7 @@ func Search(ctx context.Context, term string, limit *int, filters map[string][]s var ( searchResults []image.SearchResult ) - conn, err := bindings.GetConnectionFromContext(ctx) + conn, err := bindings.GetClient(ctx) if err != nil { return nil, err } diff --git a/pkg/bindings/network/network.go b/pkg/bindings/network/network.go index 97bbb8c42..c95b22953 100644 --- a/pkg/bindings/network/network.go +++ b/pkg/bindings/network/network.go @@ -10,7 +10,7 @@ import ( func Create() {} func Inspect(ctx context.Context, nameOrID string) (map[string]interface{}, error) { - conn, err := bindings.GetConnectionFromContext(ctx) + conn, err := bindings.GetClient(ctx) if err != nil { return nil, err } @@ -23,7 +23,7 @@ func Inspect(ctx context.Context, nameOrID string) (map[string]interface{}, erro } func Remove(ctx context.Context, nameOrID string) error { - conn, err := bindings.GetConnectionFromContext(ctx) + conn, err := bindings.GetClient(ctx) if err != nil { return err } @@ -38,7 +38,7 @@ func List(ctx context.Context) ([]*libcni.NetworkConfigList, error) { var ( netList []*libcni.NetworkConfigList ) - conn, err := bindings.GetConnectionFromContext(ctx) + conn, err := bindings.GetClient(ctx) if err != nil { return nil, err } diff --git a/pkg/bindings/pods/pods.go b/pkg/bindings/pods/pods.go index d079f01c2..838b22e43 100644 --- a/pkg/bindings/pods/pods.go +++ b/pkg/bindings/pods/pods.go @@ -16,7 +16,7 @@ func CreatePod() error { // 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.GetConnectionFromContext(ctx) + conn, err := bindings.GetClient(ctx) if err != nil { return false, err } @@ -29,7 +29,7 @@ func Exists(ctx context.Context, nameOrID string) (bool, error) { // Inspect returns low-level information about the given pod. func Inspect(ctx context.Context, nameOrID string) (*libpod.PodInspect, error) { - conn, err := bindings.GetConnectionFromContext(ctx) + conn, err := bindings.GetClient(ctx) if err != nil { return nil, err } @@ -44,7 +44,7 @@ func Inspect(ctx context.Context, nameOrID string) (*libpod.PodInspect, error) { // 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) error { - conn, err := bindings.GetConnectionFromContext(ctx) + conn, err := bindings.GetClient(ctx) if err != nil { return err } @@ -61,7 +61,7 @@ func Kill(ctx context.Context, nameOrID string, signal *string) error { // Pause pauses all running containers in a given pod. func Pause(ctx context.Context, nameOrID string) error { - conn, err := bindings.GetConnectionFromContext(ctx) + conn, err := bindings.GetClient(ctx) if err != nil { return err } @@ -74,7 +74,7 @@ func Pause(ctx context.Context, nameOrID string) error { // Prune removes all non-running pods in local storage. func Prune(ctx context.Context) error { - conn, err := bindings.GetConnectionFromContext(ctx) + conn, err := bindings.GetClient(ctx) if err != nil { return err } @@ -87,11 +87,11 @@ func Prune(ctx context.Context) error { // 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) (*[]libpod.PodInspect, error) { +func List(ctx context.Context, filters map[string][]string) ([]*libpod.PodInspect, error) { var ( - inspect []libpod.PodInspect + inspect []*libpod.PodInspect ) - conn, err := bindings.GetConnectionFromContext(ctx) + conn, err := bindings.GetClient(ctx) if err != nil { return nil, err } @@ -103,16 +103,16 @@ func List(ctx context.Context, filters map[string][]string) (*[]libpod.PodInspec } params["filters"] = stringFilter } - response, err := conn.DoRequest(nil, http.MethodPost, "/pods/json", params) + response, err := conn.DoRequest(nil, http.MethodGet, "/pods/json", params) if err != nil { - return &inspect, err + return inspect, err } - return &inspect, response.Process(&inspect) + return inspect, response.Process(&inspect) } // Restart restarts all containers in a pod. func Restart(ctx context.Context, nameOrID string) error { - conn, err := bindings.GetConnectionFromContext(ctx) + conn, err := bindings.GetClient(ctx) if err != nil { return err } @@ -126,7 +126,7 @@ func Restart(ctx context.Context, nameOrID string) error { // 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) error { - conn, err := bindings.GetConnectionFromContext(ctx) + conn, err := bindings.GetClient(ctx) if err != nil { return err } @@ -143,11 +143,11 @@ func Remove(ctx context.Context, nameOrID string, force *bool) error { // Start starts all containers in a pod. func Start(ctx context.Context, nameOrID string) error { - conn, err := bindings.GetConnectionFromContext(ctx) + conn, err := bindings.GetClient(ctx) if err != nil { return err } - response, err := conn.DoRequest(nil, http.MethodDelete, "/pods/%s/start", nil, nameOrID) + response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/start", nil, nameOrID) if err != nil { return err } @@ -162,7 +162,7 @@ func Stats() error { // 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) error { - conn, err := bindings.GetConnectionFromContext(ctx) + conn, err := bindings.GetClient(ctx) if err != nil { return err } @@ -184,7 +184,7 @@ func Top() error { // Unpause unpauses all paused containers in a Pod. func Unpause(ctx context.Context, nameOrID string) error { - conn, err := bindings.GetConnectionFromContext(ctx) + conn, err := bindings.GetClient(ctx) if err != nil { return err } diff --git a/pkg/bindings/test/common_test.go b/pkg/bindings/test/common_test.go index dba94cb35..98d64bbaa 100644 --- a/pkg/bindings/test/common_test.go +++ b/pkg/bindings/test/common_test.go @@ -114,7 +114,7 @@ func newBindingTest() *bindingTest { runRoot: filepath.Join(tmpPath, "run"), artifactDirPath: "", imageCacheDir: "", - sock: fmt.Sprintf("unix:%s", filepath.Join(tmpPath, "api.sock")), + sock: fmt.Sprintf("unix://%s", filepath.Join(tmpPath, "api.sock")), tempDirPath: tmpPath, } return &b @@ -162,16 +162,30 @@ func (b *bindingTest) restoreImageFromCache(i testImage) { p.Wait(45) } -// Run a container and add append the alpine image to it -func (b *bindingTest) RunTopContainer(name *string) { +// 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) { cmd := []string{"run", "-dt"} - if name != nil { - containerName := *name - cmd = append(cmd, "--name", containerName) + if *insidePod && podName != nil { + pName := *podName + cmd = append(cmd, "--pod", pName) + } else if containerName != nil { + cName := *containerName + cmd = append(cmd, "--name", cName) } cmd = append(cmd, alpine.name, "top") - p := b.runPodman(cmd) - p.Wait(45) + b.runPodman(cmd).Wait(45) +} + +// 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 diff --git a/pkg/bindings/test/images_test.go b/pkg/bindings/test/images_test.go index 74e0cc67a..0b51c8c9e 100644 --- a/pkg/bindings/test/images_test.go +++ b/pkg/bindings/test/images_test.go @@ -38,7 +38,7 @@ var _ = Describe("Podman images", func() { bt.RestoreImagesFromCache() s = bt.startAPIService() time.Sleep(1 * time.Second) - connText, err = bindings.NewConnection(bt.sock) + connText, err = bindings.NewConnection(context.Background(), bt.sock) Expect(err).To(BeNil()) }) @@ -93,7 +93,7 @@ var _ = Describe("Podman images", func() { // Start a container with alpine image var top string = "top" - bt.RunTopContainer(&top) + bt.RunTopContainer(&top, &falseFlag, nil) // we should now have a container called "top" running containerResponse, err := containers.Inspect(connText, "top", &falseFlag) Expect(err).To(BeNil()) diff --git a/pkg/bindings/test/pods_test.go b/pkg/bindings/test/pods_test.go new file mode 100644 index 000000000..76ccd10f2 --- /dev/null +++ b/pkg/bindings/test/pods_test.go @@ -0,0 +1,202 @@ +package test_bindings + +import ( + "context" + "net/http" + "time" + + "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/pkg/bindings" + "github.com/containers/libpod/pkg/bindings/pods" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/onsi/gomega/gexec" +) + +var _ = Describe("Podman images", func() { + var ( + bt *bindingTest + s *gexec.Session + connText context.Context + newpod string + err error + trueFlag bool = true + ) + + BeforeEach(func() { + bt = newBindingTest() + newpod = "newpod" + bt.RestoreImagesFromCache() + bt.Podcreate(&newpod) + s = bt.startAPIService() + time.Sleep(1 * time.Second) + connText, err = bindings.NewConnection(bt.sock) + Expect(err).To(BeNil()) + }) + + AfterEach(func() { + s.Kill() + bt.cleanup() + }) + + It("inspect pod", func() { + //Inspect an invalid pod name + _, err := pods.Inspect(connText, "dummyname") + Expect(err).ToNot(BeNil()) + code, _ := bindings.CheckResponseCode(err) + Expect(code).To(BeNumerically("==", http.StatusNotFound)) + + //Inspect an valid pod name + response, err := pods.Inspect(connText, 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(connText, nil) + Expect(err).To(BeNil()) + Expect(len(podSummary)).To(Equal(1)) + // Adding an alpine container to the existing pod + bt.RunTopContainer(nil, &trueFlag, &newpod) + podSummary, err = pods.List(connText, 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(connText, nil) + Expect(len(podSummary)).To(Equal(2)) + var names []string + for _, i := range podSummary { + names = append(names, i.Config.Name) + } + Expect(StringInSlice(newpod, names)).To(BeTrue()) + Expect(StringInSlice("newpod2", names)).To(BeTrue()) + + // Not working Because: code to list based on filter + // "not yet implemented", + // Validate list pod with filters + filters := make(map[string][]string) + filters["name"] = []string{newpod} + filteredPods, err := pods.List(connText, filters) + Expect(err).To(BeNil()) + Expect(len(filteredPods)).To(BeNumerically("==", 1)) + }) + + // The test validates if the exists responds + It("exists pod", func() { + response, err := pods.Exists(connText, "dummyName") + Expect(err).To(BeNil()) + Expect(response).To(BeFalse()) + + // Should exit with no error and response should be true + response, err = pods.Exists(connText, "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() { + // Pause invalid container + err := pods.Pause(connText, "dummyName") + Expect(err).ToNot(BeNil()) + code, _ := bindings.CheckResponseCode(err) + Expect(code).To(BeNumerically("==", http.StatusNotFound)) + + // Adding an alpine container to the existing pod + bt.RunTopContainer(nil, &trueFlag, &newpod) + response, err := pods.Inspect(connText, newpod) + Expect(err).To(BeNil()) + + // Binding needs to be modified to inspect the pod state. + // Since we dont have a pod state we inspect the states of the containers within the pod. + // Pause a valid container + err = pods.Pause(connText, newpod) + Expect(err).To(BeNil()) + response, err = pods.Inspect(connText, newpod) + for _, i := range response.Containers { + Expect(define.StringToContainerStatus(i.State)). + To(Equal(define.ContainerStatePaused)) + } + + // Unpause a valid container + err = pods.Unpause(connText, newpod) + Expect(err).To(BeNil()) + response, err = pods.Inspect(connText, newpod) + 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(connText, "dummyName") + Expect(err).ToNot(BeNil()) + code, _ := bindings.CheckResponseCode(err) + Expect(code).To(BeNumerically("==", http.StatusNotFound)) + + // Stop an invalid pod + err = pods.Stop(connText, "dummyName", nil) + Expect(err).ToNot(BeNil()) + code, _ = bindings.CheckResponseCode(err) + Expect(code).To(BeNumerically("==", http.StatusNotFound)) + + // Restart an invalid pod + err = pods.Restart(connText, "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(connText, newpod) + Expect(err).To(BeNil()) + + response, err := pods.Inspect(connText, newpod) + for _, i := range response.Containers { + Expect(define.StringToContainerStatus(i.State)). + To(Equal(define.ContainerStateRunning)) + } + + // Start a already running container + // (Test fails for now needs to be fixed) + err = pods.Start(connText, newpod) + Expect(err).ToNot(BeNil()) + code, _ = bindings.CheckResponseCode(err) + Expect(code).To(BeNumerically("==", http.StatusNotModified)) + + // Stop the running pods + err = pods.Stop(connText, newpod, nil) + Expect(err).To(BeNil()) + response, _ = pods.Inspect(connText, newpod) + for _, i := range response.Containers { + Expect(define.StringToContainerStatus(i.State)). + To(Equal(define.ContainerStateStopped)) + } + + // Stop a already running pod + // (Test fails for now needs to be fixed) + err = pods.Stop(connText, newpod, nil) + Expect(err).ToNot(BeNil()) + code, _ = bindings.CheckResponseCode(err) + Expect(code).To(BeNumerically("==", http.StatusNotModified)) + + err = pods.Restart(connText, newpod) + Expect(err).To(BeNil()) + response, _ = pods.Inspect(connText, newpod) + for _, i := range response.Containers { + Expect(define.StringToContainerStatus(i.State)). + To(Equal(define.ContainerStateRunning)) + } + }) + + // Remove all stopped pods and their container to be implemented. + It("prune pod", func() { + }) +}) diff --git a/pkg/bindings/volumes/volumes.go b/pkg/bindings/volumes/volumes.go index 05a4f73fd..8313a7460 100644 --- a/pkg/bindings/volumes/volumes.go +++ b/pkg/bindings/volumes/volumes.go @@ -16,7 +16,7 @@ func Create(ctx context.Context, config handlers.VolumeCreateConfig) (string, er var ( volumeID string ) - conn, err := bindings.GetConnectionFromContext(ctx) + conn, err := bindings.GetClient(ctx) if err != nil { return "", err } @@ -32,7 +32,7 @@ func Inspect(ctx context.Context, nameOrID string) (*libpod.InspectVolumeData, e var ( inspect libpod.InspectVolumeData ) - conn, err := bindings.GetConnectionFromContext(ctx) + conn, err := bindings.GetClient(ctx) if err != nil { return nil, err } @@ -55,7 +55,7 @@ func Prune(ctx context.Context) ([]string, error) { var ( pruned []string ) - conn, err := bindings.GetConnectionFromContext(ctx) + conn, err := bindings.GetClient(ctx) if err != nil { return nil, err } @@ -69,7 +69,7 @@ func Prune(ctx context.Context) ([]string, error) { // 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.GetConnectionFromContext(ctx) + conn, err := bindings.GetClient(ctx) if err != nil { return err } |