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