diff options
29 files changed, 351 insertions, 88 deletions
diff --git a/cmd/podman/common/completion.go b/cmd/podman/common/completion.go index f792b2713..83fe0723c 100644 --- a/cmd/podman/common/completion.go +++ b/cmd/podman/common/completion.go @@ -313,6 +313,10 @@ func completeKeyValues(toComplete string, k keyValueCompletion) ([]string, cobra return suggestions, directive } +func getBoolCompletion(_ string) ([]string, cobra.ShellCompDirective) { + return []string{"true", "false"}, cobra.ShellCompDirectiveNoFileComp +} + /* Autocomplete Functions for cobra ValidArgsFunction */ // AutocompleteContainers - Autocomplete all container names. @@ -797,6 +801,39 @@ func AutocompleteVolumeFlag(cmd *cobra.Command, args []string, toComplete string return volumes, directive } +// AutocompleteNetworkFlag - Autocomplete network flag options. +func AutocompleteNetworkFlag(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + kv := keyValueCompletion{ + "container:": func(s string) ([]string, cobra.ShellCompDirective) { return getContainers(cmd, s, completeDefault) }, + "ns:": func(_ string) ([]string, cobra.ShellCompDirective) { + return nil, cobra.ShellCompDirectiveDefault + }, + "bridge": nil, + "none": nil, + "host": nil, + "private": nil, + "slirp4netns:": func(s string) ([]string, cobra.ShellCompDirective) { + skv := keyValueCompletion{ + "allow_host_loopback=": getBoolCompletion, + "cidr=": nil, + "enable_ipv6=": getBoolCompletion, + "outbound_addr=": nil, + "outbound_addr6=": nil, + "port_handler=": func(_ string) ([]string, cobra.ShellCompDirective) { + return []string{"rootlesskit", "slirp4netns"}, cobra.ShellCompDirectiveNoFileComp + }, + } + return completeKeyValues(s, skv) + }, + } + + networks, _ := getNetworks(cmd, toComplete) + suggestions, dir := completeKeyValues(toComplete, kv) + // add slirp4netns here it does not work correct if we add it to the kv map + suggestions = append(suggestions, "slirp4netns") + return append(networks, suggestions...), dir +} + // AutocompleteJSONFormat - Autocomplete format flag option. // -> "json" func AutocompleteJSONFormat(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { @@ -974,17 +1011,14 @@ func AutocompletePodPsFilters(cmd *cobra.Command, args []string, toComplete stri // AutocompleteImageFilters - Autocomplete image ls --filter options. func AutocompleteImageFilters(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - getBool := func(_ string) ([]string, cobra.ShellCompDirective) { - return []string{"true", "false"}, cobra.ShellCompDirectiveNoFileComp - } getImg := func(s string) ([]string, cobra.ShellCompDirective) { return getImages(cmd, s) } kv := keyValueCompletion{ "before=": getImg, "since=": getImg, "label=": nil, "reference=": nil, - "dangling=": getBool, - "readonly=": getBool, + "dangling=": getBoolCompletion, + "readonly=": getBoolCompletion, } return completeKeyValues(toComplete, kv) } @@ -1004,14 +1038,12 @@ func AutocompleteVolumeFilters(cmd *cobra.Command, args []string, toComplete str return []string{"local"}, cobra.ShellCompDirectiveNoFileComp } kv := keyValueCompletion{ - "name=": func(s string) ([]string, cobra.ShellCompDirective) { return getVolumes(cmd, s) }, - "driver=": local, - "scope=": local, - "label=": nil, - "opt=": nil, - "dangling=": func(_ string) ([]string, cobra.ShellCompDirective) { - return []string{"true", "false"}, cobra.ShellCompDirectiveNoFileComp - }, + "name=": func(s string) ([]string, cobra.ShellCompDirective) { return getVolumes(cmd, s) }, + "driver=": local, + "scope=": local, + "label=": nil, + "opt=": nil, + "dangling=": getBoolCompletion, } return completeKeyValues(toComplete, kv) } diff --git a/cmd/podman/common/netflags.go b/cmd/podman/common/netflags.go index cae52ccaa..9cb4ed550 100644 --- a/cmd/podman/common/netflags.go +++ b/cmd/podman/common/netflags.go @@ -63,7 +63,7 @@ func DefineNetFlags(cmd *cobra.Command) { networkFlagName, containerConfig.NetNS(), "Connect a container to a network", ) - _ = cmd.RegisterFlagCompletionFunc(networkFlagName, AutocompleteNetworks) + _ = cmd.RegisterFlagCompletionFunc(networkFlagName, AutocompleteNetworkFlag) networkAliasFlagName := "network-alias" netFlags.StringSlice( diff --git a/cmd/podman/containers/run.go b/cmd/podman/containers/run.go index 6ff1b929d..46bfb4143 100644 --- a/cmd/podman/containers/run.go +++ b/cmd/podman/containers/run.go @@ -3,7 +3,6 @@ package containers import ( "fmt" "os" - "strconv" "strings" "github.com/containers/common/pkg/completion" @@ -15,7 +14,6 @@ import ( "github.com/containers/podman/v2/pkg/errorhandling" "github.com/containers/podman/v2/pkg/rootless" "github.com/containers/podman/v2/pkg/specgen" - "github.com/containers/podman/v2/pkg/util" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -108,15 +106,6 @@ func run(cmd *cobra.Command, args []string) error { return err } - if rootless.IsRootless() && !registry.IsRemote() { - userspec := strings.SplitN(cliVals.User, ":", 2)[0] - if uid, err := strconv.ParseInt(userspec, 10, 32); err == nil { - if err := util.CheckRootlessUIDRange(int(uid)); err != nil { - return err - } - } - } - if af := cliVals.Authfile; len(af) > 0 { if _, err := os.Stat(af); err != nil { return err diff --git a/cmd/podman/play/kube.go b/cmd/podman/play/kube.go index 5e227d05a..db7280b1d 100644 --- a/cmd/podman/play/kube.go +++ b/cmd/podman/play/kube.go @@ -61,7 +61,7 @@ func init() { networkFlagName := "network" flags.StringVar(&kubeOptions.Network, networkFlagName, "", "Connect pod to CNI network(s)") - _ = kubeCmd.RegisterFlagCompletionFunc(networkFlagName, common.AutocompleteNetworks) + _ = kubeCmd.RegisterFlagCompletionFunc(networkFlagName, common.AutocompleteNetworkFlag) logDriverFlagName := "log-driver" flags.StringVar(&kubeOptions.LogDriver, logDriverFlagName, "", "Logging driver for the container") diff --git a/cmd/podman/volumes/prune.go b/cmd/podman/volumes/prune.go index d1370120b..0f3ba9ef6 100644 --- a/cmd/podman/volumes/prune.go +++ b/cmd/podman/volumes/prune.go @@ -8,10 +8,12 @@ import ( "strings" "github.com/containers/common/pkg/completion" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/utils" "github.com/containers/podman/v2/cmd/podman/validate" "github.com/containers/podman/v2/pkg/domain/entities" + "github.com/containers/podman/v2/pkg/domain/filters" "github.com/spf13/cobra" ) @@ -28,6 +30,7 @@ var ( RunE: prune, ValidArgsFunction: completion.AutocompleteNone, } + filter = []string{} ) func init() { @@ -37,10 +40,17 @@ func init() { Parent: volumeCmd, }) flags := pruneCommand.Flags() + + filterFlagName := "filter" + flags.StringArrayVar(&filter, filterFlagName, []string{}, "Provide filter values (e.g. 'label=<key>=<value>')") + _ = pruneCommand.RegisterFlagCompletionFunc(filterFlagName, common.AutocompleteVolumeFilters) flags.BoolP("force", "f", false, "Do not prompt for confirmation") } func prune(cmd *cobra.Command, args []string) error { + var ( + pruneOptions = entities.VolumePruneOptions{} + ) // Prompt for confirmation if --force is not set force, err := cmd.Flags().GetBool("force") if err != nil { @@ -58,7 +68,11 @@ func prune(cmd *cobra.Command, args []string) error { return nil } } - responses, err := registry.ContainerEngine().VolumePrune(context.Background()) + pruneOptions.Filters, err = filters.ParseFilterArgumentsIntoFilters(filter) + if err != nil { + return err + } + responses, err := registry.ContainerEngine().VolumePrune(context.Background(), pruneOptions) if err != nil { return err } diff --git a/docs/source/markdown/podman-volume-prune.1.md b/docs/source/markdown/podman-volume-prune.1.md index b5f1b7e94..9477cb5d5 100644 --- a/docs/source/markdown/podman-volume-prune.1.md +++ b/docs/source/markdown/podman-volume-prune.1.md @@ -8,7 +8,8 @@ podman\-volume\-prune - Remove all unused volumes ## DESCRIPTION -Removes all unused volumes. You will be prompted to confirm the removal of all the +Removes unused volumes. By default all unused volumes will be removed, the **--filter** flag can +be used to filter specific volumes. You will be prompted to confirm the removal of all the unused volumes. To bypass the confirmation, use the **--force** flag. @@ -18,6 +19,17 @@ unused volumes. To bypass the confirmation, use the **--force** flag. Do not prompt for confirmation. +#### **--filter** + +Filter volumes to be pruned. Volumes can be filtered by the following attributes: + +- dangling +- driver +- label +- name +- opt +- scope + #### **--help** Print usage statement @@ -29,6 +41,8 @@ Print usage statement $ podman volume prune $ podman volume prune --force + +$ podman volume prune --filter label=mylabel=mylabelvalue ``` ## SEE ALSO diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index 1bf044f9d..dc1a64863 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -424,11 +424,6 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { } if c.config.User != "" { - if rootless.IsRootless() { - if err := util.CheckRootlessUIDRange(execUser.Uid); err != nil { - return nil, err - } - } // User and Group must go together g.SetProcessUID(uint32(execUser.Uid)) g.SetProcessGID(uint32(execUser.Gid)) diff --git a/libpod/events/events.go b/libpod/events/events.go index 4e7267af3..aa0401b62 100644 --- a/libpod/events/events.go +++ b/libpod/events/events.go @@ -216,8 +216,5 @@ func (e EventLogFile) getTail(options ReadOptions) (*tail.Tail, error) { reopen = false } stream := options.Stream - if len(options.Until) > 0 { - stream = false - } return tail.TailFile(e.options.LogFilePath, tail.Config{ReOpen: reopen, Follow: stream, Location: &seek, Logger: tail.DiscardingLogger, Poll: true}) } diff --git a/libpod/events/journal_linux.go b/libpod/events/journal_linux.go index 9a514e302..71c638017 100644 --- a/libpod/events/journal_linux.go +++ b/libpod/events/journal_linux.go @@ -8,6 +8,7 @@ import ( "strconv" "time" + "github.com/containers/podman/v2/pkg/util" "github.com/coreos/go-systemd/v22/journal" "github.com/coreos/go-systemd/v22/sdjournal" "github.com/pkg/errors" @@ -72,6 +73,13 @@ func (e EventJournalD) Read(ctx context.Context, options ReadOptions) error { if err != nil { return errors.Wrapf(err, "failed to generate event options") } + var untilTime time.Time + if len(options.Until) > 0 { + untilTime, err = util.ParseInputTime(options.Until) + if err != nil { + return err + } + } j, err := sdjournal.NewJournal() if err != nil { return err @@ -122,10 +130,14 @@ func (e EventJournalD) Read(ctx context.Context, options ReadOptions) error { return errors.Wrap(err, "failed to get journal cursor") } if prevCursor == newCursor { - if len(options.Until) > 0 || !options.Stream { + if !options.Stream || (len(options.Until) > 0 && time.Now().After(untilTime)) { break } - _ = j.Wait(sdjournal.IndefiniteWait) + t := sdjournal.IndefiniteWait + if len(options.Until) > 0 { + t = time.Until(untilTime) + } + _ = j.Wait(t) continue } prevCursor = newCursor diff --git a/libpod/events/logfile.go b/libpod/events/logfile.go index 57e38b815..05ae3ce52 100644 --- a/libpod/events/logfile.go +++ b/libpod/events/logfile.go @@ -4,7 +4,9 @@ import ( "context" "fmt" "os" + "time" + "github.com/containers/podman/v2/pkg/util" "github.com/containers/storage" "github.com/pkg/errors" ) @@ -51,6 +53,16 @@ func (e EventLogFile) Read(ctx context.Context, options ReadOptions) error { if err != nil { return err } + if len(options.Until) > 0 { + untilTime, err := util.ParseInputTime(options.Until) + if err != nil { + return err + } + go func() { + time.Sleep(time.Until(untilTime)) + t.Stop() + }() + } funcDone := make(chan bool) copy := true go func() { diff --git a/libpod/options.go b/libpod/options.go index bd12c0c34..c2db13560 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -502,6 +502,7 @@ func WithEventsLogger(logger string) RuntimeOption { } rt.config.Engine.EventsLogger = logger + rt.config.Engine.EventsLogFilePath = filepath.Join(rt.config.Engine.TmpDir, "events", "events.log") return nil } diff --git a/libpod/runtime_volume.go b/libpod/runtime_volume.go index 055a243c0..10c32a119 100644 --- a/libpod/runtime_volume.go +++ b/libpod/runtime_volume.go @@ -133,9 +133,9 @@ func (r *Runtime) GetAllVolumes() ([]*Volume, error) { } // PruneVolumes removes unused volumes from the system -func (r *Runtime) PruneVolumes(ctx context.Context) (map[string]error, error) { +func (r *Runtime) PruneVolumes(ctx context.Context, filterFuncs []VolumeFilter) (map[string]error, error) { reports := make(map[string]error) - vols, err := r.GetAllVolumes() + vols, err := r.Volumes(filterFuncs...) if err != nil { return nil, err } diff --git a/pkg/api/handlers/compat/events.go b/pkg/api/handlers/compat/events.go index f74491a8f..c75511a4d 100644 --- a/pkg/api/handlers/compat/events.go +++ b/pkg/api/handlers/compat/events.go @@ -110,6 +110,7 @@ func GetEvents(w http.ResponseWriter, r *http.Request) { Until: query.Until, } errorChannel <- runtime.Events(r.Context(), readOpts) + }() var flush = func() {} @@ -130,8 +131,8 @@ func GetEvents(w http.ResponseWriter, r *http.Request) { if err != nil { // FIXME StatusOK already sent above cannot send 500 here utils.InternalServerError(w, err) - return } + return case evt := <-eventChannel: if evt == nil { continue diff --git a/pkg/api/handlers/compat/volumes.go b/pkg/api/handlers/compat/volumes.go index 71b848932..f76e18ee3 100644 --- a/pkg/api/handlers/compat/volumes.go +++ b/pkg/api/handlers/compat/volumes.go @@ -3,6 +3,7 @@ package compat import ( "encoding/json" "net/http" + "net/url" "time" "github.com/containers/podman/v2/libpod" @@ -254,14 +255,15 @@ func PruneVolumes(w http.ResponseWriter, r *http.Request) { utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) return } - // TODO: We have no ability to pass pruning filters to `PruneVolumes()` so - // we'll explicitly reject the request if we see any - if len(query.Filters) > 0 { - utils.InternalServerError(w, errors.New("filters for pruning volumes is not implemented")) + + f := (url.Values)(query.Filters) + filterFuncs, err := filters.GenerateVolumeFilters(f) + if err != nil { + utils.Error(w, "Something when wrong.", http.StatusBadRequest, errors.Wrapf(err, "failed to parse filters for %s", f.Encode())) return } - pruned, err := runtime.PruneVolumes(r.Context()) + pruned, err := runtime.PruneVolumes(r.Context(), filterFuncs) if err != nil { utils.InternalServerError(w, err) return diff --git a/pkg/api/handlers/libpod/volumes.go b/pkg/api/handlers/libpod/volumes.go index b0d40fd8b..b02a6a8ce 100644 --- a/pkg/api/handlers/libpod/volumes.go +++ b/pkg/api/handlers/libpod/volumes.go @@ -3,6 +3,7 @@ package libpod import ( "encoding/json" "net/http" + "net/url" "github.com/containers/podman/v2/libpod" "github.com/containers/podman/v2/libpod/define" @@ -180,8 +181,25 @@ func PruneVolumes(w http.ResponseWriter, r *http.Request) { func pruneVolumesHelper(r *http.Request) ([]*entities.VolumePruneReport, error) { var ( runtime = r.Context().Value("runtime").(*libpod.Runtime) + decoder = r.Context().Value("decoder").(*schema.Decoder) ) - pruned, err := runtime.PruneVolumes(r.Context()) + query := struct { + Filters map[string][]string `schema:"filters"` + }{ + // override any golang type defaults + } + + if err := decoder.Decode(&query, r.URL.Query()); err != nil { + return nil, err + } + + f := (url.Values)(query.Filters) + filterFuncs, err := filters.GenerateVolumeFilters(f) + if err != nil { + return nil, err + } + + pruned, err := runtime.PruneVolumes(r.Context(), filterFuncs) if err != nil { return nil, err } diff --git a/pkg/bindings/test/volumes_test.go b/pkg/bindings/test/volumes_test.go index dc90d4d00..861a02441 100644 --- a/pkg/bindings/test/volumes_test.go +++ b/pkg/bindings/test/volumes_test.go @@ -144,16 +144,15 @@ var _ = Describe("Podman volumes", func() { Expect(vols[0].Name).To(Equal("homer")) }) - // TODO we need to add filtering to tests It("prune unused volume", func() { // Pruning when no volumes present should be ok - _, err := volumes.Prune(connText) + _, err := volumes.Prune(connText, nil) Expect(err).To(BeNil()) // Removing an unused volume should work _, err = volumes.Create(connText, entities.VolumeCreateOptions{}) Expect(err).To(BeNil()) - vols, err := volumes.Prune(connText) + vols, err := volumes.Prune(connText, nil) Expect(err).To(BeNil()) Expect(len(vols)).To(BeNumerically("==", 1)) @@ -163,11 +162,45 @@ var _ = Describe("Podman volumes", func() { Expect(err).To(BeNil()) session := bt.runPodman([]string{"run", "-dt", "-v", fmt.Sprintf("%s:/homer", "homer"), "--name", "vtest", alpine.name, "top"}) session.Wait(45) - vols, err = volumes.Prune(connText) + vols, err = volumes.Prune(connText, nil) Expect(err).To(BeNil()) Expect(len(vols)).To(BeNumerically("==", 1)) _, err = volumes.Inspect(connText, "homer") Expect(err).To(BeNil()) + + // Removing volume with non matching filter shouldn't prune any volumes + filters := make(map[string][]string) + filters["label"] = []string{"label1=idontmatch"} + _, err = volumes.Create(connText, entities.VolumeCreateOptions{Label: map[string]string{ + "label1": "value1", + }}) + Expect(err).To(BeNil()) + vols, err = volumes.Prune(connText, filters) + Expect(err).To(BeNil()) + Expect(len(vols)).To(BeNumerically("==", 0)) + vol2, err := volumes.Create(connText, entities.VolumeCreateOptions{Label: map[string]string{ + "label1": "value2", + }}) + Expect(err).To(BeNil()) + _, err = volumes.Create(connText, entities.VolumeCreateOptions{Label: map[string]string{ + "label1": "value3", + }}) + Expect(err).To(BeNil()) + + // Removing volume with matching filter label and value should remove specific entry + filters = make(map[string][]string) + filters["label"] = []string{"label1=value2"} + vols, err = volumes.Prune(connText, filters) + Expect(err).To(BeNil()) + Expect(len(vols)).To(BeNumerically("==", 1)) + Expect(vols[0].Id).To(Equal(vol2.Name)) + + // Removing volumes with matching filter label should remove all matching volumes + filters = make(map[string][]string) + filters["label"] = []string{"label1"} + vols, err = volumes.Prune(connText, filters) + Expect(err).To(BeNil()) + Expect(len(vols)).To(BeNumerically("==", 2)) }) }) diff --git a/pkg/bindings/volumes/volumes.go b/pkg/bindings/volumes/volumes.go index 00f1e5720..b1be257b8 100644 --- a/pkg/bindings/volumes/volumes.go +++ b/pkg/bindings/volumes/volumes.go @@ -75,7 +75,7 @@ func List(ctx context.Context, filters map[string][]string) ([]*entities.VolumeL } // Prune removes unused volumes from the local filesystem. -func Prune(ctx context.Context) ([]*entities.VolumePruneReport, error) { +func Prune(ctx context.Context, filters map[string][]string) ([]*entities.VolumePruneReport, error) { var ( pruned []*entities.VolumePruneReport ) @@ -83,7 +83,15 @@ func Prune(ctx context.Context) ([]*entities.VolumePruneReport, error) { if err != nil { return nil, err } - response, err := conn.DoRequest(nil, http.MethodPost, "/volumes/prune", nil, nil) + params := url.Values{} + if len(filters) > 0 { + strFilters, err := bindings.FiltersToString(filters) + if err != nil { + return nil, err + } + params.Set("filters", strFilters) + } + response, err := conn.DoRequest(nil, http.MethodPost, "/volumes/prune", params, nil) if err != nil { return nil, err } diff --git a/pkg/domain/entities/engine_container.go b/pkg/domain/entities/engine_container.go index 5ad475133..ac9073402 100644 --- a/pkg/domain/entities/engine_container.go +++ b/pkg/domain/entities/engine_container.go @@ -80,6 +80,6 @@ type ContainerEngine interface { VolumeCreate(ctx context.Context, opts VolumeCreateOptions) (*IDOrNameResponse, error) VolumeInspect(ctx context.Context, namesOrIds []string, opts InspectOptions) ([]*VolumeInspectReport, []error, error) VolumeList(ctx context.Context, opts VolumeListOptions) ([]*VolumeListReport, error) - VolumePrune(ctx context.Context) ([]*VolumePruneReport, error) + VolumePrune(ctx context.Context, options VolumePruneOptions) ([]*VolumePruneReport, error) VolumeRm(ctx context.Context, namesOrIds []string, opts VolumeRmOptions) ([]*VolumeRmReport, error) } diff --git a/pkg/domain/entities/volumes.go b/pkg/domain/entities/volumes.go index 1bc1e4301..e6b29e374 100644 --- a/pkg/domain/entities/volumes.go +++ b/pkg/domain/entities/volumes.go @@ -1,6 +1,7 @@ package entities import ( + "net/url" "time" docker_api_types "github.com/docker/docker/api/types" @@ -109,6 +110,12 @@ type VolumeInspectReport struct { *VolumeConfigResponse } +// VolumePruneOptions describes the options needed +// to prune a volume from the CLI +type VolumePruneOptions struct { + Filters url.Values `json:"filters" schema:"filters"` +} + type VolumePruneReport struct { Err error Id string //nolint diff --git a/pkg/domain/filters/helpers.go b/pkg/domain/filters/helpers.go new file mode 100644 index 000000000..6a5fb68b1 --- /dev/null +++ b/pkg/domain/filters/helpers.go @@ -0,0 +1,20 @@ +package filters + +import ( + "net/url" + "strings" + + "github.com/pkg/errors" +) + +func ParseFilterArgumentsIntoFilters(filters []string) (url.Values, error) { + parsedFilters := make(url.Values) + for _, f := range filters { + t := strings.SplitN(f, "=", 2) + if len(t) < 2 { + return parsedFilters, errors.Errorf("filter input must be in the form of filter=value: %s is invalid", f) + } + parsedFilters.Add(t[0], t[1]) + } + return parsedFilters, nil +} diff --git a/pkg/domain/filters/volumes.go b/pkg/domain/filters/volumes.go index 7819d3cdf..69bef4961 100644 --- a/pkg/domain/filters/volumes.go +++ b/pkg/domain/filters/volumes.go @@ -1,13 +1,14 @@ package filters import ( + "net/url" "strings" "github.com/containers/podman/v2/libpod" "github.com/pkg/errors" ) -func GenerateVolumeFilters(filters map[string][]string) ([]libpod.VolumeFilter, error) { +func GenerateVolumeFilters(filters url.Values) ([]libpod.VolumeFilter, error) { var vf []libpod.VolumeFilter for filter, v := range filters { for _, val := range v { diff --git a/pkg/domain/infra/abi/system.go b/pkg/domain/infra/abi/system.go index d6881fdc4..b6da364fc 100644 --- a/pkg/domain/infra/abi/system.go +++ b/pkg/domain/infra/abi/system.go @@ -224,7 +224,7 @@ func (ic *ContainerEngine) SystemPrune(ctx context.Context, options entities.Sys systemPruneReport.ImagePruneReport.Report.Id = append(systemPruneReport.ImagePruneReport.Report.Id, results...) } if options.Volume { - volumePruneReport, err := ic.pruneVolumesHelper(ctx) + volumePruneReport, err := ic.pruneVolumesHelper(ctx, nil) if err != nil { return nil, err } diff --git a/pkg/domain/infra/abi/volumes.go b/pkg/domain/infra/abi/volumes.go index a7262f61b..515e52754 100644 --- a/pkg/domain/infra/abi/volumes.go +++ b/pkg/domain/infra/abi/volumes.go @@ -127,12 +127,16 @@ func (ic *ContainerEngine) VolumeInspect(ctx context.Context, namesOrIds []strin return reports, errs, nil } -func (ic *ContainerEngine) VolumePrune(ctx context.Context) ([]*entities.VolumePruneReport, error) { - return ic.pruneVolumesHelper(ctx) +func (ic *ContainerEngine) VolumePrune(ctx context.Context, options entities.VolumePruneOptions) ([]*entities.VolumePruneReport, error) { + filterFuncs, err := filters.GenerateVolumeFilters(options.Filters) + if err != nil { + return nil, err + } + return ic.pruneVolumesHelper(ctx, filterFuncs) } -func (ic *ContainerEngine) pruneVolumesHelper(ctx context.Context) ([]*entities.VolumePruneReport, error) { - pruned, err := ic.Libpod.PruneVolumes(ctx) +func (ic *ContainerEngine) pruneVolumesHelper(ctx context.Context, filterFuncs []libpod.VolumeFilter) ([]*entities.VolumePruneReport, error) { + pruned, err := ic.Libpod.PruneVolumes(ctx, filterFuncs) if err != nil { return nil, err } diff --git a/pkg/domain/infra/tunnel/volumes.go b/pkg/domain/infra/tunnel/volumes.go index c0df2bb7b..b431fc8bd 100644 --- a/pkg/domain/infra/tunnel/volumes.go +++ b/pkg/domain/infra/tunnel/volumes.go @@ -68,8 +68,8 @@ func (ic *ContainerEngine) VolumeInspect(ctx context.Context, namesOrIds []strin return reports, errs, nil } -func (ic *ContainerEngine) VolumePrune(ctx context.Context) ([]*entities.VolumePruneReport, error) { - return volumes.Prune(ic.ClientCxt) +func (ic *ContainerEngine) VolumePrune(ctx context.Context, opts entities.VolumePruneOptions) ([]*entities.VolumePruneReport, error) { + return volumes.Prune(ic.ClientCxt, (map[string][]string)(opts.Filters)) } func (ic *ContainerEngine) VolumeList(ctx context.Context, opts entities.VolumeListOptions) ([]*entities.VolumeListReport, error) { diff --git a/pkg/util/utils_linux.go b/pkg/util/utils_linux.go index e4957f442..288137ca5 100644 --- a/pkg/util/utils_linux.go +++ b/pkg/util/utils_linux.go @@ -6,7 +6,6 @@ import ( "path/filepath" "syscall" - "github.com/containers/podman/v2/pkg/rootless" "github.com/containers/psgo" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -53,19 +52,3 @@ func FindDeviceNodes() (map[string]string, error) { return nodes, nil } - -// CheckRootlessUIDRange checks the uid within the rootless container is in the range from /etc/subuid -func CheckRootlessUIDRange(uid int) error { - uids, _, err := rootless.GetConfiguredMappings() - if err != nil { - return err - } - total := 0 - for _, u := range uids { - total += u.Size - } - if uid > total { - return errors.Errorf("requested user's UID %d is too large for the rootless user namespace", uid) - } - return nil -} diff --git a/pkg/util/utils_unsupported.go b/pkg/util/utils_unsupported.go index f8d5a37c1..62805d7c8 100644 --- a/pkg/util/utils_unsupported.go +++ b/pkg/util/utils_unsupported.go @@ -10,8 +10,3 @@ import ( func FindDeviceNodes() (map[string]string, error) { return nil, errors.Errorf("not supported on non-Linux OSes") } - -// CheckRootlessUIDRange is not implemented anywhere except Linux. -func CheckRootlessUIDRange(uid int) error { - return nil -} diff --git a/test/apiv2/30-volumes.at b/test/apiv2/30-volumes.at index aa167a97a..2cfca9d08 100644 --- a/test/apiv2/30-volumes.at +++ b/test/apiv2/30-volumes.at @@ -20,6 +20,18 @@ t POST libpod/volumes/create \ .Labels.testlabel=testonly \ .Options.type=tmpfs \ .Options.o=nodev,noexec +t POST libpod/volumes/create \ + '"Name":"foo3","Label":{"testlabel":""},"Options":{"type":"tmpfs","o":"nodev,noexec"}}' 201 \ + .Name=foo3 \ + .Labels.testlabel="" \ + .Options.type=tmpfs \ + .Options.o=nodev,noexec +t POST libpod/volumes/create \ + '"Name":"foo4","Label":{"testlabel1":"testonly"},"Options":{"type":"tmpfs","o":"nodev,noexec"}}' 201 \ + .Name=foo4 \ + .Labels.testlabel1=testonly \ + .Options.type=tmpfs \ + .Options.o=nodev,noexec # Negative test # We have created a volume named "foo1" @@ -39,6 +51,12 @@ t GET libpod/volumes/json?filters=%7B%22name%22%3A%5B%22foo1%22%5D%7D 200 length t GET libpod/volumes/json?filters=%7B%22name%22%3A%20%5B%22foo1%22%2C%20%22foo2%22%5D%7D 200 length=2 .[0].Name=foo1 .[1].Name=foo2 # -G --data-urlencode 'filters={"name":["notexist"]}' t GET libpod/volumes/json?filters=%7B%22name%22%3A%5B%22notexists%22%5D%7D 200 length=0 +# -G --data-urlencode 'filters={"label":["testlabel"]}' +t GET libpod/volumes/json?filters=%7B%22label%22:%5B%22testlabel%22%5D%7D 200 length=2 +# -G --data-urlencode 'filters={"label":["testlabel=testonly"]}' +t GET libpod/volumes/json?filters=%7B%22label%22:%5B%22testlabel=testonly%22%5D%7D 200 length=1 +# -G --data-urlencode 'filters={"label":["testlabel1=testonly"]}' +t GET libpod/volumes/json?filters=%7B%22label%22:%5B%22testlabel1=testonly%22%5D%7D 200 length=1 ## inspect volume t GET libpod/volumes/foo1/json 200 \ @@ -60,6 +78,18 @@ t DELETE libpod/volumes/foo1 404 \ .message~.* \ .response=404 +## Prune volumes with label matching 'testlabel1=testonly' +# -G --data-urlencode 'filters={"label":["testlabel1=testonly"]}' +t POST libpod/volumes/prune?filters=%7B%22label%22:%5B%22testlabel1=testonly%22%5D%7D "" 200 +# -G --data-urlencode 'filters={"label":["testlabel1=testonly"]}' +t GET libpod/volumes/json?filters=%7B%22label%22:%5B%22testlabel1=testonly%22%5D%7D 200 length=0 + +## Prune volumes with label matching 'testlabel' +# -G --data-urlencode 'filters={"label":["testlabel"]}' +t POST libpod/volumes/prune?filters=%7B%22label%22:%5B%22testlabel%22%5D%7D "" 200 +# -G --data-urlencode 'filters={"label":["testlabel"]}' +t GET libpod/volumes/json?filters=%7B%22label%22:%5B%22testlabel%22%5D%7D 200 length=0 + ## Prune volumes t POST libpod/volumes/prune "" 200 #After prune volumes, there should be no volume existing diff --git a/test/e2e/events_test.go b/test/e2e/events_test.go index b37bd584e..0c7a1bd66 100644 --- a/test/e2e/events_test.go +++ b/test/e2e/events_test.go @@ -5,9 +5,11 @@ import ( "fmt" "os" "strings" + "sync" "time" . "github.com/containers/podman/v2/test/utils" + "github.com/containers/storage/pkg/stringid" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" . "github.com/onsi/gomega/gexec" @@ -115,10 +117,7 @@ var _ = Describe("Podman events", func() { SkipIfNotFedora() _, ec, _ := podmanTest.RunLsContainer("") Expect(ec).To(Equal(0)) - test := podmanTest.Podman([]string{"events", "--help"}) - test.WaitWithDefaultTimeout() - fmt.Println(test.OutputToStringArray()) - result := podmanTest.Podman([]string{"events", "--stream=false", "--since", "1h"}) + result := podmanTest.Podman([]string{"events", "--stream=false", "--until", "1h"}) result.WaitWithDefaultTimeout() Expect(result.ExitCode()).To(BeZero()) }) @@ -154,4 +153,40 @@ var _ = Describe("Podman events", func() { Expect(eventsMap).To(HaveKey("Status")) }) + + It("podman events --until future", func() { + name1 := stringid.GenerateNonCryptoID() + name2 := stringid.GenerateNonCryptoID() + name3 := stringid.GenerateNonCryptoID() + session := podmanTest.Podman([]string{"create", "--name", name1, ALPINE}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer GinkgoRecover() + defer wg.Done() + + // wait 2 seconds to be sure events is running + time.Sleep(time.Second * 2) + session = podmanTest.Podman([]string{"create", "--name", name2, ALPINE}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + session = podmanTest.Podman([]string{"create", "--name", name3, ALPINE}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + }() + + // unix timestamp in 10 seconds + until := time.Now().Add(time.Second * 10).Unix() + result := podmanTest.Podman([]string{"events", "--since", "30s", "--until", fmt.Sprint(until)}) + result.Wait(11) + Expect(result.ExitCode()).To(BeZero()) + Expect(result.OutputToString()).To(ContainSubstring(name1)) + Expect(result.OutputToString()).To(ContainSubstring(name2)) + Expect(result.OutputToString()).To(ContainSubstring(name3)) + + wg.Wait() + }) }) diff --git a/test/e2e/volume_prune_test.go b/test/e2e/volume_prune_test.go index c8521ebe7..a910c47a7 100644 --- a/test/e2e/volume_prune_test.go +++ b/test/e2e/volume_prune_test.go @@ -62,6 +62,66 @@ var _ = Describe("Podman volume prune", func() { podmanTest.Cleanup() }) + It("podman prune volume --filter", func() { + session := podmanTest.Podman([]string{"volume", "create", "--label", "label1=value1", "myvol1"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"volume", "create", "--label", "sharedlabel1=slv1", "myvol2"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"volume", "create", "--label", "sharedlabel1=slv2", "myvol3"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"volume", "create", "--label", "sharedlabel1", "myvol4"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"create", "-v", "myvol5:/myvol5", ALPINE, "ls"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"create", "-v", "myvol6:/myvol6", ALPINE, "ls"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"volume", "ls"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(len(session.OutputToStringArray())).To(Equal(7)) + + session = podmanTest.Podman([]string{"volume", "prune", "--force", "--filter", "label=label1=value1"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"volume", "ls"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(len(session.OutputToStringArray())).To(Equal(6)) + + session = podmanTest.Podman([]string{"volume", "prune", "--force", "--filter", "label=sharedlabel1=slv1"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"volume", "ls"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(len(session.OutputToStringArray())).To(Equal(5)) + + session = podmanTest.Podman([]string{"volume", "prune", "--force", "--filter", "label=sharedlabel1"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"volume", "ls"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(len(session.OutputToStringArray())).To(Equal(3)) + + podmanTest.Cleanup() + }) + It("podman system prune --volume", func() { session := podmanTest.Podman([]string{"volume", "create"}) session.WaitWithDefaultTimeout() |