dockerd.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669
  1. package main
  2. import (
  3. "github.com/dotcloud/docker"
  4. "github.com/dotcloud/docker/rcli"
  5. "github.com/dotcloud/docker/future"
  6. "bufio"
  7. "errors"
  8. "log"
  9. "io"
  10. "flag"
  11. "fmt"
  12. "strings"
  13. "text/tabwriter"
  14. "sort"
  15. "os"
  16. "time"
  17. "net/http"
  18. "encoding/json"
  19. "bytes"
  20. )
  21. func (srv *Server) Name() string {
  22. return "docker"
  23. }
  24. func (srv *Server) Help() string {
  25. help := "Usage: docker COMMAND [arg...]\n\nA self-sufficient runtime for linux containers.\n\nCommands:\n"
  26. for _, cmd := range [][]interface{}{
  27. {"run", "Run a command in a container"},
  28. {"list", "Display a list of containers"},
  29. {"pull", "Download a tarball and create a container from it"},
  30. {"put", "Upload a tarball and create a container from it"},
  31. {"rm", "Remove containers"},
  32. {"wait", "Wait for the state of a container to change"},
  33. {"stop", "Stop a running container"},
  34. {"logs", "Fetch the logs of a container"},
  35. {"diff", "Inspect changes on a container's filesystem"},
  36. {"commit", "Save the state of a container"},
  37. {"attach", "Attach to the standard inputs and outputs of a running container"},
  38. {"info", "Display system-wide information"},
  39. {"tar", "Stream the contents of a container as a tar archive"},
  40. {"web", "Generate a web UI"},
  41. } {
  42. help += fmt.Sprintf(" %-10.10s%s\n", cmd...)
  43. }
  44. return help
  45. }
  46. func (srv *Server) CmdStop(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
  47. cmd := rcli.Subcmd(stdout, "stop", "[OPTIONS] NAME", "Stop a running container")
  48. if err := cmd.Parse(args); err != nil {
  49. cmd.Usage()
  50. return nil
  51. }
  52. if cmd.NArg() < 1 {
  53. cmd.Usage()
  54. return nil
  55. }
  56. for _, name := range cmd.Args() {
  57. if container := srv.docker.Get(name); container != nil {
  58. if err := container.Stop(); err != nil {
  59. return err
  60. }
  61. fmt.Fprintln(stdout, container.Id)
  62. } else {
  63. return errors.New("No such container: " + name)
  64. }
  65. }
  66. return nil
  67. }
  68. func (srv *Server) CmdUmount(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
  69. cmd := rcli.Subcmd(stdout, "umount", "[OPTIONS] NAME", "umount a container's filesystem (debug only)")
  70. if err := cmd.Parse(args); err != nil {
  71. cmd.Usage()
  72. return nil
  73. }
  74. if cmd.NArg() < 1 {
  75. cmd.Usage()
  76. return nil
  77. }
  78. for _, name := range cmd.Args() {
  79. if container, exists := srv.findContainer(name); exists {
  80. if err := container.Filesystem.Umount(); err != nil {
  81. return err
  82. }
  83. fmt.Fprintln(stdout, container.Id)
  84. } else {
  85. return errors.New("No such container: " + name)
  86. }
  87. }
  88. return nil
  89. }
  90. func (srv *Server) CmdMount(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
  91. cmd := rcli.Subcmd(stdout, "umount", "[OPTIONS] NAME", "mount a container's filesystem (debug only)")
  92. if err := cmd.Parse(args); err != nil {
  93. cmd.Usage()
  94. return nil
  95. }
  96. if cmd.NArg() < 1 {
  97. cmd.Usage()
  98. return nil
  99. }
  100. for _, name := range cmd.Args() {
  101. if container, exists := srv.findContainer(name); exists {
  102. if err := container.Filesystem.Mount(); err != nil {
  103. return err
  104. }
  105. fmt.Fprintln(stdout, container.Id)
  106. } else {
  107. return errors.New("No such container: " + name)
  108. }
  109. }
  110. return nil
  111. }
  112. func (srv *Server) CmdCat(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
  113. cmd := rcli.Subcmd(stdout, "cat", "[OPTIONS] CONTAINER PATH", "write the contents of a container's file to standard output")
  114. if err := cmd.Parse(args); err != nil {
  115. cmd.Usage()
  116. return nil
  117. }
  118. if cmd.NArg() < 2 {
  119. cmd.Usage()
  120. return nil
  121. }
  122. name, path := cmd.Arg(0), cmd.Arg(1)
  123. if container, exists := srv.findContainer(name); exists {
  124. if f, err := container.Filesystem.OpenFile(path, os.O_RDONLY, 0); err != nil {
  125. return err
  126. } else if _, err := io.Copy(stdout, f); err != nil {
  127. return err
  128. }
  129. return nil
  130. }
  131. return errors.New("No such container: " + name)
  132. }
  133. func (srv *Server) CmdWrite(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
  134. cmd := rcli.Subcmd(stdout, "write", "[OPTIONS] CONTAINER PATH", "write the contents of standard input to a container's file")
  135. if err := cmd.Parse(args); err != nil {
  136. cmd.Usage()
  137. return nil
  138. }
  139. if cmd.NArg() < 2 {
  140. cmd.Usage()
  141. return nil
  142. }
  143. name, path := cmd.Arg(0), cmd.Arg(1)
  144. if container, exists := srv.findContainer(name); exists {
  145. if f, err := container.Filesystem.OpenFile(path, os.O_WRONLY|os.O_CREATE, 0600); err != nil {
  146. return err
  147. } else if _, err := io.Copy(f, stdin); err != nil {
  148. return err
  149. }
  150. return nil
  151. }
  152. return errors.New("No such container: " + name)
  153. }
  154. func (srv *Server) CmdLs(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
  155. cmd := rcli.Subcmd(stdout, "ls", "[OPTIONS] CONTAINER PATH", "List the contents of a container's directory")
  156. if err := cmd.Parse(args); err != nil {
  157. cmd.Usage()
  158. return nil
  159. }
  160. if cmd.NArg() < 2 {
  161. cmd.Usage()
  162. return nil
  163. }
  164. name, path := cmd.Arg(0), cmd.Arg(1)
  165. if container, exists := srv.findContainer(name); exists {
  166. if files, err := container.Filesystem.ReadDir(path); err != nil {
  167. return err
  168. } else {
  169. for _, f := range files {
  170. fmt.Fprintln(stdout, f.Name())
  171. }
  172. }
  173. return nil
  174. }
  175. return errors.New("No such container: " + name)
  176. }
  177. func (srv *Server) CmdInspect(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
  178. cmd := rcli.Subcmd(stdout, "inspect", "[OPTIONS] CONTAINER", "Return low-level information on a container")
  179. if err := cmd.Parse(args); err != nil {
  180. cmd.Usage()
  181. return nil
  182. }
  183. if cmd.NArg() < 1 {
  184. cmd.Usage()
  185. return nil
  186. }
  187. name := cmd.Arg(0)
  188. if container, exists := srv.findContainer(name); exists {
  189. data, err := json.Marshal(container)
  190. if err != nil {
  191. return err
  192. }
  193. indented := new(bytes.Buffer)
  194. if err = json.Indent(indented, data, "", " "); err != nil {
  195. return err
  196. }
  197. if _, err := io.Copy(stdout, indented); err != nil {
  198. return err
  199. }
  200. return nil
  201. }
  202. return errors.New("No such container: " + name)
  203. }
  204. func (srv *Server) CmdList(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
  205. flags := rcli.Subcmd(stdout, "list", "[OPTIONS] [NAME]", "List containers")
  206. limit := flags.Int("l", 0, "Only show the N most recent versions of each name")
  207. quiet := flags.Bool("q", false, "only show numeric IDs")
  208. flags.Parse(args)
  209. if flags.NArg() > 1 {
  210. flags.Usage()
  211. return nil
  212. }
  213. var nameFilter string
  214. if flags.NArg() == 1 {
  215. nameFilter = flags.Arg(0)
  216. }
  217. var names []string
  218. for name := range srv.containersByName {
  219. names = append(names, name)
  220. }
  221. sort.Strings(names)
  222. w := tabwriter.NewWriter(stdout, 20, 1, 3, ' ', 0)
  223. if (!*quiet) {
  224. fmt.Fprintf(w, "NAME\tID\tCREATED\tSOURCE\tRUNNING\tMOUNTED\tCOMMAND\tPID\tEXIT\n")
  225. }
  226. for _, name := range names {
  227. if nameFilter != "" && nameFilter != name {
  228. continue
  229. }
  230. for idx, container := range *srv.containersByName[name] {
  231. if *limit > 0 && idx >= *limit {
  232. break
  233. }
  234. if !*quiet {
  235. for idx, field := range []string{
  236. /* NAME */ container.GetUserData("name"),
  237. /* ID */ container.Id,
  238. /* CREATED */ future.HumanDuration(time.Now().Sub(container.Created)) + " ago",
  239. /* SOURCE */ container.GetUserData("source"),
  240. /* RUNNING */ fmt.Sprintf("%v", container.State.Running),
  241. /* MOUNTED */ fmt.Sprintf("%v", container.Filesystem.IsMounted()),
  242. /* COMMAND */ fmt.Sprintf("%s %s", container.Path, strings.Join(container.Args, " ")),
  243. /* PID */ fmt.Sprintf("%v", container.State.Pid),
  244. /* EXIT CODE */ fmt.Sprintf("%v", container.State.ExitCode),
  245. } {
  246. if idx == 0 {
  247. w.Write([]byte(field))
  248. } else {
  249. w.Write([]byte("\t" + field))
  250. }
  251. }
  252. w.Write([]byte{'\n'})
  253. } else {
  254. stdout.Write([]byte(container.Id + "\n"))
  255. }
  256. }
  257. }
  258. if (!*quiet) {
  259. w.Flush()
  260. }
  261. return nil
  262. }
  263. func (srv *Server) findContainer(name string) (*docker.Container, bool) {
  264. // 1: look for container by ID
  265. if container := srv.docker.Get(name); container != nil {
  266. return container, true
  267. }
  268. // 2: look for a container by name (and pick the most recent)
  269. if containers, exists := srv.containersByName[name]; exists {
  270. return (*containers)[0], true
  271. }
  272. return nil, false
  273. }
  274. func (srv *Server) CmdRm(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
  275. flags := rcli.Subcmd(stdout, "rm", "[OPTIONS] CONTAINER", "Remove a container")
  276. if err := flags.Parse(args); err != nil {
  277. return nil
  278. }
  279. for _, name := range flags.Args() {
  280. if _, err := srv.rm(name); err != nil {
  281. fmt.Fprintln(stdout, "Error: " + err.Error())
  282. }
  283. }
  284. return nil
  285. }
  286. func (srv *Server) CmdPull(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
  287. if len(args) < 1 {
  288. return errors.New("Not enough arguments")
  289. }
  290. resp, err := http.Get(args[0])
  291. if err != nil {
  292. return err
  293. }
  294. layer, err := srv.layers.AddLayer(resp.Body, stdout)
  295. if err != nil {
  296. return err
  297. }
  298. container, err := srv.addContainer(layer.Id(), []string{layer.Path}, args[0], "download")
  299. if err != nil {
  300. return err
  301. }
  302. fmt.Fprintln(stdout, container.Id)
  303. return nil
  304. }
  305. func (srv *Server) CmdPut(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
  306. if len(args) < 1 {
  307. return errors.New("Not enough arguments")
  308. }
  309. fmt.Printf("Adding layer\n")
  310. layer, err := srv.layers.AddLayer(stdin, stdout)
  311. if err != nil {
  312. return err
  313. }
  314. id := layer.Id()
  315. if !srv.docker.Exists(id) {
  316. log.Println("Creating new container: " + id)
  317. log.Printf("%v\n", srv.docker.List())
  318. _, err := srv.addContainer(id, []string{layer.Path}, args[0], "upload")
  319. if err != nil {
  320. return err
  321. }
  322. }
  323. fmt.Fprintln(stdout, id)
  324. return nil
  325. }
  326. func (srv *Server) CmdCommit(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
  327. flags := rcli.Subcmd(stdout,
  328. "fork", "[OPTIONS] CONTAINER [DEST]",
  329. "Duplicate a container")
  330. // FIXME "-r" to reset changes in the new container
  331. if err := flags.Parse(args); err != nil {
  332. return nil
  333. }
  334. srcName, dstName := flags.Arg(0), flags.Arg(1)
  335. if srcName == "" {
  336. flags.Usage()
  337. return nil
  338. }
  339. if dstName == "" {
  340. dstName = srcName
  341. }
  342. /*
  343. if src, exists := srv.findContainer(srcName); exists {
  344. baseLayer := src.Filesystem.Layers[0]
  345. //dst := srv.addContainer(dstName, "snapshot:" + src.Id, src.Size)
  346. //fmt.Fprintln(stdout, dst.Id)
  347. return nil
  348. }
  349. */
  350. return errors.New("No such container: " + srcName)
  351. }
  352. func (srv *Server) CmdTar(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
  353. flags := rcli.Subcmd(stdout,
  354. "tar", "CONTAINER",
  355. "Stream the contents of a container as a tar archive")
  356. if err := flags.Parse(args); err != nil {
  357. return nil
  358. }
  359. name := flags.Arg(0)
  360. if container, exists := srv.findContainer(name); exists {
  361. data, err := container.Filesystem.Tar()
  362. if err != nil {
  363. return err
  364. }
  365. // Stream the entire contents of the container (basically a volatile snapshot)
  366. if _, err := io.Copy(stdout, data); err != nil {
  367. return err
  368. }
  369. return nil
  370. }
  371. return errors.New("No such container: " + name)
  372. }
  373. func (srv *Server) CmdDiff(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
  374. flags := rcli.Subcmd(stdout,
  375. "diff", "CONTAINER [OPTIONS]",
  376. "Inspect changes on a container's filesystem")
  377. if err := flags.Parse(args); err != nil {
  378. return nil
  379. }
  380. if flags.NArg() < 1 {
  381. return errors.New("Not enough arguments")
  382. }
  383. if container, exists := srv.findContainer(flags.Arg(0)); !exists {
  384. return errors.New("No such container")
  385. } else {
  386. changes, err := container.Filesystem.Changes()
  387. if err != nil {
  388. return err
  389. }
  390. for _, change := range changes {
  391. fmt.Fprintln(stdout, change.String())
  392. }
  393. }
  394. return nil
  395. }
  396. func (srv *Server) CmdReset(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
  397. flags := rcli.Subcmd(stdout,
  398. "reset", "CONTAINER [OPTIONS]",
  399. "Reset changes to a container's filesystem")
  400. if err := flags.Parse(args); err != nil {
  401. return nil
  402. }
  403. if flags.NArg() < 1 {
  404. return errors.New("Not enough arguments")
  405. }
  406. for _, name := range flags.Args() {
  407. if container, exists := srv.findContainer(name); exists {
  408. if err := container.Filesystem.Reset(); err != nil {
  409. return errors.New("Reset " + container.Id + ": " + err.Error())
  410. }
  411. }
  412. }
  413. return nil
  414. }
  415. // ByDate wraps an array of layers so they can be sorted by date (most recent first)
  416. type ByDate []*docker.Container
  417. func (c *ByDate) Len() int {
  418. return len(*c)
  419. }
  420. func (c *ByDate) Less(i, j int) bool {
  421. containers := *c
  422. return containers[j].Created.Before(containers[i].Created)
  423. }
  424. func (c *ByDate) Swap(i, j int) {
  425. containers := *c
  426. tmp := containers[i]
  427. containers[i] = containers[j]
  428. containers[j] = tmp
  429. }
  430. func (c *ByDate) Add(container *docker.Container) {
  431. *c = append(*c, container)
  432. sort.Sort(c)
  433. }
  434. func (c *ByDate) Del(id string) {
  435. for idx, container := range *c {
  436. if container.Id == id {
  437. *c = append((*c)[:idx], (*c)[idx + 1:]...)
  438. }
  439. }
  440. }
  441. func (srv *Server) addContainer(id string, layers []string, name string, source string) (*docker.Container, error) {
  442. c, err := srv.docker.Create(id, "", nil, layers, &docker.Config{Hostname: id, Ram: 512 * 1024 * 1024})
  443. if err != nil {
  444. return nil, err
  445. }
  446. if err := c.SetUserData("name", name); err != nil {
  447. srv.docker.Destroy(c)
  448. return nil, err
  449. }
  450. if _, exists := srv.containersByName[name]; !exists {
  451. srv.containersByName[name] = new(ByDate)
  452. }
  453. srv.containersByName[name].Add(c)
  454. return c, nil
  455. }
  456. func (srv *Server) rm(id string) (*docker.Container, error) {
  457. container := srv.docker.Get(id)
  458. if container == nil {
  459. return nil, errors.New("No such continer: " + id)
  460. }
  461. // Remove from name lookup
  462. srv.containersByName[container.GetUserData("name")].Del(container.Id)
  463. if err := srv.docker.Destroy(container); err != nil {
  464. return container, err
  465. }
  466. return container, nil
  467. }
  468. func (srv *Server) CmdLogs(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
  469. flags := rcli.Subcmd(stdout, "logs", "[OPTIONS] CONTAINER", "Fetch the logs of a container")
  470. if err := flags.Parse(args); err != nil {
  471. return nil
  472. }
  473. if flags.NArg() != 1 {
  474. flags.Usage()
  475. return nil
  476. }
  477. name := flags.Arg(0)
  478. if container, exists := srv.findContainer(name); exists {
  479. if _, err := io.Copy(stdout, container.StdoutLog()); err != nil {
  480. return err
  481. }
  482. if _, err := io.Copy(stdout, container.StderrLog()); err != nil {
  483. return err
  484. }
  485. return nil
  486. }
  487. return errors.New("No such container: " + flags.Arg(0))
  488. }
  489. func (srv *Server) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
  490. flags := rcli.Subcmd(stdout, "run", "[OPTIONS] CONTAINER COMMAND [ARG...]", "Run a command in a container")
  491. fl_attach := flags.Bool("a", false, "Attach stdin and stdout")
  492. //fl_tty := flags.Bool("t", false, "Allocate a pseudo-tty")
  493. if err := flags.Parse(args); err != nil {
  494. return nil
  495. }
  496. if flags.NArg() < 2 {
  497. flags.Usage()
  498. return nil
  499. }
  500. name, cmd := flags.Arg(0), flags.Args()[1:]
  501. if container, exists := srv.findContainer(name); exists {
  502. log.Printf("Running container %#v\n", container)
  503. container.Path = cmd[0]
  504. container.Args = cmd[1:]
  505. if *fl_attach {
  506. cmd_stdout, err := container.StdoutPipe()
  507. if err != nil {
  508. return err
  509. }
  510. cmd_stderr, err := container.StderrPipe()
  511. if err != nil {
  512. return err
  513. }
  514. if err := container.Start(); err != nil {
  515. return err
  516. }
  517. sending_stdout := future.Go(func() error { _, err := io.Copy(stdout, cmd_stdout); return err })
  518. sending_stderr := future.Go(func() error { _, err := io.Copy(stdout, cmd_stderr); return err })
  519. err_sending_stdout := <-sending_stdout
  520. err_sending_stderr := <-sending_stderr
  521. if err_sending_stdout != nil {
  522. return err_sending_stdout
  523. }
  524. return err_sending_stderr
  525. } else {
  526. if output, err := container.Output(); err != nil {
  527. return err
  528. } else {
  529. fmt.Printf("-->|%s|\n", output)
  530. }
  531. }
  532. return nil
  533. }
  534. return errors.New("No such container: " + name)
  535. }
  536. func main() {
  537. future.Seed()
  538. flag.Parse()
  539. d, err := New()
  540. if err != nil {
  541. log.Fatal(err)
  542. }
  543. go func() {
  544. if err := rcli.ListenAndServeHTTP(":8080", d); err != nil {
  545. log.Fatal(err)
  546. }
  547. }()
  548. if err := rcli.ListenAndServeTCP(":4242", d); err != nil {
  549. log.Fatal(err)
  550. }
  551. }
  552. func New() (*Server, error) {
  553. store, err := future.NewStore("/var/lib/docker/layers")
  554. if err != nil {
  555. return nil, err
  556. }
  557. if err := store.Init(); err != nil {
  558. return nil, err
  559. }
  560. d, err := docker.New()
  561. if err != nil {
  562. return nil, err
  563. }
  564. srv := &Server{
  565. containersByName: make(map[string]*ByDate),
  566. layers: store,
  567. docker: d,
  568. }
  569. for _, container := range srv.docker.List() {
  570. name := container.GetUserData("name")
  571. if _, exists := srv.containersByName[name]; !exists {
  572. srv.containersByName[name] = new(ByDate)
  573. }
  574. srv.containersByName[name].Add(container)
  575. }
  576. log.Printf("Done building index\n")
  577. return srv, nil
  578. }
  579. func (srv *Server) CmdMirror(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
  580. _, err := io.Copy(stdout, stdin)
  581. return err
  582. }
  583. func (srv *Server) CmdDebug(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
  584. for {
  585. if line, err := bufio.NewReader(stdin).ReadString('\n'); err == nil {
  586. fmt.Printf("--- %s", line)
  587. } else if err == io.EOF {
  588. if len(line) > 0 {
  589. fmt.Printf("--- %s\n", line)
  590. }
  591. break
  592. } else {
  593. return err
  594. }
  595. }
  596. return nil
  597. }
  598. func (srv *Server) CmdWeb(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
  599. flags := rcli.Subcmd(stdout, "web", "[OPTIONS]", "A web UI for docker")
  600. showurl := flags.Bool("u", false, "Return the URL of the web UI")
  601. if err := flags.Parse(args); err != nil {
  602. return nil
  603. }
  604. if *showurl {
  605. fmt.Fprintln(stdout, "http://localhost:4242/web")
  606. } else {
  607. if file, err := os.Open("dockerweb.html"); err != nil {
  608. return err
  609. } else if _, err := io.Copy(stdout, file); err != nil {
  610. return err
  611. }
  612. }
  613. return nil
  614. }
  615. type Server struct {
  616. containersByName map[string]*ByDate
  617. layers *future.Store
  618. docker *docker.Docker
  619. }