diff options
| -rw-r--r-- | libpod/container_exec.go | 64 | ||||
| -rw-r--r-- | libpod/define/inspect.go | 54 | ||||
| -rw-r--r-- | libpod/runtime_ctr.go | 18 | ||||
| -rw-r--r-- | pkg/api/handlers/compat/exec.go | 104 | ||||
| -rw-r--r-- | pkg/api/handlers/types.go | 8 | ||||
| -rw-r--r-- | pkg/api/server/register_exec.go | 16 | 
6 files changed, 200 insertions, 64 deletions
diff --git a/libpod/container_exec.go b/libpod/container_exec.go index 7ed7a3343..795eb7453 100644 --- a/libpod/container_exec.go +++ b/libpod/container_exec.go @@ -94,67 +94,14 @@ func (e *ExecSession) ContainerID() string {  	return e.ContainerId  } -// InspectExecSession contains information about a given exec session. -type InspectExecSession struct { -	// CanRemove is legacy and used purely for compatibility reasons. -	// Will always be set to true, unless the exec session is running. -	CanRemove bool `json:"CanRemove"` -	// ContainerID is the ID of the container this exec session is attached -	// to. -	ContainerID string `json:"ContainerID"` -	// DetachKeys are the detach keys used by the exec session. -	// If set to "" the default keys are being used. -	// Will show "<none>" if no detach keys are set. -	DetachKeys string `json:"DetachKeys"` -	// ExitCode is the exit code of the exec session. Will be set to 0 if -	// the exec session has not yet exited. -	ExitCode int `json:"ExitCode"` -	// ID is the ID of the exec session. -	ID string `json:"ID"` -	// OpenStderr is whether the container's STDERR stream will be attached. -	// Always set to true if the exec session created a TTY. -	OpenStderr bool `json:"OpenStderr"` -	// OpenStdin is whether the container's STDIN stream will be attached -	// to. -	OpenStdin bool `json:"OpenStdin"` -	// OpenStdout is whether the container's STDOUT stream will be attached. -	// Always set to true if the exec session created a TTY. -	OpenStdout bool `json:"OpenStdout"` -	// Running is whether the exec session is running. -	Running bool `json:"Running"` -	// Pid is the PID of the exec session's process. -	// Will be set to 0 if the exec session is not running. -	Pid int `json:"Pid"` -	// ProcessConfig contains information about the exec session's process. -	ProcessConfig *InspectExecProcess `json:"ProcessConfig"` -} - -// InspectExecProcess contains information about the process in a given exec -// session. -type InspectExecProcess struct { -	// Arguments are the arguments to the entrypoint command of the exec -	// session. -	Arguments []string `json:"arguments"` -	// Entrypoint is the entrypoint for the exec session (the command that -	// will be executed in the container). -	Entrypoint string `json:"entrypoint"` -	// Privileged is whether the exec session will be started with elevated -	// privileges. -	Privileged bool `json:"privileged"` -	// Tty is whether the exec session created a terminal. -	Tty bool `json:"tty"` -	// User is the user the exec session was started as. -	User string `json:"user"` -} -  // Inspect inspects the given exec session and produces detailed output on its  // configuration and current state. -func (e *ExecSession) Inspect() (*InspectExecSession, error) { +func (e *ExecSession) Inspect() (*define.InspectExecSession, error) {  	if e.Config == nil {  		return nil, errors.Wrapf(define.ErrInternal, "given exec session does not have a configuration block")  	} -	output := new(InspectExecSession) +	output := new(define.InspectExecSession)  	output.CanRemove = e.State != define.ExecStateRunning  	output.ContainerID = e.ContainerId  	if e.Config.DetachKeys != nil { @@ -167,7 +114,7 @@ func (e *ExecSession) Inspect() (*InspectExecSession, error) {  	output.OpenStdout = e.Config.AttachStdout  	output.Running = e.State == define.ExecStateRunning  	output.Pid = e.PID -	output.ProcessConfig = new(InspectExecProcess) +	output.ProcessConfig = new(define.InspectExecProcess)  	if len(e.Config.Command) > 0 {  		output.ProcessConfig.Entrypoint = e.Config.Command[0]  		if len(e.Config.Command) > 1 { @@ -213,6 +160,11 @@ func (c *Container) ExecCreate(config *ExecConfig) (string, error) {  		return "", errors.Wrapf(define.ErrInvalidArg, "cannot specify streams to attach to when exec session has a pseudoterminal")  	} +	// Verify that we are in a good state to continue +	if !c.ensureState(define.ContainerStateRunning) { +		return "", errors.Wrapf(define.ErrCtrStateInvalid, "can only create exec sessions on running containers") +	} +  	// Generate an ID for our new exec session  	sessionID := stringid.GenerateNonCryptoID()  	found := true diff --git a/libpod/define/inspect.go b/libpod/define/inspect.go new file mode 100644 index 000000000..b7cd13f82 --- /dev/null +++ b/libpod/define/inspect.go @@ -0,0 +1,54 @@ +package define + +// InspectExecSession contains information about a given exec session. +type InspectExecSession struct { +	// CanRemove is legacy and used purely for compatibility reasons. +	// Will always be set to true, unless the exec session is running. +	CanRemove bool `json:"CanRemove"` +	// ContainerID is the ID of the container this exec session is attached +	// to. +	ContainerID string `json:"ContainerID"` +	// DetachKeys are the detach keys used by the exec session. +	// If set to "" the default keys are being used. +	// Will show "<none>" if no detach keys are set. +	DetachKeys string `json:"DetachKeys"` +	// ExitCode is the exit code of the exec session. Will be set to 0 if +	// the exec session has not yet exited. +	ExitCode int `json:"ExitCode"` +	// ID is the ID of the exec session. +	ID string `json:"ID"` +	// OpenStderr is whether the container's STDERR stream will be attached. +	// Always set to true if the exec session created a TTY. +	OpenStderr bool `json:"OpenStderr"` +	// OpenStdin is whether the container's STDIN stream will be attached +	// to. +	OpenStdin bool `json:"OpenStdin"` +	// OpenStdout is whether the container's STDOUT stream will be attached. +	// Always set to true if the exec session created a TTY. +	OpenStdout bool `json:"OpenStdout"` +	// Running is whether the exec session is running. +	Running bool `json:"Running"` +	// Pid is the PID of the exec session's process. +	// Will be set to 0 if the exec session is not running. +	Pid int `json:"Pid"` +	// ProcessConfig contains information about the exec session's process. +	ProcessConfig *InspectExecProcess `json:"ProcessConfig"` +} + +// InspectExecProcess contains information about the process in a given exec +// session. +type InspectExecProcess struct { +	// Arguments are the arguments to the entrypoint command of the exec +	// session. +	Arguments []string `json:"arguments"` +	// Entrypoint is the entrypoint for the exec session (the command that +	// will be executed in the container). +	Entrypoint string `json:"entrypoint"` +	// Privileged is whether the exec session will be started with elevated +	// privileges. +	Privileged bool `json:"privileged"` +	// Tty is whether the exec session created a terminal. +	Tty bool `json:"tty"` +	// User is the user the exec session was started as. +	User string `json:"user"` +} diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go index 0b18436ca..b99e6df84 100644 --- a/libpod/runtime_ctr.go +++ b/libpod/runtime_ctr.go @@ -830,6 +830,24 @@ func (r *Runtime) GetLatestContainer() (*Container, error) {  	return ctrs[lastCreatedIndex], nil  } +// GetExecSessionContainer gets the container that a given exec session ID is +// attached to. +func (r *Runtime) GetExecSessionContainer(id string) (*Container, error) { +	r.lock.RLock() +	defer r.lock.RUnlock() + +	if !r.valid { +		return nil, define.ErrRuntimeStopped +	} + +	ctrID, err := r.state.GetExecSession(id) +	if err != nil { +		return nil, err +	} + +	return r.state.Container(ctrID) +} +  // PruneContainers removes stopped and exited containers from localstorage.  A set of optional filters  // can be provided to be more granular.  func (r *Runtime) PruneContainers(filterFuncs []ContainerFilter) (map[string]int64, map[string]error, error) { diff --git a/pkg/api/handlers/compat/exec.go b/pkg/api/handlers/compat/exec.go new file mode 100644 index 000000000..1fa50d23b --- /dev/null +++ b/pkg/api/handlers/compat/exec.go @@ -0,0 +1,104 @@ +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" +) + +// 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 +	} + +	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 c6b70251b..6fa776d0f 100644 --- a/pkg/api/handlers/types.go +++ b/pkg/api/handlers/types.go @@ -177,6 +177,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  }  | 
