github.com/containers/libpod@v1.9.4-0.20220419124438-4284fd425507/pkg/api/handlers/compat/containers.go (about) 1 package compat 2 3 import ( 4 "encoding/binary" 5 "fmt" 6 "net/http" 7 "strconv" 8 "strings" 9 "sync" 10 "time" 11 12 "github.com/containers/libpod/libpod" 13 "github.com/containers/libpod/libpod/define" 14 "github.com/containers/libpod/libpod/logs" 15 "github.com/containers/libpod/pkg/api/handlers" 16 "github.com/containers/libpod/pkg/api/handlers/utils" 17 "github.com/containers/libpod/pkg/signal" 18 "github.com/containers/libpod/pkg/util" 19 "github.com/gorilla/schema" 20 "github.com/pkg/errors" 21 log "github.com/sirupsen/logrus" 22 ) 23 24 func RemoveContainer(w http.ResponseWriter, r *http.Request) { 25 decoder := r.Context().Value("decoder").(*schema.Decoder) 26 query := struct { 27 Force bool `schema:"force"` 28 Vols bool `schema:"v"` 29 Link bool `schema:"link"` 30 }{ 31 // override any golang type defaults 32 } 33 34 if err := decoder.Decode(&query, r.URL.Query()); err != nil { 35 utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, 36 errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String())) 37 return 38 } 39 40 if query.Link && !utils.IsLibpodRequest(r) { 41 utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, 42 utils.ErrLinkNotSupport) 43 return 44 } 45 46 runtime := r.Context().Value("runtime").(*libpod.Runtime) 47 name := utils.GetName(r) 48 con, err := runtime.LookupContainer(name) 49 if err != nil { 50 utils.ContainerNotFound(w, name, err) 51 return 52 } 53 54 if err := runtime.RemoveContainer(r.Context(), con, query.Force, query.Vols); err != nil { 55 utils.InternalServerError(w, err) 56 return 57 } 58 utils.WriteResponse(w, http.StatusNoContent, "") 59 } 60 61 func ListContainers(w http.ResponseWriter, r *http.Request) { 62 var ( 63 containers []*libpod.Container 64 err error 65 ) 66 runtime := r.Context().Value("runtime").(*libpod.Runtime) 67 decoder := r.Context().Value("decoder").(*schema.Decoder) 68 query := struct { 69 All bool `schema:"all"` 70 Limit int `schema:"limit"` 71 Size bool `schema:"size"` 72 Filters map[string][]string `schema:"filters"` 73 }{ 74 // override any golang type defaults 75 } 76 77 if err := decoder.Decode(&query, r.URL.Query()); err != nil { 78 utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String())) 79 return 80 } 81 if query.All { 82 containers, err = runtime.GetAllContainers() 83 } else { 84 containers, err = runtime.GetRunningContainers() 85 } 86 if err != nil { 87 utils.InternalServerError(w, err) 88 return 89 } 90 if _, found := r.URL.Query()["limit"]; found && query.Limit != -1 { 91 last := query.Limit 92 if len(containers) > last { 93 containers = containers[len(containers)-last:] 94 } 95 } 96 // TODO filters still need to be applied 97 var list = make([]*handlers.Container, len(containers)) 98 for i, ctnr := range containers { 99 api, err := handlers.LibpodToContainer(ctnr, query.Size) 100 if err != nil { 101 utils.InternalServerError(w, err) 102 return 103 } 104 list[i] = api 105 } 106 utils.WriteResponse(w, http.StatusOK, list) 107 } 108 109 func GetContainer(w http.ResponseWriter, r *http.Request) { 110 runtime := r.Context().Value("runtime").(*libpod.Runtime) 111 decoder := r.Context().Value("decoder").(*schema.Decoder) 112 query := struct { 113 Size bool `schema:"size"` 114 }{ 115 // override any golang type defaults 116 } 117 118 if err := decoder.Decode(&query, r.URL.Query()); err != nil { 119 utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String())) 120 return 121 } 122 123 name := utils.GetName(r) 124 ctnr, err := runtime.LookupContainer(name) 125 if err != nil { 126 utils.ContainerNotFound(w, name, err) 127 return 128 } 129 api, err := handlers.LibpodToContainerJSON(ctnr, query.Size) 130 if err != nil { 131 utils.InternalServerError(w, err) 132 return 133 } 134 utils.WriteResponse(w, http.StatusOK, api) 135 } 136 137 func KillContainer(w http.ResponseWriter, r *http.Request) { 138 // /{version}/containers/(name)/kill 139 runtime := r.Context().Value("runtime").(*libpod.Runtime) 140 decoder := r.Context().Value("decoder").(*schema.Decoder) 141 query := struct { 142 Signal string `schema:"signal"` 143 }{ 144 Signal: "KILL", 145 } 146 if err := decoder.Decode(&query, r.URL.Query()); err != nil { 147 utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String())) 148 return 149 } 150 151 sig, err := signal.ParseSignalNameOrNumber(query.Signal) 152 if err != nil { 153 utils.InternalServerError(w, err) 154 return 155 } 156 name := utils.GetName(r) 157 con, err := runtime.LookupContainer(name) 158 if err != nil { 159 utils.ContainerNotFound(w, name, err) 160 return 161 } 162 163 state, err := con.State() 164 if err != nil { 165 utils.InternalServerError(w, err) 166 return 167 } 168 169 // If the Container is stopped already, send a 409 170 if state == define.ContainerStateStopped || state == define.ContainerStateExited { 171 utils.Error(w, fmt.Sprintf("Container %s is not running", name), http.StatusConflict, errors.New(fmt.Sprintf("Cannot kill Container %s, it is not running", name))) 172 return 173 } 174 175 err = con.Kill(uint(sig)) 176 if err != nil { 177 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrapf(err, "unable to kill Container %s", name)) 178 } 179 180 if utils.IsLibpodRequest(r) { 181 // the kill behavior for docker differs from podman in that they appear to wait 182 // for the Container to croak so the exit code is accurate immediately after the 183 // kill is sent. libpod does not. but we can add a wait here only for the docker 184 // side of things and mimic that behavior 185 if _, err = con.Wait(); err != nil { 186 utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrapf(err, "failed to wait for Container %s", con.ID())) 187 return 188 } 189 } 190 // Success 191 utils.WriteResponse(w, http.StatusNoContent, "") 192 } 193 194 func WaitContainer(w http.ResponseWriter, r *http.Request) { 195 var msg string 196 // /{version}/containers/(name)/wait 197 exitCode, err := utils.WaitContainer(w, r) 198 if err != nil { 199 return 200 } 201 utils.WriteResponse(w, http.StatusOK, handlers.ContainerWaitOKBody{ 202 StatusCode: int(exitCode), 203 Error: struct { 204 Message string 205 }{ 206 Message: msg, 207 }, 208 }) 209 } 210 211 func LogsFromContainer(w http.ResponseWriter, r *http.Request) { 212 decoder := r.Context().Value("decoder").(*schema.Decoder) 213 runtime := r.Context().Value("runtime").(*libpod.Runtime) 214 215 query := struct { 216 Follow bool `schema:"follow"` 217 Stdout bool `schema:"stdout"` 218 Stderr bool `schema:"stderr"` 219 Since string `schema:"since"` 220 Until string `schema:"until"` 221 Timestamps bool `schema:"timestamps"` 222 Tail string `schema:"tail"` 223 }{ 224 Tail: "all", 225 } 226 if err := decoder.Decode(&query, r.URL.Query()); err != nil { 227 utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String())) 228 return 229 } 230 231 if !(query.Stdout || query.Stderr) { 232 msg := fmt.Sprintf("%s: you must choose at least one stream", http.StatusText(http.StatusBadRequest)) 233 utils.Error(w, msg, http.StatusBadRequest, errors.Errorf("%s for %s", msg, r.URL.String())) 234 return 235 } 236 237 name := utils.GetName(r) 238 ctnr, err := runtime.LookupContainer(name) 239 if err != nil { 240 utils.ContainerNotFound(w, name, err) 241 return 242 } 243 244 var tail int64 = -1 245 if query.Tail != "all" { 246 tail, err = strconv.ParseInt(query.Tail, 0, 64) 247 if err != nil { 248 utils.BadRequest(w, "tail", query.Tail, err) 249 return 250 } 251 } 252 253 var since time.Time 254 if _, found := r.URL.Query()["since"]; found { 255 since, err = util.ParseInputTime(query.Since) 256 if err != nil { 257 utils.BadRequest(w, "since", query.Since, err) 258 return 259 } 260 } 261 262 var until time.Time 263 if _, found := r.URL.Query()["until"]; found { 264 // FIXME: until != since but the logs backend does not yet support until. 265 since, err = util.ParseInputTime(query.Until) 266 if err != nil { 267 utils.BadRequest(w, "until", query.Until, err) 268 return 269 } 270 } 271 272 options := &logs.LogOptions{ 273 Details: true, 274 Follow: query.Follow, 275 Since: since, 276 Tail: tail, 277 Timestamps: query.Timestamps, 278 } 279 280 var wg sync.WaitGroup 281 options.WaitGroup = &wg 282 283 logChannel := make(chan *logs.LogLine, tail+1) 284 if err := runtime.Log([]*libpod.Container{ctnr}, options, logChannel); err != nil { 285 utils.InternalServerError(w, errors.Wrapf(err, "Failed to obtain logs for Container '%s'", name)) 286 return 287 } 288 go func() { 289 wg.Wait() 290 close(logChannel) 291 }() 292 293 w.WriteHeader(http.StatusOK) 294 var builder strings.Builder 295 for ok := true; ok; ok = query.Follow { 296 for line := range logChannel { 297 if _, found := r.URL.Query()["until"]; found { 298 if line.Time.After(until) { 299 break 300 } 301 } 302 303 // Reset variables we're ready to loop again 304 builder.Reset() 305 header := [8]byte{} 306 307 switch line.Device { 308 case "stdout": 309 if !query.Stdout { 310 continue 311 } 312 header[0] = 1 313 case "stderr": 314 if !query.Stderr { 315 continue 316 } 317 header[0] = 2 318 default: 319 // Logging and moving on is the best we can do here. We may have already sent 320 // a Status and Content-Type to client therefore we can no longer report an error. 321 log.Infof("unknown Device type '%s' in log file from Container %s", line.Device, ctnr.ID()) 322 continue 323 } 324 325 if query.Timestamps { 326 builder.WriteString(line.Time.Format(time.RFC3339)) 327 builder.WriteRune(' ') 328 } 329 builder.WriteString(line.Msg) 330 // Build header and output entry 331 binary.BigEndian.PutUint32(header[4:], uint32(len(header)+builder.Len())) 332 if _, err := w.Write(header[:]); err != nil { 333 log.Errorf("unable to write log output header: %q", err) 334 } 335 if _, err := fmt.Fprint(w, builder.String()); err != nil { 336 log.Errorf("unable to write builder string: %q", err) 337 } 338 if flusher, ok := w.(http.Flusher); ok { 339 flusher.Flush() 340 } 341 } 342 } 343 }