Merge pull request #1794 from justone/add-images-tree
add -tree option to images
This commit is contained in:
commit
807a305f36
13 changed files with 1697 additions and 81 deletions
18
api.go
18
api.go
|
@ -23,7 +23,7 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
APIVERSION = 1.6
|
||||
APIVERSION = 1.7
|
||||
DEFAULTHTTPHOST = "127.0.0.1"
|
||||
DEFAULTHTTPPORT = 4243
|
||||
DEFAULTUNIXSOCKET = "/var/run/docker.sock"
|
||||
|
@ -191,10 +191,24 @@ func getImagesJSON(srv *Server, version float64, w http.ResponseWriter, r *http.
|
|||
return err
|
||||
}
|
||||
|
||||
return writeJSON(w, http.StatusOK, outs)
|
||||
if version < 1.7 {
|
||||
outs2 := []APIImagesOld{}
|
||||
for _, ctnr := range outs {
|
||||
outs2 = append(outs2, ctnr.ToLegacy()...)
|
||||
}
|
||||
|
||||
return writeJSON(w, http.StatusOK, outs2)
|
||||
} else {
|
||||
return writeJSON(w, http.StatusOK, outs)
|
||||
}
|
||||
}
|
||||
|
||||
func getImagesViz(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if version > 1.6 {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return fmt.Errorf("This is now implemented in the client.")
|
||||
}
|
||||
|
||||
if err := srv.ImagesViz(w); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package docker
|
||||
|
||||
import "strings"
|
||||
|
||||
type APIHistory struct {
|
||||
ID string `json:"Id"`
|
||||
Tags []string `json:",omitempty"`
|
||||
|
@ -9,6 +11,15 @@ type APIHistory struct {
|
|||
}
|
||||
|
||||
type APIImages struct {
|
||||
ID string `json:"Id"`
|
||||
RepoTags []string `json:",omitempty"`
|
||||
Created int64
|
||||
Size int64
|
||||
VirtualSize int64
|
||||
ParentId string `json:",omitempty"`
|
||||
}
|
||||
|
||||
type APIImagesOld struct {
|
||||
Repository string `json:",omitempty"`
|
||||
Tag string `json:",omitempty"`
|
||||
ID string `json:"Id"`
|
||||
|
@ -17,6 +28,26 @@ type APIImages struct {
|
|||
VirtualSize int64
|
||||
}
|
||||
|
||||
func (self *APIImages) ToLegacy() []APIImagesOld {
|
||||
|
||||
outs := []APIImagesOld{}
|
||||
for _, repotag := range self.RepoTags {
|
||||
|
||||
components := strings.SplitN(repotag, ":", 2)
|
||||
|
||||
outs = append(outs, APIImagesOld{
|
||||
ID: self.ID,
|
||||
Repository: components[0],
|
||||
Tag: components[1],
|
||||
Created: self.Created,
|
||||
Size: self.Size,
|
||||
VirtualSize: self.VirtualSize,
|
||||
})
|
||||
}
|
||||
|
||||
return outs
|
||||
}
|
||||
|
||||
type APIInfo struct {
|
||||
Debug bool
|
||||
Containers int
|
||||
|
|
31
api_test.go
31
api_test.go
|
@ -184,7 +184,7 @@ func TestGetImagesJSON(t *testing.T) {
|
|||
|
||||
found := false
|
||||
for _, img := range images {
|
||||
if img.Repository == unitTestImageName {
|
||||
if strings.Contains(img.RepoTags[0], unitTestImageName) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
|
@ -275,31 +275,6 @@ func TestGetImagesJSON(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestGetImagesViz(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
r := httptest.NewRecorder()
|
||||
if err := getImagesViz(srv, APIVERSION, r, nil, nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if r.Code != http.StatusOK {
|
||||
t.Fatalf("%d OK expected, received %d\n", http.StatusOK, r.Code)
|
||||
}
|
||||
|
||||
reader := bufio.NewReader(r.Body)
|
||||
line, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if line != "digraph docker {\n" {
|
||||
t.Errorf("Expected digraph docker {\n, %s found", line)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetImagesHistory(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
@ -1226,7 +1201,7 @@ func TestDeleteImages(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(images) != len(initialImages)+1 {
|
||||
if len(images[0].RepoTags) != len(initialImages[0].RepoTags)+1 {
|
||||
t.Errorf("Expected %d images, %d found", len(initialImages)+1, len(images))
|
||||
}
|
||||
|
||||
|
@ -1265,7 +1240,7 @@ func TestDeleteImages(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(images) != len(initialImages) {
|
||||
if len(images[0].RepoTags) != len(initialImages[0].RepoTags) {
|
||||
t.Errorf("Expected %d image, %d found", len(initialImages), len(images))
|
||||
}
|
||||
|
||||
|
|
149
commands.go
149
commands.go
|
@ -1057,6 +1057,7 @@ func (cli *DockerCli) CmdImages(args ...string) error {
|
|||
all := cmd.Bool("a", false, "show all images")
|
||||
noTrunc := cmd.Bool("notrunc", false, "Don't truncate output")
|
||||
flViz := cmd.Bool("viz", false, "output graph in graphviz format")
|
||||
flTree := cmd.Bool("tree", false, "output graph in tree format")
|
||||
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
|
@ -1067,11 +1068,77 @@ func (cli *DockerCli) CmdImages(args ...string) error {
|
|||
}
|
||||
|
||||
if *flViz {
|
||||
body, _, err := cli.call("GET", "/images/viz", false)
|
||||
body, _, err := cli.call("GET", "/images/json?all=1", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(cli.out, "%s", body)
|
||||
|
||||
var outs []APIImages
|
||||
err = json.Unmarshal(body, &outs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprintf(cli.out, "digraph docker {\n")
|
||||
|
||||
for _, image := range outs {
|
||||
if image.ParentId == "" {
|
||||
fmt.Fprintf(cli.out, " base -> \"%s\" [style=invis]\n", utils.TruncateID(image.ID))
|
||||
} else {
|
||||
fmt.Fprintf(cli.out, " \"%s\" -> \"%s\"\n", utils.TruncateID(image.ParentId), utils.TruncateID(image.ID))
|
||||
}
|
||||
if image.RepoTags[0] != "<none>:<none>" {
|
||||
fmt.Fprintf(cli.out, " \"%s\" [label=\"%s\\n%s\",shape=box,fillcolor=\"paleturquoise\",style=\"filled,rounded\"];\n", utils.TruncateID(image.ID), utils.TruncateID(image.ID), strings.Join(image.RepoTags, "\\n"))
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintf(cli.out, " base [style=invisible]\n}\n")
|
||||
} else if *flTree {
|
||||
body, _, err := cli.call("GET", "/images/json?all=1", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var outs []APIImages
|
||||
err = json.Unmarshal(body, &outs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var startImageArg = cmd.Arg(0)
|
||||
var startImage APIImages
|
||||
|
||||
var roots []APIImages
|
||||
var byParent = make(map[string][]APIImages)
|
||||
for _, image := range outs {
|
||||
if image.ParentId == "" {
|
||||
roots = append(roots, image)
|
||||
} else {
|
||||
if children, exists := byParent[image.ParentId]; exists {
|
||||
byParent[image.ParentId] = append(children, image)
|
||||
} else {
|
||||
byParent[image.ParentId] = []APIImages{image}
|
||||
}
|
||||
}
|
||||
|
||||
if startImageArg != "" {
|
||||
if startImageArg == image.ID || startImageArg == utils.TruncateID(image.ID) {
|
||||
startImage = image
|
||||
}
|
||||
|
||||
for _, repotag := range image.RepoTags {
|
||||
if repotag == startImageArg {
|
||||
startImage = image
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if startImageArg != "" {
|
||||
WalkTree(cli, noTrunc, []APIImages{startImage}, byParent, "")
|
||||
} else {
|
||||
WalkTree(cli, noTrunc, roots, byParent, "")
|
||||
}
|
||||
} else {
|
||||
v := url.Values{}
|
||||
if cmd.NArg() == 1 {
|
||||
|
@ -1097,27 +1164,29 @@ func (cli *DockerCli) CmdImages(args ...string) error {
|
|||
fmt.Fprintln(w, "REPOSITORY\tTAG\tIMAGE ID\tCREATED\tSIZE")
|
||||
}
|
||||
|
||||
var repo string
|
||||
var tag string
|
||||
for _, out := range outs {
|
||||
if out.Repository == "" {
|
||||
out.Repository = "<none>"
|
||||
}
|
||||
if out.Tag == "" {
|
||||
out.Tag = "<none>"
|
||||
}
|
||||
for _, repotag := range out.RepoTags {
|
||||
|
||||
if !*noTrunc {
|
||||
out.ID = utils.TruncateID(out.ID)
|
||||
}
|
||||
components := strings.SplitN(repotag, ":", 2)
|
||||
repo = components[0]
|
||||
tag = components[1]
|
||||
|
||||
if !*quiet {
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t", out.Repository, out.Tag, out.ID, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))))
|
||||
if out.VirtualSize > 0 {
|
||||
fmt.Fprintf(w, "%s (virtual %s)\n", utils.HumanSize(out.Size), utils.HumanSize(out.VirtualSize))
|
||||
} else {
|
||||
fmt.Fprintf(w, "%s\n", utils.HumanSize(out.Size))
|
||||
if !*noTrunc {
|
||||
out.ID = utils.TruncateID(out.ID)
|
||||
}
|
||||
|
||||
if !*quiet {
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t", repo, tag, out.ID, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))))
|
||||
if out.VirtualSize > 0 {
|
||||
fmt.Fprintf(w, "%s (virtual %s)\n", utils.HumanSize(out.Size), utils.HumanSize(out.VirtualSize))
|
||||
} else {
|
||||
fmt.Fprintf(w, "%s\n", utils.HumanSize(out.Size))
|
||||
}
|
||||
} else {
|
||||
fmt.Fprintln(w, out.ID)
|
||||
}
|
||||
} else {
|
||||
fmt.Fprintln(w, out.ID)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1128,6 +1197,48 @@ func (cli *DockerCli) CmdImages(args ...string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func WalkTree(cli *DockerCli, noTrunc *bool, images []APIImages, byParent map[string][]APIImages, prefix string) {
|
||||
if len(images) > 1 {
|
||||
length := len(images)
|
||||
for index, image := range images {
|
||||
if index+1 == length {
|
||||
PrintTreeNode(cli, noTrunc, image, prefix+"└─")
|
||||
if subimages, exists := byParent[image.ID]; exists {
|
||||
WalkTree(cli, noTrunc, subimages, byParent, prefix+" ")
|
||||
}
|
||||
} else {
|
||||
PrintTreeNode(cli, noTrunc, image, prefix+"|─")
|
||||
if subimages, exists := byParent[image.ID]; exists {
|
||||
WalkTree(cli, noTrunc, subimages, byParent, prefix+"| ")
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, image := range images {
|
||||
PrintTreeNode(cli, noTrunc, image, prefix+"└─")
|
||||
if subimages, exists := byParent[image.ID]; exists {
|
||||
WalkTree(cli, noTrunc, subimages, byParent, prefix+" ")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func PrintTreeNode(cli *DockerCli, noTrunc *bool, image APIImages, prefix string) {
|
||||
var imageID string
|
||||
if *noTrunc {
|
||||
imageID = image.ID
|
||||
} else {
|
||||
imageID = utils.TruncateID(image.ID)
|
||||
}
|
||||
|
||||
fmt.Fprintf(cli.out, "%s%s Size: %s (virtual %s)", prefix, imageID, utils.HumanSize(image.Size), utils.HumanSize(image.VirtualSize))
|
||||
if image.RepoTags[0] != "<none>:<none>" {
|
||||
fmt.Fprintf(cli.out, " Tags: %s\n", strings.Join(image.RepoTags, ","))
|
||||
} else {
|
||||
fmt.Fprint(cli.out, "\n")
|
||||
}
|
||||
}
|
||||
|
||||
func displayablePorts(ports []APIPort) string {
|
||||
result := []string{}
|
||||
for _, port := range ports {
|
||||
|
|
126
commands_test.go
126
commands_test.go
|
@ -6,6 +6,7 @@ import (
|
|||
"github.com/dotcloud/docker/utils"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
@ -699,3 +700,128 @@ func TestRunErrorBindNonExistingSource(t *testing.T) {
|
|||
<-c
|
||||
})
|
||||
}
|
||||
|
||||
func TestImagesViz(t *testing.T) {
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
|
||||
cli := NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalRuntime)
|
||||
|
||||
srv := &Server{runtime: globalRuntime}
|
||||
image := buildTestImages(t, srv)
|
||||
|
||||
c := make(chan struct{})
|
||||
go func() {
|
||||
defer close(c)
|
||||
if err := cli.CmdImages("-viz"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
stdoutPipe.Close()
|
||||
}()
|
||||
|
||||
setTimeout(t, "Reading command output time out", 2*time.Second, func() {
|
||||
cmdOutputBytes, err := ioutil.ReadAll(bufio.NewReader(stdout))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cmdOutput := string(cmdOutputBytes)
|
||||
|
||||
regexpStrings := []string{
|
||||
"digraph docker {",
|
||||
fmt.Sprintf("base -> \"%s\" \\[style=invis]", unitTestImageIDShort),
|
||||
fmt.Sprintf("label=\"%s\\\\n%s:latest\"", unitTestImageIDShort, unitTestImageName),
|
||||
fmt.Sprintf("label=\"%s\\\\n%s:%s\"", utils.TruncateID(image.ID), "test", "latest"),
|
||||
"base \\[style=invisible]",
|
||||
}
|
||||
|
||||
compiledRegexps := []*regexp.Regexp{}
|
||||
for _, regexpString := range regexpStrings {
|
||||
regexp, err := regexp.Compile(regexpString)
|
||||
if err != nil {
|
||||
fmt.Println("Error in regex string: ", err)
|
||||
return
|
||||
}
|
||||
compiledRegexps = append(compiledRegexps, regexp)
|
||||
}
|
||||
|
||||
for _, regexp := range compiledRegexps {
|
||||
if !regexp.MatchString(cmdOutput) {
|
||||
t.Fatalf("images -viz content '%s' did not match regexp '%s'", cmdOutput, regexp)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestImagesTree(t *testing.T) {
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
|
||||
cli := NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalRuntime)
|
||||
|
||||
srv := &Server{runtime: globalRuntime}
|
||||
image := buildTestImages(t, srv)
|
||||
|
||||
c := make(chan struct{})
|
||||
go func() {
|
||||
defer close(c)
|
||||
if err := cli.CmdImages("-tree"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
stdoutPipe.Close()
|
||||
}()
|
||||
|
||||
setTimeout(t, "Reading command output time out", 2*time.Second, func() {
|
||||
cmdOutputBytes, err := ioutil.ReadAll(bufio.NewReader(stdout))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cmdOutput := string(cmdOutputBytes)
|
||||
|
||||
regexpStrings := []string{
|
||||
fmt.Sprintf("└─%s Size: (\\d+.\\d+ MB) \\(virtual \\d+.\\d+ MB\\) Tags: %s:latest", unitTestImageIDShort, unitTestImageName),
|
||||
"(?m)^ └─[0-9a-f]+",
|
||||
"(?m)^ └─[0-9a-f]+",
|
||||
"(?m)^ └─[0-9a-f]+",
|
||||
fmt.Sprintf(" └─%s Size: \\d+ B \\(virtual \\d+.\\d+ MB\\) Tags: test:latest", utils.TruncateID(image.ID)),
|
||||
}
|
||||
|
||||
compiledRegexps := []*regexp.Regexp{}
|
||||
for _, regexpString := range regexpStrings {
|
||||
regexp, err := regexp.Compile(regexpString)
|
||||
if err != nil {
|
||||
fmt.Println("Error in regex string: ", err)
|
||||
return
|
||||
}
|
||||
compiledRegexps = append(compiledRegexps, regexp)
|
||||
}
|
||||
|
||||
for _, regexp := range compiledRegexps {
|
||||
if !regexp.MatchString(cmdOutput) {
|
||||
t.Fatalf("images -tree content '%s' did not match regexp '%s'", cmdOutput, regexp)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func buildTestImages(t *testing.T, srv *Server) *Image {
|
||||
|
||||
var testBuilder = testContextTemplate{
|
||||
`
|
||||
from {IMAGE}
|
||||
run sh -c 'echo root:testpass > /tmp/passwd'
|
||||
run mkdir -p /var/run/sshd
|
||||
run [ "$(cat /tmp/passwd)" = "root:testpass" ]
|
||||
run [ "$(ls -d /var/run/sshd)" = "/var/run/sshd" ]
|
||||
`,
|
||||
nil,
|
||||
nil,
|
||||
}
|
||||
image := buildImage(testBuilder, t, srv, true)
|
||||
|
||||
err := srv.ContainerTag(image.ID, "test", "latest", false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return image
|
||||
}
|
||||
|
|
|
@ -26,14 +26,118 @@ Docker Remote API
|
|||
2. Versions
|
||||
===========
|
||||
|
||||
The current version of the API is 1.6
|
||||
The current version of the API is 1.7
|
||||
|
||||
Calling /images/<name>/insert is the same as calling
|
||||
/v1.6/images/<name>/insert
|
||||
/v1.7/images/<name>/insert
|
||||
|
||||
You can still call an old version of the api using
|
||||
/v1.0/images/<name>/insert
|
||||
|
||||
v1.7
|
||||
****
|
||||
|
||||
Full Documentation
|
||||
------------------
|
||||
|
||||
:doc:`docker_remote_api_v1.7`
|
||||
|
||||
What's new
|
||||
----------
|
||||
|
||||
.. http:get:: /images/json
|
||||
|
||||
The format of the json returned from this uri changed. Instead of an entry
|
||||
for each repo/tag on an image, each image is only represented once, with a
|
||||
nested attribute indicating the repo/tags that apply to that image.
|
||||
|
||||
Instead of:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
|
||||
[
|
||||
{
|
||||
"VirtualSize": 131506275,
|
||||
"Size": 131506275,
|
||||
"Created": 1365714795,
|
||||
"Id": "8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c",
|
||||
"Tag": "12.04",
|
||||
"Repository": "ubuntu"
|
||||
},
|
||||
{
|
||||
"VirtualSize": 131506275,
|
||||
"Size": 131506275,
|
||||
"Created": 1365714795,
|
||||
"Id": "8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c",
|
||||
"Tag": "latest",
|
||||
"Repository": "ubuntu"
|
||||
},
|
||||
{
|
||||
"VirtualSize": 131506275,
|
||||
"Size": 131506275,
|
||||
"Created": 1365714795,
|
||||
"Id": "8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c",
|
||||
"Tag": "precise",
|
||||
"Repository": "ubuntu"
|
||||
},
|
||||
{
|
||||
"VirtualSize": 180116135,
|
||||
"Size": 24653,
|
||||
"Created": 1364102658,
|
||||
"Id": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc",
|
||||
"Tag": "12.10",
|
||||
"Repository": "ubuntu"
|
||||
},
|
||||
{
|
||||
"VirtualSize": 180116135,
|
||||
"Size": 24653,
|
||||
"Created": 1364102658,
|
||||
"Id": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc",
|
||||
"Tag": "quantal",
|
||||
"Repository": "ubuntu"
|
||||
}
|
||||
]
|
||||
|
||||
The returned json looks like this:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
|
||||
[
|
||||
{
|
||||
"RepoTag": [
|
||||
"ubuntu:12.04",
|
||||
"ubuntu:precise",
|
||||
"ubuntu:latest"
|
||||
],
|
||||
"Id": "8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c",
|
||||
"Created": 1365714795,
|
||||
"Size": 131506275,
|
||||
"VirtualSize": 131506275
|
||||
},
|
||||
{
|
||||
"RepoTag": [
|
||||
"ubuntu:12.10",
|
||||
"ubuntu:quantal"
|
||||
],
|
||||
"ParentId": "27cf784147099545",
|
||||
"Id": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc",
|
||||
"Created": 1364102658,
|
||||
"Size": 24653,
|
||||
"VirtualSize": 180116135
|
||||
}
|
||||
]
|
||||
|
||||
.. http:get:: /images/viz
|
||||
|
||||
This URI no longer exists. The ``images -viz`` output is now generated in
|
||||
the client, using the ``/images/json`` data.
|
||||
|
||||
v1.6
|
||||
****
|
||||
|
||||
|
|
1185
docs/sources/api/docker_remote_api_v1.7.rst
Normal file
1185
docs/sources/api/docker_remote_api_v1.7.rst
Normal file
File diff suppressed because it is too large
Load diff
|
@ -315,8 +315,10 @@ Shell 1: (Again .. now showing events)
|
|||
List images
|
||||
|
||||
-a=false: show all images
|
||||
-notrunc=false: Don't truncate output
|
||||
-q=false: only show numeric IDs
|
||||
-viz=false: output in graphviz format
|
||||
-tree=false: output graph in tree format
|
||||
-viz=false: output graph in graphviz format
|
||||
|
||||
Displaying images visually
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
@ -328,6 +330,36 @@ Displaying images visually
|
|||
.. image:: docker_images.gif
|
||||
:alt: Example inheritance graph of Docker images.
|
||||
|
||||
|
||||
Displaying image hierarchy
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
::
|
||||
|
||||
sudo docker images -tree
|
||||
|
||||
|─8dbd9e392a96 Size: 131.5 MB (virtual 131.5 MB) Tags: ubuntu:12.04,ubuntu:latest,ubuntu:precise
|
||||
└─27cf78414709 Size: 180.1 MB (virtual 180.1 MB)
|
||||
└─b750fe79269d Size: 24.65 kB (virtual 180.1 MB) Tags: ubuntu:12.10,ubuntu:quantal
|
||||
|─f98de3b610d5 Size: 12.29 kB (virtual 180.1 MB)
|
||||
| └─7da80deb7dbf Size: 16.38 kB (virtual 180.1 MB)
|
||||
| └─65ed2fee0a34 Size: 20.66 kB (virtual 180.2 MB)
|
||||
| └─a2b9ea53dddc Size: 819.7 MB (virtual 999.8 MB)
|
||||
| └─a29b932eaba8 Size: 28.67 kB (virtual 999.9 MB)
|
||||
| └─e270a44f124d Size: 12.29 kB (virtual 999.9 MB) Tags: progrium/buildstep:latest
|
||||
└─17e74ac162d8 Size: 53.93 kB (virtual 180.2 MB)
|
||||
└─339a3f56b760 Size: 24.65 kB (virtual 180.2 MB)
|
||||
└─904fcc40e34d Size: 96.7 MB (virtual 276.9 MB)
|
||||
└─b1b0235328dd Size: 363.3 MB (virtual 640.2 MB)
|
||||
└─7cb05d1acb3b Size: 20.48 kB (virtual 640.2 MB)
|
||||
└─47bf6f34832d Size: 20.48 kB (virtual 640.2 MB)
|
||||
└─f165104e82ed Size: 12.29 kB (virtual 640.2 MB)
|
||||
└─d9cf85a47b7e Size: 1.911 MB (virtual 642.2 MB)
|
||||
└─3ee562df86ca Size: 17.07 kB (virtual 642.2 MB)
|
||||
└─b05fc2d00e4a Size: 24.96 kB (virtual 642.2 MB)
|
||||
└─c96a99614930 Size: 12.29 kB (virtual 642.2 MB)
|
||||
└─a6a357a48c49 Size: 12.29 kB (virtual 642.2 MB) Tags: ndj/mongodb:latest
|
||||
|
||||
.. _cli_import:
|
||||
|
||||
``import``
|
||||
|
|
|
@ -22,6 +22,7 @@ import (
|
|||
const (
|
||||
unitTestImageName = "docker-test-image"
|
||||
unitTestImageID = "83599e29c455eb719f77d799bc7c51521b9551972f5a850d7ad265bc1b5292f6" // 1.0
|
||||
unitTestImageIDShort = "83599e29c455"
|
||||
unitTestNetworkBridge = "testdockbr0"
|
||||
unitTestStoreBase = "/var/lib/docker/unit-tests"
|
||||
testDaemonAddr = "127.0.0.1:4270"
|
||||
|
|
43
server.go
43
server.go
|
@ -247,7 +247,7 @@ func (srv *Server) ImagesViz(out io.Writer) error {
|
|||
for _, image := range images {
|
||||
parentImage, err = image.GetParent()
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("Error while getting parent image: %v", err)
|
||||
}
|
||||
if parentImage != nil {
|
||||
out.Write([]byte(" \"" + parentImage.ShortID() + "\" -> \"" + image.ShortID() + "\"\n"))
|
||||
|
@ -284,7 +284,7 @@ func (srv *Server) Images(all bool, filter string) ([]APIImages, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
outs := []APIImages{} //produce [] when empty instead of 'null'
|
||||
lookup := make(map[string]APIImages)
|
||||
for name, repository := range srv.runtime.repositories.Repositories {
|
||||
if filter != "" {
|
||||
if match, _ := path.Match(filter, name); !match {
|
||||
|
@ -292,27 +292,46 @@ func (srv *Server) Images(all bool, filter string) ([]APIImages, error) {
|
|||
}
|
||||
}
|
||||
for tag, id := range repository {
|
||||
var out APIImages
|
||||
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)
|
||||
out.Repository = name
|
||||
out.Tag = tag
|
||||
out.ID = image.ID
|
||||
out.Created = image.Created.Unix()
|
||||
out.Size = image.Size
|
||||
out.VirtualSize = image.getParentsSize(0) + image.Size
|
||||
outs = append(outs, out)
|
||||
|
||||
if out, exists := lookup[id]; exists {
|
||||
out.RepoTags = append(out.RepoTags, fmt.Sprintf("%s:%s", name, tag))
|
||||
|
||||
lookup[id] = out
|
||||
} else {
|
||||
var out APIImages
|
||||
|
||||
delete(allImages, id)
|
||||
|
||||
out.ParentId = image.Parent
|
||||
out.RepoTags = []string{fmt.Sprintf("%s:%s", name, tag)}
|
||||
out.ID = image.ID
|
||||
out.Created = image.Created.Unix()
|
||||
out.Size = image.Size
|
||||
out.VirtualSize = image.getParentsSize(0) + image.Size
|
||||
|
||||
lookup[id] = out
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
// Display images which aren't part of a
|
||||
|
||||
outs := make([]APIImages, 0, len(lookup))
|
||||
for _, value := range lookup {
|
||||
outs = append(outs, value)
|
||||
}
|
||||
|
||||
// Display images which aren't part of a repository/tag
|
||||
if filter == "" {
|
||||
for _, image := range allImages {
|
||||
var out APIImages
|
||||
out.ID = image.ID
|
||||
out.ParentId = image.Parent
|
||||
out.RepoTags = []string{"<none>:<none>"}
|
||||
out.Created = image.Created.Unix()
|
||||
out.Size = image.Size
|
||||
out.VirtualSize = image.getParentsSize(0) + image.Size
|
||||
|
|
|
@ -34,8 +34,8 @@ func TestContainerTagImageDelete(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(images) != len(initialImages)+3 {
|
||||
t.Errorf("Expected %d images, %d found", len(initialImages)+2, len(images))
|
||||
if len(images[0].RepoTags) != len(initialImages[0].RepoTags)+3 {
|
||||
t.Errorf("Expected %d images, %d found", len(initialImages)+3, len(images))
|
||||
}
|
||||
|
||||
if _, err := srv.ImageDelete("utest/docker:tag2", true); err != nil {
|
||||
|
@ -47,7 +47,7 @@ func TestContainerTagImageDelete(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(images) != len(initialImages)+2 {
|
||||
if len(images[0].RepoTags) != len(initialImages[0].RepoTags)+2 {
|
||||
t.Errorf("Expected %d images, %d found", len(initialImages)+2, len(images))
|
||||
}
|
||||
|
||||
|
@ -60,7 +60,7 @@ func TestContainerTagImageDelete(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(images) != len(initialImages)+1 {
|
||||
if len(images[0].RepoTags) != len(initialImages[0].RepoTags)+1 {
|
||||
t.Errorf("Expected %d images, %d found", len(initialImages)+1, len(images))
|
||||
}
|
||||
|
||||
|
@ -462,7 +462,7 @@ func TestRmi(t *testing.T) {
|
|||
if strings.Contains(unitTestImageID, image.ID) {
|
||||
continue
|
||||
}
|
||||
if image.Repository == "" {
|
||||
if image.RepoTags[0] == "<none>:<none>" {
|
||||
t.Fatalf("Expected tagged image, got untagged one.")
|
||||
}
|
||||
}
|
||||
|
@ -490,7 +490,7 @@ func TestImagesFilter(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(images) != 2 {
|
||||
if len(images[0].RepoTags) != 2 {
|
||||
t.Fatal("incorrect number of matches returned")
|
||||
}
|
||||
|
||||
|
@ -499,7 +499,7 @@ func TestImagesFilter(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(images) != 1 {
|
||||
if len(images[0].RepoTags) != 1 {
|
||||
t.Fatal("incorrect number of matches returned")
|
||||
}
|
||||
|
||||
|
@ -508,7 +508,7 @@ func TestImagesFilter(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(images) != 1 {
|
||||
if len(images[0].RepoTags) != 1 {
|
||||
t.Fatal("incorrect number of matches returned")
|
||||
}
|
||||
|
||||
|
@ -517,7 +517,7 @@ func TestImagesFilter(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(images) != 1 {
|
||||
if len(images[0].RepoTags) != 1 {
|
||||
t.Fatal("incorrect number of matches returned")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ func (s *imageSorter) Less(i, j int) bool {
|
|||
// Sort []ApiImages by most recent creation date and tag name.
|
||||
func sortImagesByCreationAndTag(images []APIImages) {
|
||||
creationAndTag := func(i1, i2 *APIImages) bool {
|
||||
return i1.Created > i2.Created || (i1.Created == i2.Created && i2.Tag > i1.Tag)
|
||||
return i1.Created > i2.Created
|
||||
}
|
||||
|
||||
sorter := &imageSorter{
|
||||
|
|
|
@ -3,6 +3,7 @@ package docker
|
|||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestServerListOrderedImagesByCreationDate(t *testing.T) {
|
||||
|
@ -34,29 +35,46 @@ func TestServerListOrderedImagesByCreationDateAndTag(t *testing.T) {
|
|||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
archive, err := fakeTar()
|
||||
err := generateImage("bar", runtime)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
image, err := runtime.graph.Create(archive, nil, "Testing", "", nil)
|
||||
|
||||
time.Sleep(time.Second)
|
||||
|
||||
err = generateImage("zed", runtime)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
srv.ContainerTag(image.ID, "repo", "foo", false)
|
||||
srv.ContainerTag(image.ID, "repo", "bar", false)
|
||||
|
||||
images, err := srv.Images(true, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if images[0].Created != images[1].Created || images[0].Tag >= images[1].Tag {
|
||||
t.Error("Expected []APIImges to be ordered by most recent creation date and tag name.")
|
||||
if images[0].RepoTags[0] != "repo:zed" && images[0].RepoTags[0] != "repo:bar" {
|
||||
t.Errorf("Expected []APIImges to be ordered by most recent creation date. %s", images)
|
||||
}
|
||||
}
|
||||
|
||||
func generateImage(name string, runtime *Runtime) error {
|
||||
|
||||
archive, err := fakeTar()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
image, err := runtime.graph.Create(archive, nil, "Testing", "", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
srv.ContainerTag(image.ID, "repo", name, false)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestSortUniquePorts(t *testing.T) {
|
||||
ports := []Port{
|
||||
Port("6379/tcp"),
|
||||
|
|
Loading…
Add table
Reference in a new issue