diff --git a/container.go b/container.go index 9de0df4758..5b1eceba0b 100644 --- a/container.go +++ b/container.go @@ -11,6 +11,8 @@ import ( "path" "syscall" "time" + "strings" + "bytes" ) type Container struct { @@ -30,6 +32,9 @@ type Container struct { cmd *exec.Cmd stdout *writeBroadcaster stderr *writeBroadcaster + + stdoutLog *bytes.Buffer + stderrLog *bytes.Buffer } type Config struct { @@ -51,8 +56,13 @@ func createContainer(id string, root string, command string, args []string, laye lxcConfigPath: path.Join(root, "config.lxc"), stdout: newWriteBroadcaster(), stderr: newWriteBroadcaster(), + stdoutLog: new(bytes.Buffer), + stderrLog: new(bytes.Buffer), } + container.stdout.AddWriter(NopWriteCloser(container.stdoutLog)) + container.stderr.AddWriter(NopWriteCloser(container.stderrLog)) + if err := os.Mkdir(root, 0700); err != nil { return nil, err } @@ -73,6 +83,8 @@ func loadContainer(containerPath string) (*Container, error) { container := &Container{ stdout: newWriteBroadcaster(), stderr: newWriteBroadcaster(), + stdoutLog: new(bytes.Buffer), + stderrLog: new(bytes.Buffer), } if err := json.Unmarshal(data, container); err != nil { return nil, err @@ -81,6 +93,11 @@ func loadContainer(containerPath string) (*Container, error) { return container, nil } + +func (container *Container) Cmd() *exec.Cmd { + return container.cmd +} + func (container *Container) loadUserData() (map[string]string, error) { jsonData, err := ioutil.ReadFile(path.Join(container.Root, "userdata.json")) if err != nil { @@ -200,12 +217,21 @@ func (container *Container) StdoutPipe() (io.ReadCloser, error) { return newBufReader(reader), nil } +func (container *Container) StdoutLog() io.Reader { + return strings.NewReader(container.stdoutLog.String()) +} + + func (container *Container) StderrPipe() (io.ReadCloser, error) { reader, writer := io.Pipe() container.stderr.AddWriter(writer) return newBufReader(reader), nil } +func (container *Container) StderrLog() io.Reader { + return strings.NewReader(container.stderrLog.String()) +} + func (container *Container) monitor() { // Wait for the program to exit container.cmd.Wait() diff --git a/dockerd/dockerd.go b/dockerd/dockerd.go index 5490f07d1d..a18e565f4c 100644 --- a/dockerd/dockerd.go +++ b/dockerd/dockerd.go @@ -9,11 +9,9 @@ import ( "errors" "log" "io" - "io/ioutil" "flag" "fmt" "strings" - "bytes" "text/tabwriter" "sort" "os" @@ -49,6 +47,29 @@ func (srv *Server) Help() string { } +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: " + container.Id) + } + } + return nil +} + 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") @@ -87,7 +108,7 @@ func (srv *Server) CmdList(stdin io.ReadCloser, stdout io.Writer, args ...string /* SOURCE */ container.GetUserData("source"), /* SIZE */ fmt.Sprintf("%.1fM", float32(fake.RandomContainerSize()) / 1024 / 1024), /* CHANGES */ fmt.Sprintf("%.1fM", float32(fake.RandomBytesChanged() / 1024 / 1024)), - /* RUNNING */ fmt.Sprintf("%v", fake.ContainerRunning()), + /* RUNNING */ fmt.Sprintf("%v", container.State.Running), /* COMMAND */ fmt.Sprintf("%s %s", container.Path, strings.Join(container.Args, " ")), } { if idx == 0 { @@ -193,11 +214,14 @@ func (srv *Server) CmdCommit(stdin io.ReadCloser, stdout io.Writer, args ...stri if dstName == "" { dstName = srcName } - if _, exists := srv.findContainer(srcName); exists { + /* + 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) } @@ -220,9 +244,6 @@ func (srv *Server) CmdDiff(stdin io.ReadCloser, stdout io.Writer, args ...string flags := rcli.Subcmd(stdout, "diff", "CONTAINER [OPTIONS]", "Inspect changes on a container's filesystem") - fl_diff := flags.Bool("d", true, "Show changes in diff format") - fl_bytes := flags.Bool("b", false, "Show how many bytes have been changed") - fl_list := flags.Bool("l", false, "Show a list of changed files") if err := flags.Parse(args); err != nil { return nil } @@ -231,44 +252,37 @@ func (srv *Server) CmdDiff(stdin io.ReadCloser, stdout io.Writer, args ...string } if container, exists := srv.findContainer(flags.Arg(0)); !exists { return errors.New("No such container") - } else if *fl_bytes { - fmt.Fprintf(stdout, "%d\n", container.BytesChanged) - } else if *fl_list { - // FAKE - fmt.Fprintf(stdout, strings.Join([]string{ - "/etc/postgres/pg.conf", - "/etc/passwd", - "/var/lib/postgres", - "/usr/bin/postgres", - "/usr/bin/psql", - "/var/log/postgres", - "/var/log/postgres/postgres.log", - "/var/log/postgres/postgres.log.0", - "/var/log/postgres/postgres.log.1.gz"}, "\n")) - } else if *fl_diff { - // Achievement unlocked: embed a diff of your code as a string in your code - fmt.Fprintf(stdout, ` -diff --git a/dockerd/dockerd.go b/dockerd/dockerd.go -index 2dae694..e43caca 100644 ---- a/dockerd/dockerd.go -+++ b/dockerd/dockerd.go -@@ -158,6 +158,7 @@ func (srv *Server) CmdDiff(stdin io.ReadCloser, stdout io.Writer, args ...str - flags := rcli.Subcmd(stdout, - "diff", "CONTAINER [OPTIONS]", - "Inspect changes on a container's filesystem") -+ fl_diff := flags.Bool("d", true, "Show changes in diff format") - fl_bytes := flags.Bool("b", false, "Show how many bytes have been changes") - fl_list := flags.Bool("l", false, "Show a list of changed files") - fl_download := flags.Bool("d", false, "Download the changes as gzipped tar stream") -`) - return nil } else { - flags.Usage() - return nil + 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) @@ -349,15 +363,19 @@ func (srv *Server) CmdLogs(stdin io.ReadCloser, stdout io.Writer, args ...string 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") + //fl_tty := flags.Bool("t", false, "Allocate a pseudo-tty") if err := flags.Parse(args); err != nil { return nil } @@ -367,16 +385,37 @@ func (srv *Server) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...string) } name, cmd := flags.Arg(0), flags.Args()[1:] if container, exists := srv.findContainer(name); exists { - if container.Running { - return errors.New("Already running: " + name) - } + log.Printf("Running container %#v\n", container) + container.Path = cmd[0] + container.Args = cmd[1:] if *fl_attach { - return container.Run(cmd[0], cmd[1:], stdin, stdout, *fl_tty) + 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 { - go container.Run(cmd[0], cmd[1:], ioutil.NopCloser(new(bytes.Buffer)), ioutil.Discard, *fl_tty) - fmt.Fprintln(stdout, container.Id) - return nil + if output, err := container.Output(); err != nil { + return err + } else { + fmt.Printf("-->|%s|\n", output) + } } + return nil } return errors.New("No such container: " + name) } @@ -415,10 +454,7 @@ func New() (*Server, error) { layers: store, docker: d, } - // Update name index - log.Printf("Building name index from %s...\n") for _, container := range srv.docker.List() { - log.Printf("Indexing %s to %s\n", container.Id, container.GetUserData("name")) name := container.GetUserData("name") if _, exists := srv.containersByName[name]; !exists { srv.containersByName[name] = new(ByDate) diff --git a/fake/fake.go b/fake/fake.go index 546d7a1730..990d84a0b5 100644 --- a/fake/fake.go +++ b/fake/fake.go @@ -6,11 +6,8 @@ import ( "io" "archive/tar" "github.com/dotcloud/docker" - "github.com/dotcloud/docker/future" - "errors" "os/exec" "strings" - "fmt" "github.com/kr/pty" ) @@ -66,77 +63,14 @@ type Container struct { Size uint FilesChanged uint BytesChanged uint - Running bool - stdoutLog *bytes.Buffer - stdinLog *bytes.Buffer } func NewContainer(c *docker.Container) *Container { return &Container{ Container: c, Name: c.GetUserData("name"), - stdoutLog: new(bytes.Buffer), - stdinLog: new(bytes.Buffer), } } - -func (c *Container) Run(command string, args []string, stdin io.ReadCloser, stdout io.Writer, tty bool) error { - // Not thread-safe - if c.Running { - return errors.New("Already running") - } - c.Path = command - c.Args = args - // Reset logs - c.stdoutLog.Reset() - c.stdinLog.Reset() - cmd := exec.Command(c.Path, c.Args...) - cmd_stdin, cmd_stdout, err := startCommand(cmd, tty) - if err != nil { - return err - } - c.Running = true - // ADD FAKE RANDOM CHANGES - c.FilesChanged = RandomFilesChanged() - c.BytesChanged = RandomBytesChanged() - copy_out := future.Go(func() error { - _, err := io.Copy(io.MultiWriter(stdout, c.stdoutLog), cmd_stdout) - return err - }) - future.Go(func() error { - _, err := io.Copy(io.MultiWriter(cmd_stdin, c.stdinLog), stdin) - cmd_stdin.Close() - stdin.Close() - return err - }) - wait := future.Go(func() error { - err := cmd.Wait() - c.Running = false - return err - }) - if err := <-copy_out; err != nil { - if c.Running { - return err - } - } - if err := <-wait; err != nil { - if status, ok := err.(*exec.ExitError); ok { - fmt.Fprintln(stdout, status) - return nil - } - return err - } - return nil -} - -func (c *Container) StdoutLog() io.Reader { - return strings.NewReader(c.stdoutLog.String()) -} - -func (c *Container) StdinLog() io.Reader { - return strings.NewReader(c.stdinLog.String()) -} - func (c *Container) CmdString() string { return strings.Join(append([]string{c.Path}, c.Args...), " ") } diff --git a/filesystem.go b/filesystem.go index 41dacd5fdd..54a7e613a2 100644 --- a/filesystem.go +++ b/filesystem.go @@ -80,6 +80,16 @@ type Change struct { Kind ChangeType } +func (change *Change) String() string { + var kind string + switch change.Kind { + case ChangeModify: kind = "C" + case ChangeAdd: kind = "A" + case ChangeDelete: kind = "D" + } + return fmt.Sprintf("%s %s", kind, change.Path) +} + func (fs *Filesystem) Changes() ([]Change, error) { var changes []Change err := filepath.Walk(fs.RWPath, func(path string, f os.FileInfo, err error) error { @@ -152,6 +162,16 @@ func (fs *Filesystem) Changes() ([]Change, error) { return changes, nil } +func (fs *Filesystem) Reset() error { + if err := os.RemoveAll(fs.RWPath); err != nil { + return err + } + if err := fs.createMountPoints(); err != nil { + return err + } + return nil +} + func newFilesystem(rootfs string, rwpath string, layers []string) *Filesystem { return &Filesystem{ RootFS: rootfs, diff --git a/utils.go b/utils.go index 63b88956c5..4661a31d81 100644 --- a/utils.go +++ b/utils.go @@ -7,6 +7,16 @@ import ( "sync" ) +type nopWriteCloser struct { + io.Writer +} + +func (w *nopWriteCloser) Close() error { return nil } + +func NopWriteCloser(w io.Writer) io.WriteCloser { + return &nopWriteCloser{w} +} + type bufReader struct { buf *bytes.Buffer reader io.Reader