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:
Vincent Batts 2014-02-26 17:04:11 -05:00
parent afb2d5de3d
commit 5f3812ec97
6 changed files with 1678 additions and 20 deletions

View File

@ -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")

View File

@ -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"))

File diff suppressed because it is too large Load Diff

View File

@ -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
View 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
View 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, ";")
}