diff options
27 files changed, 607 insertions, 278 deletions
diff --git a/cmd/cli/main.go b/cmd/cli/main.go deleted file mode 100644 index 4eec05ef2..000000000 --- a/cmd/cli/main.go +++ /dev/null @@ -1,113 +0,0 @@ -package main - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io/ioutil" - "net" - "net/http" - "net/url" - "os" - - "golang.org/x/crypto/ssh" -) - -// remote PODMAN_HOST=ssh://<user>@<host>[:port]/run/podman/podman.sock -// local PODMAN_HOST=unix://run/podman/podman.sock - -var ( - DefaultURL = "unix://root@localhost/run/podman/podman.sock" -) - -func main() { - connectionURL := DefaultURL - if value, found := os.LookupEnv("PODMAN_HOST"); found { - connectionURL = value - } - - _url, err := url.Parse(connectionURL) - if err != nil { - die("Value of PODMAN_HOST is not a valid url: %s\n", connectionURL) - } - - if _url.Scheme != "ssh" && _url.Scheme != "unix" { - die("Scheme from PODMAN_HOST is not supported: %s\n", _url.Scheme) - } - - // Now we setup the http client to use the connection above - client := &http.Client{} - if _url.Scheme == "ssh" { - var auth ssh.AuthMethod - if value, found := os.LookupEnv("PODMAN_SSHKEY"); found { - auth, err = publicKey(value) - if err != nil { - die("Failed to parse %s: %v\n", value, err) - } - } else { - die("PODMAN_SSHKEY was not defined\n") - } - - // Connect to sshd - bastion, err := ssh.Dial("tcp", - net.JoinHostPort(_url.Hostname(), _url.Port()), - &ssh.ClientConfig{ - User: _url.User.Username(), - Auth: []ssh.AuthMethod{auth}, - HostKeyCallback: ssh.InsecureIgnoreHostKey(), - }, - ) - if err != nil { - die("Failed to build ssh tunnel") - } - defer bastion.Close() - - client.Transport = &http.Transport{ - DialContext: func(_ context.Context, _, _ string) (net.Conn, error) { - // Now we make the connection to the unix domain socket on the server using the ssh tunnel - return bastion.Dial("unix", _url.Path) - }, - } - } else { - 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, - } - } - - resp, err := client.Get("http://localhost/v1.24/images/json") - if err != nil { - die(err.Error()) - } - defer resp.Body.Close() - body, _ := ioutil.ReadAll(resp.Body) - - var output bytes.Buffer - _ = json.Indent(&output, body, "", " ") - fmt.Printf("%s\n", output.String()) - os.Exit(0) -} - -func die(format string, a ...interface{}) { - fmt.Fprintf(os.Stderr, format, a...) - fmt.Fprintf(os.Stderr, "\n") - os.Exit(1) -} - -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 -} diff --git a/cmd/podman/login.go b/cmd/podman/login.go index e5ff273b8..e09117833 100644 --- a/cmd/podman/login.go +++ b/cmd/podman/login.go @@ -82,6 +82,10 @@ func loginCmd(c *cliconfig.LoginValues) error { server = registryFromFullName(scrubServer(args[0])) } + if c.Flag("password").Changed { + fmt.Fprintf(os.Stderr, "WARNING! Using --password via the cli is insecure. Please consider using --password-stdin\n") + } + sc := image.GetSystemContext("", c.Authfile, false) if c.Flag("tls-verify").Changed { sc.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!c.TlsVerify) diff --git a/cni/87-podman-bridge.conflist b/cni/87-podman-bridge.conflist index a7bcf47bb..39e79b13c 100644 --- a/cni/87-podman-bridge.conflist +++ b/cni/87-podman-bridge.conflist @@ -1,41 +1,37 @@ { - "cniVersion": "0.4.0", - "name": "podman", - "plugins": [ - { - "type": "bridge", - "bridge": "cni-podman0", - "isGateway": true, - "ipMasq": true, - "ipam": { - "type": "host-local", - "routes": [ - { - "dst": "0.0.0.0/0" - } - ], - "ranges": [ - [ - { - "subnet": "10.88.0.0/16", - "gateway": "10.88.0.1" - } - ] - ] + "cniVersion": "0.4.0", + "name": "podman", + "plugins": [ + { + "type": "bridge", + "bridge": "cni-podman0", + "isGateway": true, + "ipMasq": true, + "ipam": { + "type": "host-local", + "routes": [{ "dst": "0.0.0.0/0" }], + "ranges": [ + [ + { + "subnet": "10.88.0.0/16", + "gateway": "10.88.0.1" } - }, - { - "type": "portmap", - "capabilities": { - "portMappings": true - } - }, - { - "type": "firewall", - "backend": "iptables" - }, - { - "type": "tuning" - } - ] + ] + ] + } + }, + { + "type": "portmap", + "capabilities": { + "portMappings": true + } + }, + { + "type": "firewall", + "backend": "iptables" + }, + { + "type": "tuning" + } + ] } diff --git a/docs/source/markdown/podman-images.1.md b/docs/source/markdown/podman-images.1.md index d22fb940f..09778e3c2 100644 --- a/docs/source/markdown/podman-images.1.md +++ b/docs/source/markdown/podman-images.1.md @@ -29,11 +29,11 @@ Filter output based on conditions provided Filters: - **after==TIMESTRING** - Filter on images created after the given time.Time. + **since=IMAGE** + Filter on images created after the given IMAGE (name or tag). - **before==TIMESTRING** - Filter on images created before the given time.Time. + **before=IMAGE** + Filter on images created before the given IMAGE (name or tag). **dangling=true|false** Show dangling images. Dangling images are a file system layer that was used in a previous build of an image and is no longer referenced by any active images. They are denoted with the <none> tag, consume disk space and serve no active purpose. @@ -638,6 +638,7 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72 h1:bw9doJza/SFBEweII/rHQh338oozWyiFsBRHtrflcws= golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/libpod/image/filters.go b/libpod/image/filters.go index d545f1bfc..7c7394930 100644 --- a/libpod/image/filters.go +++ b/libpod/image/filters.go @@ -141,7 +141,7 @@ func (ir *Runtime) createFilterFuncs(filters []string, img *Image) ([]ResultFilt return nil, errors.Wrapf(err, "unable to find image %s in local stores", splitFilter[1]) } filterFuncs = append(filterFuncs, CreatedBeforeFilter(before.Created())) - case "after": + case "since", "after": after, err := ir.NewFromLocal(splitFilter[1]) if err != nil { return nil, errors.Wrapf(err, "unable to find image %s in local stores", splitFilter[1]) diff --git a/pkg/api/handlers/decoder.go b/pkg/api/handlers/decoder.go index 890d77ecc..03b86275d 100644 --- a/pkg/api/handlers/decoder.go +++ b/pkg/api/handlers/decoder.go @@ -3,8 +3,10 @@ package handlers import ( "encoding/json" "reflect" + "syscall" "time" + "github.com/containers/libpod/pkg/util" "github.com/gorilla/schema" "github.com/sirupsen/logrus" ) @@ -17,6 +19,9 @@ func NewAPIDecoder() *schema.Decoder { d.IgnoreUnknownKeys(true) d.RegisterConverter(map[string][]string{}, convertUrlValuesString) d.RegisterConverter(time.Time{}, convertTimeString) + + var Signal syscall.Signal + d.RegisterConverter(Signal, convertSignal) return d } @@ -89,3 +94,11 @@ func convertTimeString(query string) reflect.Value { func ParseDateTime(query string) time.Time { return convertTimeString(query).Interface().(time.Time) } + +func convertSignal(query string) reflect.Value { + signal, err := util.ParseSignal(query) + if err != nil { + logrus.Infof("convertSignal: Failed to parse %s: %s", query, err.Error()) + } + return reflect.ValueOf(signal) +} diff --git a/pkg/api/handlers/libpod/pods.go b/pkg/api/handlers/libpod/pods.go index 008b9b14b..f5700579b 100644 --- a/pkg/api/handlers/libpod/pods.go +++ b/pkg/api/handlers/libpod/pods.go @@ -119,7 +119,7 @@ func Pods(w http.ResponseWriter, r *http.Request) { return } - if _, found := r.URL.Query()["filters"]; found { + if len(query.Filters) > 0 { utils.Error(w, "filters are not implemented yet", http.StatusInternalServerError, define.ErrNotImplemented) return } 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 } diff --git a/pkg/spec/storage.go b/pkg/spec/storage.go index e37fa2451..c365701de 100644 --- a/pkg/spec/storage.go +++ b/pkg/spec/storage.go @@ -739,6 +739,7 @@ func (config *CreateConfig) getImageVolumes() (map[string]spec.Mount, map[string for vol := range config.BuiltinImgVolumes { cleanDest := filepath.Clean(vol) + logrus.Debugf("Adding image volume at %s", cleanDest) if config.ImageVolumeType == "tmpfs" { // Tmpfs image volumes are handled as mounts mount := spec.Mount{ @@ -747,13 +748,13 @@ func (config *CreateConfig) getImageVolumes() (map[string]spec.Mount, map[string Type: TypeTmpfs, Options: []string{"rprivate", "rw", "nodev", "exec"}, } - mounts[vol] = mount + mounts[cleanDest] = mount } else { // Anonymous volumes have no name. namedVolume := new(libpod.ContainerNamedVolume) namedVolume.Options = []string{"rprivate", "rw", "nodev", "exec"} namedVolume.Dest = cleanDest - volumes[vol] = namedVolume + volumes[cleanDest] = namedVolume } } diff --git a/test/e2e/create_staticip_test.go b/test/e2e/create_staticip_test.go index 72a0638f9..693795637 100644 --- a/test/e2e/create_staticip_test.go +++ b/test/e2e/create_staticip_test.go @@ -4,6 +4,7 @@ package integration import ( "os" + "time" . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" @@ -86,8 +87,23 @@ var _ = Describe("Podman create with --ip flag", func() { result = podmanTest.Podman([]string{"start", "test1"}) result.WaitWithDefaultTimeout() Expect(result.ExitCode()).To(Equal(0)) + + // race prevention: wait until IP address is assigned + for i := 0; i < 5; i++ { + result = podmanTest.Podman([]string{"inspect", "--format", "{{.NetworkSettings.IPAddress}}", "test1"}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + if result.OutputToString() != "" { + break + } + time.Sleep(1 * time.Second) + } + Expect(result.OutputToString()).To(Equal(ip)) + + // test1 container is running with the given IP. result = podmanTest.Podman([]string{"start", "test2"}) result.WaitWithDefaultTimeout() Expect(result).To(ExitWithError()) + Expect(result.ErrorToString()).To(ContainSubstring("requested IP address " + ip + " is not available")) }) }) diff --git a/test/e2e/run_volume_test.go b/test/e2e/run_volume_test.go index 46c27dc2e..e31338dbc 100644 --- a/test/e2e/run_volume_test.go +++ b/test/e2e/run_volume_test.go @@ -15,6 +15,10 @@ import ( "github.com/onsi/gomega/gexec" ) +var VolumeTrailingSlashDockerfile = ` +FROM alpine:latest +VOLUME /test/` + var _ = Describe("Podman run with volumes", func() { var ( tempdir string @@ -421,4 +425,20 @@ var _ = Describe("Podman run with volumes", func() { Expect(len(outputArr)).To(Equal(1)) Expect(strings.Contains(outputArr[0], fileName)).To(BeTrue()) }) + + It("Podman mount over image volume with trailing /", func() { + image := "podman-volume-test:trailing" + podmanTest.BuildImage(VolumeTrailingSlashDockerfile, image, "false") + + ctrName := "testCtr" + create := podmanTest.Podman([]string{"create", "-v", "/tmp:/test", "--name", ctrName, image, "ls"}) + create.WaitWithDefaultTimeout() + Expect(create.ExitCode()).To(Equal(0)) + + data := podmanTest.InspectContainer(ctrName) + Expect(len(data)).To(Equal(1)) + Expect(len(data[0].Mounts)).To(Equal(1)) + Expect(data[0].Mounts[0].Source).To(Equal("/tmp")) + Expect(data[0].Mounts[0].Destination).To(Equal("/test")) + }) }) diff --git a/test/e2e/search_test.go b/test/e2e/search_test.go index d88231510..a697831ab 100644 --- a/test/e2e/search_test.go +++ b/test/e2e/search_test.go @@ -9,6 +9,7 @@ import ( "os" "strconv" "text/template" + "time" . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" @@ -165,8 +166,16 @@ registries = ['{{.Host}}:{{.Port}}']` }) It("podman search v2 registry with empty query", func() { - search := podmanTest.Podman([]string{"search", "registry.fedoraproject.org/"}) - search.WaitWithDefaultTimeout() + var search *PodmanSessionIntegration + for i := 0; i < 5; i++ { + search = podmanTest.Podman([]string{"search", "registry.fedoraproject.org/"}) + search.WaitWithDefaultTimeout() + if search.ExitCode() == 0 { + break + } + fmt.Println("Search failed; sleeping & retrying...") + time.Sleep(2 * time.Second) + } Expect(search.ExitCode()).To(Equal(0)) Expect(len(search.OutputToStringArray())).To(BeNumerically(">=", 1)) }) diff --git a/test/system/010-images.bats b/test/system/010-images.bats index 66ef53590..3224c9b42 100644 --- a/test/system/010-images.bats +++ b/test/system/010-images.bats @@ -74,4 +74,40 @@ size | [0-9]\\\+ run_podman rm my-container } +@test "podman images - filter" { + skip_if_remote "podman commit -q is broken in podman-remote" + + run_podman inspect --format '{{.ID}}' $IMAGE + iid=$output + + run_podman images --noheading --filter=after=$iid + is "$output" "" "baseline: empty results from filter (after)" + + run_podman images --noheading --filter=before=$iid + is "$output" "" "baseline: empty results from filter (before)" + + # Create a dummy container, then commit that as an image. We will + # now be able to use before/after/since queries + run_podman run --name mytinycontainer $IMAGE true + run_podman commit -q mytinycontainer mynewimage + new_iid=$output + + # (refactor common options for legibility) + opts='--noheading --no-trunc --format={{.ID}}--{{.Repository}}:{{.Tag}}' + + run_podman images ${opts} --filter=after=$iid + is "$output" "sha256:$new_iid--localhost/mynewimage:latest" "filter: after" + + # Same thing, with 'since' instead of 'after' + run_podman images ${opts} --filter=since=$iid + is "$output" "sha256:$new_iid--localhost/mynewimage:latest" "filter: since" + + run_podman images ${opts} --filter=before=mynewimage + is "$output" "sha256:$iid--$IMAGE" "filter: before" + + # Clean up + run_podman rmi mynewimage + run_podman rm mytinycontainer +} + # vim: filetype=sh diff --git a/test/system/150-login.bats b/test/system/150-login.bats index 9c9593311..e33217e14 100644 --- a/test/system/150-login.bats +++ b/test/system/150-login.bats @@ -108,8 +108,8 @@ function setup() { @test "podman login - basic test" { run_podman login --tls-verify=false \ --username ${PODMAN_LOGIN_USER} \ - --password ${PODMAN_LOGIN_PASS} \ - localhost:${PODMAN_LOGIN_REGISTRY_PORT} + --password-stdin \ + localhost:${PODMAN_LOGIN_REGISTRY_PORT} <<<"${PODMAN_LOGIN_PASS}" is "$output" "Login Succeeded!" "output from podman login" # Now log out @@ -123,8 +123,8 @@ function setup() { run_podman 125 login --tls-verify=false \ --username ${PODMAN_LOGIN_USER} \ - --password "x${PODMAN_LOGIN_PASS}" \ - $registry + --password-stdin \ + $registry <<< "x${PODMAN_LOGIN_PASS}" is "$output" \ "Error: error logging into \"$registry\": invalid username/password" \ 'output from podman login' |