diff --git a/commands.go b/commands.go index 95a6753b95..3593e2463c 100644 --- a/commands.go +++ b/commands.go @@ -608,85 +608,126 @@ func (srv *Server) CmdImages(stdin io.ReadCloser, stdout io.Writer, args ...stri //limit := cmd.Int("l", 0, "Only show the N most recent versions of each image") quiet := cmd.Bool("q", false, "only show numeric IDs") flAll := cmd.Bool("a", false, "show all images") + flViz := cmd.Bool("viz", false, "output graph in graphviz format") if err := cmd.Parse(args); err != nil { return nil } - if cmd.NArg() > 1 { - cmd.Usage() - return nil - } - var nameFilter string - if cmd.NArg() == 1 { - nameFilter = cmd.Arg(0) - } - w := tabwriter.NewWriter(stdout, 20, 1, 3, ' ', 0) - if !*quiet { - fmt.Fprintln(w, "REPOSITORY\tTAG\tID\tCREATED") - } - var allImages map[string]*Image - var err error - if *flAll { - allImages, err = srv.runtime.graph.Map() - } else { - allImages, err = srv.runtime.graph.Heads() - } - if err != nil { - return err - } - for name, repository := range srv.runtime.repositories.Repositories { - if nameFilter != "" && name != nameFilter { - continue + + if *flViz { + images, _ := srv.runtime.graph.All() + if images == nil { + return nil } - for tag, id := range repository { - image, err := srv.runtime.graph.Get(id) + + fmt.Fprintf(stdout, "digraph G {\n") + + var parentImage *Image + var err error + for _, image := range images { + parentImage, err = image.GetParent() if err != nil { - log.Printf("Warning: couldn't load %s from %s/%s: %s", id, name, tag, err) + fmt.Errorf("Error while getting parent image: %v", err) + return nil + } + if parentImage != nil { + fmt.Fprintf(stdout, " \"%s\" -> \"%s\"\n", parentImage.ShortId(), image.ShortId()) + } else { + fmt.Fprintf(stdout, " base -> \"%s\" [style=invis]\n", image.ShortId()) + } + } + + reporefs := make(map[string][]string) + + for name, repository := range srv.runtime.repositories.Repositories { + for tag, id := range repository { + reporefs[TruncateId(id)] = append(reporefs[TruncateId(id)], fmt.Sprintf("%s:%s", name, tag)) + } + } + + for id, repos := range reporefs { + fmt.Fprintf(stdout, " \"%s\" [label=\"%s\\n%s\",shape=box,fillcolor=\"paleturquoise\",style=\"filled,rounded\"];\n", id, id, strings.Join(repos, "\\n")) + } + + fmt.Fprintf(stdout, " base [style=invisible]\n") + fmt.Fprintf(stdout, "}\n") + } else { + if cmd.NArg() > 1 { + cmd.Usage() + return nil + } + var nameFilter string + if cmd.NArg() == 1 { + nameFilter = cmd.Arg(0) + } + w := tabwriter.NewWriter(stdout, 20, 1, 3, ' ', 0) + if !*quiet { + fmt.Fprintln(w, "REPOSITORY\tTAG\tID\tCREATED") + } + var allImages map[string]*Image + var err error + if *flAll { + allImages, err = srv.runtime.graph.Map() + } else { + allImages, err = srv.runtime.graph.Heads() + } + if err != nil { + return err + } + for name, repository := range srv.runtime.repositories.Repositories { + if nameFilter != "" && name != nameFilter { continue } - delete(allImages, id) - if !*quiet { - for idx, field := range []string{ - /* REPOSITORY */ name, - /* TAG */ tag, - /* ID */ TruncateId(id), - /* CREATED */ HumanDuration(time.Now().Sub(image.Created)) + " ago", - } { - if idx == 0 { - w.Write([]byte(field)) - } else { - w.Write([]byte("\t" + field)) - } + for tag, id := range repository { + image, err := srv.runtime.graph.Get(id) + if err != nil { + log.Printf("Warning: couldn't load %s from %s/%s: %s", id, name, tag, err) + continue + } + delete(allImages, id) + if !*quiet { + for idx, field := range []string{ + /* REPOSITORY */ name, + /* TAG */ tag, + /* ID */ TruncateId(id), + /* CREATED */ HumanDuration(time.Now().Sub(image.Created)) + " ago", + } { + if idx == 0 { + w.Write([]byte(field)) + } else { + w.Write([]byte("\t" + field)) + } + } + w.Write([]byte{'\n'}) + } else { + stdout.Write([]byte(image.ShortId() + "\n")) } - w.Write([]byte{'\n'}) - } else { - stdout.Write([]byte(image.ShortId() + "\n")) } } - } - // Display images which aren't part of a - if nameFilter == "" { - for id, image := range allImages { - if !*quiet { - for idx, field := range []string{ - /* REPOSITORY */ "", - /* TAG */ "", - /* ID */ TruncateId(id), - /* CREATED */ HumanDuration(time.Now().Sub(image.Created)) + " ago", - } { - if idx == 0 { - w.Write([]byte(field)) - } else { - w.Write([]byte("\t" + field)) + // Display images which aren't part of a + if nameFilter == "" { + for id, image := range allImages { + if !*quiet { + for idx, field := range []string{ + /* REPOSITORY */ "", + /* TAG */ "", + /* ID */ TruncateId(id), + /* CREATED */ HumanDuration(time.Now().Sub(image.Created)) + " ago", + } { + if idx == 0 { + w.Write([]byte(field)) + } else { + w.Write([]byte("\t" + field)) + } } + w.Write([]byte{'\n'}) + } else { + stdout.Write([]byte(image.ShortId() + "\n")) } - w.Write([]byte{'\n'}) - } else { - stdout.Write([]byte(image.ShortId() + "\n")) } } - } - if !*quiet { - w.Flush() + if !*quiet { + w.Flush() + } } return nil }