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  }