mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
filters, for images: start with untagged/tagged boolean
This is a new feature and flag. (replaces the suggestion of a flag for --untagged images). The concept is to have a syntax to filter. This begins with this filtering for the 'images' subcommand, and at that only filtering for whether images are untagged. example like: docker rmi $(docker images -q --filter 'untagged=true') Docker-DCO-1.1-Signed-off-by: Vincent Batts <vbatts@redhat.com> (github: vbatts)
This commit is contained in:
parent
afb2d5de3d
commit
5f3812ec97
6 changed files with 1678 additions and 20 deletions
|
@ -26,12 +26,14 @@ import (
|
||||||
"github.com/dotcloud/docker/dockerversion"
|
"github.com/dotcloud/docker/dockerversion"
|
||||||
"github.com/dotcloud/docker/engine"
|
"github.com/dotcloud/docker/engine"
|
||||||
"github.com/dotcloud/docker/nat"
|
"github.com/dotcloud/docker/nat"
|
||||||
|
"github.com/dotcloud/docker/opts"
|
||||||
"github.com/dotcloud/docker/pkg/signal"
|
"github.com/dotcloud/docker/pkg/signal"
|
||||||
"github.com/dotcloud/docker/pkg/term"
|
"github.com/dotcloud/docker/pkg/term"
|
||||||
"github.com/dotcloud/docker/pkg/units"
|
"github.com/dotcloud/docker/pkg/units"
|
||||||
"github.com/dotcloud/docker/registry"
|
"github.com/dotcloud/docker/registry"
|
||||||
"github.com/dotcloud/docker/runconfig"
|
"github.com/dotcloud/docker/runconfig"
|
||||||
"github.com/dotcloud/docker/utils"
|
"github.com/dotcloud/docker/utils"
|
||||||
|
"github.com/dotcloud/docker/utils/filters"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (cli *DockerCli) CmdHelp(args ...string) error {
|
func (cli *DockerCli) CmdHelp(args ...string) error {
|
||||||
|
@ -1145,6 +1147,9 @@ func (cli *DockerCli) CmdImages(args ...string) error {
|
||||||
flViz := cmd.Bool([]string{"#v", "#viz", "#-viz"}, false, "Output graph in graphviz format")
|
flViz := cmd.Bool([]string{"#v", "#viz", "#-viz"}, false, "Output graph in graphviz format")
|
||||||
flTree := cmd.Bool([]string{"#t", "#tree", "#-tree"}, false, "Output graph in tree format")
|
flTree := cmd.Bool([]string{"#t", "#tree", "#-tree"}, false, "Output graph in tree format")
|
||||||
|
|
||||||
|
var flFilter opts.ListOpts
|
||||||
|
cmd.Var(&flFilter, []string{"f", "-filter"}, "Provide filter values (i.e. 'tagged=false')")
|
||||||
|
|
||||||
if err := cmd.Parse(args); err != nil {
|
if err := cmd.Parse(args); err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -1153,11 +1158,41 @@ func (cli *DockerCli) CmdImages(args ...string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
filter := cmd.Arg(0)
|
// Consolidate all filter flags, and sanity check them early.
|
||||||
|
// They'll get process in the daemon/server.
|
||||||
|
imageFilters := map[string]string{}
|
||||||
|
for _, f := range flFilter.GetAll() {
|
||||||
|
var err error
|
||||||
|
imageFilters, err = filters.ParseFlag(f, imageFilters)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
var (
|
||||||
|
untagged bool
|
||||||
|
)
|
||||||
|
for k,v := range imageFilters {
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
// seeing -all untagged images is redundant, and no point in seeing a visualization of that
|
||||||
|
/*
|
||||||
|
if *flUntagged && (*all || *flViz || *flTree) {
|
||||||
|
fmt.Fprintln(cli.err, "Notice: --untagged is not to be used with --all, --tree or --viz")
|
||||||
|
*flUntagged = false
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
matchName := cmd.Arg(0)
|
||||||
|
|
||||||
// FIXME: --viz and --tree are deprecated. Remove them in a future version.
|
// FIXME: --viz and --tree are deprecated. Remove them in a future version.
|
||||||
if *flViz || *flTree {
|
if *flViz || *flTree {
|
||||||
body, _, err := readBody(cli.call("GET", "/images/json?all=1", nil, false))
|
v := url.Values{
|
||||||
|
"all": []string{"1"},
|
||||||
|
"filters": []string{filters.ToParam(imageFilters)},
|
||||||
|
}
|
||||||
|
body, _, err := readBody(cli.call("GET", "/images/json?"+v.Encode(), nil, false))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -1187,13 +1222,13 @@ func (cli *DockerCli) CmdImages(args ...string) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if filter != "" {
|
if matchName != "" {
|
||||||
if filter == image.Get("Id") || filter == utils.TruncateID(image.Get("Id")) {
|
if matchName == image.Get("Id") || matchName == utils.TruncateID(image.Get("Id")) {
|
||||||
startImage = image
|
startImage = image
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, repotag := range image.GetList("RepoTags") {
|
for _, repotag := range image.GetList("RepoTags") {
|
||||||
if repotag == filter {
|
if repotag == matchName {
|
||||||
startImage = image
|
startImage = image
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1211,16 +1246,19 @@ func (cli *DockerCli) CmdImages(args ...string) error {
|
||||||
root := engine.NewTable("Created", 1)
|
root := engine.NewTable("Created", 1)
|
||||||
root.Add(startImage)
|
root.Add(startImage)
|
||||||
cli.WalkTree(*noTrunc, root, byParent, "", printNode)
|
cli.WalkTree(*noTrunc, root, byParent, "", printNode)
|
||||||
} else if filter == "" {
|
} else if matchName == "" {
|
||||||
cli.WalkTree(*noTrunc, roots, byParent, "", printNode)
|
cli.WalkTree(*noTrunc, roots, byParent, "", printNode)
|
||||||
}
|
}
|
||||||
if *flViz {
|
if *flViz {
|
||||||
fmt.Fprintf(cli.out, " base [style=invisible]\n}\n")
|
fmt.Fprintf(cli.out, " base [style=invisible]\n}\n")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
v := url.Values{}
|
v := url.Values{
|
||||||
|
"filters": []string{filters.ToParam(imageFilters)},
|
||||||
|
}
|
||||||
if cmd.NArg() == 1 {
|
if cmd.NArg() == 1 {
|
||||||
v.Set("filter", filter)
|
// FIXME rename this parameter, to not be confused with the filters flag
|
||||||
|
v.Set("filter", matchName)
|
||||||
}
|
}
|
||||||
if *all {
|
if *all {
|
||||||
v.Set("all", "1")
|
v.Set("all", "1")
|
||||||
|
|
|
@ -188,6 +188,8 @@ func getImagesJSON(eng *engine.Engine, version version.Version, w http.ResponseW
|
||||||
job = eng.Job("images")
|
job = eng.Job("images")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
job.Setenv("filters", r.Form.Get("filters"))
|
||||||
|
// FIXME rename this parameter, to not be confused with the filters flag
|
||||||
job.Setenv("filter", r.Form.Get("filter"))
|
job.Setenv("filter", r.Form.Get("filter"))
|
||||||
job.Setenv("all", r.Form.Get("all"))
|
job.Setenv("all", r.Form.Get("all"))
|
||||||
|
|
||||||
|
|
1499
docs/sources/reference/commandline/cli.rst
Normal file
1499
docs/sources/reference/commandline/cli.rst
Normal file
File diff suppressed because it is too large
Load diff
|
@ -55,6 +55,7 @@ import (
|
||||||
"github.com/dotcloud/docker/registry"
|
"github.com/dotcloud/docker/registry"
|
||||||
"github.com/dotcloud/docker/runconfig"
|
"github.com/dotcloud/docker/runconfig"
|
||||||
"github.com/dotcloud/docker/utils"
|
"github.com/dotcloud/docker/utils"
|
||||||
|
"github.com/dotcloud/docker/utils/filters"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (srv *Server) handlerWrap(h engine.Handler) engine.Handler {
|
func (srv *Server) handlerWrap(h engine.Handler) engine.Handler {
|
||||||
|
@ -694,10 +695,23 @@ func (srv *Server) ImagesViz(job *engine.Job) engine.Status {
|
||||||
|
|
||||||
func (srv *Server) Images(job *engine.Job) engine.Status {
|
func (srv *Server) Images(job *engine.Job) engine.Status {
|
||||||
var (
|
var (
|
||||||
allImages map[string]*image.Image
|
allImages map[string]*image.Image
|
||||||
err error
|
err error
|
||||||
|
filt_tagged = true
|
||||||
)
|
)
|
||||||
if job.GetenvBool("all") {
|
|
||||||
|
imageFilters, err := filters.ParseFlag(job.Getenv("filters"), nil)
|
||||||
|
if err != nil {
|
||||||
|
return job.Error(err)
|
||||||
|
}
|
||||||
|
if i, ok := imageFilters["untagged"]; ok && strings.ToLower(i) == "true" {
|
||||||
|
filt_tagged = false
|
||||||
|
}
|
||||||
|
if i, ok := imageFilters["tagged"]; ok && strings.ToLower(i) == "false" {
|
||||||
|
filt_tagged = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if job.GetenvBool("all") && !filt_tagged {
|
||||||
allImages, err = srv.daemon.Graph().Map()
|
allImages, err = srv.daemon.Graph().Map()
|
||||||
} else {
|
} else {
|
||||||
allImages, err = srv.daemon.Graph().Heads()
|
allImages, err = srv.daemon.Graph().Heads()
|
||||||
|
@ -721,17 +735,22 @@ func (srv *Server) Images(job *engine.Job) engine.Status {
|
||||||
}
|
}
|
||||||
|
|
||||||
if out, exists := lookup[id]; exists {
|
if out, exists := lookup[id]; exists {
|
||||||
out.SetList("RepoTags", append(out.GetList("RepoTags"), fmt.Sprintf("%s:%s", name, tag)))
|
if filt_tagged {
|
||||||
|
out.SetList("RepoTags", append(out.GetList("RepoTags"), fmt.Sprintf("%s:%s", name, tag)))
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
out := &engine.Env{}
|
// get the boolean list for if only the untagged images are requested
|
||||||
delete(allImages, id)
|
delete(allImages, id)
|
||||||
out.Set("ParentId", image.Parent)
|
if filt_tagged {
|
||||||
out.SetList("RepoTags", []string{fmt.Sprintf("%s:%s", name, tag)})
|
out := &engine.Env{}
|
||||||
out.Set("Id", image.ID)
|
out.Set("ParentId", image.Parent)
|
||||||
out.SetInt64("Created", image.Created.Unix())
|
out.SetList("RepoTags", []string{fmt.Sprintf("%s:%s", name, tag)})
|
||||||
out.SetInt64("Size", image.Size)
|
out.Set("Id", image.ID)
|
||||||
out.SetInt64("VirtualSize", image.GetParentsSize(0)+image.Size)
|
out.SetInt64("Created", image.Created.Unix())
|
||||||
lookup[id] = out
|
out.SetInt64("Size", image.Size)
|
||||||
|
out.SetInt64("VirtualSize", image.GetParentsSize(0)+image.Size)
|
||||||
|
lookup[id] = out
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
53
utils/filters/filter.go
Normal file
53
utils/filters/filter.go
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
package filters
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
var DefaultFilterProcs = FilterProcSet{}
|
||||||
|
|
||||||
|
func Register(name string, fp FilterProc) error {
|
||||||
|
return DefaultFilterProcs.Register(name, fp)
|
||||||
|
}
|
||||||
|
|
||||||
|
var ErrorFilterExists = errors.New("filter already exists and ")
|
||||||
|
var ErrorFilterExistsConflict = errors.New("filter already exists and FilterProc are different")
|
||||||
|
|
||||||
|
type FilterProcSet map[string]FilterProc
|
||||||
|
|
||||||
|
func (fs FilterProcSet) Process(context string) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs FilterProcSet) Register(name string, fp FilterProc) error {
|
||||||
|
if v, ok := fs[name]; ok {
|
||||||
|
if v == fp {
|
||||||
|
return ErrorFilterExists
|
||||||
|
} else {
|
||||||
|
return ErrorFilterExistsConflict
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fs[name] = fp
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type FilterProc interface {
|
||||||
|
Process(context, key, value string, output io.Writer) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type UnknownFilterProc struct{}
|
||||||
|
|
||||||
|
func (ufp UnknownFilterProc) Process(context, key, value string, output io.Writer) error {
|
||||||
|
if output != nil {
|
||||||
|
fmt.Fprintf(output, "do not know how to process [%s : %s]", key, value)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Filter interface {
|
||||||
|
Scope() string
|
||||||
|
Target() string
|
||||||
|
Expressions() []string
|
||||||
|
Match(interface{}) bool
|
||||||
|
}
|
47
utils/filters/parse.go
Normal file
47
utils/filters/parse.go
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
package filters
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Parse the argument to the filter flag. Like
|
||||||
|
|
||||||
|
`docker ps -f 'created=today;image.name=ubuntu*'`
|
||||||
|
|
||||||
|
Filters delimited by ';', and expected to be 'name=value'
|
||||||
|
|
||||||
|
If prev map is provided, then it is appended to, and returned. By default a new
|
||||||
|
map is created.
|
||||||
|
*/
|
||||||
|
func ParseFlag(arg string, prev map[string]string) (map[string]string, error) {
|
||||||
|
var filters map[string]string
|
||||||
|
if prev != nil {
|
||||||
|
filters = prev
|
||||||
|
} else {
|
||||||
|
filters = map[string]string{}
|
||||||
|
}
|
||||||
|
if len(arg) == 0 {
|
||||||
|
return filters, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, chunk := range strings.Split(arg, ";") {
|
||||||
|
if !strings.Contains(chunk, "=") {
|
||||||
|
return filters, ErrorBadFormat
|
||||||
|
}
|
||||||
|
f := strings.SplitN(chunk, "=", 2)
|
||||||
|
filters[f[0]] = f[1]
|
||||||
|
}
|
||||||
|
return filters, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var ErrorBadFormat = errors.New("bad format of filter (expected name=value)")
|
||||||
|
|
||||||
|
func ToParam(f map[string]string) string {
|
||||||
|
fs := []string{}
|
||||||
|
for k, v := range f {
|
||||||
|
fs = append(fs, k+"="+v)
|
||||||
|
}
|
||||||
|
return strings.Join(fs, ";")
|
||||||
|
}
|
Loading…
Reference in a new issue