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/engine"
"github.com/dotcloud/docker/nat"
"github.com/dotcloud/docker/opts"
"github.com/dotcloud/docker/pkg/signal"
"github.com/dotcloud/docker/pkg/term"
"github.com/dotcloud/docker/pkg/units"
"github.com/dotcloud/docker/registry"
"github.com/dotcloud/docker/runconfig"
"github.com/dotcloud/docker/utils"
"github.com/dotcloud/docker/utils/filters"
)
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")
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 {
return nil
}
@ -1153,11 +1158,41 @@ func (cli *DockerCli) CmdImages(args ...string) error {
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.
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 {
return err
}
@ -1187,13 +1222,13 @@ func (cli *DockerCli) CmdImages(args ...string) error {
}
}
if filter != "" {
if filter == image.Get("Id") || filter == utils.TruncateID(image.Get("Id")) {
if matchName != "" {
if matchName == image.Get("Id") || matchName == utils.TruncateID(image.Get("Id")) {
startImage = image
}
for _, repotag := range image.GetList("RepoTags") {
if repotag == filter {
if repotag == matchName {
startImage = image
}
}
@ -1211,16 +1246,19 @@ func (cli *DockerCli) CmdImages(args ...string) error {
root := engine.NewTable("Created", 1)
root.Add(startImage)
cli.WalkTree(*noTrunc, root, byParent, "", printNode)
} else if filter == "" {
} else if matchName == "" {
cli.WalkTree(*noTrunc, roots, byParent, "", printNode)
}
if *flViz {
fmt.Fprintf(cli.out, " base [style=invisible]\n}\n")
}
} else {
v := url.Values{}
v := url.Values{
"filters": []string{filters.ToParam(imageFilters)},
}
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 {
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.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("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/runconfig"
"github.com/dotcloud/docker/utils"
"github.com/dotcloud/docker/utils/filters"
)
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 {
var (
allImages map[string]*image.Image
err error
allImages map[string]*image.Image
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()
} else {
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 {
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 {
out := &engine.Env{}
// get the boolean list for if only the untagged images are requested
delete(allImages, id)
out.Set("ParentId", image.Parent)
out.SetList("RepoTags", []string{fmt.Sprintf("%s:%s", name, tag)})
out.Set("Id", image.ID)
out.SetInt64("Created", image.Created.Unix())
out.SetInt64("Size", image.Size)
out.SetInt64("VirtualSize", image.GetParentsSize(0)+image.Size)
lookup[id] = out
if filt_tagged {
out := &engine.Env{}
out.Set("ParentId", image.Parent)
out.SetList("RepoTags", []string{fmt.Sprintf("%s:%s", name, tag)})
out.Set("Id", image.ID)
out.SetInt64("Created", image.Created.Unix())
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, ";")
}