diff options
Diffstat (limited to 'pkg')
-rw-r--r-- | pkg/api/handlers/compat/events.go | 154 | ||||
-rw-r--r-- | pkg/bindings/test/system_test.go | 29 | ||||
-rw-r--r-- | pkg/network/config.go | 4 | ||||
-rw-r--r-- | pkg/specgen/generate/container_create.go | 14 | ||||
-rw-r--r-- | pkg/specgen/specgen.go | 12 | ||||
-rw-r--r-- | pkg/util/mountOpts.go | 4 |
6 files changed, 155 insertions, 62 deletions
diff --git a/pkg/api/handlers/compat/events.go b/pkg/api/handlers/compat/events.go index 5acc94153..9d5cb5045 100644 --- a/pkg/api/handlers/compat/events.go +++ b/pkg/api/handlers/compat/events.go @@ -1,9 +1,10 @@ package compat import ( - "context" + "encoding/json" "fmt" "net/http" + "sync" "github.com/containers/libpod/v2/libpod" "github.com/containers/libpod/v2/libpod/events" @@ -15,77 +16,132 @@ import ( "github.com/sirupsen/logrus" ) +// filtersFromRequests extracts the "filters" parameter from the specified +// http.Request. The paramater can either be a `map[string][]string` as done +// in new versions of Docker and libpod, or a `map[string]map[string]bool` as +// done in older versions of Docker. We have to do a bit of Yoga to support +// both - just as Docker does as well. +// +// Please refer to https://github.com/containers/podman/issues/6899 for some +// background. +func filtersFromRequest(r *http.Request) ([]string, error) { + var ( + compatFilters map[string]map[string]bool + filters map[string][]string + libpodFilters []string + ) + raw := []byte(r.Form.Get("filters")) + + // Backwards compat with older versions of Docker. + if err := json.Unmarshal(raw, &compatFilters); err == nil { + for filterKey, filterMap := range compatFilters { + for filterValue, toAdd := range filterMap { + if toAdd { + libpodFilters = append(libpodFilters, fmt.Sprintf("%s=%s", filterKey, filterValue)) + } + } + } + return libpodFilters, nil + } + + if err := json.Unmarshal(raw, &filters); err != nil { + return nil, err + } + + for filterKey, filterSlice := range filters { + for _, filterValue := range filterSlice { + libpodFilters = append(libpodFilters, fmt.Sprintf("%s=%s", filterKey, filterValue)) + } + } + + return libpodFilters, nil +} + +// NOTE: this endpoint serves both the docker-compatible one and the new libpod +// one. func GetEvents(w http.ResponseWriter, r *http.Request) { var ( - fromStart bool - eventsError error - decoder = r.Context().Value("decoder").(*schema.Decoder) - runtime = r.Context().Value("runtime").(*libpod.Runtime) + fromStart bool + decoder = r.Context().Value("decoder").(*schema.Decoder) + runtime = r.Context().Value("runtime").(*libpod.Runtime) + json = jsoniter.ConfigCompatibleWithStandardLibrary // FIXME: this should happen on the package level ) + // NOTE: the "filters" parameter is extracted separately for backwards + // compat via `fitlerFromRequest()`. query := struct { - Since string `schema:"since"` - Until string `schema:"until"` - Filters map[string][]string `schema:"filters"` - Stream bool `schema:"stream"` + Since string `schema:"since"` + Until string `schema:"until"` + Stream bool `schema:"stream"` }{ Stream: true, } if err := decoder.Decode(&query, r.URL.Query()); err != nil { utils.Error(w, "Failed to parse parameters", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String())) - } - - var libpodFilters = []string{} - if _, found := r.URL.Query()["filters"]; found { - for k, v := range query.Filters { - libpodFilters = append(libpodFilters, fmt.Sprintf("%s=%s", k, v[0])) - } + return } if len(query.Since) > 0 || len(query.Until) > 0 { fromStart = true } - eventCtx, eventCancel := context.WithCancel(r.Context()) - eventChannel := make(chan *events.Event) - go func() { - readOpts := events.ReadOptions{FromStart: fromStart, Stream: query.Stream, Filters: libpodFilters, EventChannel: eventChannel, Since: query.Since, Until: query.Until} - eventsError = runtime.Events(eventCtx, readOpts) - }() - if eventsError != nil { - utils.InternalServerError(w, eventsError) - eventCancel() - close(eventChannel) + libpodFilters, err := filtersFromRequest(r) + if err != nil { + utils.Error(w, "Failed to parse parameters", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String())) return } - // If client disappears we need to stop listening for events - go func(done <-chan struct{}) { - <-done - eventCancel() - if _, ok := <-eventChannel; ok { - close(eventChannel) + eventChannel := make(chan *events.Event) + errorChannel := make(chan error) + + // Start reading events. + go func() { + readOpts := events.ReadOptions{ + FromStart: fromStart, + Stream: query.Stream, + Filters: libpodFilters, + EventChannel: eventChannel, + Since: query.Since, + Until: query.Until, } - }(r.Context().Done()) + errorChannel <- runtime.Events(r.Context(), readOpts) + }() - // Headers need to be written out before turning Writer() over to json encoder - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - if flusher, ok := w.(http.Flusher); ok { - flusher.Flush() - } + var coder *jsoniter.Encoder + var writeHeader sync.Once - json := jsoniter.ConfigCompatibleWithStandardLibrary - coder := json.NewEncoder(w) - coder.SetEscapeHTML(true) + for stream := true; stream; stream = query.Stream { + select { + case err := <-errorChannel: + if err != nil { + utils.InternalServerError(w, err) + return + } + case evt := <-eventChannel: + writeHeader.Do(func() { + // Use a sync.Once so that we write the header + // only once. + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + if flusher, ok := w.(http.Flusher); ok { + flusher.Flush() + } + coder = json.NewEncoder(w) + coder.SetEscapeHTML(true) + }) - for event := range eventChannel { - e := entities.ConvertToEntitiesEvent(*event) - if err := coder.Encode(e); err != nil { - logrus.Errorf("unable to write json: %q", err) - } - if flusher, ok := w.(http.Flusher); ok { - flusher.Flush() + if evt == nil { + continue + } + + e := entities.ConvertToEntitiesEvent(*evt) + if err := coder.Encode(e); err != nil { + logrus.Errorf("unable to write json: %q", err) + } + if flusher, ok := w.(http.Flusher); ok { + flusher.Flush() + } } + } } diff --git a/pkg/bindings/test/system_test.go b/pkg/bindings/test/system_test.go index 93141400b..430184f4a 100644 --- a/pkg/bindings/test/system_test.go +++ b/pkg/bindings/test/system_test.go @@ -1,6 +1,7 @@ package test_bindings import ( + "sync" "time" "github.com/containers/libpod/v2/pkg/bindings" @@ -38,22 +39,28 @@ var _ = Describe("Podman system", func() { }) It("podman events", func() { - eChan := make(chan entities.Event, 1) - var messages []entities.Event - cancelChan := make(chan bool, 1) + var name = "top" + _, err := bt.RunTopContainer(&name, bindings.PFalse, nil) + Expect(err).To(BeNil()) + + filters := make(map[string][]string) + filters["container"] = []string{name} + + binChan := make(chan entities.Event) + done := sync.Mutex{} + done.Lock() + eventCounter := 0 go func() { - for e := range eChan { - messages = append(messages, e) + defer done.Unlock() + for range binChan { + eventCounter++ } }() - go func() { - system.Events(bt.conn, eChan, cancelChan, nil, nil, nil, bindings.PFalse) - }() - _, err := bt.RunTopContainer(nil, nil, nil) + err = system.Events(bt.conn, binChan, nil, nil, nil, filters, bindings.PFalse) Expect(err).To(BeNil()) - cancelChan <- true - Expect(len(messages)).To(BeNumerically("==", 5)) + done.Lock() + Expect(eventCounter).To(BeNumerically(">", 0)) }) It("podman system prune - pod,container stopped", func() { diff --git a/pkg/network/config.go b/pkg/network/config.go index e5c981419..a504e0ad0 100644 --- a/pkg/network/config.go +++ b/pkg/network/config.go @@ -6,8 +6,8 @@ import ( "net" ) -// TODO once the libpod.conf file stuff is worked out, this should be modified -// to honor defines in the libpod.conf as well as overrides? +// TODO once the containers.conf file stuff is worked out, this should be modified +// to honor defines in the containers.conf as well as overrides? const ( // CNIConfigDir is the path where CNI config files exist diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go index c1ceac69e..6dbc45c16 100644 --- a/pkg/specgen/generate/container_create.go +++ b/pkg/specgen/generate/container_create.go @@ -201,6 +201,9 @@ func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen. for _, volume := range volumes { destinations = append(destinations, volume.Dest) } + for _, overlayVolume := range s.OverlayVolumes { + destinations = append(destinations, overlayVolume.Destination) + } options = append(options, libpod.WithUserVolumes(destinations)) if len(volumes) != 0 { @@ -215,6 +218,17 @@ func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen. options = append(options, libpod.WithNamedVolumes(vols)) } + if len(s.OverlayVolumes) != 0 { + var vols []*libpod.ContainerOverlayVolume + for _, v := range s.OverlayVolumes { + vols = append(vols, &libpod.ContainerOverlayVolume{ + Dest: v.Destination, + Source: v.Source, + }) + } + options = append(options, libpod.WithOverlayVolumes(vols)) + } + if s.Command != nil { options = append(options, libpod.WithCommand(s.Command)) } diff --git a/pkg/specgen/specgen.go b/pkg/specgen/specgen.go index a346a9742..c6079be33 100644 --- a/pkg/specgen/specgen.go +++ b/pkg/specgen/specgen.go @@ -198,6 +198,9 @@ type ContainerStorageConfig struct { // there are conflicts. // Optional. Volumes []*NamedVolume `json:"volumes,omitempty"` + // Overlay volumes are named volumes that will be added to the container. + // Optional. + OverlayVolumes []*OverlayVolume `json:"overlay_volumes,omitempty"` // Devices are devices that will be added to the container. // Optional. Devices []spec.LinuxDevice `json:"devices,omitempty"` @@ -443,6 +446,15 @@ type NamedVolume struct { Options []string } +// OverlayVolume holds information about a overlay volume that will be mounted into +// the container. +type OverlayVolume struct { + // Destination is the absolute path where the mount will be placed in the container. + Destination string `json:"destination"` + // Source specifies the source path of the mount. + Source string `json:"source,omitempty"` +} + // PortMapping is one or more ports that will be mapped into the container. type PortMapping struct { // HostIP is the IP that we will bind to on the host. diff --git a/pkg/util/mountOpts.go b/pkg/util/mountOpts.go index 416e60728..eab2657e3 100644 --- a/pkg/util/mountOpts.go +++ b/pkg/util/mountOpts.go @@ -33,6 +33,10 @@ func ProcessOptions(options []string, isTmpfs bool, sourcePath string) ([]string // Some options have parameters - size, mode splitOpt := strings.SplitN(opt, "=", 2) switch splitOpt[0] { + case "O": + if len(options) > 1 { + return nil, errors.Wrapf(ErrDupeMntOption, "'O' option can not be used with other options") + } case "exec", "noexec": if foundExec { return nil, errors.Wrapf(ErrDupeMntOption, "only one of 'noexec' and 'exec' can be used") |