diff --git a/.gitignore b/.gitignore index 495c502187..fc4ca5da3b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ .vagrant -docker -dockerd +docker/docker +dockerd/dockerd .*.swp a.out diff --git a/README.md b/README.md index cf3f74dc61..aea3d10e9d 100644 --- a/README.md +++ b/README.md @@ -157,7 +157,7 @@ Step by step host setup 3. Type the following commands: apt-get update - apt-get install lxc wget bsdtar + apt-get install lxc wget bsdtar curl 4. Download the latest version of the [docker binaries](https://dl.dropbox.com/u/20637798/docker.tar.gz) (`wget https://dl.dropbox.com/u/20637798/docker.tar.gz`) (warning: this may not be the most up-to-date build) 5. Extract the contents of the tar file `tar -xf docker.tar.gz` diff --git a/client/client.go b/client/client.go index 5a8aac3807..4c4ea1c5e3 100644 --- a/client/client.go +++ b/client/client.go @@ -1,8 +1,8 @@ package client import ( - "github.com/dotcloud/docker/rcli" "github.com/dotcloud/docker/future" + "github.com/dotcloud/docker/rcli" "io" "io/ioutil" "log" @@ -112,7 +112,7 @@ func InteractiveMode(scripts ...string) error { return err } io.WriteString(rcfile, "enable -n help\n") - os.Setenv("PATH", tmp + ":" + os.Getenv("PATH")) + os.Setenv("PATH", tmp+":"+os.Getenv("PATH")) os.Setenv("PS1", "\\h docker> ") shell := exec.Command("/bin/bash", append([]string{"--rcfile", rcfile.Name()}, scripts...)...) shell.Stdin = os.Stdin diff --git a/client/term.go b/client/term.go index ed52be96b4..a988d0d796 100644 --- a/client/term.go +++ b/client/term.go @@ -15,7 +15,6 @@ type Termios struct { Ospeed uintptr } - const ( // Input flags inpck = 0x010 @@ -35,113 +34,110 @@ const ( ) const ( - HUPCL = 0x4000 - ICANON = 0x100 - ICRNL = 0x100 - IEXTEN = 0x400 - BRKINT = 0x2 - CFLUSH = 0xf - CLOCAL = 0x8000 - CREAD = 0x800 - CS5 = 0x0 - CS6 = 0x100 - CS7 = 0x200 - CS8 = 0x300 - CSIZE = 0x300 - CSTART = 0x11 - CSTATUS = 0x14 - CSTOP = 0x13 - CSTOPB = 0x400 - CSUSP = 0x1a - IGNBRK = 0x1 - IGNCR = 0x80 - IGNPAR = 0x4 - IMAXBEL = 0x2000 - INLCR = 0x40 - INPCK = 0x10 - ISIG = 0x80 - ISTRIP = 0x20 - IUTF8 = 0x4000 - IXANY = 0x800 - IXOFF = 0x400 - IXON = 0x200 - NOFLSH = 0x80000000 - OCRNL = 0x10 - OFDEL = 0x20000 - OFILL = 0x80 - ONLCR = 0x2 - ONLRET = 0x40 - ONOCR = 0x20 - ONOEOT = 0x8 - OPOST = 0x1 -RENB = 0x1000 - PARMRK = 0x8 - PARODD = 0x2000 + HUPCL = 0x4000 + ICANON = 0x100 + ICRNL = 0x100 + IEXTEN = 0x400 + BRKINT = 0x2 + CFLUSH = 0xf + CLOCAL = 0x8000 + CREAD = 0x800 + CS5 = 0x0 + CS6 = 0x100 + CS7 = 0x200 + CS8 = 0x300 + CSIZE = 0x300 + CSTART = 0x11 + CSTATUS = 0x14 + CSTOP = 0x13 + CSTOPB = 0x400 + CSUSP = 0x1a + IGNBRK = 0x1 + IGNCR = 0x80 + IGNPAR = 0x4 + IMAXBEL = 0x2000 + INLCR = 0x40 + INPCK = 0x10 + ISIG = 0x80 + ISTRIP = 0x20 + IUTF8 = 0x4000 + IXANY = 0x800 + IXOFF = 0x400 + IXON = 0x200 + NOFLSH = 0x80000000 + OCRNL = 0x10 + OFDEL = 0x20000 + OFILL = 0x80 + ONLCR = 0x2 + ONLRET = 0x40 + ONOCR = 0x20 + ONOEOT = 0x8 + OPOST = 0x1 + RENB = 0x1000 + PARMRK = 0x8 + PARODD = 0x2000 - TOSTOP = 0x400000 - VDISCARD = 0xf - VDSUSP = 0xb - VEOF = 0x0 - VEOL = 0x1 - VEOL2 = 0x2 - VERASE = 0x3 - VINTR = 0x8 - VKILL = 0x5 - VLNEXT = 0xe - VMIN = 0x10 - VQUIT = 0x9 - VREPRINT = 0x6 - VSTART = 0xc - VSTATUS = 0x12 - VSTOP = 0xd - VSUSP = 0xa - VT0 = 0x0 - VT1 = 0x10000 - VTDLY = 0x10000 - VTIME = 0x11 - ECHO = 0x00000008 + TOSTOP = 0x400000 + VDISCARD = 0xf + VDSUSP = 0xb + VEOF = 0x0 + VEOL = 0x1 + VEOL2 = 0x2 + VERASE = 0x3 + VINTR = 0x8 + VKILL = 0x5 + VLNEXT = 0xe + VMIN = 0x10 + VQUIT = 0x9 + VREPRINT = 0x6 + VSTART = 0xc + VSTATUS = 0x12 + VSTOP = 0xd + VSUSP = 0xa + VT0 = 0x0 + VT1 = 0x10000 + VTDLY = 0x10000 + VTIME = 0x11 + ECHO = 0x00000008 - PENDIN = 0x20000000 + PENDIN = 0x20000000 ) type State struct { - termios Termios + termios Termios } // IsTerminal returns true if the given file descriptor is a terminal. func IsTerminal(fd int) bool { - var termios Termios - _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(getTermios), uintptr(unsafe.Pointer(&termios)), 0, 0, 0) - return err == 0 + var termios Termios + _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(getTermios), uintptr(unsafe.Pointer(&termios)), 0, 0, 0) + return err == 0 } // MakeRaw put the terminal connected to the given file descriptor into raw // mode and returns the previous state of the terminal so that it can be // restored. func MakeRaw(fd int) (*State, error) { - var oldState State - if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(getTermios), uintptr(unsafe.Pointer(&oldState.termios)), 0, 0, 0); err != 0 { - return nil, err - } + var oldState State + if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(getTermios), uintptr(unsafe.Pointer(&oldState.termios)), 0, 0, 0); err != 0 { + return nil, err + } - newState := oldState.termios - newState.Iflag &^= ISTRIP | INLCR | IGNCR | IXON | IXOFF - newState.Iflag |= ICRNL - newState.Oflag |= ONLCR - newState.Lflag &^= ECHO | ICANON | ISIG - if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(setTermios), uintptr(unsafe.Pointer(&newState)), 0, 0, 0); err != 0 { - return nil, err - } + newState := oldState.termios + newState.Iflag &^= ISTRIP | INLCR | IGNCR | IXON | IXOFF + newState.Iflag |= ICRNL + newState.Oflag |= ONLCR + newState.Lflag &^= ECHO | ICANON | ISIG + if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(setTermios), uintptr(unsafe.Pointer(&newState)), 0, 0, 0); err != 0 { + return nil, err + } - return &oldState, nil + return &oldState, nil } - // Restore restores the terminal connected to the given file descriptor to a // previous state. func Restore(fd int, state *State) error { - _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(setTermios), uintptr(unsafe.Pointer(&state.termios)), 0, 0, 0) - return err + _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(setTermios), uintptr(unsafe.Pointer(&state.termios)), 0, 0, 0) + return err } - - diff --git a/container.go b/container.go index 67c450282f..7f5d592e81 100644 --- a/container.go +++ b/container.go @@ -460,10 +460,13 @@ func (container *Container) Restart() error { return nil } -func (container *Container) Wait() { +// Wait blocks until the container stops running, then returns its exit code. +func (container *Container) Wait() int { + for container.State.Running { container.State.wait() } + return container.State.ExitCode } func (container *Container) WaitTimeout(timeout time.Duration) error { diff --git a/docker/docker.go b/docker/docker.go index efc93620a4..fa9011defa 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -2,10 +2,10 @@ package main import ( "flag" + "github.com/dotcloud/docker/client" "log" "os" "path" - "github.com/dotcloud/docker/client" ) func main() { @@ -27,4 +27,3 @@ func main() { } } } - diff --git a/examples/pybuilder b/examples/pybuilder index a9a2e8276f..dfab8ab2dd 100755 --- a/examples/pybuilder +++ b/examples/pybuilder @@ -49,7 +49,7 @@ if [ "$CMD" = "build" ]; then fi if attach $BUILD_JOB ; then - BUILD_STATUS=`docker ps -a | sed -E -n "s/^$BUILD_JOB.*Exit ([0-9]+) *$/\1/p"` + BUILD_STATUS=`docker wait $BUILD_JOB` if [ -z "$BUILD_STATUS" -o "$BUILD_STATUS" != 0 ]; then echo "Build failed" exit 1 diff --git a/fake/fake.go b/fake/fake.go index c1e694515b..8edbd88e6b 100644 --- a/fake/fake.go +++ b/fake/fake.go @@ -1,20 +1,19 @@ package fake import ( - "bytes" - "math/rand" - "io" "archive/tar" - "os/exec" + "bytes" "github.com/kr/pty" + "io" + "math/rand" + "os/exec" ) - func FakeTar() (io.Reader, error) { content := []byte("Hello world!\n") buf := new(bytes.Buffer) tw := tar.NewWriter(buf) - for _, name := range []string {"hello", "etc/postgres/postgres.conf", "etc/passwd", "var/log/postgres/postgres.conf"} { + for _, name := range []string{"hello", "etc/postgres/postgres.conf", "etc/passwd", "var/log/postgres/postgres.conf"} { hdr := new(tar.Header) hdr.Size = int64(len(content)) hdr.Name = name @@ -27,7 +26,6 @@ func FakeTar() (io.Reader, error) { return buf, nil } - func WriteFakeTar(dst io.Writer) error { if data, err := FakeTar(); err != nil { return err @@ -37,7 +35,6 @@ func WriteFakeTar(dst io.Writer) error { return nil } - func RandomBytesChanged() uint { return uint(rand.Int31n(24 * 1024 * 1024)) } @@ -54,7 +51,6 @@ func ContainerRunning() bool { return false } - func StartCommand(cmd *exec.Cmd, interactive bool) (io.WriteCloser, io.ReadCloser, error) { if interactive { term, err := pty.Start(cmd) @@ -76,5 +72,3 @@ func StartCommand(cmd *exec.Cmd, interactive bool) (io.WriteCloser, io.ReadClose } return stdin, stdout, nil } - - diff --git a/filesystem.go b/filesystem.go index eadc6f69be..26686b3a59 100644 --- a/filesystem.go +++ b/filesystem.go @@ -3,6 +3,7 @@ package docker import ( "errors" "fmt" + "github.com/dotcloud/docker/image" "io" "io/ioutil" "os" @@ -10,7 +11,6 @@ import ( "strings" "syscall" "time" - "github.com/dotcloud/docker/image" ) type Filesystem struct { diff --git a/future/future.go b/future/future.go index b1427e1007..33f1f8925c 100644 --- a/future/future.go +++ b/future/future.go @@ -1,12 +1,13 @@ package future import ( - "crypto/sha256" - "io" - "fmt" - "time" "bytes" + "crypto/sha256" + "fmt" + "io" "math/rand" + "os/exec" + "time" ) func Seed() { @@ -30,18 +31,18 @@ func HumanDuration(d time.Duration) string { return "About a minute" } else if minutes < 60 { return fmt.Sprintf("%d minutes", minutes) - } else if hours := int(d.Hours()); hours == 1{ + } else if hours := int(d.Hours()); hours == 1 { return "About an hour" } else if hours < 48 { return fmt.Sprintf("%d hours", hours) - } else if hours < 24 * 7 * 2 { - return fmt.Sprintf("%d days", hours / 24) - } else if hours < 24 * 30 * 3 { - return fmt.Sprintf("%d weeks", hours / 24 / 7) - } else if hours < 24 * 365 * 2 { - return fmt.Sprintf("%d months", hours / 24 / 30) + } else if hours < 24*7*2 { + return fmt.Sprintf("%d days", hours/24) + } else if hours < 24*30*3 { + return fmt.Sprintf("%d weeks", hours/24/7) + } else if hours < 24*365*2 { + return fmt.Sprintf("%d months", hours/24/30) } - return fmt.Sprintf("%d years", d.Hours() / 24 / 365) + return fmt.Sprintf("%d years", d.Hours()/24/365) } func randomBytes() io.Reader { @@ -84,3 +85,18 @@ func Pv(src io.Reader, info io.Writer) io.Reader { return r } +// Curl makes an http request by executing the unix command 'curl', and returns +// the body of the response. If `stderr` is not nil, a progress bar will be +// written to it. +func Curl(url string, stderr io.Writer) (io.Reader, error) { + curl := exec.Command("curl", "-#", "-L", url) + output, err := curl.StdoutPipe() + if err != nil { + return nil, err + } + curl.Stderr = stderr + if err := curl.Start(); err != nil { + return nil, err + } + return output, nil +} diff --git a/image/archive.go b/image/archive.go index bc8edb4bca..501d549cc8 100644 --- a/image/archive.go +++ b/image/archive.go @@ -1,30 +1,32 @@ package image import ( + "errors" "io" "io/ioutil" "os/exec" - "errors" ) type Compression uint32 const ( - Uncompressed Compression = iota + Uncompressed Compression = iota Bzip2 Gzip ) func (compression *Compression) Flag() string { switch *compression { - case Bzip2: return "j" - case Gzip: return "z" + case Bzip2: + return "j" + case Gzip: + return "z" } return "" } func Tar(path string, compression Compression) (io.Reader, error) { - cmd := exec.Command("bsdtar", "-f", "-", "-C", path, "-c" + compression.Flag(), ".") + cmd := exec.Command("bsdtar", "-f", "-", "-C", path, "-c"+compression.Flag(), ".") return CmdStream(cmd) } diff --git a/image/archive_test.go b/image/archive_test.go index 0c19e605fe..4271849797 100644 --- a/image/archive_test.go +++ b/image/archive_test.go @@ -1,10 +1,10 @@ package image import ( - "testing" + "io/ioutil" "os" "os/exec" - "io/ioutil" + "testing" ) func TestCmdStreamBad(t *testing.T) { diff --git a/image/image.go b/image/image.go index d819237a7e..ebc425db09 100644 --- a/image/image.go +++ b/image/image.go @@ -1,27 +1,25 @@ package image import ( + "encoding/json" + "errors" + "github.com/dotcloud/docker/future" "io" "io/ioutil" - "encoding/json" - "time" + "os" "path" "path/filepath" - "errors" "sort" - "os" - "github.com/dotcloud/docker/future" "strings" + "time" ) - type Store struct { *Index - Root string - Layers *LayerStore + Root string + Layers *LayerStore } - func New(root string) (*Store, error) { abspath, err := filepath.Abs(root) if err != nil { @@ -38,8 +36,8 @@ func New(root string) (*Store, error) { return nil, err } return &Store{ - Root: abspath, - Index: NewIndex(path.Join(root, "index.json")), + Root: abspath, + Index: NewIndex(path.Join(root, "index.json")), Layers: layers, }, nil } @@ -73,20 +71,19 @@ func (store *Store) Create(name string, source string, layers ...string) (*Image return image, nil } - // Index type Index struct { - Path string - ByName map[string]*History - ById map[string]*Image + Path string + ByName map[string]*History + ById map[string]*Image } func NewIndex(path string) *Index { return &Index{ - Path: path, + Path: path, ByName: make(map[string]*History), - ById: make(map[string]*Image), + ById: make(map[string]*Image), } } @@ -216,7 +213,7 @@ func (index *Index) Names() []string { if err := index.load(); err != nil { return []string{} } - var names[]string + var names []string for name := range index.ByName { names = append(names, name) } @@ -279,23 +276,23 @@ func (history *History) Add(image *Image) { func (history *History) Del(id string) { for idx, image := range *history { if image.Id == id { - *history = append((*history)[:idx], (*history)[idx + 1:]...) + *history = append((*history)[:idx], (*history)[idx+1:]...) } } } type Image struct { - Id string // Globally unique identifier - Layers []string // Absolute paths - Created time.Time - Parent string + Id string // Globally unique identifier + Layers []string // Absolute paths + Created time.Time + Parent string } func (image *Image) IdParts() (string, string) { if len(image.Id) < 8 { return "", image.Id } - hash := image.Id[len(image.Id)-8:len(image.Id)] + hash := image.Id[len(image.Id)-8 : len(image.Id)] name := image.Id[:len(image.Id)-9] return name, hash } @@ -316,7 +313,7 @@ func generateImageId(name string, layers []string) (string, error) { for _, layer := range layers { ids += path.Base(layer) } - if h, err := future.ComputeId(strings.NewReader(ids)); err != nil { + if h, err := future.ComputeId(strings.NewReader(ids)); err != nil { return "", err } else { hash = h @@ -331,9 +328,9 @@ func NewImage(name string, layers []string, parent string) (*Image, error) { return nil, err } return &Image{ - Id: id, - Layers: layers, - Created: time.Now(), - Parent: parent, + Id: id, + Layers: layers, + Created: time.Now(), + Parent: parent, }, nil } diff --git a/image/layers.go b/image/layers.go index 61782cd88c..7a24b7bd57 100644 --- a/image/layers.go +++ b/image/layers.go @@ -2,16 +2,16 @@ package image import ( "errors" - "path" - "path/filepath" + "github.com/dotcloud/docker/future" "io" "io/ioutil" "os" - "github.com/dotcloud/docker/future" + "path" + "path/filepath" ) type LayerStore struct { - Root string + Root string } func NewLayerStore(root string) (*LayerStore, error) { @@ -66,10 +66,9 @@ func (store *LayerStore) Init() error { return os.Mkdir(store.Root, 0700) } - func (store *LayerStore) Mktemp() (string, error) { tmpName := future.RandomId() - tmpPath := path.Join(store.Root, "tmp-" + tmpName) + tmpPath := path.Join(store.Root, "tmp-"+tmpName) if err := os.Mkdir(tmpPath, 0700); err != nil { return "", err } @@ -80,7 +79,6 @@ func (store *LayerStore) layerPath(id string) string { return path.Join(store.Root, id) } - func (store *LayerStore) AddLayer(archive io.Reader) (string, error) { errors := make(chan error) // Untar @@ -109,9 +107,10 @@ func (store *LayerStore) AddLayer(archive io.Reader) (string, error) { return "", err } // Wait for goroutines - for i:=0; i<2; i+=1 { + for i := 0; i < 2; i += 1 { select { - case err := <-errors: { + case err := <-errors: + { if err != nil { return "", err } diff --git a/image/layers_test.go b/image/layers_test.go index a69edb1892..eec59ab3d0 100644 --- a/image/layers_test.go +++ b/image/layers_test.go @@ -1,12 +1,12 @@ package image import ( + "bytes" + "github.com/dotcloud/docker/fake" + "github.com/dotcloud/docker/future" + "io/ioutil" "os" "testing" - "io/ioutil" - "bytes" - "github.com/dotcloud/docker/future" - "github.com/dotcloud/docker/fake" ) func TestAddLayer(t *testing.T) { @@ -45,4 +45,3 @@ func TestComputeId(t *testing.T) { t.Fatalf("Identical checksums for difference content (%s == %s)", id1, id2) } } - diff --git a/mount_linux.go b/mount_linux.go index a5a24e8480..0efb253003 100644 --- a/mount_linux.go +++ b/mount_linux.go @@ -2,7 +2,6 @@ package docker import "syscall" - func mount(source string, target string, fstype string, flags uintptr, data string) (err error) { return syscall.Mount(source, target, fstype, flags, data) } diff --git a/server/server.go b/server/server.go index 0e57c3da42..2ef42f8b16 100644 --- a/server/server.go +++ b/server/server.go @@ -53,6 +53,7 @@ func (srv *Server) Help() string { {"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"}, + {"wait", "Block until a container exits, then print its exit code"}, {"info", "Display system-wide information"}, {"tar", "Stream the contents of a container as a tar archive"}, {"web", "Generate a web UI"}, @@ -63,6 +64,27 @@ func (srv *Server) Help() string { return help } +// 'docker wait': block until a container stops +func (srv *Server) CmdWait(stdin io.ReadCloser, stdout io.Writer, args ...string) error { + cmd := rcli.Subcmd(stdout, "wait", "[OPTIONS] NAME", "Block until a container stops, then print its exit code.") + 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.containers.Get(name); container != nil { + fmt.Fprintln(stdout, container.Wait()) + } else { + return errors.New("No such container: " + name) + } + } + return nil +} + // 'docker info': display system-wide information. func (srv *Server) CmdInfo(stdin io.ReadCloser, stdout io.Writer, args ...string) error { fmt.Fprintf(stdout, "containers: %d\nversion: %s\nimages: %d\n", @@ -368,12 +390,18 @@ func (srv *Server) CmdPull(stdin io.ReadCloser, stdout io.Writer, args ...string u.Path = path.Join("/docker.io/images", u.Path) } fmt.Fprintf(stdout, "Downloading from %s\n", u.String()) - resp, err := http.Get(u.String()) + // Download with curl (pretty progress bar) + // If curl is not available, fallback to http.Get() + archive, err := future.Curl(u.String(), stdout) if err != nil { - return err + if resp, err := http.Get(u.String()); err != nil { + return err + } else { + archive = resp.Body + } } fmt.Fprintf(stdout, "Unpacking to %s\n", name) - img, err := srv.images.Import(name, resp.Body, nil) + img, err := srv.images.Import(name, archive, nil) if err != nil { return err }