merge conflicts

This commit is contained in:
Andrea Luzzardi 2013-01-28 12:06:49 -08:00
commit 2cb444248d
5 changed files with 558 additions and 231 deletions

View file

@ -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,7 +56,15 @@ 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),
}
if err := container.Filesystem.createMountPoints(); err != nil {
return nil, err
}
container.stdout.AddWriter(NopWriteCloser(container.stdoutLog))
container.stderr.AddWriter(NopWriteCloser(container.stderrLog))
if err := os.Mkdir(root, 0700); err != nil {
return nil, err
@ -73,14 +86,68 @@ 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
}
if err := container.Filesystem.createMountPoints(); err != nil {
return nil, err
}
container.State = newState()
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 {
if os.IsNotExist(err) {
return make(map[string]string), nil
}
return nil, err
}
data := make(map[string]string)
if err := json.Unmarshal(jsonData, &data); err != nil {
return nil, err
}
return data, nil
}
func (container *Container) saveUserData(data map[string]string) error {
jsonData, err := json.Marshal(data)
if err != nil {
return err
}
return ioutil.WriteFile(path.Join(container.Root, "userdata.json"), jsonData, 0700)
}
func (container *Container) SetUserData(key, value string) error {
data, err := container.loadUserData()
if err != nil {
return err
}
data[key] = value
return container.saveUserData(data)
}
func (container *Container) GetUserData(key string) (string) {
data, err := container.loadUserData()
if err != nil {
return ""
}
if value, exists := data[key]; exists {
return value
}
return ""
}
func (container *Container) save() (err error) {
data, err := json.Marshal(container)
if err != nil {
@ -103,7 +170,7 @@ func (container *Container) generateLXCConfig() error {
}
func (container *Container) Start() error {
if err := container.Filesystem.Mount(); err != nil {
if err := container.Filesystem.EnsureMounted(); err != nil {
return err
}
@ -156,12 +223,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()

View file

@ -1,33 +1,31 @@
package main
import (
"github.com/dotcloud/docker"
"github.com/dotcloud/docker/rcli"
"github.com/dotcloud/docker/fake"
"github.com/dotcloud/docker/future"
"bufio"
"errors"
"log"
"io"
"io/ioutil"
"os/exec"
"flag"
"fmt"
"github.com/kr/pty"
"strings"
"bytes"
"text/tabwriter"
"sort"
"os"
"time"
"net/http"
"encoding/json"
"bytes"
)
func (docker *Docker) Name() string {
func (srv *Server) Name() string {
return "docker"
}
func (docker *Docker) Help() string {
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"},
@ -42,6 +40,7 @@ func (docker *Docker) Help() string {
{"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...)
@ -50,7 +49,176 @@ func (docker *Docker) Help() string {
}
func (docker *Docker) CmdList(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
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")
@ -64,32 +232,33 @@ func (docker *Docker) CmdList(stdin io.ReadCloser, stdout io.Writer, args ...str
nameFilter = flags.Arg(0)
}
var names []string
for name := range docker.containersByName {
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\tSIZE\tCHANGES\tRUNNING\tCOMMAND\n")
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 *docker.containersByName[name] {
for idx, container := range *srv.containersByName[name] {
if *limit > 0 && idx >= *limit {
break
}
if !*quiet {
for idx, field := range []string{
/* NAME */ container.Name,
/* NAME */ container.GetUserData("name"),
/* ID */ container.Id,
/* CREATED */ future.HumanDuration(time.Now().Sub(container.Created)) + " ago",
/* SOURCE */ container.Source,
/* SIZE */ fmt.Sprintf("%.1fM", float32(container.Size) / 1024 / 1024),
/* CHANGES */ fmt.Sprintf("%.1fM", float32(container.BytesChanged) / 1024 / 1024),
/* RUNNING */ fmt.Sprintf("%v", container.Running),
/* COMMAND */ container.CmdString(),
/* 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))
@ -109,33 +278,33 @@ func (docker *Docker) CmdList(stdin io.ReadCloser, stdout io.Writer, args ...str
return nil
}
func (docker *Docker) findContainer(name string) (*Container, bool) {
func (srv *Server) findContainer(name string) (*docker.Container, bool) {
// 1: look for container by ID
if container, exists := docker.containers[name]; exists {
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 := docker.containersByName[name]; exists {
if containers, exists := srv.containersByName[name]; exists {
return (*containers)[0], true
}
return nil, false
}
func (docker *Docker) CmdRm(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
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 := docker.rm(name); err != nil {
if _, err := srv.rm(name); err != nil {
fmt.Fprintln(stdout, "Error: " + err.Error())
}
}
return nil
}
func (docker *Docker) CmdPull(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
func (srv *Server) CmdPull(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
if len(args) < 1 {
return errors.New("Not enough arguments")
}
@ -143,31 +312,42 @@ func (docker *Docker) CmdPull(stdin io.ReadCloser, stdout io.Writer, args ...str
if err != nil {
return err
}
layer, err := docker.layers.AddLayer(resp.Body, stdout)
layer, err := srv.layers.AddLayer(resp.Body, stdout)
if err != nil {
return err
}
docker.addContainer(args[0], "download", 0)
fmt.Fprintln(stdout, layer.Id())
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 (docker *Docker) CmdPut(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
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 := docker.layers.AddLayer(stdin, stdout)
layer, err := srv.layers.AddLayer(stdin, stdout)
if err != nil {
return err
}
docker.addContainer(args[0], "upload", 0)
fmt.Fprintln(stdout, layer.Id())
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 (docker *Docker) CmdCommit(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
func (srv *Server) CmdCommit(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
flags := rcli.Subcmd(stdout,
"fork", "[OPTIONS] CONTAINER [DEST]",
"Duplicate a container")
@ -183,15 +363,18 @@ func (docker *Docker) CmdCommit(stdin io.ReadCloser, stdout io.Writer, args ...s
if dstName == "" {
dstName = srcName
}
if src, exists := docker.findContainer(srcName); exists {
dst := docker.addContainer(dstName, "snapshot:" + src.Id, src.Size)
fmt.Fprintln(stdout, dst.Id)
/*
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 (docker *Docker) CmdTar(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
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")
@ -199,70 +382,67 @@ func (docker *Docker) CmdTar(stdin io.ReadCloser, stdout io.Writer, args ...stri
return nil
}
name := flags.Arg(0)
if _, exists := docker.findContainer(name); exists {
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)
return fake.WriteFakeTar(stdout)
if _, err := io.Copy(stdout, data); err != nil {
return err
}
return nil
}
return errors.New("No such container: " + name)
}
func (docker *Docker) CmdDiff(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
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")
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
}
if flags.NArg() < 1 {
return errors.New("Not enough arguments")
}
if container, exists := docker.findContainer(flags.Arg(0)); !exists {
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 (docker *Docker) 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)
type ByDate []*Container
type ByDate []*docker.Container
func (c *ByDate) Len() int {
return len(*c)
@ -280,7 +460,7 @@ func (c *ByDate) Swap(i, j int) {
containers[j] = tmp
}
func (c *ByDate) Add(container *Container) {
func (c *ByDate) Add(container *docker.Container) {
*c = append(*c, container)
sort.Sort(c)
}
@ -294,46 +474,38 @@ func (c *ByDate) Del(id string) {
}
func (docker *Docker) addContainer(name string, source string, size uint) *Container {
if size == 0 {
size = fake.RandomContainerSize()
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
}
c := &Container{
Id: future.RandomId(),
Name: name,
Created: time.Now(),
Source: source,
Size: size,
stdinLog: new(bytes.Buffer),
stdoutLog: new(bytes.Buffer),
if err := c.SetUserData("name", name); err != nil {
srv.docker.Destroy(c)
return nil, err
}
docker.containers[c.Id] = c
if _, exists := docker.containersByName[c.Name]; !exists {
docker.containersByName[c.Name] = new(ByDate)
if _, exists := srv.containersByName[name]; !exists {
srv.containersByName[name] = new(ByDate)
}
docker.containersByName[c.Name].Add(c)
return c
srv.containersByName[name].Add(c)
return c, nil
}
func (docker *Docker) rm(id string) (*Container, error) {
if container, exists := docker.containers[id]; exists {
if container.Running {
return nil, errors.New("Container is running: " + id)
} else {
// Remove from name lookup
docker.containersByName[container.Name].Del(container.Id)
// Remove from id lookup
delete(docker.containers, container.Id)
return container, 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)
}
return nil, errors.New(fmt.Sprintf("No such container: %s", 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 (docker *Docker) CmdLogs(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
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
@ -343,19 +515,23 @@ func (docker *Docker) CmdLogs(stdin io.ReadCloser, stdout io.Writer, args ...str
return nil
}
name := flags.Arg(0)
if container, exists := docker.findContainer(name); exists {
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 (docker *Docker) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
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
}
@ -364,62 +540,60 @@ func (docker *Docker) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...stri
return nil
}
name, cmd := flags.Arg(0), flags.Args()[1:]
if container, exists := docker.findContainer(name); exists {
if container.Running {
return errors.New("Already running: " + name)
}
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 {
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)
}
func startCommand(cmd *exec.Cmd, interactive bool) (io.WriteCloser, io.ReadCloser, error) {
if interactive {
term, err := pty.Start(cmd)
if err != nil {
return nil, nil, err
}
return term, term, nil
}
stdin, err := cmd.StdinPipe()
if err != nil {
return nil, nil, err
}
stdout, err := cmd.StdoutPipe()
if err != nil {
return nil, nil, err
}
if err := cmd.Start(); err != nil {
return nil, nil, err
}
return stdin, stdout, nil
}
func main() {
future.Seed()
flag.Parse()
docker, err := New()
d, err := New()
if err != nil {
log.Fatal(err)
}
go func() {
if err := rcli.ListenAndServeHTTP(":8080", docker); err != nil {
if err := rcli.ListenAndServeHTTP(":8080", d); err != nil {
log.Fatal(err)
}
}()
if err := rcli.ListenAndServeTCP(":4242", docker); err != nil {
if err := rcli.ListenAndServeTCP(":4242", d); err != nil {
log.Fatal(err)
}
}
func New() (*Docker, error) {
func New() (*Server, error) {
store, err := future.NewStore("/var/lib/docker/layers")
if err != nil {
return nil, err
@ -427,20 +601,32 @@ func New() (*Docker, error) {
if err := store.Init(); err != nil {
return nil, err
}
return &Docker{
d, err := docker.New()
if err != nil {
return nil, err
}
srv := &Server{
containersByName: make(map[string]*ByDate),
containers: make(map[string]*Container),
layers: store,
}, nil
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 (docker *Docker) CmdMirror(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
func (srv *Server) CmdMirror(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
_, err := io.Copy(stdout, stdin)
return err
}
func (docker *Docker) CmdDebug(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
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)
@ -456,7 +642,7 @@ func (docker *Docker) CmdDebug(stdin io.ReadCloser, stdout io.Writer, args ...st
return nil
}
func (docker *Docker) CmdWeb(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
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 {
@ -475,85 +661,9 @@ func (docker *Docker) CmdWeb(stdin io.ReadCloser, stdout io.Writer, args ...stri
}
type Docker struct {
containers map[string]*Container
type Server struct {
containersByName map[string]*ByDate
layers *future.Store
}
type Container struct {
Id string
Name string
Created time.Time
Source string
Size uint
FilesChanged uint
BytesChanged uint
Running bool
Cmd string
Args []string
stdoutLog *bytes.Buffer
stdinLog *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.Cmd = command
c.Args = args
// Reset logs
c.stdoutLog.Reset()
c.stdinLog.Reset()
cmd := exec.Command(c.Cmd, c.Args...)
cmd_stdin, cmd_stdout, err := startCommand(cmd, tty)
if err != nil {
return err
}
c.Running = true
// ADD FAKE RANDOM CHANGES
c.FilesChanged = fake.RandomFilesChanged()
c.BytesChanged = fake.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.Cmd}, c.Args...), " ")
docker *docker.Docker
}

View file

@ -5,6 +5,10 @@ import (
"math/rand"
"io"
"archive/tar"
"github.com/dotcloud/docker"
"os/exec"
"strings"
"github.com/kr/pty"
)
@ -47,3 +51,51 @@ func RandomFilesChanged() uint {
func RandomContainerSize() uint {
return uint(rand.Int31n(142 * 1024 * 1024))
}
func ContainerRunning() bool {
return false
}
type Container struct {
*docker.Container
Name string
Source string
Size uint
FilesChanged uint
BytesChanged uint
}
func NewContainer(c *docker.Container) *Container {
return &Container{
Container: c,
Name: c.GetUserData("name"),
}
}
func (c *Container) CmdString() string {
return strings.Join(append([]string{c.Path}, c.Args...), " ")
}
func startCommand(cmd *exec.Cmd, interactive bool) (io.WriteCloser, io.ReadCloser, error) {
if interactive {
term, err := pty.Start(cmd)
if err != nil {
return nil, nil, err
}
return term, term, nil
}
stdin, err := cmd.StdinPipe()
if err != nil {
return nil, nil, err
}
stdout, err := cmd.StdoutPipe()
if err != nil {
return nil, nil, err
}
if err := cmd.Start(); err != nil {
return nil, nil, err
}
return stdin, stdout, nil
}

View file

@ -8,6 +8,8 @@ import (
"strings"
"syscall"
"time"
"io"
"io/ioutil"
)
type Filesystem struct {
@ -94,6 +96,24 @@ func (fs *Filesystem) IsMounted() bool {
return mntpointSt.Dev != parentSt.Dev
}
// Tar returns the contents of the filesystem as an uncompressed tar stream
func (fs *Filesystem) Tar() (io.Reader, error) {
if err := fs.EnsureMounted(); err != nil {
return nil, err
}
return Tar(fs.RootFS)
}
func (fs *Filesystem) EnsureMounted() error {
if !fs.IsMounted() {
if err := fs.Mount(); err != nil {
return err
}
}
return nil
}
type ChangeType int
const (
@ -107,6 +127,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 {
@ -179,6 +209,35 @@ func (fs *Filesystem) Changes() ([]Change, error) {
return changes, nil
}
// Reset removes all changes to the filesystem, reverting it to its initial state.
func (fs *Filesystem) Reset() error {
if err := os.RemoveAll(fs.RWPath); err != nil {
return err
}
// We removed the RW directory itself along with its content: let's re-create an empty one.
if err := fs.createMountPoints(); err != nil {
return err
}
return nil
}
// Open opens the named file for reading.
func (fs *Filesystem) OpenFile(path string, flag int, perm os.FileMode) (*os.File, error) {
if err := fs.EnsureMounted(); err != nil {
return nil, err
}
return os.OpenFile(filepath.Join(fs.RootFS, path), flag, perm)
}
// ReadDir reads the directory named by dirname, relative to the Filesystem's root,
// and returns a list of sorted directory entries
func (fs *Filesystem) ReadDir(dirname string) ([]os.FileInfo, error) {
if err := fs.EnsureMounted(); err != nil {
return nil, err
}
return ioutil.ReadDir(filepath.Join(fs.RootFS, dirname))
}
func newFilesystem(rootfs string, rwpath string, layers []string) *Filesystem {
return &Filesystem{
RootFS: rootfs,

View file

@ -5,8 +5,38 @@ import (
"container/list"
"io"
"sync"
"os/exec"
)
// Tar generates a tar archive from a filesystem path, and returns it as a stream.
// Path must point to a directory.
func Tar(path string) (io.Reader, error) {
cmd := exec.Command("tar", "-C", path, "-c", ".")
output, err := cmd.StdoutPipe()
if err != nil {
return nil, err
}
if err := cmd.Start(); err != nil {
return nil, err
}
// FIXME: errors will not be passed because we don't wait for the command.
// Instead, consumers will hit EOF right away.
// This can be fixed by waiting for the process to exit, or for the first write
// on stdout, whichever comes first.
return output, nil
}
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