github.com/containers/podman/v2@v2.2.2-0.20210501105131-c1e07d070c4c/pkg/api/handlers/compat/events.go (about) 1 package compat 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "net/http" 7 8 "github.com/containers/podman/v2/libpod" 9 "github.com/containers/podman/v2/libpod/events" 10 "github.com/containers/podman/v2/pkg/api/handlers/utils" 11 "github.com/containers/podman/v2/pkg/domain/entities" 12 "github.com/gorilla/schema" 13 jsoniter "github.com/json-iterator/go" 14 "github.com/pkg/errors" 15 "github.com/sirupsen/logrus" 16 ) 17 18 // filtersFromRequests extracts the "filters" parameter from the specified 19 // http.Request. The parameter can either be a `map[string][]string` as done 20 // in new versions of Docker and libpod, or a `map[string]map[string]bool` as 21 // done in older versions of Docker. We have to do a bit of Yoga to support 22 // both - just as Docker does as well. 23 // 24 // Please refer to https://github.com/containers/podman/issues/6899 for some 25 // background. 26 func filtersFromRequest(r *http.Request) ([]string, error) { 27 var ( 28 compatFilters map[string]map[string]bool 29 filters map[string][]string 30 libpodFilters []string 31 raw []byte 32 ) 33 34 if _, found := r.URL.Query()["filters"]; found { 35 raw = []byte(r.Form.Get("filters")) 36 } else { 37 return []string{}, nil 38 } 39 40 // Backwards compat with older versions of Docker. 41 if err := json.Unmarshal(raw, &compatFilters); err == nil { 42 for filterKey, filterMap := range compatFilters { 43 for filterValue, toAdd := range filterMap { 44 if toAdd { 45 libpodFilters = append(libpodFilters, fmt.Sprintf("%s=%s", filterKey, filterValue)) 46 } 47 } 48 } 49 return libpodFilters, nil 50 } 51 52 if err := json.Unmarshal(raw, &filters); err != nil { 53 return nil, err 54 } 55 56 for filterKey, filterSlice := range filters { 57 for _, filterValue := range filterSlice { 58 libpodFilters = append(libpodFilters, fmt.Sprintf("%s=%s", filterKey, filterValue)) 59 } 60 } 61 62 return libpodFilters, nil 63 } 64 65 // NOTE: this endpoint serves both the docker-compatible one and the new libpod 66 // one. 67 func GetEvents(w http.ResponseWriter, r *http.Request) { 68 var ( 69 fromStart bool 70 decoder = r.Context().Value("decoder").(*schema.Decoder) 71 runtime = r.Context().Value("runtime").(*libpod.Runtime) 72 json = jsoniter.ConfigCompatibleWithStandardLibrary // FIXME: this should happen on the package level 73 ) 74 75 // NOTE: the "filters" parameter is extracted separately for backwards 76 // compat via `fitlerFromRequest()`. 77 query := struct { 78 Since string `schema:"since"` 79 Until string `schema:"until"` 80 Stream bool `schema:"stream"` 81 }{ 82 Stream: true, 83 } 84 if err := decoder.Decode(&query, r.URL.Query()); err != nil { 85 utils.Error(w, "failed to parse parameters", http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) 86 return 87 } 88 89 if len(query.Since) > 0 || len(query.Until) > 0 { 90 fromStart = true 91 } 92 93 libpodFilters, err := filtersFromRequest(r) 94 if err != nil { 95 utils.Error(w, "failed to parse parameters", http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) 96 return 97 } 98 99 eventChannel := make(chan *events.Event) 100 errorChannel := make(chan error) 101 102 // Start reading events. 103 go func() { 104 readOpts := events.ReadOptions{ 105 FromStart: fromStart, 106 Stream: query.Stream, 107 Filters: libpodFilters, 108 EventChannel: eventChannel, 109 Since: query.Since, 110 Until: query.Until, 111 } 112 errorChannel <- runtime.Events(r.Context(), readOpts) 113 }() 114 115 var flush = func() {} 116 if flusher, ok := w.(http.Flusher); ok { 117 flush = flusher.Flush 118 } 119 120 w.Header().Set("Content-Type", "application/json") 121 w.WriteHeader(http.StatusOK) 122 flush() 123 124 coder := json.NewEncoder(w) 125 coder.SetEscapeHTML(true) 126 127 for stream := true; stream; stream = query.Stream { 128 select { 129 case err := <-errorChannel: 130 if err != nil { 131 // FIXME StatusOK already sent above cannot send 500 here 132 utils.InternalServerError(w, err) 133 return 134 } 135 case evt := <-eventChannel: 136 if evt == nil { 137 continue 138 } 139 140 e := entities.ConvertToEntitiesEvent(*evt) 141 if err := coder.Encode(e); err != nil { 142 logrus.Errorf("unable to write json: %q", err) 143 } 144 flush() 145 case <-r.Context().Done(): 146 return 147 } 148 } 149 }