diff options
-rw-r--r-- | cmd/podman/system/prune.go | 3 | ||||
-rw-r--r-- | docs/source/markdown/podman-container-prune.1.md | 12 | ||||
-rw-r--r-- | docs/source/markdown/podman-image-prune.1.md | 12 | ||||
-rw-r--r-- | docs/source/markdown/podman-system-prune.1.md | 16 | ||||
-rw-r--r-- | pkg/api/handlers/compat/containers_create.go | 15 | ||||
-rw-r--r-- | pkg/api/handlers/compat/networks.go | 6 | ||||
-rw-r--r-- | pkg/bindings/generator/generator.go | 234 | ||||
-rw-r--r-- | pkg/bindings/images/removeoptions_types.go | 93 | ||||
-rw-r--r-- | pkg/bindings/images/rm.go | 10 | ||||
-rw-r--r-- | pkg/bindings/images/types.go | 8 | ||||
-rw-r--r-- | pkg/bindings/test/images_test.go | 17 | ||||
-rw-r--r-- | pkg/bindings/test/manifests_test.go | 2 | ||||
-rw-r--r-- | pkg/domain/entities/system.go | 1 | ||||
-rw-r--r-- | pkg/domain/infra/abi/system.go | 14 | ||||
-rw-r--r-- | test/apiv2/20-containers.at | 11 | ||||
-rw-r--r-- | test/apiv2/35-networks.at | 8 |
16 files changed, 442 insertions, 20 deletions
diff --git a/cmd/podman/system/prune.go b/cmd/podman/system/prune.go index f57689584..5ee017581 100644 --- a/cmd/podman/system/prune.go +++ b/cmd/podman/system/prune.go @@ -46,6 +46,9 @@ func init() { flags.BoolVarP(&force, "force", "f", false, "Do not prompt for confirmation. The default is false") flags.BoolVarP(&pruneOptions.All, "all", "a", false, "Remove all unused data") flags.BoolVar(&pruneOptions.Volume, "volumes", false, "Prune volumes") + filterFlagName := "filter" + flags.StringArrayVar(&pruneOptions.Filter, filterFlagName, []string{}, "Provide filter values (e.g. 'label=<key>=<value>')") + _ = pruneCommand.RegisterFlagCompletionFunc(filterFlagName, completion.AutocompleteNone) } diff --git a/docs/source/markdown/podman-container-prune.1.md b/docs/source/markdown/podman-container-prune.1.md index 4b4effb0b..b199f9ebb 100644 --- a/docs/source/markdown/podman-container-prune.1.md +++ b/docs/source/markdown/podman-container-prune.1.md @@ -15,6 +15,17 @@ podman-container-prune - Remove all stopped containers from local storage Provide filter values. +The --filter flag format is of “key=value”. If there is more than one filter, then pass multiple flags (e.g., --filter "foo=bar" --filter "bif=baz") + +Supported filters: + +- `until` (_timestamp_) - only remove containers and images created before given timestamp +- `label` (label=_key_, label=_key=value_, label!=_key_, or label!=_key=value_) - only remove containers and images, with (or without, in case label!=... is used) the specified labels. + +The until filter can be Unix timestamps, date formatted timestamps, or Go duration strings (e.g. 10m, 1h30m) computed relative to the machine’s time. + +The label filter accepts two formats. One is the label=... (label=_key_ or label=_key=value_), which removes containers with the specified labels. The other format is the label!=... (label!=_key_ or label!=_key=value_), which removes containers without the specified labels. + #### **--force**, **-f** Do not provide an interactive prompt for container removal. @@ -63,3 +74,4 @@ podman(1), podman-ps ## HISTORY December 2018, Originally compiled by Brent Baude (bbaude at redhat dot com) +December 2020, converted filter information from docs.docker.com documentation by Dan Walsh (dwalsh at redhat dot com) diff --git a/docs/source/markdown/podman-image-prune.1.md b/docs/source/markdown/podman-image-prune.1.md index d8558d244..73024ffb8 100644 --- a/docs/source/markdown/podman-image-prune.1.md +++ b/docs/source/markdown/podman-image-prune.1.md @@ -22,6 +22,17 @@ Remove dangling images and images that have no associated containers. Provide filter values. +The --filter flag format is of “key=value”. If there is more than one filter, then pass multiple flags (e.g., --filter "foo=bar" --filter "bif=baz") + +Supported filters: + +- `until` (_timestamp_) - only remove containers and images created before given timestamp +- `label` (label=_key_, label=_key=value_, label!=_key_, or label!=_key=value_) - only remove containers and images, with (or without, in case label!=... is used) the specified labels. + +The until filter can be Unix timestamps, date formatted timestamps, or Go duration strings (e.g. 10m, 1h30m) computed relative to the machine’s time. + +The label filter accepts two formats. One is the label=... (label=_key_ or label=_key=value_), which removes images with the specified labels. The other format is the label!=... (label!=_key_ or label!=_key=value_), which removes images without the specified labels. + #### **--force**, **-f** Do not provide an interactive prompt for container removal. @@ -93,3 +104,4 @@ podman(1), podman-images ## HISTORY December 2018, Originally compiled by Brent Baude (bbaude at redhat dot com) +December 2020, converted filter information from docs.docker.com documentation by Dan Walsh (dwalsh at redhat dot com) diff --git a/docs/source/markdown/podman-system-prune.1.md b/docs/source/markdown/podman-system-prune.1.md index 431a11267..791503a2c 100644 --- a/docs/source/markdown/podman-system-prune.1.md +++ b/docs/source/markdown/podman-system-prune.1.md @@ -18,6 +18,21 @@ By default, volumes are not removed to prevent important data from being deleted Recursively remove all unused pod, container, image and volume data (Maximum 50 iterations.) +#### **--filter**=*filters* + +Provide filter values. + +The --filter flag format is of “key=value”. If there is more than one filter, then pass multiple flags (e.g., --filter "foo=bar" --filter "bif=baz") + +Supported filters: + +- `until` (_timestamp_) - only remove containers and images created before given timestamp +- `label` (label=_key_, label=_key=value_, label!=_key_, or label!=_key=value_) - only remove containers and images, with (or without, in case label!=... is used) the specified labels. + +The until filter can be Unix timestamps, date formatted timestamps, or Go duration strings (e.g. 10m, 1h30m) computed relative to the machine’s time. + +The label filter accepts two formats. One is the label=... (label=_key_ or label=_key=value_), which removes containers and images with the specified labels. The other format is the label!=... (label!=_key_ or label!=_key=value_), which removes containers and images without the specified labels. + #### **--force**, **-f** Do not prompt for confirmation @@ -35,3 +50,4 @@ podman(1), podman-image-prune(1), podman-container-prune(1), podman-pod-prune(1) ## HISTORY February 2019, Originally compiled by Dan Walsh (dwalsh at redhat dot com) +December 2020, converted filter information from docs.docker.com documentation by Dan Walsh (dwalsh at redhat dot com) diff --git a/pkg/api/handlers/compat/containers_create.go b/pkg/api/handlers/compat/containers_create.go index 409a74de2..6e85872b2 100644 --- a/pkg/api/handlers/compat/containers_create.go +++ b/pkg/api/handlers/compat/containers_create.go @@ -66,7 +66,20 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "make cli opts()")) return } - sg := specgen.NewSpecGenerator(newImage.ID(), cliOpts.RootFS) + + imgNameOrID := newImage.ID() + // if the img had multi names with the same sha256 ID, should use the InputName, not the ID + if len(newImage.Names()) > 1 { + imageRef, err := utils.ParseDockerReference(newImage.InputName) + if err != nil { + utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, err) + return + } + // maybe the InputName has no tag, so use full name to display + imgNameOrID = imageRef.DockerReference().String() + } + + sg := specgen.NewSpecGenerator(imgNameOrID, cliOpts.RootFS) if err := common.FillOutSpecGen(sg, cliOpts, args); err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "fill out specgen")) return diff --git a/pkg/api/handlers/compat/networks.go b/pkg/api/handlers/compat/networks.go index fe13971b0..f0b922885 100644 --- a/pkg/api/handlers/compat/networks.go +++ b/pkg/api/handlers/compat/networks.go @@ -131,7 +131,7 @@ func getNetworkResourceByNameOrID(nameOrID string, runtime *libpod.Runtime, filt Name: conf.Name, ID: network.GetNetworkID(conf.Name), Created: time.Unix(int64(stat.Ctim.Sec), int64(stat.Ctim.Nsec)), // nolint: unconvert - Scope: "", + Scope: "local", Driver: network.DefaultNetworkDriver, EnableIPv6: false, IPAM: dockerNetwork.IPAM{ @@ -197,7 +197,7 @@ func ListNetworks(w http.ResponseWriter, r *http.Request) { } var reports []*types.NetworkResource - logrus.Errorf("netNames: %q", strings.Join(netNames, ", ")) + logrus.Debugf("netNames: %q", strings.Join(netNames, ", ")) for _, name := range netNames { report, err := getNetworkResourceByNameOrID(name, runtime, query.Filters) if err != nil { @@ -239,7 +239,7 @@ func CreateNetwork(w http.ResponseWriter, r *http.Request) { Internal: networkCreate.Internal, Labels: networkCreate.Labels, } - if networkCreate.IPAM != nil && networkCreate.IPAM.Config != nil { + if networkCreate.IPAM != nil && len(networkCreate.IPAM.Config) > 0 { if len(networkCreate.IPAM.Config) > 1 { utils.InternalServerError(w, errors.New("compat network create can only support one IPAM config")) return diff --git a/pkg/bindings/generator/generator.go b/pkg/bindings/generator/generator.go new file mode 100644 index 000000000..24c2310ff --- /dev/null +++ b/pkg/bindings/generator/generator.go @@ -0,0 +1,234 @@ +package main + +import ( + "errors" + "fmt" + "go/ast" + "go/parser" + "go/token" + "io/ioutil" + "os" + "os/exec" + "strings" + "text/template" + "time" +) + +var bodyTmpl = `package {{.PackageName}} + +import ( +{{range $import := .Imports}} {{$import}} +{{end}} + +) + +/* +This file is generated automatically by go generate. Do not edit. + +Created {{.Date}} +*/ + +// Changed +func (o *{{.StructName}}) Changed(fieldName string) bool { + r := reflect.ValueOf(o) + value := reflect.Indirect(r).FieldByName(fieldName) + return !value.IsNil() +} + +// ToParams +func (o *{{.StructName}}) ToParams() (url.Values, error) { + params := url.Values{} + if o == nil { + return params, nil + } + json := jsoniter.ConfigCompatibleWithStandardLibrary + s := reflect.ValueOf(o) + if reflect.Ptr == s.Kind() { + s = s.Elem() + } + sType := s.Type() + for i := 0; i < s.NumField(); i++ { + fieldName := sType.Field(i).Name + if !o.Changed(fieldName) { + continue + } + f := s.Field(i) + if reflect.Ptr == f.Kind() { + f = f.Elem() + } + switch f.Kind() { + case reflect.Bool: + params.Set(fieldName, strconv.FormatBool(f.Bool())) + case reflect.String: + params.Set(fieldName, f.String()) + case reflect.Int, reflect.Int64: + // f.Int() is always an int64 + params.Set(fieldName, strconv.FormatInt(f.Int(), 10)) + case reflect.Slice: + typ := reflect.TypeOf(f.Interface()).Elem() + slice := reflect.MakeSlice(reflect.SliceOf(typ), f.Len(), f.Cap()) + switch typ.Kind() { + case reflect.String: + s, ok := slice.Interface().([]string) + if !ok { + return nil, errors.New("failed to convert to string slice") + } + for _, val := range s { + params.Add(fieldName, val) + } + default: + return nil, errors.Errorf("unknown slice type %s", f.Kind().String()) + } + case reflect.Map: + lowerCaseKeys := make(map[string][]string) + // I dont know if this code is needed anymore, TBD + // for k, v := range filters { + // lowerCaseKeys[strings.ToLower(k)] = v + // } + s, err := json.MarshalToString(lowerCaseKeys) + if err != nil { + return nil, err + } + + params.Set(fieldName, s) + default: + return nil, errors.Errorf("unknown type %s", f.Kind().String()) + } + } + return params, nil +} +` + +var fieldTmpl = ` +// With{{.Name}} +func(o *{{.StructName}}) With{{.Name}}(value {{.Type}}) *{{.StructName}} { + v := &value + o.{{.Name}} = v + return o +} +` + +type fieldStruct struct { + Name string + StructName string + Type string +} + +func main() { + var ( + closed bool + fieldStructs []fieldStruct + structNode ast.Node + ) + srcFile := os.Getenv("GOFILE") + pkg := os.Getenv("GOPACKAGE") + inputStructName := os.Args[1] + b, err := ioutil.ReadFile(srcFile) + if err != nil { + panic(err) + } + fset := token.NewFileSet() // positions are relative to fset + f, err := parser.ParseFile(fset, "", b, parser.ParseComments) + if err != nil { + panic(err) + } + + // always add reflect + imports := []string{"\"reflect\""} + for _, imp := range f.Imports { + imports = append(imports, imp.Path.Value) + } + + out, err := os.Create(strings.ToLower(inputStructName) + "_" + srcFile) + if err != nil { + panic(err) + } + defer func() { + if !closed { + out.Close() + } + }() + bodyStruct := struct { + PackageName string + Imports []string + Date string + StructName string + }{ + PackageName: pkg, + Imports: imports, + Date: time.Now().String(), + StructName: inputStructName, + } + + body := template.Must(template.New("body").Parse(bodyTmpl)) + fields := template.Must(template.New("fields").Parse(fieldTmpl)) + ast.Inspect(f, func(n ast.Node) bool { + ref, refOK := n.(*ast.TypeSpec) + if refOK { + if ref.Name.Name == inputStructName { + structNode = n + x := ref.Type.(*ast.StructType) + for _, field := range x.Fields.List { + var ( + name string + ) + typeExpr := field.Type + start := typeExpr.Pos() - 1 + end := typeExpr.End() - 1 + fieldType := strings.Replace(string(b[start:end]), "*", "", 1) + if len(field.Names) > 0 { + name = field.Names[0].Name + if len(name) < 1 { + panic(errors.New("bad name")) + } + } + fStruct := fieldStruct{ + Name: name, + StructName: inputStructName, + Type: fieldType, + } + fieldStructs = append(fieldStructs, fStruct) + } // for + + // create the body + if err := body.Execute(out, bodyStruct); err != nil { + fmt.Println(err) + os.Exit(1) + } + + // create with func from the struct fields + for _, fs := range fieldStructs { + if err := fields.Execute(out, fs); err != nil { + fmt.Println(err) + os.Exit(1) + } + } + + // close out file + if err := out.Close(); err != nil { + fmt.Println(err) + os.Exit(1) + } + closed = true + + // go fmt file + gofmt := exec.Command("gofmt", "-w", "-s", out.Name()) + gofmt.Stderr = os.Stdout + if err := gofmt.Run(); err != nil { + fmt.Println(err) + os.Exit(1) + } + + // go import file + goimport := exec.Command("goimports", "-w", out.Name()) + goimport.Stderr = os.Stdout + if err := goimport.Run(); err != nil { + fmt.Println(err) + os.Exit(1) + } + } + + } + return true + }) +} diff --git a/pkg/bindings/images/removeoptions_types.go b/pkg/bindings/images/removeoptions_types.go new file mode 100644 index 000000000..5902bf908 --- /dev/null +++ b/pkg/bindings/images/removeoptions_types.go @@ -0,0 +1,93 @@ +package images + +import ( + "net/url" + "reflect" + "strconv" + + jsoniter "github.com/json-iterator/go" + "github.com/pkg/errors" +) + +/* +This file is generated automatically by go generate. Do not edit. + +Created 2020-12-10 12:51:06.090426622 -0600 CST m=+0.000133169 +*/ + +// Changed +func (o *RemoveOptions) Changed(fieldName string) bool { + r := reflect.ValueOf(o) + value := reflect.Indirect(r).FieldByName(fieldName) + return !value.IsNil() +} + +// ToParams +func (o *RemoveOptions) ToParams() (url.Values, error) { + params := url.Values{} + if o == nil { + return params, nil + } + json := jsoniter.ConfigCompatibleWithStandardLibrary + s := reflect.ValueOf(o) + if reflect.Ptr == s.Kind() { + s = s.Elem() + } + sType := s.Type() + for i := 0; i < s.NumField(); i++ { + fieldName := sType.Field(i).Name + if !o.Changed(fieldName) { + continue + } + f := s.Field(i) + if reflect.Ptr == f.Kind() { + f = f.Elem() + } + switch f.Kind() { + case reflect.Bool: + params.Set(fieldName, strconv.FormatBool(f.Bool())) + case reflect.String: + params.Set(fieldName, f.String()) + case reflect.Int, reflect.Int64: + // f.Int() is always an int64 + params.Set(fieldName, strconv.FormatInt(f.Int(), 10)) + case reflect.Slice: + typ := reflect.TypeOf(f.Interface()).Elem() + slice := reflect.MakeSlice(reflect.SliceOf(typ), f.Len(), f.Cap()) + switch typ.Kind() { + case reflect.String: + s, ok := slice.Interface().([]string) + if !ok { + return nil, errors.New("failed to convert to string slice") + } + for _, val := range s { + params.Add(fieldName, val) + } + default: + return nil, errors.Errorf("unknown slice type %s", f.Kind().String()) + } + case reflect.Map: + lowerCaseKeys := make(map[string][]string) + // I dont know if this code is needed anymore, TBD + // for k, v := range filters { + // lowerCaseKeys[strings.ToLower(k)] = v + // } + s, err := json.MarshalToString(lowerCaseKeys) + if err != nil { + return nil, err + } + + params.Set(fieldName, s) + default: + return nil, errors.Errorf("unknown type %s", f.Kind().String()) + } + } + return params, nil +} + +// WithForce +func (o *RemoveOptions) WithForce(value bool) *RemoveOptions { + v := &value + o.Force = v + return o +} diff --git a/pkg/bindings/images/rm.go b/pkg/bindings/images/rm.go index 9685b75e4..0b3b88165 100644 --- a/pkg/bindings/images/rm.go +++ b/pkg/bindings/images/rm.go @@ -41,17 +41,19 @@ func BatchRemove(ctx context.Context, images []string, opts entities.ImageRemove return &report.ImageRemoveReport, errorhandling.StringsToErrors(report.Errors) } -// Remove removes an image from the local storage. Use force to remove an +// Remove removes an image from the local storage. Use optional force option to remove an // image, even if it's used by containers. -func Remove(ctx context.Context, nameOrID string, force bool) (*entities.ImageRemoveReport, error) { +func Remove(ctx context.Context, nameOrID string, options *RemoveOptions) (*entities.ImageRemoveReport, error) { var report handlers.LibpodImagesRemoveReport conn, err := bindings.GetClient(ctx) if err != nil { return nil, err } - params := url.Values{} - params.Set("force", strconv.FormatBool(force)) + params, err := options.ToParams() + if err != nil { + return nil, err + } response, err := conn.DoRequest(nil, http.MethodDelete, "/images/%s", params, nil, nameOrID) if err != nil { return nil, err diff --git a/pkg/bindings/images/types.go b/pkg/bindings/images/types.go new file mode 100644 index 000000000..340c7bdb9 --- /dev/null +++ b/pkg/bindings/images/types.go @@ -0,0 +1,8 @@ +package images + +//go:generate go run ../generator/generator.go RemoveOptions +// RemoveOptions are optional options for image removal +type RemoveOptions struct { + // Forces removes all containers based on the image + Force *bool +} diff --git a/pkg/bindings/test/images_test.go b/pkg/bindings/test/images_test.go index 7d9415f91..684f110e8 100644 --- a/pkg/bindings/test/images_test.go +++ b/pkg/bindings/test/images_test.go @@ -84,7 +84,7 @@ var _ = Describe("Podman images", func() { // Test to validate the remove image api It("remove image", func() { // Remove invalid image should be a 404 - response, err := images.Remove(bt.conn, "foobar5000", false) + response, err := images.Remove(bt.conn, "foobar5000", nil) Expect(err).ToNot(BeNil()) Expect(response).To(BeNil()) code, _ := bindings.CheckResponseCode(err) @@ -93,7 +93,7 @@ var _ = Describe("Podman images", func() { // Remove an image by name, validate image is removed and error is nil inspectData, err := images.GetImage(bt.conn, busybox.shortName, nil) Expect(err).To(BeNil()) - response, err = images.Remove(bt.conn, busybox.shortName, false) + response, err = images.Remove(bt.conn, busybox.shortName, nil) Expect(err).To(BeNil()) code, _ = bindings.CheckResponseCode(err) @@ -113,12 +113,13 @@ var _ = Describe("Podman images", func() { // try to remove the image "alpine". This should fail since we are not force // deleting hence image cannot be deleted until the container is deleted. - response, err = images.Remove(bt.conn, alpine.shortName, false) + response, err = images.Remove(bt.conn, alpine.shortName, nil) code, _ = bindings.CheckResponseCode(err) Expect(code).To(BeNumerically("==", http.StatusConflict)) // Removing the image "alpine" where force = true - response, err = images.Remove(bt.conn, alpine.shortName, true) + options := images.RemoveOptions{} + response, err = images.Remove(bt.conn, alpine.shortName, options.WithForce(true)) Expect(err).To(BeNil()) // To be extra sure, check if the previously created container // is gone as well. @@ -213,7 +214,7 @@ var _ = Describe("Podman images", func() { It("Load|Import Image", func() { // load an image - _, err := images.Remove(bt.conn, alpine.name, false) + _, err := images.Remove(bt.conn, alpine.name, nil) Expect(err).To(BeNil()) exists, err := images.Exists(bt.conn, alpine.name) Expect(err).To(BeNil()) @@ -231,7 +232,7 @@ var _ = Describe("Podman images", func() { // load with a repo name f, err = os.Open(filepath.Join(ImageCacheDir, alpine.tarballName)) Expect(err).To(BeNil()) - _, err = images.Remove(bt.conn, alpine.name, false) + _, err = images.Remove(bt.conn, alpine.name, nil) Expect(err).To(BeNil()) exists, err = images.Exists(bt.conn, alpine.name) Expect(err).To(BeNil()) @@ -247,7 +248,7 @@ var _ = Describe("Podman images", func() { // load with a bad repo name should trigger a 500 f, err = os.Open(filepath.Join(ImageCacheDir, alpine.tarballName)) Expect(err).To(BeNil()) - _, err = images.Remove(bt.conn, alpine.name, false) + _, err = images.Remove(bt.conn, alpine.name, nil) Expect(err).To(BeNil()) exists, err = images.Exists(bt.conn, alpine.name) Expect(err).To(BeNil()) @@ -275,7 +276,7 @@ var _ = Describe("Podman images", func() { It("Import Image", func() { // load an image - _, err = images.Remove(bt.conn, alpine.name, false) + _, err = images.Remove(bt.conn, alpine.name, nil) Expect(err).To(BeNil()) exists, err := images.Exists(bt.conn, alpine.name) Expect(err).To(BeNil()) diff --git a/pkg/bindings/test/manifests_test.go b/pkg/bindings/test/manifests_test.go index 55fc4cb0d..a4ecaa20f 100644 --- a/pkg/bindings/test/manifests_test.go +++ b/pkg/bindings/test/manifests_test.go @@ -47,7 +47,7 @@ var _ = Describe("Podman containers ", func() { code, _ := bindings.CheckResponseCode(err) Expect(code).To(BeNumerically("==", http.StatusInternalServerError)) - _, err = images.Remove(bt.conn, id, false) + _, err = images.Remove(bt.conn, id, nil) Expect(err).To(BeNil()) // create manifest list with images diff --git a/pkg/domain/entities/system.go b/pkg/domain/entities/system.go index bde2b6ef2..4af013134 100644 --- a/pkg/domain/entities/system.go +++ b/pkg/domain/entities/system.go @@ -19,6 +19,7 @@ type ServiceOptions struct { type SystemPruneOptions struct { All bool Volume bool + Filter []string } // SystemPruneReport provides report after system prune is executed. diff --git a/pkg/domain/infra/abi/system.go b/pkg/domain/infra/abi/system.go index 7ed58092b..d6881fdc4 100644 --- a/pkg/domain/infra/abi/system.go +++ b/pkg/domain/infra/abi/system.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "io/ioutil" + "net/url" "os" "os/exec" "path/filepath" @@ -179,7 +180,16 @@ func (ic *ContainerEngine) SystemPrune(ctx context.Context, options entities.Sys found = true } systemPruneReport.PodPruneReport = append(systemPruneReport.PodPruneReport, podPruneReport...) - containerPruneReport, err := ic.pruneContainersHelper(nil) + containerPruneOptions := entities.ContainerPruneOptions{} + for _, f := range options.Filter { + t := strings.SplitN(f, "=", 2) + containerPruneOptions.Filters = make(url.Values) + if len(t) < 2 { + return nil, errors.Errorf("filter input must be in the form of filter=value: %s is invalid", f) + } + containerPruneOptions.Filters.Add(t[0], t[1]) + } + containerPruneReport, err := ic.ContainerPrune(ctx, containerPruneOptions) if err != nil { return nil, err } @@ -194,7 +204,7 @@ func (ic *ContainerEngine) SystemPrune(ctx context.Context, options entities.Sys } } - results, err := ic.Libpod.ImageRuntime().PruneImages(ctx, options.All, nil) + results, err := ic.Libpod.ImageRuntime().PruneImages(ctx, options.All, options.Filter) if err != nil { return nil, err diff --git a/test/apiv2/20-containers.at b/test/apiv2/20-containers.at index 5c35edf2b..bc6efc20d 100644 --- a/test/apiv2/20-containers.at +++ b/test/apiv2/20-containers.at @@ -5,8 +5,10 @@ # WORKDIR=/data ENV_WORKDIR_IMG=quay.io/libpod/testimage:20200929 +MultiTagName=localhost/test/testformultitag:tag podman pull $IMAGE &>/dev/null +podman tag $IMAGE $MultiTagName podman pull $ENV_WORKDIR_IMG &>/dev/null # Unimplemented #t POST libpod/containers/create '' 201 'sdf' @@ -216,4 +218,13 @@ t GET containers/$cid/json 200 \ t DELETE containers/$cid 204 +# when the image had multi tags, the container's Image should be correct +# Fixes https://github.com/containers/podman/issues/8547 +t POST containers/create Image=${MultiTagName} 201 \ + .Id~[0-9a-f]\\{64\\} +cid=$(jq -r '.Id' <<<"$output") +t GET containers/$cid/json 200 \ + .Image=${MultiTagName} +t DELETE containers/$cid 204 +t DELETE images/${MultiTagName}?force=true 200 # vim: filetype=sh diff --git a/test/apiv2/35-networks.at b/test/apiv2/35-networks.at index 0ce56ee3c..5327bd076 100644 --- a/test/apiv2/35-networks.at +++ b/test/apiv2/35-networks.at @@ -50,7 +50,13 @@ t GET networks?filters=%7B%22dangling%22%3A%5B%221%22%5D%7D 500 \ # network inspect docker t GET networks/a7662f44d65029fd4635c91feea3d720a57cef52e2a9fcc7772b69072cc1ccd1 200 \ .Name=network1 \ -.Id=a7662f44d65029fd4635c91feea3d720a57cef52e2a9fcc7772b69072cc1ccd1 +.Id=a7662f44d65029fd4635c91feea3d720a57cef52e2a9fcc7772b69072cc1ccd1 \ +.Scope=local + +# network create docker +t POST networks/create '"Name":"net3","IPAM":{"Config":[]}' 201 +# network delete docker +t DELETE networks/net3 204 # clean the network t DELETE libpod/networks/network1 200 \ |