github.com/hanks177/podman/v4@v4.1.3-0.20220613032544-16d90015bc83/pkg/api/handlers/utils/containers.go (about) 1 package utils 2 3 import ( 4 "context" 5 "fmt" 6 "net/http" 7 "strconv" 8 "time" 9 10 "github.com/hanks177/podman/v4/libpod/events" 11 api "github.com/hanks177/podman/v4/pkg/api/types" 12 "github.com/hanks177/podman/v4/pkg/domain/entities" 13 "github.com/hanks177/podman/v4/pkg/domain/infra/abi" 14 15 "github.com/hanks177/podman/v4/pkg/api/handlers" 16 "github.com/sirupsen/logrus" 17 18 "github.com/hanks177/podman/v4/libpod/define" 19 20 "github.com/hanks177/podman/v4/libpod" 21 "github.com/gorilla/schema" 22 "github.com/pkg/errors" 23 ) 24 25 type waitQueryDocker struct { 26 Condition string `schema:"condition"` 27 } 28 29 type waitQueryLibpod struct { 30 Interval string `schema:"interval"` 31 Condition []define.ContainerStatus `schema:"condition"` 32 } 33 34 func WaitContainerDocker(w http.ResponseWriter, r *http.Request) { 35 var err error 36 ctx := r.Context() 37 38 query := waitQueryDocker{} 39 40 decoder := ctx.Value(api.DecoderKey).(*schema.Decoder) 41 if err = decoder.Decode(&query, r.URL.Query()); err != nil { 42 Error(w, http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) 43 return 44 } 45 46 interval := time.Nanosecond 47 48 condition := "not-running" 49 if _, found := r.URL.Query()["condition"]; found { 50 condition = query.Condition 51 if !isValidDockerCondition(query.Condition) { 52 BadRequest(w, "condition", condition, errors.New("not a valid docker condition")) 53 return 54 } 55 } 56 57 name := GetName(r) 58 59 exists, err := containerExists(ctx, name) 60 if err != nil { 61 InternalServerError(w, err) 62 return 63 } 64 if !exists { 65 ContainerNotFound(w, name, define.ErrNoSuchCtr) 66 return 67 } 68 69 // In docker compatibility mode we have to send headers in advance, 70 // otherwise docker client would freeze. 71 w.Header().Set("Content-Type", "application/json") 72 w.WriteHeader(200) 73 if flusher, ok := w.(http.Flusher); ok { 74 flusher.Flush() 75 } 76 77 exitCode, err := waitDockerCondition(ctx, name, interval, condition) 78 var errStruct *struct{ Message string } 79 if err != nil { 80 logrus.Errorf("While waiting on condition: %q", err) 81 errStruct = &struct { 82 Message string 83 }{ 84 Message: err.Error(), 85 } 86 } 87 88 responseData := handlers.ContainerWaitOKBody{ 89 StatusCode: int(exitCode), 90 Error: errStruct, 91 } 92 enc := json.NewEncoder(w) 93 enc.SetEscapeHTML(true) 94 err = enc.Encode(&responseData) 95 if err != nil { 96 logrus.Errorf("Unable to write json: %q", err) 97 } 98 } 99 100 func WaitContainerLibpod(w http.ResponseWriter, r *http.Request) { 101 var ( 102 err error 103 interval = time.Millisecond * 250 104 conditions = []define.ContainerStatus{define.ContainerStateStopped, define.ContainerStateExited} 105 ) 106 decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder) 107 query := waitQueryLibpod{} 108 if err := decoder.Decode(&query, r.URL.Query()); err != nil { 109 Error(w, http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) 110 return 111 } 112 113 if _, found := r.URL.Query()["interval"]; found { 114 interval, err = time.ParseDuration(query.Interval) 115 if err != nil { 116 InternalServerError(w, err) 117 return 118 } 119 } 120 121 if _, found := r.URL.Query()["condition"]; found { 122 if len(query.Condition) > 0 { 123 conditions = query.Condition 124 } 125 } 126 127 name := GetName(r) 128 129 waitFn := createContainerWaitFn(r.Context(), name, interval) 130 131 exitCode, err := waitFn(conditions...) 132 if err != nil { 133 if errors.Cause(err) == define.ErrNoSuchCtr { 134 ContainerNotFound(w, name, err) 135 return 136 } 137 InternalServerError(w, err) 138 return 139 } 140 WriteResponse(w, http.StatusOK, strconv.Itoa(int(exitCode))) 141 } 142 143 type containerWaitFn func(conditions ...define.ContainerStatus) (int32, error) 144 145 func createContainerWaitFn(ctx context.Context, containerName string, interval time.Duration) containerWaitFn { 146 runtime := ctx.Value(api.RuntimeKey).(*libpod.Runtime) 147 var containerEngine entities.ContainerEngine = &abi.ContainerEngine{Libpod: runtime} 148 149 return func(conditions ...define.ContainerStatus) (int32, error) { 150 opts := entities.WaitOptions{ 151 Condition: conditions, 152 Interval: interval, 153 } 154 ctrWaitReport, err := containerEngine.ContainerWait(ctx, []string{containerName}, opts) 155 if err != nil { 156 return -1, err 157 } 158 if len(ctrWaitReport) != 1 { 159 return -1, fmt.Errorf("the ContainerWait() function returned unexpected count of reports: %d", len(ctrWaitReport)) 160 } 161 return ctrWaitReport[0].ExitCode, ctrWaitReport[0].Error 162 } 163 } 164 165 func isValidDockerCondition(cond string) bool { 166 switch cond { 167 case "next-exit", "removed", "not-running", "": 168 return true 169 } 170 return false 171 } 172 173 func waitDockerCondition(ctx context.Context, containerName string, interval time.Duration, dockerCondition string) (int32, error) { 174 containerWait := createContainerWaitFn(ctx, containerName, interval) 175 176 var err error 177 var code int32 178 switch dockerCondition { 179 case "next-exit": 180 code, err = waitNextExit(ctx, containerName) 181 case "removed": 182 code, err = waitRemoved(containerWait) 183 case "not-running", "": 184 code, err = waitNotRunning(containerWait) 185 default: 186 panic("not a valid docker condition") 187 } 188 return code, err 189 } 190 191 var notRunningStates = []define.ContainerStatus{ 192 define.ContainerStateCreated, 193 define.ContainerStateRemoving, 194 define.ContainerStateStopped, 195 define.ContainerStateExited, 196 define.ContainerStateConfigured, 197 } 198 199 func waitRemoved(ctrWait containerWaitFn) (int32, error) { 200 code, err := ctrWait(define.ContainerStateUnknown) 201 if err != nil && errors.Cause(err) == define.ErrNoSuchCtr { 202 return code, nil 203 } 204 return code, err 205 } 206 207 func waitNextExit(ctx context.Context, containerName string) (int32, error) { 208 runtime := ctx.Value(api.RuntimeKey).(*libpod.Runtime) 209 containerEngine := &abi.ContainerEngine{Libpod: runtime} 210 eventChannel := make(chan *events.Event) 211 errChannel := make(chan error) 212 opts := entities.EventsOptions{ 213 EventChan: eventChannel, 214 Filter: []string{"event=died", fmt.Sprintf("container=%s", containerName)}, 215 Stream: true, 216 } 217 218 // ctx is used to cancel event watching goroutine 219 ctx, cancel := context.WithCancel(ctx) 220 defer cancel() 221 go func() { 222 errChannel <- containerEngine.Events(ctx, opts) 223 }() 224 225 evt, ok := <-eventChannel 226 if ok { 227 return int32(evt.ContainerExitCode), nil 228 } 229 // if ok == false then containerEngine.Events() has exited 230 // it may happen if request was canceled (e.g. client closed connection prematurely) or 231 // the server is in process of shutting down 232 return -1, <-errChannel 233 } 234 235 func waitNotRunning(ctrWait containerWaitFn) (int32, error) { 236 return ctrWait(notRunningStates...) 237 } 238 239 func containerExists(ctx context.Context, name string) (bool, error) { 240 runtime := ctx.Value(api.RuntimeKey).(*libpod.Runtime) 241 var containerEngine entities.ContainerEngine = &abi.ContainerEngine{Libpod: runtime} 242 243 var ctrExistsOpts entities.ContainerExistsOptions 244 ctrExistRep, err := containerEngine.ContainerExists(ctx, name, ctrExistsOpts) 245 if err != nil { 246 return false, err 247 } 248 return ctrExistRep.Value, nil 249 }