123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669 |
- package main
- import (
- "github.com/dotcloud/docker"
- "github.com/dotcloud/docker/rcli"
- "github.com/dotcloud/docker/future"
- "bufio"
- "errors"
- "log"
- "io"
- "flag"
- "fmt"
- "strings"
- "text/tabwriter"
- "sort"
- "os"
- "time"
- "net/http"
- "encoding/json"
- "bytes"
- )
- func (srv *Server) Name() string {
- return "docker"
- }
- func (srv *Server) Help() string {
- help := "Usage: docker COMMAND [arg...]\n\nA self-sufficient runtime for linux containers.\n\nCommands:\n"
- for _, cmd := range [][]interface{}{
- {"run", "Run a command in a container"},
- {"list", "Display a list of containers"},
- {"pull", "Download a tarball and create a container from it"},
- {"put", "Upload a tarball and create a container from it"},
- {"rm", "Remove containers"},
- {"wait", "Wait for the state of a container to change"},
- {"stop", "Stop a running container"},
- {"logs", "Fetch the logs of a container"},
- {"diff", "Inspect changes on a container's filesystem"},
- {"commit", "Save the state of a container"},
- {"attach", "Attach to the standard inputs and outputs of a running container"},
- {"info", "Display system-wide information"},
- {"tar", "Stream the contents of a container as a tar archive"},
- {"web", "Generate a web UI"},
- } {
- help += fmt.Sprintf(" %-10.10s%s\n", cmd...)
- }
- return help
- }
- func (srv *Server) CmdStop(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
- cmd := rcli.Subcmd(stdout, "stop", "[OPTIONS] NAME", "Stop a running container")
- if err := cmd.Parse(args); err != nil {
- cmd.Usage()
- return nil
- }
- if cmd.NArg() < 1 {
- cmd.Usage()
- return nil
- }
- for _, name := range cmd.Args() {
- if container := srv.docker.Get(name); container != nil {
- if err := container.Stop(); err != nil {
- return err
- }
- fmt.Fprintln(stdout, container.Id)
- } else {
- return errors.New("No such container: " + name)
- }
- }
- return nil
- }
- func (srv *Server) CmdUmount(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
- cmd := rcli.Subcmd(stdout, "umount", "[OPTIONS] NAME", "umount a container's filesystem (debug only)")
- if err := cmd.Parse(args); err != nil {
- cmd.Usage()
- return nil
- }
- if cmd.NArg() < 1 {
- cmd.Usage()
- return nil
- }
- for _, name := range cmd.Args() {
- if container, exists := srv.findContainer(name); exists {
- if err := container.Filesystem.Umount(); err != nil {
- return err
- }
- fmt.Fprintln(stdout, container.Id)
- } else {
- return errors.New("No such container: " + name)
- }
- }
- return nil
- }
- func (srv *Server) CmdMount(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
- cmd := rcli.Subcmd(stdout, "umount", "[OPTIONS] NAME", "mount a container's filesystem (debug only)")
- if err := cmd.Parse(args); err != nil {
- cmd.Usage()
- return nil
- }
- if cmd.NArg() < 1 {
- cmd.Usage()
- return nil
- }
- for _, name := range cmd.Args() {
- if container, exists := srv.findContainer(name); exists {
- if err := container.Filesystem.Mount(); err != nil {
- return err
- }
- fmt.Fprintln(stdout, container.Id)
- } else {
- return errors.New("No such container: " + name)
- }
- }
- return nil
- }
- func (srv *Server) CmdCat(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
- cmd := rcli.Subcmd(stdout, "cat", "[OPTIONS] CONTAINER PATH", "write the contents of a container's file to standard output")
- if err := cmd.Parse(args); err != nil {
- cmd.Usage()
- return nil
- }
- if cmd.NArg() < 2 {
- cmd.Usage()
- return nil
- }
- name, path := cmd.Arg(0), cmd.Arg(1)
- if container, exists := srv.findContainer(name); exists {
- if f, err := container.Filesystem.OpenFile(path, os.O_RDONLY, 0); err != nil {
- return err
- } else if _, err := io.Copy(stdout, f); err != nil {
- return err
- }
- return nil
- }
- return errors.New("No such container: " + name)
- }
- func (srv *Server) CmdWrite(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
- cmd := rcli.Subcmd(stdout, "write", "[OPTIONS] CONTAINER PATH", "write the contents of standard input to a container's file")
- if err := cmd.Parse(args); err != nil {
- cmd.Usage()
- return nil
- }
- if cmd.NArg() < 2 {
- cmd.Usage()
- return nil
- }
- name, path := cmd.Arg(0), cmd.Arg(1)
- if container, exists := srv.findContainer(name); exists {
- if f, err := container.Filesystem.OpenFile(path, os.O_WRONLY|os.O_CREATE, 0600); err != nil {
- return err
- } else if _, err := io.Copy(f, stdin); err != nil {
- return err
- }
- return nil
- }
- return errors.New("No such container: " + name)
- }
- func (srv *Server) CmdLs(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
- cmd := rcli.Subcmd(stdout, "ls", "[OPTIONS] CONTAINER PATH", "List the contents of a container's directory")
- if err := cmd.Parse(args); err != nil {
- cmd.Usage()
- return nil
- }
- if cmd.NArg() < 2 {
- cmd.Usage()
- return nil
- }
- name, path := cmd.Arg(0), cmd.Arg(1)
- if container, exists := srv.findContainer(name); exists {
- if files, err := container.Filesystem.ReadDir(path); err != nil {
- return err
- } else {
- for _, f := range files {
- fmt.Fprintln(stdout, f.Name())
- }
- }
- return nil
- }
- return errors.New("No such container: " + name)
- }
- func (srv *Server) CmdInspect(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
- cmd := rcli.Subcmd(stdout, "inspect", "[OPTIONS] CONTAINER", "Return low-level information on a container")
- if err := cmd.Parse(args); err != nil {
- cmd.Usage()
- return nil
- }
- if cmd.NArg() < 1 {
- cmd.Usage()
- return nil
- }
- name := cmd.Arg(0)
- if container, exists := srv.findContainer(name); exists {
- data, err := json.Marshal(container)
- if err != nil {
- return err
- }
- indented := new(bytes.Buffer)
- if err = json.Indent(indented, data, "", " "); err != nil {
- return err
- }
- if _, err := io.Copy(stdout, indented); err != nil {
- return err
- }
- return nil
- }
- return errors.New("No such container: " + name)
- }
- func (srv *Server) CmdList(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
- flags := rcli.Subcmd(stdout, "list", "[OPTIONS] [NAME]", "List containers")
- limit := flags.Int("l", 0, "Only show the N most recent versions of each name")
- quiet := flags.Bool("q", false, "only show numeric IDs")
- flags.Parse(args)
- if flags.NArg() > 1 {
- flags.Usage()
- return nil
- }
- var nameFilter string
- if flags.NArg() == 1 {
- nameFilter = flags.Arg(0)
- }
- var names []string
- for name := range srv.containersByName {
- names = append(names, name)
- }
- sort.Strings(names)
- w := tabwriter.NewWriter(stdout, 20, 1, 3, ' ', 0)
- if (!*quiet) {
- fmt.Fprintf(w, "NAME\tID\tCREATED\tSOURCE\tRUNNING\tMOUNTED\tCOMMAND\tPID\tEXIT\n")
- }
- for _, name := range names {
- if nameFilter != "" && nameFilter != name {
- continue
- }
- for idx, container := range *srv.containersByName[name] {
- if *limit > 0 && idx >= *limit {
- break
- }
- if !*quiet {
- for idx, field := range []string{
- /* NAME */ container.GetUserData("name"),
- /* ID */ container.Id,
- /* CREATED */ future.HumanDuration(time.Now().Sub(container.Created)) + " ago",
- /* SOURCE */ container.GetUserData("source"),
- /* RUNNING */ fmt.Sprintf("%v", container.State.Running),
- /* MOUNTED */ fmt.Sprintf("%v", container.Filesystem.IsMounted()),
- /* COMMAND */ fmt.Sprintf("%s %s", container.Path, strings.Join(container.Args, " ")),
- /* PID */ fmt.Sprintf("%v", container.State.Pid),
- /* EXIT CODE */ fmt.Sprintf("%v", container.State.ExitCode),
- } {
- if idx == 0 {
- w.Write([]byte(field))
- } else {
- w.Write([]byte("\t" + field))
- }
- }
- w.Write([]byte{'\n'})
- } else {
- stdout.Write([]byte(container.Id + "\n"))
- }
- }
- }
- if (!*quiet) {
- w.Flush()
- }
- return nil
- }
- func (srv *Server) findContainer(name string) (*docker.Container, bool) {
- // 1: look for container by ID
- if container := srv.docker.Get(name); container != nil {
- return container, true
- }
- // 2: look for a container by name (and pick the most recent)
- if containers, exists := srv.containersByName[name]; exists {
- return (*containers)[0], true
- }
- return nil, false
- }
- func (srv *Server) CmdRm(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
- flags := rcli.Subcmd(stdout, "rm", "[OPTIONS] CONTAINER", "Remove a container")
- if err := flags.Parse(args); err != nil {
- return nil
- }
- for _, name := range flags.Args() {
- if _, err := srv.rm(name); err != nil {
- fmt.Fprintln(stdout, "Error: " + err.Error())
- }
- }
- return nil
- }
- func (srv *Server) CmdPull(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
- if len(args) < 1 {
- return errors.New("Not enough arguments")
- }
- resp, err := http.Get(args[0])
- if err != nil {
- return err
- }
- layer, err := srv.layers.AddLayer(resp.Body, stdout)
- if err != nil {
- return err
- }
- container, err := srv.addContainer(layer.Id(), []string{layer.Path}, args[0], "download")
- if err != nil {
- return err
- }
- fmt.Fprintln(stdout, container.Id)
- return nil
- }
- func (srv *Server) CmdPut(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
- if len(args) < 1 {
- return errors.New("Not enough arguments")
- }
- fmt.Printf("Adding layer\n")
- layer, err := srv.layers.AddLayer(stdin, stdout)
- if err != nil {
- return err
- }
- id := layer.Id()
- if !srv.docker.Exists(id) {
- log.Println("Creating new container: " + id)
- log.Printf("%v\n", srv.docker.List())
- _, err := srv.addContainer(id, []string{layer.Path}, args[0], "upload")
- if err != nil {
- return err
- }
- }
- fmt.Fprintln(stdout, id)
- return nil
- }
- func (srv *Server) CmdCommit(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
- flags := rcli.Subcmd(stdout,
- "fork", "[OPTIONS] CONTAINER [DEST]",
- "Duplicate a container")
- // FIXME "-r" to reset changes in the new container
- if err := flags.Parse(args); err != nil {
- return nil
- }
- srcName, dstName := flags.Arg(0), flags.Arg(1)
- if srcName == "" {
- flags.Usage()
- return nil
- }
- if dstName == "" {
- dstName = srcName
- }
- /*
- if src, exists := srv.findContainer(srcName); exists {
- baseLayer := src.Filesystem.Layers[0]
- //dst := srv.addContainer(dstName, "snapshot:" + src.Id, src.Size)
- //fmt.Fprintln(stdout, dst.Id)
- return nil
- }
- */
- return errors.New("No such container: " + srcName)
- }
- func (srv *Server) CmdTar(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
- flags := rcli.Subcmd(stdout,
- "tar", "CONTAINER",
- "Stream the contents of a container as a tar archive")
- if err := flags.Parse(args); err != nil {
- return nil
- }
- name := flags.Arg(0)
- if container, exists := srv.findContainer(name); exists {
- data, err := container.Filesystem.Tar()
- if err != nil {
- return err
- }
- // Stream the entire contents of the container (basically a volatile snapshot)
- if _, err := io.Copy(stdout, data); err != nil {
- return err
- }
- return nil
- }
- return errors.New("No such container: " + name)
- }
- func (srv *Server) CmdDiff(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
- flags := rcli.Subcmd(stdout,
- "diff", "CONTAINER [OPTIONS]",
- "Inspect changes on a container's filesystem")
- if err := flags.Parse(args); err != nil {
- return nil
- }
- if flags.NArg() < 1 {
- return errors.New("Not enough arguments")
- }
- if container, exists := srv.findContainer(flags.Arg(0)); !exists {
- return errors.New("No such container")
- } else {
- changes, err := container.Filesystem.Changes()
- if err != nil {
- return err
- }
- for _, change := range changes {
- fmt.Fprintln(stdout, change.String())
- }
- }
- return nil
- }
- func (srv *Server) CmdReset(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
- flags := rcli.Subcmd(stdout,
- "reset", "CONTAINER [OPTIONS]",
- "Reset changes to a container's filesystem")
- if err := flags.Parse(args); err != nil {
- return nil
- }
- if flags.NArg() < 1 {
- return errors.New("Not enough arguments")
- }
- for _, name := range flags.Args() {
- if container, exists := srv.findContainer(name); exists {
- if err := container.Filesystem.Reset(); err != nil {
- return errors.New("Reset " + container.Id + ": " + err.Error())
- }
- }
- }
- return nil
- }
- // ByDate wraps an array of layers so they can be sorted by date (most recent first)
- type ByDate []*docker.Container
- func (c *ByDate) Len() int {
- return len(*c)
- }
- func (c *ByDate) Less(i, j int) bool {
- containers := *c
- return containers[j].Created.Before(containers[i].Created)
- }
- func (c *ByDate) Swap(i, j int) {
- containers := *c
- tmp := containers[i]
- containers[i] = containers[j]
- containers[j] = tmp
- }
- func (c *ByDate) Add(container *docker.Container) {
- *c = append(*c, container)
- sort.Sort(c)
- }
- func (c *ByDate) Del(id string) {
- for idx, container := range *c {
- if container.Id == id {
- *c = append((*c)[:idx], (*c)[idx + 1:]...)
- }
- }
- }
- func (srv *Server) addContainer(id string, layers []string, name string, source string) (*docker.Container, error) {
- c, err := srv.docker.Create(id, "", nil, layers, &docker.Config{Hostname: id, Ram: 512 * 1024 * 1024})
- if err != nil {
- return nil, err
- }
- if err := c.SetUserData("name", name); err != nil {
- srv.docker.Destroy(c)
- return nil, err
- }
- if _, exists := srv.containersByName[name]; !exists {
- srv.containersByName[name] = new(ByDate)
- }
- srv.containersByName[name].Add(c)
- return c, nil
- }
- func (srv *Server) rm(id string) (*docker.Container, error) {
- container := srv.docker.Get(id)
- if container == nil {
- return nil, errors.New("No such continer: " + id)
- }
- // Remove from name lookup
- srv.containersByName[container.GetUserData("name")].Del(container.Id)
- if err := srv.docker.Destroy(container); err != nil {
- return container, err
- }
- return container, nil
- }
- func (srv *Server) CmdLogs(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
- flags := rcli.Subcmd(stdout, "logs", "[OPTIONS] CONTAINER", "Fetch the logs of a container")
- if err := flags.Parse(args); err != nil {
- return nil
- }
- if flags.NArg() != 1 {
- flags.Usage()
- return nil
- }
- name := flags.Arg(0)
- if container, exists := srv.findContainer(name); exists {
- if _, err := io.Copy(stdout, container.StdoutLog()); err != nil {
- return err
- }
- if _, err := io.Copy(stdout, container.StderrLog()); err != nil {
- return err
- }
- return nil
- }
- return errors.New("No such container: " + flags.Arg(0))
- }
- func (srv *Server) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
- flags := rcli.Subcmd(stdout, "run", "[OPTIONS] CONTAINER COMMAND [ARG...]", "Run a command in a container")
- fl_attach := flags.Bool("a", false, "Attach stdin and stdout")
- //fl_tty := flags.Bool("t", false, "Allocate a pseudo-tty")
- if err := flags.Parse(args); err != nil {
- return nil
- }
- if flags.NArg() < 2 {
- flags.Usage()
- return nil
- }
- name, cmd := flags.Arg(0), flags.Args()[1:]
- if container, exists := srv.findContainer(name); exists {
- log.Printf("Running container %#v\n", container)
- container.Path = cmd[0]
- container.Args = cmd[1:]
- if *fl_attach {
- cmd_stdout, err := container.StdoutPipe()
- if err != nil {
- return err
- }
- cmd_stderr, err := container.StderrPipe()
- if err != nil {
- return err
- }
- if err := container.Start(); err != nil {
- return err
- }
- sending_stdout := future.Go(func() error { _, err := io.Copy(stdout, cmd_stdout); return err })
- sending_stderr := future.Go(func() error { _, err := io.Copy(stdout, cmd_stderr); return err })
- err_sending_stdout := <-sending_stdout
- err_sending_stderr := <-sending_stderr
- if err_sending_stdout != nil {
- return err_sending_stdout
- }
- return err_sending_stderr
- } else {
- if output, err := container.Output(); err != nil {
- return err
- } else {
- fmt.Printf("-->|%s|\n", output)
- }
- }
- return nil
- }
- return errors.New("No such container: " + name)
- }
- func main() {
- future.Seed()
- flag.Parse()
- d, err := New()
- if err != nil {
- log.Fatal(err)
- }
- go func() {
- if err := rcli.ListenAndServeHTTP(":8080", d); err != nil {
- log.Fatal(err)
- }
- }()
- if err := rcli.ListenAndServeTCP(":4242", d); err != nil {
- log.Fatal(err)
- }
- }
- func New() (*Server, error) {
- store, err := future.NewStore("/var/lib/docker/layers")
- if err != nil {
- return nil, err
- }
- if err := store.Init(); err != nil {
- return nil, err
- }
- d, err := docker.New()
- if err != nil {
- return nil, err
- }
- srv := &Server{
- containersByName: make(map[string]*ByDate),
- layers: store,
- docker: d,
- }
- for _, container := range srv.docker.List() {
- name := container.GetUserData("name")
- if _, exists := srv.containersByName[name]; !exists {
- srv.containersByName[name] = new(ByDate)
- }
- srv.containersByName[name].Add(container)
- }
- log.Printf("Done building index\n")
- return srv, nil
- }
- func (srv *Server) CmdMirror(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
- _, err := io.Copy(stdout, stdin)
- return err
- }
- func (srv *Server) CmdDebug(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
- for {
- if line, err := bufio.NewReader(stdin).ReadString('\n'); err == nil {
- fmt.Printf("--- %s", line)
- } else if err == io.EOF {
- if len(line) > 0 {
- fmt.Printf("--- %s\n", line)
- }
- break
- } else {
- return err
- }
- }
- return nil
- }
- func (srv *Server) CmdWeb(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
- flags := rcli.Subcmd(stdout, "web", "[OPTIONS]", "A web UI for docker")
- showurl := flags.Bool("u", false, "Return the URL of the web UI")
- if err := flags.Parse(args); err != nil {
- return nil
- }
- if *showurl {
- fmt.Fprintln(stdout, "http://localhost:4242/web")
- } else {
- if file, err := os.Open("dockerweb.html"); err != nil {
- return err
- } else if _, err := io.Copy(stdout, file); err != nil {
- return err
- }
- }
- return nil
- }
- type Server struct {
- containersByName map[string]*ByDate
- layers *future.Store
- docker *docker.Docker
- }
|