github.com/containers/podman/v2@v2.2.2-0.20210501105131-c1e07d070c4c/pkg/api/handlers/compat/exec.go (about) 1 package compat 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "net/http" 7 "strings" 8 9 "github.com/containers/podman/v2/libpod" 10 "github.com/containers/podman/v2/libpod/define" 11 "github.com/containers/podman/v2/pkg/api/handlers" 12 "github.com/containers/podman/v2/pkg/api/handlers/utils" 13 "github.com/containers/podman/v2/pkg/api/server/idle" 14 "github.com/containers/podman/v2/pkg/specgen/generate" 15 "github.com/gorilla/mux" 16 "github.com/pkg/errors" 17 "github.com/sirupsen/logrus" 18 ) 19 20 // ExecCreateHandler creates an exec session for a given container. 21 func ExecCreateHandler(w http.ResponseWriter, r *http.Request) { 22 runtime := r.Context().Value("runtime").(*libpod.Runtime) 23 24 input := new(handlers.ExecCreateConfig) 25 if err := json.NewDecoder(r.Body).Decode(&input); err != nil { 26 utils.InternalServerError(w, errors.Wrapf(err, "error decoding request body as JSON")) 27 return 28 } 29 30 ctrName := utils.GetName(r) 31 ctr, err := runtime.LookupContainer(ctrName) 32 if err != nil { 33 utils.ContainerNotFound(w, ctrName, err) 34 return 35 } 36 37 libpodConfig := new(libpod.ExecConfig) 38 libpodConfig.Command = input.Cmd 39 libpodConfig.Terminal = input.Tty 40 libpodConfig.AttachStdin = input.AttachStdin 41 libpodConfig.AttachStderr = input.AttachStderr 42 libpodConfig.AttachStdout = input.AttachStdout 43 if input.DetachKeys != "" { 44 libpodConfig.DetachKeys = &input.DetachKeys 45 } 46 libpodConfig.Environment = make(map[string]string) 47 for _, envStr := range input.Env { 48 split := strings.SplitN(envStr, "=", 2) 49 if len(split) != 2 { 50 utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, errors.Errorf("environment variable %q badly formed, must be key=value", envStr)) 51 return 52 } 53 libpodConfig.Environment[split[0]] = split[1] 54 } 55 libpodConfig.WorkDir = input.WorkingDir 56 libpodConfig.Privileged = input.Privileged 57 libpodConfig.User = input.User 58 59 // Make our exit command 60 storageConfig := runtime.StorageConfig() 61 runtimeConfig, err := runtime.GetConfig() 62 if err != nil { 63 utils.InternalServerError(w, err) 64 return 65 } 66 // Automatically log to syslog if the server has log-level=debug set 67 exitCommandArgs, err := generate.CreateExitCommandArgs(storageConfig, runtimeConfig, logrus.IsLevelEnabled(logrus.DebugLevel), true, true) 68 if err != nil { 69 utils.InternalServerError(w, err) 70 return 71 } 72 libpodConfig.ExitCommand = exitCommandArgs 73 74 // Run the exit command after 5 minutes, to mimic Docker's exec cleanup 75 // behavior. 76 libpodConfig.ExitCommandDelay = 5 * 60 77 78 sessID, err := ctr.ExecCreate(libpodConfig) 79 if err != nil { 80 if errors.Cause(err) == define.ErrCtrStateInvalid { 81 // Check if the container is paused. If so, return a 409 82 state, err := ctr.State() 83 if err == nil { 84 // Ignore the error != nil case. We're already 85 // throwing an InternalServerError below. 86 if state == define.ContainerStatePaused { 87 utils.Error(w, "Container is paused", http.StatusConflict, errors.Errorf("cannot create exec session as container %s is paused", ctr.ID())) 88 return 89 } 90 } 91 } 92 utils.InternalServerError(w, err) 93 return 94 } 95 96 resp := new(handlers.ExecCreateResponse) 97 resp.ID = sessID 98 99 utils.WriteResponse(w, http.StatusCreated, resp) 100 } 101 102 // ExecInspectHandler inspects a given exec session. 103 func ExecInspectHandler(w http.ResponseWriter, r *http.Request) { 104 runtime := r.Context().Value("runtime").(*libpod.Runtime) 105 106 sessionID := mux.Vars(r)["id"] 107 sessionCtr, err := runtime.GetExecSessionContainer(sessionID) 108 if err != nil { 109 utils.Error(w, fmt.Sprintf("No such exec session: %s", sessionID), http.StatusNotFound, err) 110 return 111 } 112 113 logrus.Debugf("Inspecting exec session %s of container %s", sessionID, sessionCtr.ID()) 114 115 session, err := sessionCtr.ExecSession(sessionID) 116 if err != nil { 117 utils.InternalServerError(w, errors.Wrapf(err, "error retrieving exec session %s from container %s", sessionID, sessionCtr.ID())) 118 return 119 } 120 121 inspectOut, err := session.Inspect() 122 if err != nil { 123 utils.InternalServerError(w, err) 124 return 125 } 126 127 utils.WriteResponse(w, http.StatusOK, inspectOut) 128 } 129 130 // ExecStartHandler runs a given exec session. 131 func ExecStartHandler(w http.ResponseWriter, r *http.Request) { 132 runtime := r.Context().Value("runtime").(*libpod.Runtime) 133 134 sessionID := mux.Vars(r)["id"] 135 136 // TODO: We should read/support Tty from here. 137 bodyParams := new(handlers.ExecStartConfig) 138 139 if err := json.NewDecoder(r.Body).Decode(&bodyParams); err != nil { 140 utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, 141 errors.Wrapf(err, "failed to decode parameters for %s", r.URL.String())) 142 return 143 } 144 // TODO: Verify TTY setting against what inspect session was made with 145 146 sessionCtr, err := runtime.GetExecSessionContainer(sessionID) 147 if err != nil { 148 utils.Error(w, fmt.Sprintf("No such exec session: %s", sessionID), http.StatusNotFound, err) 149 return 150 } 151 152 logrus.Debugf("Starting exec session %s of container %s", sessionID, sessionCtr.ID()) 153 154 state, err := sessionCtr.State() 155 if err != nil { 156 utils.InternalServerError(w, err) 157 return 158 } 159 if state != define.ContainerStateRunning { 160 utils.Error(w, http.StatusText(http.StatusConflict), http.StatusConflict, errors.Errorf("cannot exec in a container that is not running; container %s is %s", sessionCtr.ID(), state.String())) 161 return 162 } 163 164 if bodyParams.Detach { 165 // If we are detaching, we do NOT want to hijack. 166 // Instead, we perform a detached start, and return 200 if 167 // successful. 168 if err := sessionCtr.ExecStart(sessionID); err != nil { 169 utils.InternalServerError(w, err) 170 return 171 } 172 // This is a 200 despite having no content 173 utils.WriteResponse(w, http.StatusOK, "") 174 return 175 } 176 177 logErr := func(e error) { 178 logrus.Error(errors.Wrapf(e, "error attaching to container %s exec session %s", sessionCtr.ID(), sessionID)) 179 } 180 181 hijackChan := make(chan bool, 1) 182 err = sessionCtr.ExecHTTPStartAndAttach(sessionID, r, w, nil, nil, nil, hijackChan) 183 184 if <-hijackChan { 185 // If connection was Hijacked, we have to signal it's being closed 186 t := r.Context().Value("idletracker").(*idle.Tracker) 187 defer t.Close() 188 189 if err != nil { 190 // Cannot report error to client as a 500 as the Upgrade set status to 101 191 logErr(err) 192 } 193 } else { 194 // If the Hijack failed we are going to assume we can still inform client of failure 195 utils.InternalServerError(w, err) 196 logErr(err) 197 } 198 logrus.Debugf("Attach for container %s exec session %s completed successfully", sessionCtr.ID(), sessionID) 199 }