Merge pull request #12184 from duglin/RemoveJobImages

Remove Job from `docker images`
This commit is contained in:
Alexander Morozov 2015-04-09 09:19:09 -07:00
commit 6ba7bf440e
13 changed files with 112 additions and 468 deletions

View file

@ -24,6 +24,7 @@ import (
"github.com/docker/docker/daemon"
"github.com/docker/docker/daemon/networkdriver/bridge"
"github.com/docker/docker/engine"
"github.com/docker/docker/graph"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/docker/docker/pkg/parsers"
"github.com/docker/docker/pkg/parsers/filters"
@ -253,48 +254,40 @@ func getImagesJSON(eng *engine.Engine, version version.Version, w http.ResponseW
return err
}
var (
err error
outs *engine.Table
job = eng.Job("images")
)
imagesConfig := graph.ImagesConfig{
Filters: r.Form.Get("filters"),
// FIXME this parameter could just be a match filter
Filter: r.Form.Get("filter"),
All: toBool(r.Form.Get("all")),
}
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"))
images, err := getDaemon(eng).Repositories().Images(&imagesConfig)
if err != nil {
return err
}
if version.GreaterThanOrEqualTo("1.7") {
streamJSON(job, w, false)
} else if outs, err = job.Stdout.AddListTable(); err != nil {
return err
return writeJSON(w, http.StatusOK, images)
}
if err := job.Run(); err != nil {
return err
}
legacyImages := []types.LegacyImage{}
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)
for _, image := range images {
for _, repoTag := range image.RepoTags {
repo, tag := parsers.ParseRepositoryTag(repoTag)
legacyImage := types.LegacyImage{
Repository: repo,
Tag: tag,
ID: image.ID,
Created: image.Created,
Size: image.Size,
VirtualSize: image.VirtualSize,
}
}
w.Header().Set("Content-Type", "application/json")
if _, err := outsLegacy.WriteListTo(w); err != nil {
return err
legacyImages = append(legacyImages, legacyImage)
}
}
return nil
return writeJSON(w, http.StatusOK, legacyImages)
}
func getImagesViz(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
@ -477,8 +470,8 @@ func getContainersJSON(eng *engine.Engine, version version.Version, w http.Respo
}
config := &daemon.ContainersConfig{
All: r.Form.Get("all") == "1",
Size: r.Form.Get("size") == "1",
All: toBool(r.Form.Get("all")),
Size: toBool(r.Form.Get("size")),
Since: r.Form.Get("since"),
Before: r.Form.Get("before"),
Filters: r.Form.Get("filters"),
@ -1129,14 +1122,14 @@ func postBuild(eng *engine.Engine, version version.Version, w http.ResponseWrite
job.Stdout.Add(utils.NewWriteFlusher(w))
}
if r.FormValue("forcerm") == "1" && version.GreaterThanOrEqualTo("1.12") {
if toBool(r.FormValue("forcerm")) && version.GreaterThanOrEqualTo("1.12") {
job.Setenv("rm", "1")
} else if r.FormValue("rm") == "" && version.GreaterThanOrEqualTo("1.12") {
job.Setenv("rm", "1")
} else {
job.Setenv("rm", r.FormValue("rm"))
}
if r.FormValue("pull") == "1" && version.GreaterThanOrEqualTo("1.16") {
if toBool(r.FormValue("pull")) && version.GreaterThanOrEqualTo("1.16") {
job.Setenv("pull", "1")
}
job.Stdin.Add(r.Body)
@ -1546,3 +1539,8 @@ func ServeApi(job *engine.Job) error {
return nil
}
func toBool(s string) bool {
s = strings.ToLower(strings.TrimSpace(s))
return !(s == "" || s == "0" || s == "no" || s == "false" || s == "none")
}

View file

@ -7,7 +7,6 @@ import (
"io"
"net/http"
"net/http/httptest"
"reflect"
"strings"
"testing"
@ -92,106 +91,6 @@ func TestGetInfo(t *testing.T) {
assertContentType(r, "application/json", t)
}
func TestGetImagesJSON(t *testing.T) {
eng := engine.New()
var called bool
eng.Register("images", func(job *engine.Job) error {
called = true
v := createEnvFromGetImagesJSONStruct(sampleImage)
if err := json.NewEncoder(job.Stdout).Encode(v); err != nil {
return err
}
return nil
})
r := serveRequest("GET", "/images/json", nil, eng, t)
if !called {
t.Fatal("handler was not called")
}
assertHttpNotError(r, t)
assertContentType(r, "application/json", t)
var observed getImagesJSONStruct
if err := json.Unmarshal(r.Body.Bytes(), &observed); err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(observed, sampleImage) {
t.Errorf("Expected %#v but got %#v", sampleImage, observed)
}
}
func TestGetImagesJSONFilter(t *testing.T) {
eng := engine.New()
filter := "nothing"
eng.Register("images", func(job *engine.Job) error {
filter = job.Getenv("filter")
return nil
})
serveRequest("GET", "/images/json?filter=aaaa", nil, eng, t)
if filter != "aaaa" {
t.Errorf("%#v", filter)
}
}
func TestGetImagesJSONFilters(t *testing.T) {
eng := engine.New()
filter := "nothing"
eng.Register("images", func(job *engine.Job) error {
filter = job.Getenv("filters")
return nil
})
serveRequest("GET", "/images/json?filters=nnnn", nil, eng, t)
if filter != "nnnn" {
t.Errorf("%#v", filter)
}
}
func TestGetImagesJSONAll(t *testing.T) {
eng := engine.New()
allFilter := "-1"
eng.Register("images", func(job *engine.Job) error {
allFilter = job.Getenv("all")
return nil
})
serveRequest("GET", "/images/json?all=1", nil, eng, t)
if allFilter != "1" {
t.Errorf("%#v", allFilter)
}
}
func TestGetImagesJSONLegacyFormat(t *testing.T) {
eng := engine.New()
var called bool
eng.Register("images", func(job *engine.Job) error {
called = true
images := []types.Image{
createEnvFromGetImagesJSONStruct(sampleImage),
}
if err := json.NewEncoder(job.Stdout).Encode(images); 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 TestGetContainersByName(t *testing.T) {
eng := engine.New()
name := "container_name"

View file

@ -69,6 +69,15 @@ type Image struct {
Labels map[string]string
}
type LegacyImage struct {
ID string `json:"Id"`
Repository string
Tag string
Created int
Size int
VirtualSize int
}
// GET "/containers/json"
type Port struct {
IP string

View file

@ -4,7 +4,6 @@ import (
"bytes"
"fmt"
"io"
"io/ioutil"
"strings"
"sync"
"unicode"
@ -187,39 +186,3 @@ func (o *Output) AddEnv() (dst *Env, err error) {
}()
return dst, nil
}
func (o *Output) AddListTable() (dst *Table, err error) {
src, err := o.AddPipe()
if err != nil {
return nil, err
}
dst = NewTable("", 0)
o.tasks.Add(1)
go func() {
defer o.tasks.Done()
content, err := ioutil.ReadAll(src)
if err != nil {
return
}
if _, err := dst.ReadListFrom(content); err != nil {
return
}
}()
return dst, nil
}
func (o *Output) AddTable() (dst *Table, err error) {
src, err := o.AddPipe()
if err != nil {
return nil, err
}
dst = NewTable("", 0)
o.tasks.Add(1)
go func() {
defer o.tasks.Done()
if _, err := dst.ReadFrom(src); err != nil {
return
}
}()
return dst, nil
}

View file

@ -1,140 +0,0 @@
package engine
import (
"bytes"
"encoding/json"
"io"
"sort"
"strconv"
)
type Table struct {
Data []*Env
sortKey string
Chan chan *Env
}
func NewTable(sortKey string, sizeHint int) *Table {
return &Table{
make([]*Env, 0, sizeHint),
sortKey,
make(chan *Env),
}
}
func (t *Table) SetKey(sortKey string) {
t.sortKey = sortKey
}
func (t *Table) Add(env *Env) {
t.Data = append(t.Data, env)
}
func (t *Table) Len() int {
return len(t.Data)
}
func (t *Table) Less(a, b int) bool {
return t.lessBy(a, b, t.sortKey)
}
func (t *Table) lessBy(a, b int, by string) bool {
keyA := t.Data[a].Get(by)
keyB := t.Data[b].Get(by)
intA, errA := strconv.ParseInt(keyA, 10, 64)
intB, errB := strconv.ParseInt(keyB, 10, 64)
if errA == nil && errB == nil {
return intA < intB
}
return keyA < keyB
}
func (t *Table) Swap(a, b int) {
tmp := t.Data[a]
t.Data[a] = t.Data[b]
t.Data[b] = tmp
}
func (t *Table) Sort() {
sort.Sort(t)
}
func (t *Table) ReverseSort() {
sort.Sort(sort.Reverse(t))
}
func (t *Table) WriteListTo(dst io.Writer) (n int64, err error) {
if _, err := dst.Write([]byte{'['}); err != nil {
return -1, err
}
n = 1
for i, env := range t.Data {
bytes, err := env.WriteTo(dst)
if err != nil {
return -1, err
}
n += bytes
if i != len(t.Data)-1 {
if _, err := dst.Write([]byte{','}); err != nil {
return -1, err
}
n++
}
}
if _, err := dst.Write([]byte{']'}); err != nil {
return -1, err
}
return n + 1, nil
}
func (t *Table) ToListString() (string, error) {
buffer := bytes.NewBuffer(nil)
if _, err := t.WriteListTo(buffer); err != nil {
return "", err
}
return buffer.String(), nil
}
func (t *Table) WriteTo(dst io.Writer) (n int64, err error) {
for _, env := range t.Data {
bytes, err := env.WriteTo(dst)
if err != nil {
return -1, err
}
n += bytes
}
return n, nil
}
func (t *Table) ReadListFrom(src []byte) (n int64, err error) {
var array []interface{}
if err := json.Unmarshal(src, &array); err != nil {
return -1, err
}
for _, item := range array {
if m, ok := item.(map[string]interface{}); ok {
env := &Env{}
for key, value := range m {
env.SetAuto(key, value)
}
t.Add(env)
}
}
return int64(len(src)), nil
}
func (t *Table) ReadFrom(src io.Reader) (n int64, err error) {
decoder := NewDecoder(src)
for {
env, err := decoder.Decode()
if err == io.EOF {
return 0, nil
} else if err != nil {
return -1, err
}
t.Add(env)
}
}

View file

@ -1,112 +0,0 @@
package engine
import (
"bytes"
"encoding/json"
"testing"
)
func TestTableWriteTo(t *testing.T) {
table := NewTable("", 0)
e := &Env{}
e.Set("foo", "bar")
table.Add(e)
var buf bytes.Buffer
if _, err := table.WriteTo(&buf); err != nil {
t.Fatal(err)
}
output := make(map[string]string)
if err := json.Unmarshal(buf.Bytes(), &output); err != nil {
t.Fatal(err)
}
if len(output) != 1 {
t.Fatalf("Incorrect output: %v", output)
}
if val, exists := output["foo"]; !exists || val != "bar" {
t.Fatalf("Inccorect output: %v", output)
}
}
func TestTableSortStringValue(t *testing.T) {
table := NewTable("Key", 0)
e := &Env{}
e.Set("Key", "A")
table.Add(e)
e = &Env{}
e.Set("Key", "D")
table.Add(e)
e = &Env{}
e.Set("Key", "B")
table.Add(e)
e = &Env{}
e.Set("Key", "C")
table.Add(e)
table.Sort()
if len := table.Len(); len != 4 {
t.Fatalf("Expected 4, got %d", len)
}
if value := table.Data[0].Get("Key"); value != "A" {
t.Fatalf("Expected A, got %s", value)
}
if value := table.Data[1].Get("Key"); value != "B" {
t.Fatalf("Expected B, got %s", value)
}
if value := table.Data[2].Get("Key"); value != "C" {
t.Fatalf("Expected C, got %s", value)
}
if value := table.Data[3].Get("Key"); value != "D" {
t.Fatalf("Expected D, got %s", value)
}
}
func TestTableReverseSortStringValue(t *testing.T) {
table := NewTable("Key", 0)
e := &Env{}
e.Set("Key", "A")
table.Add(e)
e = &Env{}
e.Set("Key", "D")
table.Add(e)
e = &Env{}
e.Set("Key", "B")
table.Add(e)
e = &Env{}
e.Set("Key", "C")
table.Add(e)
table.ReverseSort()
if len := table.Len(); len != 4 {
t.Fatalf("Expected 4, got %d", len)
}
if value := table.Data[0].Get("Key"); value != "D" {
t.Fatalf("Expected D, got %s", value)
}
if value := table.Data[1].Get("Key"); value != "C" {
t.Fatalf("Expected B, got %s", value)
}
if value := table.Data[2].Get("Key"); value != "B" {
t.Fatalf("Expected C, got %s", value)
}
if value := table.Data[3].Get("Key"); value != "A" {
t.Fatalf("Expected A, got %s", value)
}
}

View file

@ -1,7 +1,6 @@
package graph
import (
"encoding/json"
"fmt"
"log"
"path"
@ -9,7 +8,6 @@ import (
"strings"
"github.com/docker/docker/api/types"
"github.com/docker/docker/engine"
"github.com/docker/docker/image"
"github.com/docker/docker/pkg/parsers/filters"
"github.com/docker/docker/utils"
@ -20,13 +18,19 @@ var acceptedImageFilterTags = map[string]struct{}{
"label": {},
}
type ImagesConfig struct {
Filters string
Filter string
All bool
}
type ByCreated []*types.Image
func (r ByCreated) Len() int { return len(r) }
func (r ByCreated) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
func (r ByCreated) Less(i, j int) bool { return r[i].Created < r[j].Created }
func (s *TagStore) CmdImages(job *engine.Job) error {
func (s *TagStore) Images(config *ImagesConfig) ([]*types.Image, error) {
var (
allImages map[string]*image.Image
err error
@ -34,13 +38,13 @@ func (s *TagStore) CmdImages(job *engine.Job) error {
filtLabel = false
)
imageFilters, err := filters.FromParam(job.Getenv("filters"))
imageFilters, err := filters.FromParam(config.Filters)
if err != nil {
return err
return nil, err
}
for name := range imageFilters {
if _, ok := acceptedImageFilterTags[name]; !ok {
return fmt.Errorf("Invalid filter '%s'", name)
return nil, fmt.Errorf("Invalid filter '%s'", name)
}
}
@ -54,20 +58,20 @@ func (s *TagStore) CmdImages(job *engine.Job) error {
_, filtLabel = imageFilters["label"]
if job.GetenvBool("all") && filtTagged {
if config.All && filtTagged {
allImages, err = s.graph.Map()
} else {
allImages, err = s.graph.Heads()
}
if err != nil {
return err
return nil, err
}
lookup := make(map[string]*types.Image)
s.Lock()
for repoName, repository := range s.Repositories {
if job.Getenv("filter") != "" {
if match, _ := path.Match(job.Getenv("filter"), repoName); !match {
if config.Filter != "" {
if match, _ := path.Match(config.Filter, repoName); !match {
continue
}
}
@ -124,7 +128,7 @@ func (s *TagStore) CmdImages(job *engine.Job) error {
}
// Display images which aren't part of a repository/tag
if job.Getenv("filter") == "" || filtLabel {
if config.Filter == "" || filtLabel {
for _, image := range allImages {
if !imageFilters.MatchKVList("label", image.ContainerConfig.Labels) {
continue
@ -145,8 +149,5 @@ func (s *TagStore) CmdImages(job *engine.Job) error {
sort.Sort(sort.Reverse(ByCreated(images)))
if err = json.NewEncoder(job.Stdout).Encode(images); err != nil {
return err
}
return nil
return images, nil
}

View file

@ -18,7 +18,6 @@ func (s *TagStore) Install(eng *engine.Engine) error {
"image_tarlayer": s.CmdTarLayer,
"image_export": s.CmdImageExport,
"history": s.CmdHistory,
"images": s.CmdImages,
"viz": s.CmdViz,
"load": s.CmdLoad,
"import": s.CmdImport,

View file

@ -0,0 +1,26 @@
package main
import (
"encoding/json"
"testing"
"github.com/docker/docker/api/types"
)
func TestLegacyImages(t *testing.T) {
body, err := sockRequest("GET", "/v1.6/images/json", nil)
if err != nil {
t.Fatalf("Error on GET: %s", err)
}
images := []types.LegacyImage{}
if err = json.Unmarshal(body, &images); err != nil {
t.Fatalf("Error on unmarshal: %s", err)
}
if len(images) == 0 || images[0].Tag == "" || images[0].Repository == "" {
t.Fatalf("Bad data: %q", images)
}
logDone("images - checking legacy json")
}

View file

@ -767,8 +767,8 @@ func TestDeleteImages(t *testing.T) {
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")))
if len(images[0].RepoTags) != len(initialImages[0].RepoTags)+1 {
t.Errorf("Expected %d images, %d found", len(initialImages[0].RepoTags)+1, len(images[0].RepoTags))
}
req, err := http.NewRequest("DELETE", "/images/"+unitTestImageID, nil)
@ -805,8 +805,8 @@ func TestDeleteImages(t *testing.T) {
}
images = getImages(eng, t, false, "")
if images.Len() != initialImages.Len() {
t.Errorf("Expected %d image, %d found", initialImages.Len(), images.Len())
if len(images) != len(initialImages) {
t.Errorf("Expected %d image, %d found", len(initialImages), len(images))
}
}

View file

@ -20,6 +20,7 @@ import (
"github.com/docker/docker/daemon"
"github.com/docker/docker/daemon/execdriver"
"github.com/docker/docker/engine"
"github.com/docker/docker/graph"
"github.com/docker/docker/image"
"github.com/docker/docker/nat"
"github.com/docker/docker/pkg/ioutils"
@ -65,17 +66,13 @@ func cleanup(eng *engine.Engine, t *testing.T) error {
container.Kill()
daemon.Rm(container)
}
job := eng.Job("images")
images, err := job.Stdout.AddTable()
images, err := daemon.Repositories().Images(&graph.ImagesConfig{})
if err != nil {
t.Fatal(err)
}
if err := job.Run(); err != nil {
t.Fatal(err)
}
for _, image := range images.Data {
if image.Get("Id") != unitTestImageID {
eng.Job("image_delete", image.Get("Id")).Run()
for _, image := range images {
if image.ID != unitTestImageID {
eng.Job("image_delete", image.ID).Run()
}
}
return nil

View file

@ -253,25 +253,25 @@ func TestImagesFilter(t *testing.T) {
images := getImages(eng, t, false, "utest*/*")
if len(images.Data[0].GetList("RepoTags")) != 2 {
if len(images[0].RepoTags) != 2 {
t.Fatal("incorrect number of matches returned")
}
images = getImages(eng, t, false, "utest")
if len(images.Data[0].GetList("RepoTags")) != 1 {
if len(images[0].RepoTags) != 1 {
t.Fatal("incorrect number of matches returned")
}
images = getImages(eng, t, false, "utest*")
if len(images.Data[0].GetList("RepoTags")) != 1 {
if len(images[0].RepoTags) != 1 {
t.Fatal("incorrect number of matches returned")
}
images = getImages(eng, t, false, "*5000*/*")
if len(images.Data[0].GetList("RepoTags")) != 1 {
if len(images[0].RepoTags) != 1 {
t.Fatal("incorrect number of matches returned")
}
}

View file

@ -16,9 +16,11 @@ import (
"github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
"github.com/docker/docker/api/types"
"github.com/docker/docker/builtins"
"github.com/docker/docker/daemon"
"github.com/docker/docker/engine"
"github.com/docker/docker/graph"
flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/registry"
"github.com/docker/docker/runconfig"
@ -329,19 +331,17 @@ func fakeTar() (io.ReadCloser, error) {
return ioutil.NopCloser(buf), nil
}
func getImages(eng *engine.Engine, t *testing.T, all bool, filter string) *engine.Table {
job := eng.Job("images")
job.SetenvBool("all", all)
job.Setenv("filter", filter)
images, err := job.Stdout.AddListTable()
func getImages(eng *engine.Engine, t *testing.T, all bool, filter string) []*types.Image {
config := graph.ImagesConfig{
Filter: filter,
All: all,
}
images, err := getDaemon(eng).Repositories().Images(&config)
if err != nil {
t.Fatal(err)
}
if err := job.Run(); err != nil {
t.Fatal(err)
}
return images
return images
}
func parseRun(args []string) (*runconfig.Config, *runconfig.HostConfig, *flag.FlagSet, error) {
@ -350,3 +350,7 @@ func parseRun(args []string) (*runconfig.Config, *runconfig.HostConfig, *flag.Fl
cmd.Usage = nil
return runconfig.Parse(cmd, args)
}
func getDaemon(eng *engine.Engine) *daemon.Daemon {
return eng.HackGetGlobalVar("httpapi.daemon").(*daemon.Daemon)
}