diff options
author | OpenShift Merge Robot <openshift-merge-robot@users.noreply.github.com> | 2020-03-26 20:10:08 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-03-26 20:10:08 +0100 |
commit | 913426c70c37a87d425085f60af397f7b38bd65d (patch) | |
tree | 97bee43dab0b8cd1edfa0977b78048d61fc59ed9 /pkg | |
parent | 14ece7ecaf6e2b7fe65d64ed872fbfe2d740f48b (diff) | |
parent | e42cbdd1b2f6788a814d6aa1838111989cd424ad (diff) | |
download | podman-913426c70c37a87d425085f60af397f7b38bd65d.tar.gz podman-913426c70c37a87d425085f60af397f7b38bd65d.tar.bz2 podman-913426c70c37a87d425085f60af397f7b38bd65d.zip |
Merge pull request #5573 from mheon/add_basic_exec_endpoints
Implement APIv2 Exec Create and Inspect Endpoints
Diffstat (limited to 'pkg')
-rw-r--r-- | pkg/api/handlers/compat/exec.go | 107 | ||||
-rw-r--r-- | pkg/api/handlers/types.go | 8 | ||||
-rw-r--r-- | pkg/api/server/register_exec.go | 16 | ||||
-rw-r--r-- | pkg/bindings/containers/exec.go | 71 | ||||
-rw-r--r-- | pkg/bindings/test/containers_test.go | 2 | ||||
-rw-r--r-- | pkg/bindings/test/exec_test.go | 77 | ||||
-rw-r--r-- | pkg/bindings/test/pods_test.go | 4 |
7 files changed, 273 insertions, 12 deletions
diff --git a/pkg/api/handlers/compat/exec.go b/pkg/api/handlers/compat/exec.go new file mode 100644 index 000000000..ec1a8ac96 --- /dev/null +++ b/pkg/api/handlers/compat/exec.go @@ -0,0 +1,107 @@ +package compat + +import ( + "encoding/json" + "fmt" + "net/http" + "strings" + + "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/gorilla/mux" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +// ExecCreateHandler creates an exec session for a given container. +func ExecCreateHandler(w http.ResponseWriter, r *http.Request) { + runtime := r.Context().Value("runtime").(*libpod.Runtime) + + input := new(handlers.ExecCreateConfig) + if err := json.NewDecoder(r.Body).Decode(&input); err != nil { + utils.InternalServerError(w, errors.Wrapf(err, "error decoding request body as JSON")) + return + } + + ctrName := utils.GetName(r) + ctr, err := runtime.LookupContainer(ctrName) + if err != nil { + utils.ContainerNotFound(w, ctrName, err) + return + } + + libpodConfig := new(libpod.ExecConfig) + libpodConfig.Command = input.Cmd + libpodConfig.Terminal = input.Tty + libpodConfig.AttachStdin = input.AttachStdin + libpodConfig.AttachStderr = input.AttachStderr + libpodConfig.AttachStdout = input.AttachStdout + if input.DetachKeys != "" { + libpodConfig.DetachKeys = &input.DetachKeys + } + libpodConfig.Environment = make(map[string]string) + for _, envStr := range input.Env { + split := strings.SplitN(envStr, "=", 2) + if len(split) != 2 { + utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, errors.Errorf("environment variable %q badly formed, must be key=value", envStr)) + return + } + libpodConfig.Environment[split[0]] = split[1] + } + libpodConfig.WorkDir = input.WorkingDir + libpodConfig.Privileged = input.Privileged + libpodConfig.User = input.User + + sessID, err := ctr.ExecCreate(libpodConfig) + if err != nil { + if errors.Cause(err) == define.ErrCtrStateInvalid { + // Check if the container is paused. If so, return a 409 + state, err := ctr.State() + if err == nil { + // Ignore the error != nil case. We're already + // throwing an InternalServerError below. + if state == define.ContainerStatePaused { + utils.Error(w, "Container is paused", http.StatusConflict, errors.Errorf("cannot create exec session as container %s is paused", ctr.ID())) + return + } + } + } + utils.InternalServerError(w, err) + return + } + + resp := new(handlers.ExecCreateResponse) + resp.ID = sessID + + utils.WriteResponse(w, http.StatusCreated, resp) +} + +// ExecInspectHandler inspects a given exec session. +func ExecInspectHandler(w http.ResponseWriter, r *http.Request) { + runtime := r.Context().Value("runtime").(*libpod.Runtime) + + sessionID := mux.Vars(r)["id"] + sessionCtr, err := runtime.GetExecSessionContainer(sessionID) + if err != nil { + utils.Error(w, fmt.Sprintf("No such exec session: %s", sessionID), http.StatusNotFound, err) + return + } + + logrus.Debugf("Inspecting exec session %s of container %s", sessionID, sessionCtr.ID()) + + session, err := sessionCtr.ExecSession(sessionID) + if err != nil { + utils.InternalServerError(w, errors.Wrapf(err, "error retrieving exec session %s from container %s", sessionID, sessionCtr.ID())) + return + } + + inspectOut, err := session.Inspect() + if err != nil { + utils.InternalServerError(w, err) + return + } + + utils.WriteResponse(w, http.StatusOK, inspectOut) +} diff --git a/pkg/api/handlers/types.go b/pkg/api/handlers/types.go index 84ca0fbed..fe4198c37 100644 --- a/pkg/api/handlers/types.go +++ b/pkg/api/handlers/types.go @@ -172,6 +172,14 @@ type ImageTreeResponse struct { Layers []ImageLayer `json:"layers"` } +type ExecCreateConfig struct { + docker.ExecConfig +} + +type ExecCreateResponse struct { + docker.IDResponse +} + func EventToApiEvent(e *events.Event) *Event { return &Event{dockerEvents.Message{ Type: e.Type.String(), diff --git a/pkg/api/server/register_exec.go b/pkg/api/server/register_exec.go index d27d21a04..71fb50307 100644 --- a/pkg/api/server/register_exec.go +++ b/pkg/api/server/register_exec.go @@ -8,7 +8,7 @@ import ( ) func (s *APIServer) registerExecHandlers(r *mux.Router) error { - // swagger:operation POST /containers/{name}/create compat createExec + // swagger:operation POST /containers/{name}/exec compat createExec // --- // tags: // - exec (compat) @@ -74,9 +74,9 @@ func (s *APIServer) registerExecHandlers(r *mux.Router) error { // description: container is paused // 500: // $ref: "#/responses/InternalError" - r.Handle(VersionedPath("/containers/{name}/create"), s.APIHandler(compat.UnsupportedHandler)).Methods(http.MethodPost) + r.Handle(VersionedPath("/containers/{name}/exec"), s.APIHandler(compat.ExecCreateHandler)).Methods(http.MethodPost) // Added non version path to URI to support docker non versioned paths - r.Handle("/containers/{name}/create", s.APIHandler(compat.UnsupportedHandler)).Methods(http.MethodPost) + r.Handle("/containers/{name}/exec", s.APIHandler(compat.ExecCreateHandler)).Methods(http.MethodPost) // swagger:operation POST /exec/{id}/start compat startExec // --- // tags: @@ -169,15 +169,15 @@ func (s *APIServer) registerExecHandlers(r *mux.Router) error { // $ref: "#/responses/NoSuchExecInstance" // 500: // $ref: "#/responses/InternalError" - r.Handle(VersionedPath("/exec/{id}/json"), s.APIHandler(compat.UnsupportedHandler)).Methods(http.MethodGet) + r.Handle(VersionedPath("/exec/{id}/json"), s.APIHandler(compat.ExecInspectHandler)).Methods(http.MethodGet) // Added non version path to URI to support docker non versioned paths - r.Handle("/exec/{id}/json", s.APIHandler(compat.UnsupportedHandler)).Methods(http.MethodGet) + r.Handle("/exec/{id}/json", s.APIHandler(compat.ExecInspectHandler)).Methods(http.MethodGet) /* libpod api follows */ - // swagger:operation POST /libpod/containers/{name}/create libpod libpodCreateExec + // swagger:operation POST /libpod/containers/{name}/exec libpod libpodCreateExec // --- // tags: // - exec @@ -243,7 +243,7 @@ func (s *APIServer) registerExecHandlers(r *mux.Router) error { // description: container is paused // 500: // $ref: "#/responses/InternalError" - r.Handle(VersionedPath("/libpod/containers/{name}/create"), s.APIHandler(compat.UnsupportedHandler)).Methods(http.MethodPost) + r.Handle(VersionedPath("/libpod/containers/{name}/exec"), s.APIHandler(compat.ExecCreateHandler)).Methods(http.MethodPost) // swagger:operation POST /libpod/exec/{id}/start libpod libpodStartExec // --- // tags: @@ -332,6 +332,6 @@ func (s *APIServer) registerExecHandlers(r *mux.Router) error { // $ref: "#/responses/NoSuchExecInstance" // 500: // $ref: "#/responses/InternalError" - r.Handle(VersionedPath("/libpod/exec/{id}/json"), s.APIHandler(compat.UnsupportedHandler)).Methods(http.MethodGet) + r.Handle(VersionedPath("/libpod/exec/{id}/json"), s.APIHandler(compat.ExecInspectHandler)).Methods(http.MethodGet) return nil } diff --git a/pkg/bindings/containers/exec.go b/pkg/bindings/containers/exec.go new file mode 100644 index 000000000..48f9ed697 --- /dev/null +++ b/pkg/bindings/containers/exec.go @@ -0,0 +1,71 @@ +package containers + +import ( + "context" + "net/http" + "strings" + + "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/pkg/api/handlers" + "github.com/containers/libpod/pkg/bindings" + jsoniter "github.com/json-iterator/go" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +var json = jsoniter.ConfigCompatibleWithStandardLibrary + +// ExecCreate creates a new exec session in an existing container. +// The exec session will not be started; that is done with ExecStart. +// Returns ID of new exec session, or an error if one occurred. +func ExecCreate(ctx context.Context, nameOrID string, config *handlers.ExecCreateConfig) (string, error) { + conn, err := bindings.GetClient(ctx) + if err != nil { + return "", err + } + + if config == nil { + return "", errors.Errorf("must provide a configuration for exec session") + } + + requestJSON, err := json.Marshal(config) + if err != nil { + return "", errors.Wrapf(err, "error marshalling exec config to JSON") + } + jsonReader := strings.NewReader(string(requestJSON)) + + resp, err := conn.DoRequest(jsonReader, http.MethodPost, "/containers/%s/exec", nil, nameOrID) + if err != nil { + return "", err + } + + respStruct := new(handlers.ExecCreateResponse) + if err := resp.Process(respStruct); err != nil { + return "", err + } + + return respStruct.ID, nil +} + +// ExecInspect inspects an existing exec session, returning detailed information +// about it. +func ExecInspect(ctx context.Context, sessionID string) (*define.InspectExecSession, error) { + conn, err := bindings.GetClient(ctx) + if err != nil { + return nil, err + } + + logrus.Debugf("Inspecting session ID %q", sessionID) + + resp, err := conn.DoRequest(nil, http.MethodGet, "/exec/%s/json", nil, sessionID) + if err != nil { + return nil, err + } + + respStruct := new(define.InspectExecSession) + if err := resp.Process(respStruct); err != nil { + return nil, err + } + + return respStruct, nil +} diff --git a/pkg/bindings/test/containers_test.go b/pkg/bindings/test/containers_test.go index f5465c803..55c739865 100644 --- a/pkg/bindings/test/containers_test.go +++ b/pkg/bindings/test/containers_test.go @@ -1,12 +1,12 @@ package test_bindings import ( - "github.com/containers/libpod/libpod/define" "net/http" "strconv" "strings" "time" + "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/bindings" "github.com/containers/libpod/pkg/bindings/containers" "github.com/containers/libpod/pkg/specgen" diff --git a/pkg/bindings/test/exec_test.go b/pkg/bindings/test/exec_test.go new file mode 100644 index 000000000..1ef2197b6 --- /dev/null +++ b/pkg/bindings/test/exec_test.go @@ -0,0 +1,77 @@ +package test_bindings + +import ( + "time" + + "github.com/containers/libpod/pkg/api/handlers" + "github.com/containers/libpod/pkg/bindings" + "github.com/containers/libpod/pkg/bindings/containers" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/onsi/gomega/gexec" +) + +var _ = Describe("Podman containers exec", func() { + var ( + bt *bindingTest + s *gexec.Session + ) + + BeforeEach(func() { + bt = newBindingTest() + bt.RestoreImagesFromCache() + s = bt.startAPIService() + time.Sleep(1 * time.Second) + err := bt.NewConnection() + Expect(err).To(BeNil()) + }) + + AfterEach(func() { + s.Kill() + bt.cleanup() + }) + + It("Podman exec create makes an exec session", func() { + name := "testCtr" + cid, err := bt.RunTopContainer(&name, &bindings.PFalse, nil) + Expect(err).To(BeNil()) + + execConfig := new(handlers.ExecCreateConfig) + execConfig.Cmd = []string{"echo", "hello world"} + + sessionID, err := containers.ExecCreate(bt.conn, name, execConfig) + Expect(err).To(BeNil()) + Expect(sessionID).To(Not(Equal(""))) + + inspectOut, err := containers.ExecInspect(bt.conn, sessionID) + Expect(err).To(BeNil()) + Expect(inspectOut.ContainerID).To(Equal(cid)) + Expect(inspectOut.ProcessConfig.Entrypoint).To(Equal("echo")) + Expect(len(inspectOut.ProcessConfig.Arguments)).To(Equal(1)) + Expect(inspectOut.ProcessConfig.Arguments[0]).To(Equal("hello world")) + }) + + It("Podman exec create with bad command fails", func() { + name := "testCtr" + _, err := bt.RunTopContainer(&name, &bindings.PFalse, nil) + Expect(err).To(BeNil()) + + execConfig := new(handlers.ExecCreateConfig) + + _, err = containers.ExecCreate(bt.conn, name, execConfig) + Expect(err).To(Not(BeNil())) + }) + + It("Podman exec create with invalid container fails", func() { + execConfig := new(handlers.ExecCreateConfig) + execConfig.Cmd = []string{"echo", "hello world"} + + _, err := containers.ExecCreate(bt.conn, "doesnotexist", execConfig) + Expect(err).To(Not(BeNil())) + }) + + It("Podman exec inspect on invalid session fails", func() { + _, err := containers.ExecInspect(bt.conn, "0000000000000000000000000000000000000000000000000000000000000000") + Expect(err).To(Not(BeNil())) + }) +}) diff --git a/pkg/bindings/test/pods_test.go b/pkg/bindings/test/pods_test.go index e94048a9c..a1d6ee184 100644 --- a/pkg/bindings/test/pods_test.go +++ b/pkg/bindings/test/pods_test.go @@ -79,9 +79,7 @@ var _ = Describe("Podman pods", func() { // The test validates the list pod endpoint with passing filters as the params. It("List pods with filters", func() { - var ( - newpod2 string = "newpod2" - ) + newpod2 := "newpod2" bt.Podcreate(&newpod2) _, err = bt.RunTopContainer(nil, &bindings.PTrue, &newpod) Expect(err).To(BeNil()) |