diff --git a/api/server/server.go b/api/server/server.go index e59aec5a79..7b794be539 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -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" @@ -264,48 +265,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 { @@ -488,8 +481,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"), @@ -1140,14 +1133,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) @@ -1557,3 +1550,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") +} diff --git a/api/server/server_unit_test.go b/api/server/server_unit_test.go index 6441b42cdf..ee0fa6bc27 100644 --- a/api/server/server_unit_test.go +++ b/api/server/server_unit_test.go @@ -7,7 +7,6 @@ import ( "io" "net/http" "net/http/httptest" - "reflect" "strings" "testing" @@ -117,106 +116,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" diff --git a/api/types/types.go b/api/types/types.go index 79e6f1e8d4..36983f68d2 100644 --- a/api/types/types.go +++ b/api/types/types.go @@ -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 diff --git a/engine/streams.go b/engine/streams.go index 216fb8980a..2863e94487 100644 --- a/engine/streams.go +++ b/engine/streams.go @@ -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 -} diff --git a/engine/table.go b/engine/table.go deleted file mode 100644 index 4498bdf1ec..0000000000 --- a/engine/table.go +++ /dev/null @@ -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) - } -} diff --git a/engine/table_test.go b/engine/table_test.go deleted file mode 100644 index 9a32ac9cdb..0000000000 --- a/engine/table_test.go +++ /dev/null @@ -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) - } -} diff --git a/graph/list.go b/graph/list.go index 5af4b87e31..f95508e950 100644 --- a/graph/list.go +++ b/graph/list.go @@ -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 } diff --git a/graph/service.go b/graph/service.go index 98523aef05..a51d106e13 100644 --- a/graph/service.go +++ b/graph/service.go @@ -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, diff --git a/integration-cli/docker_api_images_test.go b/integration-cli/docker_api_images_test.go new file mode 100644 index 0000000000..38d891fd5a --- /dev/null +++ b/integration-cli/docker_api_images_test.go @@ -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") +} diff --git a/integration/api_test.go b/integration/api_test.go index cd6b9669be..98e683d004 100644 --- a/integration/api_test.go +++ b/integration/api_test.go @@ -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)) } } diff --git a/integration/runtime_test.go b/integration/runtime_test.go index 07d9de7c62..b5e404d59d 100644 --- a/integration/runtime_test.go +++ b/integration/runtime_test.go @@ -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 diff --git a/integration/server_test.go b/integration/server_test.go index acbec8c2c4..9cdd3d774b 100644 --- a/integration/server_test.go +++ b/integration/server_test.go @@ -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") } } diff --git a/integration/utils_test.go b/integration/utils_test.go index c0e826a0f0..f8afe62b6a 100644 --- a/integration/utils_test.go +++ b/integration/utils_test.go @@ -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) +}