diff options
Diffstat (limited to 'pkg')
-rw-r--r-- | pkg/api/handlers/compat/containers_attach.go | 2 | ||||
-rw-r--r-- | pkg/api/handlers/compat/containers_start.go | 3 | ||||
-rw-r--r-- | pkg/api/handlers/compat/containers_stop.go (renamed from pkg/api/handlers/compat/container_start.go) | 0 | ||||
-rw-r--r-- | pkg/api/handlers/compat/ping.go | 8 | ||||
-rw-r--r-- | pkg/api/handlers/compat/version.go | 42 | ||||
-rw-r--r-- | pkg/api/handlers/handler.go | 6 | ||||
-rw-r--r-- | pkg/api/handlers/utils/handler.go | 86 | ||||
-rw-r--r-- | pkg/api/handlers/utils/handler_test.go | 139 | ||||
-rw-r--r-- | pkg/api/server/register_images.go | 2 | ||||
-rw-r--r-- | pkg/bindings/bindings.go | 7 | ||||
-rw-r--r-- | pkg/bindings/connection.go | 17 | ||||
-rw-r--r-- | pkg/bindings/containers/containers.go | 8 | ||||
-rw-r--r-- | pkg/bindings/test/attach_test.go | 4 | ||||
-rw-r--r-- | pkg/bindings/version.go | 3 | ||||
-rw-r--r-- | pkg/domain/entities/network.go | 2 | ||||
-rw-r--r-- | pkg/domain/infra/abi/network.go | 37 | ||||
-rw-r--r-- | pkg/domain/infra/tunnel/containers.go | 9 |
17 files changed, 328 insertions, 47 deletions
diff --git a/pkg/api/handlers/compat/containers_attach.go b/pkg/api/handlers/compat/containers_attach.go index 5fc3117b9..012e20daf 100644 --- a/pkg/api/handlers/compat/containers_attach.go +++ b/pkg/api/handlers/compat/containers_attach.go @@ -94,7 +94,7 @@ func AttachContainer(w http.ResponseWriter, r *http.Request) { return } } else if !(state == define.ContainerStateCreated || state == define.ContainerStateRunning) { - utils.InternalServerError(w, errors.Wrapf(define.ErrCtrStateInvalid, "can only attach to created or running containers")) + utils.InternalServerError(w, errors.Wrapf(define.ErrCtrStateInvalid, "can only attach to created or running containers - currently in state %s", state.String())) return } diff --git a/pkg/api/handlers/compat/containers_start.go b/pkg/api/handlers/compat/containers_start.go index 9cb1492fb..cdbc8ff76 100644 --- a/pkg/api/handlers/compat/containers_start.go +++ b/pkg/api/handlers/compat/containers_start.go @@ -33,7 +33,6 @@ func StartContainer(w http.ResponseWriter, r *http.Request) { utils.ContainerNotFound(w, name, err) return } - state, err := con.State() if err != nil { utils.InternalServerError(w, err) @@ -43,7 +42,7 @@ func StartContainer(w http.ResponseWriter, r *http.Request) { utils.WriteResponse(w, http.StatusNotModified, "") return } - if err := con.Start(r.Context(), false); err != nil { + if err := con.Start(r.Context(), len(con.PodID()) > 0); err != nil { utils.InternalServerError(w, err) return } diff --git a/pkg/api/handlers/compat/container_start.go b/pkg/api/handlers/compat/containers_stop.go index d26ef2c82..d26ef2c82 100644 --- a/pkg/api/handlers/compat/container_start.go +++ b/pkg/api/handlers/compat/containers_stop.go diff --git a/pkg/api/handlers/compat/ping.go b/pkg/api/handlers/compat/ping.go index 6e77e270f..abee3d8e8 100644 --- a/pkg/api/handlers/compat/ping.go +++ b/pkg/api/handlers/compat/ping.go @@ -5,22 +5,22 @@ import ( "net/http" "github.com/containers/buildah" - "github.com/containers/libpod/pkg/api/handlers" + "github.com/containers/libpod/pkg/api/handlers/utils" ) // Ping returns headers to client about the service // // This handler must always be the same for the compatibility and libpod URL trees! // Clients will use the Header availability to test which backend engine is in use. +// Note: Additionally handler supports GET and HEAD methods func Ping(w http.ResponseWriter, r *http.Request) { - w.Header().Set("API-Version", handlers.DefaultApiVersion) + w.Header().Set("API-Version", utils.ApiVersion[utils.CompatTree][utils.CurrentApiVersion].String()) w.Header().Set("BuildKit-Version", "") w.Header().Set("Docker-Experimental", "true") w.Header().Set("Cache-Control", "no-cache") w.Header().Set("Pragma", "no-cache") - // API-Version and Libpod-API-Version may not always be equal - w.Header().Set("Libpod-API-Version", handlers.DefaultApiVersion) + w.Header().Set("Libpod-API-Version", utils.ApiVersion[utils.LibpodTree][utils.CurrentApiVersion].String()) w.Header().Set("Libpod-Buildha-Version", buildah.Version) w.WriteHeader(http.StatusOK) diff --git a/pkg/api/handlers/compat/version.go b/pkg/api/handlers/compat/version.go index 8786f1d5b..bfc226bb8 100644 --- a/pkg/api/handlers/compat/version.go +++ b/pkg/api/handlers/compat/version.go @@ -8,7 +8,6 @@ import ( "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/define" - "github.com/containers/libpod/pkg/api/handlers" "github.com/containers/libpod/pkg/api/handlers/utils" "github.com/containers/libpod/pkg/domain/entities" docker "github.com/docker/docker/api/types" @@ -35,34 +34,35 @@ func VersionHandler(w http.ResponseWriter, r *http.Request) { Name: "Podman Engine", Version: versionInfo.Version, Details: map[string]string{ - "APIVersion": handlers.DefaultApiVersion, + "APIVersion": utils.ApiVersion[utils.LibpodTree][utils.CurrentApiVersion].String(), "Arch": goRuntime.GOARCH, "BuildTime": time.Unix(versionInfo.Built, 0).Format(time.RFC3339), "Experimental": "true", "GitCommit": versionInfo.GitCommit, "GoVersion": versionInfo.GoVersion, "KernelVersion": infoData.Host.Kernel, - "MinAPIVersion": handlers.MinimalApiVersion, + "MinAPIVersion": utils.ApiVersion[utils.LibpodTree][utils.MinimalApiVersion].String(), "Os": goRuntime.GOOS, }, }} - utils.WriteResponse(w, http.StatusOK, entities.ComponentVersion{Version: docker.Version{ - Platform: struct { - Name string - }{ - Name: fmt.Sprintf("%s/%s/%s-%s", goRuntime.GOOS, goRuntime.GOARCH, infoData.Host.Distribution.Distribution, infoData.Host.Distribution.Version), - }, - APIVersion: components[0].Details["APIVersion"], - Arch: components[0].Details["Arch"], - BuildTime: components[0].Details["BuildTime"], - Components: components, - Experimental: true, - GitCommit: components[0].Details["GitCommit"], - GoVersion: components[0].Details["GoVersion"], - KernelVersion: components[0].Details["KernelVersion"], - MinAPIVersion: components[0].Details["MinAPIVersion"], - Os: components[0].Details["Os"], - Version: components[0].Version, - }}) + utils.WriteResponse(w, http.StatusOK, entities.ComponentVersion{ + Version: docker.Version{ + Platform: struct { + Name string + }{ + Name: fmt.Sprintf("%s/%s/%s-%s", goRuntime.GOOS, goRuntime.GOARCH, infoData.Host.Distribution.Distribution, infoData.Host.Distribution.Version), + }, + APIVersion: components[0].Details["APIVersion"], + Arch: components[0].Details["Arch"], + BuildTime: components[0].Details["BuildTime"], + Components: components, + Experimental: true, + GitCommit: components[0].Details["GitCommit"], + GoVersion: components[0].Details["GoVersion"], + KernelVersion: components[0].Details["KernelVersion"], + MinAPIVersion: components[0].Details["MinAPIVersion"], + Os: components[0].Details["Os"], + Version: components[0].Version, + }}) } diff --git a/pkg/api/handlers/handler.go b/pkg/api/handlers/handler.go deleted file mode 100644 index 2dd2c886b..000000000 --- a/pkg/api/handlers/handler.go +++ /dev/null @@ -1,6 +0,0 @@ -package handlers - -const ( - DefaultApiVersion = "1.40" // See https://docs.docker.com/engine/api/v1.40/ - MinimalApiVersion = "1.24" -) diff --git a/pkg/api/handlers/utils/handler.go b/pkg/api/handlers/utils/handler.go index b5bd488fb..2f4a54b98 100644 --- a/pkg/api/handlers/utils/handler.go +++ b/pkg/api/handlers/utils/handler.go @@ -9,11 +9,55 @@ import ( "os" "strings" + "github.com/blang/semver" "github.com/gorilla/mux" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) +type ( + // VersionTree determines which API endpoint tree for version + VersionTree int + // VersionLevel determines which API level, current or something from the past + VersionLevel int +) + +const ( + // LibpodTree supports Libpod endpoints + LibpodTree = VersionTree(iota) + // CompatTree supports Libpod endpoints + CompatTree + + // CurrentApiVersion announces what is the current API level + CurrentApiVersion = VersionLevel(iota) + // MinimalApiVersion announces what is the oldest API level supported + MinimalApiVersion +) + +var ( + // See https://docs.docker.com/engine/api/v1.40/ + // libpod compat handlers are expected to honor docker API versions + + // ApiVersion provides the current and minimal API versions for compat and libpod endpoint trees + // Note: GET|HEAD /_ping is never versioned and provides the API-Version and Libpod-API-Version headers to allow + // clients to shop for the Version they wish to support + ApiVersion = map[VersionTree]map[VersionLevel]semver.Version{ + LibpodTree: { + CurrentApiVersion: semver.MustParse("1.0.0"), + MinimalApiVersion: semver.MustParse("1.0.0"), + }, + CompatTree: { + CurrentApiVersion: semver.MustParse("1.40.0"), + MinimalApiVersion: semver.MustParse("1.24.0"), + }, + } + + // ErrVersionNotGiven returned when version not given by client + ErrVersionNotGiven = errors.New("version not given in URL path") + // ErrVersionNotSupported returned when given version is too old + ErrVersionNotSupported = errors.New("given version is not supported") +) + // IsLibpodRequest returns true if the request related to a libpod endpoint // (e.g., /v2/libpod/...). func IsLibpodRequest(r *http.Request) bool { @@ -21,6 +65,48 @@ func IsLibpodRequest(r *http.Request) bool { return len(split) >= 3 && split[2] == "libpod" } +// SupportedVersion validates that the version provided by client is included in the given condition +// https://github.com/blang/semver#ranges provides the details for writing conditions +// If a version is not given in URL path, ErrVersionNotGiven is returned +func SupportedVersion(r *http.Request, condition string) (semver.Version, error) { + version := semver.Version{} + val, ok := mux.Vars(r)["version"] + if !ok { + return version, ErrVersionNotGiven + } + safeVal, err := url.PathUnescape(val) + if err != nil { + return version, errors.Wrapf(err, "unable to unescape given API version: %q", val) + } + version, err = semver.ParseTolerant(safeVal) + if err != nil { + return version, errors.Wrapf(err, "unable to parse given API version: %q from %q", safeVal, val) + } + + inRange, err := semver.ParseRange(condition) + if err != nil { + return version, err + } + + if inRange(version) { + return version, nil + } + return version, ErrVersionNotSupported +} + +// SupportedVersionWithDefaults validates that the version provided by client valid is supported by server +// minimal API version <= client path version <= maximum API version focused on the endpoint tree from URL +func SupportedVersionWithDefaults(r *http.Request) (semver.Version, error) { + tree := CompatTree + if IsLibpodRequest(r) { + tree = LibpodTree + } + + return SupportedVersion(r, + fmt.Sprintf(">=%s <=%s", ApiVersion[tree][MinimalApiVersion].String(), + ApiVersion[tree][CurrentApiVersion].String())) +} + // WriteResponse encodes the given value as JSON or string and renders it for http client func WriteResponse(w http.ResponseWriter, code int, value interface{}) { // RFC2616 explicitly states that the following status codes "MUST NOT diff --git a/pkg/api/handlers/utils/handler_test.go b/pkg/api/handlers/utils/handler_test.go new file mode 100644 index 000000000..6009432b5 --- /dev/null +++ b/pkg/api/handlers/utils/handler_test.go @@ -0,0 +1,139 @@ +package utils + +import ( + "errors" + "fmt" + "net/http" + "net/http/httptest" + "testing" + + "github.com/gorilla/mux" +) + +func TestSupportedVersion(t *testing.T) { + req, err := http.NewRequest("GET", + fmt.Sprintf("/v%s/libpod/testing/versions", ApiVersion[LibpodTree][CurrentApiVersion]), + nil) + if err != nil { + t.Fatal(err) + } + req = mux.SetURLVars(req, map[string]string{"version": ApiVersion[LibpodTree][CurrentApiVersion].String()}) + + rr := httptest.NewRecorder() + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, err := SupportedVersionWithDefaults(r) + switch { + case errors.Is(err, ErrVersionNotGiven): // for compat endpoints version optional + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprint(w, err.Error()) + case errors.Is(err, ErrVersionNotSupported): // version given but not supported + w.WriteHeader(http.StatusBadRequest) + fmt.Fprint(w, err.Error()) + case err != nil: + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprint(w, err.Error()) + default: // all good + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, "OK") + } + }) + handler.ServeHTTP(rr, req) + + if status := rr.Code; status != http.StatusOK { + t.Errorf("handler returned wrong status code: got %v want %v", + status, http.StatusOK) + } + + // Check the response body is what we expect. + expected := `OK` + if rr.Body.String() != expected { + t.Errorf("handler returned unexpected body: got %q want %q", + rr.Body.String(), expected) + } +} + +func TestUnsupportedVersion(t *testing.T) { + version := "999.999.999" + req, err := http.NewRequest("GET", + fmt.Sprintf("/v%s/libpod/testing/versions", version), + nil) + if err != nil { + t.Fatal(err) + } + req = mux.SetURLVars(req, map[string]string{"version": version}) + + rr := httptest.NewRecorder() + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, err := SupportedVersionWithDefaults(r) + switch { + case errors.Is(err, ErrVersionNotGiven): // for compat endpoints version optional + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprint(w, err.Error()) + case errors.Is(err, ErrVersionNotSupported): // version given but not supported + w.WriteHeader(http.StatusBadRequest) + fmt.Fprint(w, err.Error()) + case err != nil: + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprint(w, err.Error()) + default: // all good + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, "OK") + } + }) + handler.ServeHTTP(rr, req) + + if status := rr.Code; status != http.StatusBadRequest { + t.Errorf("handler returned wrong status code: got %v want %v", + status, http.StatusBadRequest) + } + + // Check the response body is what we expect. + expected := ErrVersionNotSupported.Error() + if rr.Body.String() != expected { + t.Errorf("handler returned unexpected body: got %q want %q", + rr.Body.String(), expected) + } +} + +func TestEqualVersion(t *testing.T) { + version := "1.30.0" + req, err := http.NewRequest("GET", + fmt.Sprintf("/v%s/libpod/testing/versions", version), + nil) + if err != nil { + t.Fatal(err) + } + req = mux.SetURLVars(req, map[string]string{"version": version}) + + rr := httptest.NewRecorder() + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, err := SupportedVersion(r, "=="+version) + switch { + case errors.Is(err, ErrVersionNotGiven): // for compat endpoints version optional + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprint(w, err.Error()) + case errors.Is(err, ErrVersionNotSupported): // version given but not supported + w.WriteHeader(http.StatusBadRequest) + fmt.Fprint(w, err.Error()) + case err != nil: + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprint(w, err.Error()) + default: // all good + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, "OK") + } + }) + handler.ServeHTTP(rr, req) + + if status := rr.Code; status != http.StatusOK { + t.Errorf("handler returned wrong status code: got %v want %v", + status, http.StatusOK) + } + + // Check the response body is what we expect. + expected := http.StatusText(http.StatusOK) + if rr.Body.String() != expected { + t.Errorf("handler returned unexpected body: got %q want %q", + rr.Body.String(), expected) + } +} diff --git a/pkg/api/server/register_images.go b/pkg/api/server/register_images.go index 36f939779..01854b9c4 100644 --- a/pkg/api/server/register_images.go +++ b/pkg/api/server/register_images.go @@ -698,7 +698,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // responses: // 200: // $ref: '#/responses/LibpodImageTreeResponse' - // 401: + // 404: // $ref: '#/responses/NoSuchImage' // 500: // $ref: '#/responses/InternalError' diff --git a/pkg/bindings/bindings.go b/pkg/bindings/bindings.go index 5e2882aae..7e2a444bd 100644 --- a/pkg/bindings/bindings.go +++ b/pkg/bindings/bindings.go @@ -8,6 +8,10 @@ package bindings +import ( + "github.com/blang/semver" +) + var ( // PTrue is a convenience variable that can be used in bindings where // a pointer to a bool (optional parameter) is required. @@ -17,4 +21,7 @@ var ( // a pointer to a bool (optional parameter) is required. pFalse = false PFalse = &pFalse + + // _*YES*- podman will fail to run if this value is wrong + APIVersion = semver.MustParse("1.0.0") ) diff --git a/pkg/bindings/connection.go b/pkg/bindings/connection.go index d83c0482c..d21d55beb 100644 --- a/pkg/bindings/connection.go +++ b/pkg/bindings/connection.go @@ -15,6 +15,7 @@ import ( "strings" "time" + "github.com/blang/semver" "github.com/containers/libpod/pkg/api/types" jsoniter "github.com/json-iterator/go" "github.com/pkg/errors" @@ -143,7 +144,7 @@ func tcpClient(_url *url.URL) (Connection, error) { } // pingNewConnection pings to make sure the RESTFUL service is up -// and running. it should only be used where initializing a connection +// and running. it should only be used when initializing a connection func pingNewConnection(ctx context.Context) error { client, err := GetClient(ctx) if err != nil { @@ -154,8 +155,20 @@ func pingNewConnection(ctx context.Context) error { if err != nil { return err } + if response.StatusCode == http.StatusOK { - return nil + v, err := semver.ParseTolerant(response.Header.Get("Libpod-API-Version")) + if err != nil { + return err + } + + switch APIVersion.Compare(v) { + case 1, 0: + // Server's job when client version is equal or older + return nil + case -1: + return errors.Errorf("server API version is too old. client %q server %q", APIVersion.String(), v.String()) + } } return errors.Errorf("ping response was %q", response.StatusCode) } diff --git a/pkg/bindings/containers/containers.go b/pkg/bindings/containers/containers.go index f0984b8e3..74f6ded45 100644 --- a/pkg/bindings/containers/containers.go +++ b/pkg/bindings/containers/containers.go @@ -346,7 +346,7 @@ func ContainerInit(ctx context.Context, nameOrID string) error { } // Attach attaches to a running container -func Attach(ctx context.Context, nameOrId string, detachKeys *string, logs, stream *bool, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { +func Attach(ctx context.Context, nameOrId string, detachKeys *string, logs, stream *bool, stdin io.Reader, stdout io.Writer, stderr io.Writer, attachReady chan bool) error { conn, err := bindings.GetClient(ctx) if err != nil { return err @@ -427,6 +427,12 @@ func Attach(ctx context.Context, nameOrId string, detachKeys *string, logs, stre return err } defer response.Body.Close() + // If we are attaching around a start, we need to "signal" + // back that we are in fact attached so that started does + // not execute before we can attach. + if attachReady != nil { + attachReady <- true + } if !(response.IsSuccess() || response.IsInformational()) { return response.Process(nil) } diff --git a/pkg/bindings/test/attach_test.go b/pkg/bindings/test/attach_test.go index 906bd2950..6fb166828 100644 --- a/pkg/bindings/test/attach_test.go +++ b/pkg/bindings/test/attach_test.go @@ -54,7 +54,7 @@ var _ = Describe("Podman containers attach", func() { go func() { defer GinkgoRecover() - err := containers.Attach(bt.conn, id, nil, bindings.PTrue, bindings.PTrue, nil, stdout, stderr) + err := containers.Attach(bt.conn, id, nil, bindings.PTrue, bindings.PTrue, nil, stdout, stderr, nil) Expect(err).ShouldNot(HaveOccurred()) }() @@ -98,7 +98,7 @@ var _ = Describe("Podman containers attach", func() { go func() { defer GinkgoRecover() - err := containers.Attach(bt.conn, ctnr.ID, nil, bindings.PFalse, bindings.PTrue, stdin, stdout, stderr) + err := containers.Attach(bt.conn, ctnr.ID, nil, bindings.PFalse, bindings.PTrue, stdin, stdout, stderr, nil) Expect(err).ShouldNot(HaveOccurred()) }() diff --git a/pkg/bindings/version.go b/pkg/bindings/version.go deleted file mode 100644 index c833a644c..000000000 --- a/pkg/bindings/version.go +++ /dev/null @@ -1,3 +0,0 @@ -package bindings - -func (c Connection) Version() {} diff --git a/pkg/domain/entities/network.go b/pkg/domain/entities/network.go index d001553e0..9beeeb042 100644 --- a/pkg/domain/entities/network.go +++ b/pkg/domain/entities/network.go @@ -10,6 +10,7 @@ import ( type NetworkListOptions struct { Format string Quiet bool + Filter string } // NetworkListReport describes the results from listing networks @@ -19,6 +20,7 @@ type NetworkListReport struct { // NetworkInspectOptions describes options for inspect networks type NetworkInspectOptions struct { + Format string } // NetworkInspectReport describes the results from inspect networks diff --git a/pkg/domain/infra/abi/network.go b/pkg/domain/infra/abi/network.go index dfde3a939..8e3515824 100644 --- a/pkg/domain/infra/abi/network.go +++ b/pkg/domain/infra/abi/network.go @@ -6,7 +6,9 @@ import ( "fmt" "io/ioutil" "path/filepath" + "strings" + "github.com/containernetworking/cni/libcni" cniversion "github.com/containernetworking/cni/pkg/version" "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/domain/entities" @@ -28,8 +30,19 @@ func (ic *ContainerEngine) NetworkList(ctx context.Context, options entities.Net return nil, err } + var tokens []string + // tokenize the networkListOptions.Filter in key=value. + if len(options.Filter) > 0 { + tokens = strings.Split(options.Filter, "=") + if len(tokens) != 2 { + return nil, fmt.Errorf("invalid filter syntax : %s", options.Filter) + } + } + for _, n := range networks { - reports = append(reports, &entities.NetworkListReport{NetworkConfigList: n}) + if ifPassesFilterTest(n, tokens) { + reports = append(reports, &entities.NetworkListReport{NetworkConfigList: n}) + } } return reports, nil } @@ -255,3 +268,25 @@ func createMacVLAN(r *libpod.Runtime, name string, options entities.NetworkCreat err = ioutil.WriteFile(cniPathName, b, 0644) return cniPathName, err } + +func ifPassesFilterTest(netconf *libcni.NetworkConfigList, filter []string) bool { + result := false + if len(filter) == 0 { + // No filter, so pass + return true + } + switch strings.ToLower(filter[0]) { + case "name": + if filter[1] == netconf.Name { + result = true + } + case "plugin": + plugins := network.GetCNIPlugins(netconf) + if strings.Contains(plugins, filter[1]) { + result = true + } + default: + result = false + } + return result +} diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go index cebd332e3..828bfae5b 100644 --- a/pkg/domain/infra/tunnel/containers.go +++ b/pkg/domain/infra/tunnel/containers.go @@ -326,7 +326,7 @@ func (ic *ContainerEngine) ContainerLogs(ctx context.Context, containers []strin } func (ic *ContainerEngine) ContainerAttach(ctx context.Context, nameOrId string, options entities.AttachOptions) error { - return containers.Attach(ic.ClientCxt, nameOrId, &options.DetachKeys, nil, bindings.PTrue, options.Stdin, options.Stdout, options.Stderr) + return containers.Attach(ic.ClientCxt, nameOrId, &options.DetachKeys, nil, bindings.PTrue, options.Stdin, options.Stdout, options.Stderr, nil) } func (ic *ContainerEngine) ContainerExec(ctx context.Context, nameOrId string, options entities.ExecOptions) (int, error) { @@ -335,11 +335,14 @@ func (ic *ContainerEngine) ContainerExec(ctx context.Context, nameOrId string, o func startAndAttach(ic *ContainerEngine, name string, detachKeys *string, input, output, errput *os.File) error { //nolint attachErr := make(chan error) + attachReady := make(chan bool) go func() { - err := containers.Attach(ic.ClientCxt, name, detachKeys, bindings.PFalse, bindings.PTrue, input, output, errput) + err := containers.Attach(ic.ClientCxt, name, detachKeys, bindings.PFalse, bindings.PTrue, input, output, errput, attachReady) attachErr <- err }() - + // Wait for the attach to actually happen before starting + // the container. + <-attachReady if err := containers.Start(ic.ClientCxt, name, detachKeys); err != nil { return err } |