func TestGetImagesJSONLegacyFormat(t *testing.T) { eng := engine.New() var called bool eng.Register("images", func(job *engine.Job) error { called = true outsLegacy := engine.NewTable("Created", 0) outsLegacy.Add(createEnvFromGetImagesJSONStruct(sampleImage)) if _, err := outsLegacy.WriteListTo(job.Stdout); err != nil { return err } return nil }) r := serveRequestUsingVersion("GET", "/images/json", "1.6", nil, eng, t) if !called { t.Fatal("handler was not called") } assertHttpNotError(r, t) assertContentType(r, "application/json", t) images := engine.NewTable("Created", 0) if _, err := images.ReadListFrom(r.Body.Bytes()); err != nil { t.Fatal(err) } if images.Len() != 1 { t.Fatalf("Expected 1 image, %d found", images.Len()) } image := images.Data[0] if image.Get("Tag") != "test-tag" { t.Errorf("Expected tag 'test-tag', found '%s'", image.Get("Tag")) } if image.Get("Repository") != "test-name" { t.Errorf("Expected repository 'test-name', found '%s'", image.Get("Repository")) } }
func (daemon *Daemon) ContainerChanges(job *engine.Job) error { if n := len(job.Args); n != 1 { return fmt.Errorf("Usage: %s CONTAINER", job.Name) } name := job.Args[0] container, err := daemon.Get(name) if err != nil { return err } outs := engine.NewTable("", 0) changes, err := container.Changes() if err != nil { return err } for _, change := range changes { out := &engine.Env{} if err := out.Import(change); err != nil { return err } outs.Add(out) } if _, err := outs.WriteListTo(job.Stdout); err != nil { return err } return nil }
// Search queries the public registry for images matching the specified // search terms, and returns the results. // // Argument syntax: search TERM // // Option environment: // 'authConfig': json-encoded credentials to authenticate against the registry. // The search extends to images only accessible via the credentials. // // 'metaHeaders': extra HTTP headers to include in the request to the registry. // The headers should be passed as a json-encoded dictionary. // // Output: // Results are sent as a collection of structured messages (using engine.Table). // Each result is sent as a separate message. // Results are ordered by number of stars on the public registry. func (s *Service) Search(job *engine.Job) engine.Status { if n := len(job.Args); n != 1 { return job.Errorf("Usage: %s TERM", job.Name) } var ( term = job.Args[0] metaHeaders = map[string][]string{} authConfig = &AuthConfig{} ) job.GetenvJson("authConfig", authConfig) job.GetenvJson("metaHeaders", metaHeaders) r, err := NewRegistry(authConfig, HTTPRequestFactory(metaHeaders), IndexServerAddress(), true) if err != nil { return job.Error(err) } results, err := r.SearchRepositories(term) if err != nil { return job.Error(err) } outs := engine.NewTable("star_count", 0) for _, result := range results.Results { out := &engine.Env{} out.Import(result) outs.Add(out) } outs.ReverseSort() if _, err := outs.WriteListTo(job.Stdout); err != nil { return job.Error(err) } return engine.StatusOK }
func (srv *Server) ContainerChanges(job *engine.Job) engine.Status { if n := len(job.Args); n != 1 { return job.Errorf("Usage: %s CONTAINER", job.Name) } name := job.Args[0] if container := srv.daemon.Get(name); container != nil { outs := engine.NewTable("", 0) changes, err := container.Changes() if err != nil { return job.Error(err) } for _, change := range changes { out := &engine.Env{} if err := out.Import(change); err != nil { return job.Error(err) } outs.Add(out) } if _, err := outs.WriteListTo(job.Stdout); err != nil { return job.Error(err) } } else { return job.Errorf("No such container: %s", name) } return engine.StatusOK }
// CmdDiff shows changes on a container's filesystem. // // Each changed file is printed on a separate line, prefixed with a single character that indicates the status of the file: C (modified), A (added), or D (deleted). // // Usage: docker diff CONTAINER func (cli *DockerCli) CmdDiff(args ...string) error { cmd := cli.Subcmd("diff", "CONTAINER", "Inspect changes on a container's filesystem", true) cmd.Require(flag.Exact, 1) utils.ParseFlags(cmd, args, true) body, _, err := readBody(cli.call("GET", "/containers/"+cmd.Arg(0)+"/changes", nil, nil)) if err != nil { return err } outs := engine.NewTable("", 0) if _, err := outs.ReadListFrom(body); err != nil { return err } for _, change := range outs.Data { var kind string switch change.GetInt("Kind") { case archive.ChangeModify: kind = "C" case archive.ChangeAdd: kind = "A" case archive.ChangeDelete: kind = "D" } fmt.Fprintf(cli.out, "%s %s\n", kind, change.Get("Path")) } return nil }
func TestDeleteImages(t *testing.T) { eng := NewTestEngine(t) //we expect errors, so we disable stderr eng.Stderr = ioutil.Discard defer mkDaemonFromEngine(eng, t).Nuke() initialImages := getImages(eng, t, true, "") if err := eng.Job("tag", unitTestImageName, "test", "test").Run(); err != nil { t.Fatal(err) } images := getImages(eng, t, true, "") if len(images.Data[0].GetList("RepoTags")) != len(initialImages.Data[0].GetList("RepoTags"))+1 { t.Errorf("Expected %d images, %d found", len(initialImages.Data[0].GetList("RepoTags"))+1, len(images.Data[0].GetList("RepoTags"))) } req, err := http.NewRequest("DELETE", "/images/"+unitTestImageID, nil) if err != nil { t.Fatal(err) } r := httptest.NewRecorder() if err := server.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } if r.Code != http.StatusConflict { t.Fatalf("Expected http status 409-conflict, got %v", r.Code) } req2, err := http.NewRequest("DELETE", "/images/test:test", nil) if err != nil { t.Fatal(err) } r2 := httptest.NewRecorder() if err := server.ServeRequest(eng, api.APIVERSION, r2, req2); err != nil { t.Fatal(err) } assertHttpNotError(r2, t) if r2.Code != http.StatusOK { t.Fatalf("%d OK expected, received %d\n", http.StatusOK, r.Code) } outs := engine.NewTable("Created", 0) if _, err := outs.ReadListFrom(r2.Body.Bytes()); err != nil { t.Fatal(err) } if len(outs.Data) != 1 { t.Fatalf("Expected %d event (untagged), got %d", 1, len(outs.Data)) } images = getImages(eng, t, false, "") if images.Len() != initialImages.Len() { t.Errorf("Expected %d image, %d found", initialImages.Len(), images.Len()) } }
// CmdSearch searches the Docker Hub for images. // // Usage: docker search [OPTIONS] TERM func (cli *DockerCli) CmdSearch(args ...string) error { cmd := cli.Subcmd("search", "TERM", "Search the Docker Hub for images", true) noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output") trusted := cmd.Bool([]string{"#t", "#trusted", "#-trusted"}, false, "Only show trusted builds") automated := cmd.Bool([]string{"-automated"}, false, "Only show automated builds") stars := cmd.Int([]string{"s", "#stars", "-stars"}, 0, "Only displays with at least x stars") cmd.Require(flag.Exact, 1) utils.ParseFlags(cmd, args, true) name := cmd.Arg(0) v := url.Values{} v.Set("term", name) // Resolve the Repository name from fqn to hostname + name taglessRemote, _ := parsers.ParseRepositoryTag(name) repoInfo, err := registry.ParseRepositoryInfo(taglessRemote) if err != nil { return err } cli.LoadConfigFile() body, statusCode, errReq := cli.clientRequestAttemptLogin("GET", "/images/search?"+v.Encode(), nil, nil, repoInfo.Index, "search") rawBody, _, err := readBody(body, statusCode, errReq) if err != nil { return err } outs := engine.NewTable("star_count", 0) if _, err := outs.ReadListFrom(rawBody); err != nil { return err } w := tabwriter.NewWriter(cli.out, 10, 1, 3, ' ', 0) fmt.Fprintf(w, "NAME\tDESCRIPTION\tSTARS\tOFFICIAL\tAUTOMATED\n") for _, out := range outs.Data { if ((*automated || *trusted) && (!out.GetBool("is_trusted") && !out.GetBool("is_automated"))) || (*stars > out.GetInt("star_count")) { continue } desc := strings.Replace(out.Get("description"), "\n", " ", -1) desc = strings.Replace(desc, "\r", " ", -1) if !*noTrunc && len(desc) > 45 { desc = utils.Trunc(desc, 42) + "..." } fmt.Fprintf(w, "%s\t%s\t%d\t", out.Get("name"), desc, out.GetInt("star_count")) if out.GetBool("is_official") { fmt.Fprint(w, "[OK]") } fmt.Fprint(w, "\t") if out.GetBool("is_automated") || out.GetBool("is_trusted") { fmt.Fprint(w, "[OK]") } fmt.Fprint(w, "\n") } w.Flush() return nil }
// CmdHistory shows the history of an image. // // Usage: docker history [OPTIONS] IMAGE func (cli *DockerCli) CmdHistory(args ...string) error { cmd := cli.Subcmd("history", "IMAGE", "Show the history of an image", true) quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only show numeric IDs") noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output") cmd.Require(flag.Exact, 1) utils.ParseFlags(cmd, args, true) body, _, err := readBody(cli.call("GET", "/images/"+cmd.Arg(0)+"/history", nil, nil)) if err != nil { return err } outs := engine.NewTable("Created", 0) if _, err := outs.ReadListFrom(body); err != nil { return err } w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) if !*quiet { fmt.Fprintln(w, "IMAGE\tCREATED\tCREATED BY\tSIZE") } for _, out := range outs.Data { outID := out.Get("Id") if !*quiet { if *noTrunc { fmt.Fprintf(w, "%s\t", outID) } else { fmt.Fprintf(w, "%s\t", stringid.TruncateID(outID)) } fmt.Fprintf(w, "%s ago\t", units.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0)))) if *noTrunc { fmt.Fprintf(w, "%s\t", out.Get("CreatedBy")) } else { fmt.Fprintf(w, "%s\t", utils.Trunc(out.Get("CreatedBy"), 45)) } fmt.Fprintf(w, "%s\n", units.HumanSize(float64(out.GetInt64("Size")))) } else { if *noTrunc { fmt.Fprintln(w, outID) } else { fmt.Fprintln(w, stringid.TruncateID(outID)) } } } w.Flush() return nil }
// Regression test for being able to untag an image with an existing // container func TestDeleteTagWithExistingContainers(t *testing.T) { eng := NewTestEngine(t) defer nuke(mkDaemonFromEngine(eng, t)) srv := mkServerFromEngine(eng, t) // Tag the image if err := eng.Job("tag", unitTestImageID, "utest", "tag1").Run(); err != nil { t.Fatal(err) } // Create a container from the image config, _, _, err := runconfig.Parse([]string{unitTestImageID, "echo test"}, nil) if err != nil { t.Fatal(err) } id := createNamedTestContainer(eng, config, t, "testingtags") if id == "" { t.Fatal("No id returned") } job := srv.Eng.Job("containers") job.SetenvBool("all", true) outs, err := job.Stdout.AddListTable() if err != nil { t.Fatal(err) } if err := job.Run(); err != nil { t.Fatal(err) } if len(outs.Data) != 1 { t.Fatalf("Expected 1 container got %d", len(outs.Data)) } // Try to remove the tag imgs := engine.NewTable("", 0) if err := srv.DeleteImage("utest:tag1", imgs, true, false, false); err != nil { t.Fatal(err) } if len(imgs.Data) != 1 { t.Fatalf("Should only have deleted one untag %d", len(imgs.Data)) } if untag := imgs.Data[0].Get("Untagged"); untag != "utest:tag1" { t.Fatalf("Expected %s got %s", unitTestImageID, untag) } }
func getImagesJSON(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } var ( err error outs *engine.Table job = eng.Job("images") ) job.Setenv("filters", r.Form.Get("filters")) // FIXME this parameter could just be a match filter job.Setenv("filter", r.Form.Get("filter")) job.Setenv("all", r.Form.Get("all")) if version.GreaterThanOrEqualTo("1.7") { streamJSON(job, w, false) } else if outs, err = job.Stdout.AddListTable(); err != nil { return err } if err := job.Run(); err != nil { return err } if version.LessThan("1.7") && outs != nil { // Convert to legacy format outsLegacy := engine.NewTable("Created", 0) for _, out := range outs.Data { for _, repoTag := range out.GetList("RepoTags") { repo, tag := parsers.ParseRepositoryTag(repoTag) outLegacy := &engine.Env{} outLegacy.Set("Repository", repo) outLegacy.SetJson("Tag", tag) outLegacy.Set("Id", out.Get("Id")) outLegacy.SetInt64("Created", out.GetInt64("Created")) outLegacy.SetInt64("Size", out.GetInt64("Size")) outLegacy.SetInt64("VirtualSize", out.GetInt64("VirtualSize")) outsLegacy.Add(outLegacy) } } w.Header().Set("Content-Type", "application/json") if _, err := outsLegacy.WriteListTo(w); err != nil { return err } } return nil }
func (daemon *Daemon) ImageDelete(job *engine.Job) engine.Status { if n := len(job.Args); n != 1 { return job.Errorf("Usage: %s IMAGE", job.Name) } imgs := engine.NewTable("", 0) if err := daemon.DeleteImage(job.Eng, job.Args[0], imgs, true, job.GetenvBool("force"), job.GetenvBool("noprune")); err != nil { return job.Error(err) } if len(imgs.Data) == 0 { return job.Errorf("Conflict, %s wasn't deleted", job.Args[0]) } if _, err := imgs.WriteListTo(job.Stdout); err != nil { return job.Error(err) } return engine.StatusOK }
func TestGetContainersJSON(t *testing.T) { eng := NewTestEngine(t) defer mkDaemonFromEngine(eng, t).Nuke() job := eng.Job("containers") job.SetenvBool("all", true) outs, err := job.Stdout.AddTable() if err != nil { t.Fatal(err) } if err := job.Run(); err != nil { t.Fatal(err) } beginLen := len(outs.Data) containerID := createTestContainer(eng, &runconfig.Config{ Image: unitTestImageID, Cmd: []string{"echo", "test"}, }, t) if containerID == "" { t.Fatalf("Received empty container ID") } req, err := http.NewRequest("GET", "/containers/json?all=1", nil) if err != nil { t.Fatal(err) } r := httptest.NewRecorder() if err := server.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } assertHttpNotError(r, t) containers := engine.NewTable("", 0) if _, err := containers.ReadListFrom(r.Body.Bytes()); err != nil { t.Fatal(err) } if len(containers.Data) != beginLen+1 { t.Fatalf("Expected %d container, %d found (started with: %d)", beginLen+1, len(containers.Data), beginLen) } if id := containers.Data[0].Get("Id"); id != containerID { t.Fatalf("Container ID mismatch. Expected: %s, received: %s\n", containerID, id) } }
// CmdRmi removes all images with the specified name(s). // // Usage: docker rmi [OPTIONS] IMAGE [IMAGE...] func (cli *DockerCli) CmdRmi(args ...string) error { var ( cmd = cli.Subcmd("rmi", "IMAGE [IMAGE...]", "Remove one or more images", true) force = cmd.Bool([]string{"f", "-force"}, false, "Force removal of the image") noprune = cmd.Bool([]string{"-no-prune"}, false, "Do not delete untagged parents") ) cmd.Require(flag.Min, 1) utils.ParseFlags(cmd, args, true) v := url.Values{} if *force { v.Set("force", "1") } if *noprune { v.Set("noprune", "1") } var encounteredError error for _, name := range cmd.Args() { body, _, err := readBody(cli.call("DELETE", "/images/"+name+"?"+v.Encode(), nil, nil)) if err != nil { fmt.Fprintf(cli.err, "%s\n", err) encounteredError = fmt.Errorf("Error: failed to remove one or more images") } else { outs := engine.NewTable("Created", 0) if _, err := outs.ReadListFrom(body); err != nil { fmt.Fprintf(cli.err, "%s\n", err) encounteredError = fmt.Errorf("Error: failed to remove one or more images") continue } for _, out := range outs.Data { if out.Get("Deleted") != "" { fmt.Fprintf(cli.out, "Deleted: %s\n", out.Get("Deleted")) } else { fmt.Fprintf(cli.out, "Untagged: %s\n", out.Get("Untagged")) } } } } return encounteredError }
// Search queries the public registry for images matching the specified // search terms, and returns the results. // // Argument syntax: search TERM // // Option environment: // 'authConfig': json-encoded credentials to authenticate against the registry. // The search extends to images only accessible via the credentials. // // 'metaHeaders': extra HTTP headers to include in the request to the registry. // The headers should be passed as a json-encoded dictionary. // // Output: // Results are sent as a collection of structured messages (using engine.Table). // Each result is sent as a separate message. // Results are ordered by number of stars on the public registry. func (s *Service) Search(job *engine.Job) engine.Status { if n := len(job.Args); n != 1 { return job.Errorf("Usage: %s TERM", job.Name) } var ( term = job.Args[0] metaHeaders = map[string][]string{} authConfig = &AuthConfig{} ) job.GetenvJson("authConfig", authConfig) job.GetenvJson("metaHeaders", metaHeaders) repoInfo, err := ResolveRepositoryInfo(job, term) if err != nil { return job.Error(err) } // *TODO: Search multiple indexes. endpoint, err := repoInfo.GetEndpoint() if err != nil { return job.Error(err) } r, err := NewSession(authConfig, HTTPRequestFactory(metaHeaders), endpoint, true) if err != nil { return job.Error(err) } results, err := r.SearchRepositories(repoInfo.GetSearchTerm()) if err != nil { return job.Error(err) } outs := engine.NewTable("star_count", 0) for _, result := range results.Results { out := &engine.Env{} out.Import(result) outs.Add(out) } outs.ReverseSort() if _, err := outs.WriteListTo(job.Stdout); err != nil { return job.Error(err) } return engine.StatusOK }
func TestGetContainersChanges(t *testing.T) { eng := NewTestEngine(t) defer mkDaemonFromEngine(eng, t).Nuke() // Create a container and remove a file containerID := createTestContainer(eng, &runconfig.Config{ Image: unitTestImageID, Cmd: []string{"/bin/rm", "/etc/passwd"}, }, t, ) containerRun(eng, containerID, t) r := httptest.NewRecorder() req, err := http.NewRequest("GET", "/containers/"+containerID+"/changes", nil) if err != nil { t.Fatal(err) } if err := server.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } assertHttpNotError(r, t) outs := engine.NewTable("", 0) if _, err := outs.ReadListFrom(r.Body.Bytes()); err != nil { t.Fatal(err) } // Check the changelog success := false for _, elem := range outs.Data { if elem.Get("Path") == "/etc/passwd" && elem.GetInt("Kind") == 2 { success = true } } if !success { t.Fatalf("/etc/passwd as been removed but is not present in the diff") } }
func getContainersJSON(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } var ( err error outs *engine.Table job = eng.Job("containers") ) job.Setenv("all", r.Form.Get("all")) job.Setenv("size", r.Form.Get("size")) job.Setenv("since", r.Form.Get("since")) job.Setenv("before", r.Form.Get("before")) job.Setenv("limit", r.Form.Get("limit")) job.Setenv("filters", r.Form.Get("filters")) if version.GreaterThanOrEqualTo("1.5") { streamJSON(job, w, false) } else if outs, err = job.Stdout.AddTable(); err != nil { return err } if err = job.Run(); err != nil { return err } if version.LessThan("1.5") { // Convert to legacy format for _, out := range outs.Data { ports := engine.NewTable("", 0) ports.ReadListFrom([]byte(out.Get("Ports"))) out.Set("Ports", api.DisplayablePorts(ports)) } w.Header().Set("Content-Type", "application/json") if _, err = outs.WriteListTo(w); err != nil { return err } } return nil }
func (settings *NetworkSettings) PortMappingAPI() *engine.Table { var outs = engine.NewTable("", 0) for port, bindings := range settings.Ports { p, _ := nat.ParsePort(port.Port()) if len(bindings) == 0 { out := &engine.Env{} out.SetInt("PrivatePort", p) out.Set("Type", port.Proto()) outs.Add(out) continue } for _, binding := range bindings { out := &engine.Env{} h, _ := nat.ParsePort(binding.HostPort) out.SetInt("PrivatePort", p) out.SetInt("PublicPort", h) out.Set("Type", port.Proto()) out.Set("IP", binding.HostIp) outs.Add(out) } } return outs }
func (s *TagStore) CmdHistory(job *engine.Job) error { if n := len(job.Args); n != 1 { return fmt.Errorf("Usage: %s IMAGE", job.Name) } name := job.Args[0] foundImage, err := s.LookupImage(name) if err != nil { return err } lookupMap := make(map[string][]string) for name, repository := range s.Repositories { for tag, id := range repository { // If the ID already has a reverse lookup, do not update it unless for "latest" if _, exists := lookupMap[id]; !exists { lookupMap[id] = []string{} } lookupMap[id] = append(lookupMap[id], utils.ImageReference(name, tag)) } } outs := engine.NewTable("Created", 0) err = foundImage.WalkHistory(func(img *image.Image) error { out := &engine.Env{} out.SetJson("Id", img.ID) out.SetInt64("Created", img.Created.Unix()) out.Set("CreatedBy", strings.Join(img.ContainerConfig.Cmd, " ")) out.SetList("Tags", lookupMap[img.ID]) out.SetInt64("Size", img.Size) outs.Add(out) return nil }) if _, err := outs.WriteListTo(job.Stdout); err != nil { return err } return nil }
func (srv *Server) Containers(job *engine.Job) engine.Status { var ( foundBefore bool displayed int all = job.GetenvBool("all") since = job.Getenv("since") before = job.Getenv("before") n = job.GetenvInt("limit") size = job.GetenvBool("size") ) outs := engine.NewTable("Created", 0) names := map[string][]string{} srv.daemon.ContainerGraph().Walk("/", func(p string, e *graphdb.Entity) error { names[e.ID()] = append(names[e.ID()], p) return nil }, -1) var beforeCont, sinceCont *daemon.Container if before != "" { beforeCont = srv.daemon.Get(before) if beforeCont == nil { return job.Error(fmt.Errorf("Could not find container with name or id %s", before)) } } if since != "" { sinceCont = srv.daemon.Get(since) if sinceCont == nil { return job.Error(fmt.Errorf("Could not find container with name or id %s", since)) } } errLast := errors.New("last container") writeCont := func(container *daemon.Container) error { container.Lock() defer container.Unlock() if !container.State.IsRunning() && !all && n <= 0 && since == "" && before == "" { return nil } if before != "" && !foundBefore { if container.ID == beforeCont.ID { foundBefore = true } return nil } if n > 0 && displayed == n { return errLast } if since != "" { if container.ID == sinceCont.ID { return errLast } } displayed++ out := &engine.Env{} out.Set("Id", container.ID) out.SetList("Names", names[container.ID]) out.Set("Image", srv.daemon.Repositories().ImageName(container.Image)) if len(container.Args) > 0 { args := []string{} for _, arg := range container.Args { if strings.Contains(arg, " ") { args = append(args, fmt.Sprintf("'%s'", arg)) } else { args = append(args, arg) } } argsAsString := strings.Join(args, " ") out.Set("Command", fmt.Sprintf("\"%s %s\"", container.Path, argsAsString)) } else { out.Set("Command", fmt.Sprintf("\"%s\"", container.Path)) } out.SetInt64("Created", container.Created.Unix()) out.Set("Status", container.State.String()) str, err := container.NetworkSettings.PortMappingAPI().ToListString() if err != nil { return err } out.Set("Ports", str) if size { sizeRw, sizeRootFs := container.GetSize() out.SetInt64("SizeRw", sizeRw) out.SetInt64("SizeRootFs", sizeRootFs) } outs.Add(out) return nil } for _, container := range srv.daemon.List() { if err := writeCont(container); err != nil { if err != errLast { return job.Error(err) } break } } outs.ReverseSort() if _, err := outs.WriteListTo(job.Stdout); err != nil { return job.Error(err) } return engine.StatusOK }
func (daemon *Daemon) Containers(job *engine.Job) engine.Status { var ( foundBefore bool displayed int all = job.GetenvBool("all") since = job.Getenv("since") before = job.Getenv("before") n = job.GetenvInt("limit") size = job.GetenvBool("size") psFilters filters.Args filt_exited []int ) outs := engine.NewTable("Created", 0) psFilters, err := filters.FromParam(job.Getenv("filters")) if err != nil { return job.Error(err) } if i, ok := psFilters["exited"]; ok { for _, value := range i { code, err := strconv.Atoi(value) if err != nil { return job.Error(err) } filt_exited = append(filt_exited, code) } } if i, ok := psFilters["status"]; ok { for _, value := range i { if value == "exited" { all = true } } } names := map[string][]string{} daemon.ContainerGraph().Walk("/", func(p string, e *graphdb.Entity) error { names[e.ID()] = append(names[e.ID()], p) return nil }, 1) var beforeCont, sinceCont *Container if before != "" { beforeCont, err = daemon.Get(before) if err != nil { return job.Error(err) } } if since != "" { sinceCont, err = daemon.Get(since) if err != nil { return job.Error(err) } } errLast := errors.New("last container") writeCont := func(container *Container) error { container.Lock() defer container.Unlock() if !container.Running && !all && n <= 0 && since == "" && before == "" { return nil } if !psFilters.Match("name", container.Name) { return nil } if !psFilters.Match("id", container.ID) { return nil } if !psFilters.MatchKVList("label", container.Config.Labels) { return nil } if before != "" && !foundBefore { if container.ID == beforeCont.ID { foundBefore = true } return nil } if n > 0 && displayed == n { return errLast } if since != "" { if container.ID == sinceCont.ID { return errLast } } if len(filt_exited) > 0 { should_skip := true for _, code := range filt_exited { if code == container.ExitCode && !container.Running { should_skip = false break } } if should_skip { return nil } } if !psFilters.Match("status", container.State.StateString()) { return nil } displayed++ out := &engine.Env{} out.SetJson("Id", container.ID) out.SetList("Names", names[container.ID]) img := container.Config.Image _, tag := parsers.ParseRepositoryTag(container.Config.Image) if tag == "" { img = utils.ImageReference(img, graph.DEFAULTTAG) } out.SetJson("Image", img) if len(container.Args) > 0 { args := []string{} for _, arg := range container.Args { if strings.Contains(arg, " ") { args = append(args, fmt.Sprintf("'%s'", arg)) } else { args = append(args, arg) } } argsAsString := strings.Join(args, " ") out.Set("Command", fmt.Sprintf("\"%s %s\"", container.Path, argsAsString)) } else { out.Set("Command", fmt.Sprintf("\"%s\"", container.Path)) } out.SetInt64("Created", container.Created.Unix()) out.Set("Status", container.State.String()) str, err := container.NetworkSettings.PortMappingAPI().ToListString() if err != nil { return err } out.Set("Ports", str) if size { sizeRw, sizeRootFs := container.GetSize() out.SetInt64("SizeRw", sizeRw) out.SetInt64("SizeRootFs", sizeRootFs) } out.SetJson("Labels", container.Config.Labels) outs.Add(out) return nil } for _, container := range daemon.List() { if err := writeCont(container); err != nil { if err != errLast { return job.Error(err) } break } } outs.ReverseSort() if _, err := outs.WriteListTo(job.Stdout); err != nil { return job.Error(err) } return engine.StatusOK }
// CmdImages lists the images in a specified repository, or all top-level images if no repository is specified. // // Usage: docker images [OPTIONS] [REPOSITORY] func (cli *DockerCli) CmdImages(args ...string) error { cmd := cli.Subcmd("images", "[REPOSITORY]", "List images", true) quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only show numeric IDs") all := cmd.Bool([]string{"a", "-all"}, false, "Show all images (default hides intermediate images)") noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output") showDigests := cmd.Bool([]string{"-digests"}, false, "Show digests") // FIXME: --viz and --tree are deprecated. Remove them in a future version. 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") flFilter := opts.NewListOpts(nil) cmd.Var(&flFilter, []string{"f", "-filter"}, "Filter output based on conditions provided") cmd.Require(flag.Max, 1) utils.ParseFlags(cmd, args, true) // Consolidate all filter flags, and sanity check them early. // They'll get process in the daemon/server. imageFilterArgs := filters.Args{} for _, f := range flFilter.GetAll() { var err error imageFilterArgs, err = filters.ParseFlag(f, imageFilterArgs) if err != nil { return err } } matchName := cmd.Arg(0) // FIXME: --viz and --tree are deprecated. Remove them in a future version. if *flViz || *flTree { v := url.Values{ "all": []string{"1"}, } if len(imageFilterArgs) > 0 { filterJSON, err := filters.ToParam(imageFilterArgs) if err != nil { return err } v.Set("filters", filterJSON) } body, _, err := readBody(cli.call("GET", "/images/json?"+v.Encode(), nil, nil)) if err != nil { return err } outs := engine.NewTable("Created", 0) if _, err := outs.ReadListFrom(body); err != nil { return err } var ( printNode func(cli *DockerCli, noTrunc bool, image *engine.Env, prefix string) startImage *engine.Env roots = engine.NewTable("Created", outs.Len()) byParent = make(map[string]*engine.Table) ) for _, image := range outs.Data { if image.Get("ParentId") == "" { roots.Add(image) } else { if children, exists := byParent[image.Get("ParentId")]; exists { children.Add(image) } else { byParent[image.Get("ParentId")] = engine.NewTable("Created", 1) byParent[image.Get("ParentId")].Add(image) } } if matchName != "" { if matchName == image.Get("Id") || matchName == stringid.TruncateID(image.Get("Id")) { startImage = image } for _, repotag := range image.GetList("RepoTags") { if repotag == matchName { startImage = image } } } } if *flViz { fmt.Fprintf(cli.out, "digraph docker {\n") printNode = (*DockerCli).printVizNode } else { printNode = (*DockerCli).printTreeNode } if startImage != nil { root := engine.NewTable("Created", 1) root.Add(startImage) cli.WalkTree(*noTrunc, root, byParent, "", printNode) } else if matchName == "" { cli.WalkTree(*noTrunc, roots, byParent, "", printNode) } if *flViz { fmt.Fprintf(cli.out, " base [style=invisible]\n}\n") } } else { v := url.Values{} if len(imageFilterArgs) > 0 { filterJSON, err := filters.ToParam(imageFilterArgs) if err != nil { return err } v.Set("filters", filterJSON) } if cmd.NArg() == 1 { // FIXME rename this parameter, to not be confused with the filters flag v.Set("filter", matchName) } if *all { v.Set("all", "1") } body, _, err := readBody(cli.call("GET", "/images/json?"+v.Encode(), nil, nil)) if err != nil { return err } outs := engine.NewTable("Created", 0) if _, err := outs.ReadListFrom(body); err != nil { return err } w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) if !*quiet { if *showDigests { fmt.Fprintln(w, "REPOSITORY\tTAG\tDIGEST\tIMAGE ID\tCREATED\tVIRTUAL SIZE") } else { fmt.Fprintln(w, "REPOSITORY\tTAG\tIMAGE ID\tCREATED\tVIRTUAL SIZE") } } for _, out := range outs.Data { outID := out.Get("Id") if !*noTrunc { outID = stringid.TruncateID(outID) } repoTags := out.GetList("RepoTags") repoDigests := out.GetList("RepoDigests") if len(repoTags) == 1 && repoTags[0] == "<none>:<none>" && len(repoDigests) == 1 && repoDigests[0] == "<none>@<none>" { // dangling image - clear out either repoTags or repoDigsts so we only show it once below repoDigests = []string{} } // combine the tags and digests lists tagsAndDigests := append(repoTags, repoDigests...) for _, repoAndRef := range tagsAndDigests { repo, ref := parsers.ParseRepositoryTag(repoAndRef) // default tag and digest to none - if there's a value, it'll be set below tag := "<none>" digest := "<none>" if utils.DigestReference(ref) { digest = ref } else { tag = ref } if !*quiet { if *showDigests { fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s ago\t%s\n", repo, tag, digest, outID, units.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0))), units.HumanSize(float64(out.GetInt64("VirtualSize")))) } else { fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\n", repo, tag, outID, units.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0))), units.HumanSize(float64(out.GetInt64("VirtualSize")))) } } else { fmt.Fprintln(w, outID) } } } if !*quiet { w.Flush() } } return nil }
// CmdPs outputs a list of Docker containers. // // Usage: docker ps [OPTIONS] func (cli *DockerCli) CmdPs(args ...string) error { var ( err error psFilterArgs = filters.Args{} v = url.Values{} cmd = cli.Subcmd("ps", "", "List containers", true) quiet = cmd.Bool([]string{"q", "-quiet"}, false, "Only display numeric IDs") size = cmd.Bool([]string{"s", "-size"}, false, "Display total file sizes") all = cmd.Bool([]string{"a", "-all"}, false, "Show all containers (default shows just running)") noTrunc = cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output") nLatest = cmd.Bool([]string{"l", "-latest"}, false, "Show the latest created container, include non-running") since = cmd.String([]string{"#sinceId", "#-since-id", "-since"}, "", "Show created since Id or Name, include non-running") before = cmd.String([]string{"#beforeId", "#-before-id", "-before"}, "", "Show only container created before Id or Name") last = cmd.Int([]string{"n"}, -1, "Show n last created containers, include non-running") flFilter = opts.NewListOpts(nil) ) cmd.Require(flag.Exact, 0) cmd.Var(&flFilter, []string{"f", "-filter"}, "Filter output based on conditions provided") utils.ParseFlags(cmd, args, true) if *last == -1 && *nLatest { *last = 1 } if *all { v.Set("all", "1") } if *last != -1 { v.Set("limit", strconv.Itoa(*last)) } if *since != "" { v.Set("since", *since) } if *before != "" { v.Set("before", *before) } if *size { v.Set("size", "1") } // Consolidate all filter flags, and sanity check them. // They'll get processed in the daemon/server. for _, f := range flFilter.GetAll() { if psFilterArgs, err = filters.ParseFlag(f, psFilterArgs); err != nil { return err } } if len(psFilterArgs) > 0 { filterJSON, err := filters.ToParam(psFilterArgs) if err != nil { return err } v.Set("filters", filterJSON) } body, _, err := readBody(cli.call("GET", "/containers/json?"+v.Encode(), nil, nil)) if err != nil { return err } outs := engine.NewTable("Created", 0) if _, err := outs.ReadListFrom(body); err != nil { return err } w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) if !*quiet { fmt.Fprint(w, "CONTAINER ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS\tNAMES") if *size { fmt.Fprintln(w, "\tSIZE") } else { fmt.Fprint(w, "\n") } } stripNamePrefix := func(ss []string) []string { for i, s := range ss { ss[i] = s[1:] } return ss } for _, out := range outs.Data { outID := out.Get("Id") if !*noTrunc { outID = stringid.TruncateID(outID) } if *quiet { fmt.Fprintln(w, outID) continue } var ( outNames = stripNamePrefix(out.GetList("Names")) outCommand = strconv.Quote(out.Get("Command")) ports = engine.NewTable("", 0) ) if !*noTrunc { outCommand = utils.Trunc(outCommand, 20) // only display the default name for the container with notrunc is passed for _, name := range outNames { if len(strings.Split(name, "/")) == 1 { outNames = []string{name} break } } } ports.ReadListFrom([]byte(out.Get("Ports"))) image := out.Get("Image") if image == "" { image = "<no image>" } fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t%s\t", outID, image, outCommand, units.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0))), out.Get("Status"), api.DisplayablePorts(ports), strings.Join(outNames, ",")) if *size { if out.GetInt("SizeRootFs") > 0 { fmt.Fprintf(w, "%s (virtual %s)\n", units.HumanSize(float64(out.GetInt64("SizeRw"))), units.HumanSize(float64(out.GetInt64("SizeRootFs")))) } else { fmt.Fprintf(w, "%s\n", units.HumanSize(float64(out.GetInt64("SizeRw")))) } continue } fmt.Fprint(w, "\n") } if !*quiet { w.Flush() } return nil }
func TestContainerOrphaning(t *testing.T) { // setup a temporary directory tmpDir, err := ioutil.TempDir("", "project") if err != nil { t.Fatal(err) } defer os.RemoveAll(tmpDir) // setup a CLI and server cli := client.NewDockerCli(nil, ioutil.Discard, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) defer cleanup(globalEngine, t) srv := mkServerFromEngine(globalEngine, t) // closure to build something buildSomething := func(template string, image string) string { dockerfile := path.Join(tmpDir, "Dockerfile") replacer := strings.NewReplacer("{IMAGE}", unitTestImageID) contents := replacer.Replace(template) ioutil.WriteFile(dockerfile, []byte(contents), 0x777) if err := cli.CmdBuild("-t", image, tmpDir); err != nil { t.Fatal(err) } job := globalEngine.Job("image_get", image) info, _ := job.Stdout.AddEnv() if err := job.Run(); err != nil { t.Fatal(err) } return info.Get("Id") } // build an image imageName := "orphan-test" template1 := ` from {IMAGE} cmd ["/bin/echo", "holla"] ` img1 := buildSomething(template1, imageName) // create a container using the fist image if err := cli.CmdRun(imageName); err != nil { t.Fatal(err) } // build a new image that splits lineage template2 := ` from {IMAGE} cmd ["/bin/echo", "holla"] expose 22 ` buildSomething(template2, imageName) // remove the second image by name resp := engine.NewTable("", 0) if err := srv.DeleteImage(imageName, resp, true, false, false); err == nil { t.Fatal("Expected error, got none") } // see if we deleted the first image (and orphaned the container) for _, i := range resp.Data { if img1 == i.Get("Deleted") { t.Fatal("Orphaned image with container") } } }
func (s *TagStore) CmdImages(job *engine.Job) engine.Status { var ( allImages map[string]*image.Image err error filt_tagged = true ) imageFilters, err := filters.FromParam(job.Getenv("filters")) if err != nil { return job.Error(err) } for name := range imageFilters { if _, ok := acceptedImageFilterTags[name]; !ok { return job.Errorf("Invalid filter '%s'", name) } } if i, ok := imageFilters["dangling"]; ok { for _, value := range i { if strings.ToLower(value) == "true" { filt_tagged = false } } } if job.GetenvBool("all") && filt_tagged { allImages, err = s.graph.Map() } else { allImages, err = s.graph.Heads() } if err != nil { return job.Error(err) } lookup := make(map[string]*engine.Env) s.Lock() for name, repository := range s.Repositories { if job.Getenv("filter") != "" { if match, _ := path.Match(job.Getenv("filter"), name); !match { continue } } for tag, id := range repository { image, err := s.graph.Get(id) if err != nil { log.Printf("Warning: couldn't load %s from %s/%s: %s", id, name, tag, err) continue } if out, exists := lookup[id]; exists { if filt_tagged { out.SetList("RepoTags", append(out.GetList("RepoTags"), fmt.Sprintf("%s:%s", name, tag))) } } else { // get the boolean list for if only the untagged images are requested delete(allImages, id) if filt_tagged { out := &engine.Env{} out.SetJson("ParentId", image.Parent) out.SetList("RepoTags", []string{fmt.Sprintf("%s:%s", name, tag)}) out.SetJson("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 } } } } s.Unlock() outs := engine.NewTable("Created", len(lookup)) for _, value := range lookup { outs.Add(value) } // Display images which aren't part of a repository/tag if job.Getenv("filter") == "" { for _, image := range allImages { out := &engine.Env{} out.SetJson("ParentId", image.Parent) out.SetList("RepoTags", []string{"<none>:<none>"}) out.SetJson("Id", image.ID) out.SetInt64("Created", image.Created.Unix()) out.SetInt64("Size", image.Size) out.SetInt64("VirtualSize", image.GetParentsSize(0)+image.Size) outs.Add(out) } } outs.ReverseSort() if _, err := outs.WriteListTo(job.Stdout); err != nil { return job.Error(err) } return engine.StatusOK }
func (cli *KraneCli) CmdPs(args ...string) error { cmd := cli.Subcmd("ps", "[OPTIONS]", "List containers") quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only display numeric IDs") size := cmd.Bool([]string{"s", "-size"}, false, "Display sizes") all := cmd.Bool([]string{"a", "-all"}, false, "Show all containers. Only running containers are shown by default.") noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output") nLatest := cmd.Bool([]string{"l", "-latest"}, false, "Show only the latest created container, include non-running ones.") since := cmd.String([]string{"#sinceId", "#-since-id", "-since"}, "", "Show only containers created since Id or Name, include non-running ones.") before := cmd.String([]string{"#beforeId", "#-before-id", "-before"}, "", "Show only container created before Id or Name, include non-running ones.") last := cmd.Int([]string{"n"}, -1, "Show n last created containers, include non-running ones.") if err := cmd.Parse(args); err != nil { return nil } v := url.Values{} if *last == -1 && *nLatest { *last = 1 } if *all { v.Set("all", "1") } if *last != -1 { v.Set("limit", strconv.Itoa(*last)) } if *since != "" { v.Set("since", *since) } if *before != "" { v.Set("before", *before) } if *size { v.Set("size", "1") } body, _, err := readBody(cli.call("GET", "/containers/json?"+v.Encode(), nil, false)) if err != nil { return err } ships := engine.NewTable("Created", 0) if _, err := ships.ReadListFrom(body); err != nil { return err } w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) if !*quiet { fmt.Fprint(w, "CONTAINER ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS\tNAMES\tSHIP") if *size { fmt.Fprintln(w, "\tSIZE") } else { fmt.Fprint(w, "\n") } } for _, outShip := range ships.Data { var ( outShipFQDN = outShip.Get("fqdn") outContainers = outShip.Get("Containers") ) outs := engine.NewTable("Created", 0) if _, err := outs.ReadListFrom([]byte(outContainers)); err != nil { return err } for _, out := range outs.Data { var ( outID = out.Get("Id") outNames = out.GetList("Names") ) if !*noTrunc { outID = utils.TruncateID(outID) } // Remove the leading / from the names for i := 0; i < len(outNames); i++ { outNames[i] = outNames[i][1:] } if !*quiet { var ( outCommand = out.Get("Command") ports = engine.NewTable("", 0) ) outCommand = strconv.Quote(outCommand) if !*noTrunc { outCommand = utils.Trunc(outCommand, 20) } ports.ReadListFrom([]byte(out.Get("Ports"))) fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s ago\t%s\t%s\t%s\t", outID, out.Get("Image"), outCommand, units.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0))), out.Get("Status"), api.DisplayablePorts(ports), strings.Join(outNames, ","), outShipFQDN) if *size { if out.GetInt("SizeRootFs") > 0 { fmt.Fprintf(w, "%s (virtual %s)\n", units.HumanSize(out.GetInt64("SizeRw")), units.HumanSize(out.GetInt64("SizeRootFs"))) } else { fmt.Fprintf(w, "%s\n", units.HumanSize(out.GetInt64("SizeRw"))) } } else { fmt.Fprint(w, "\n") } } else { fmt.Fprintln(w, outID) } } } if !*quiet { w.Flush() } return nil }