diff --git a/components/engine/.gitignore b/components/engine/.gitignore index d557404214..686ac83428 100644 --- a/components/engine/.gitignore +++ b/components/engine/.gitignore @@ -1,6 +1,5 @@ .vagrant docker/docker -dockerd/dockerd .*.swp a.out *.orig diff --git a/components/engine/README.md b/components/engine/README.md index 8a4a78b1e3..07449fa3fa 100644 --- a/components/engine/README.md +++ b/components/engine/README.md @@ -50,11 +50,37 @@ Under the hood, Docker is built on the following components: * [lxc](http://lxc.sourceforge.net/), a set of convenience scripts to simplify the creation of linux containers. -Setup instructions +Install instructions ================== -Requirements ------------- +Installing on Ubuntu 12.04 and 12.10 +------------------------------------ + +1. Install dependencies: + +```bash + sudo apt-get install lxc wget bsdtar curl +``` + +2. Install the latest docker binary: + +```bash + wget http://get.docker.io/builds/$(uname -s)/$(uname -m)/docker-master.tgz + tar -xf docker-master.tgz +``` + +3. Run your first container! + +```bash + cd docker-master + sudo ./docker import base + sudo ./docker run -a -i -t base /bin/bash +``` + +Consider adding docker to your `PATH` for simplicity. + +Installing on other Linux distributions +--------------------------------------- Right now, the officially supported distributions are: @@ -64,28 +90,68 @@ Right now, the officially supported distributions are: Docker probably works on other distributions featuring a recent kernel, the AUFS patch, and up-to-date lxc. However this has not been tested. -Installation ---------------- +Usage examples +============== -1. Set up your host of choice on a physical / virtual machine -2. Assume root identity on your newly installed environment (`sudo -s`) -3. Type the following commands: +Running an interactive shell +---------------------------- - apt-get update - apt-get install lxc wget bsdtar curl +```bash + # Download a base image + docker import base -4. Download the latest docker binaries: `wget http://docker.io.s3.amazonaws.com/builds/$(uname -s)/$(uname -m)/docker-master.tgz` ([Or get the Linux/x86_64 binaries here](http://docker.io.s3.amazonaws.com/builds/Linux/x86_64/docker-master.tgz) ) -5. Extract the contents of the tar file `tar -xf docker-master.tar.gz` -6. Launch the docker daemon in the background `./dockerd &` -7. Download a base image `./docker pull base` -8. Run your first container! `./docker run -i -a -t base /bin/bash` -9. Start exploring `./docker --help` + # Run an interactive shell in the base image, + # allocate a tty, attach stdin and stdout + docker run -a -i -t base /bin/bash +``` -Consider adding docker and dockerd to your `PATH` for simplicity. + +Starting a long-running worker process +-------------------------------------- + +```bash + # Run docker in daemon mode + (docker -d || echo "Docker daemon already running") & + + # Start a very useful long-running process + JOB=$(docker run /bin/sh -c "while true; do echo Hello world!; sleep 1; done") + + # Collect the output of the job so far + docker logs $JOB + + # Kill the job + docker kill $JOB +``` + + +Listing all running containers +------------------------------ + +```bash + docker ps +``` + + +Expose a service on a TCP port +------------------------------ + +```bash + # Expose port 4444 of this container, and tell netcat to listen on it + JOB=$(docker run -p 4444 base /bin/nc -l -p 4444) + + # Which public port is NATed to my container? + PORT=$(docker port $JOB 4444) + + # Connect to the public port via the host's public address + echo hello world | nc $(hostname) $PORT + + # Verify that the network connection worked + echo "Daemon received: $(docker logs $JOB)" +``` What is a Standard Container? ------------------------------ +============================= Docker defines a unit of software delivery called a Standard Container. The goal of a Standard Container is to encapsulate a software component and all its dependencies in a format that is self-describing and portable, so that any compliant runtime can run it without extra dependency, regardless of the underlying machine and the contents of the container. diff --git a/components/engine/client/client.go b/components/engine/client/client.go deleted file mode 100644 index 814aed2f18..0000000000 --- a/components/engine/client/client.go +++ /dev/null @@ -1,126 +0,0 @@ -package client - -import ( - "github.com/dotcloud/docker/future" - "github.com/dotcloud/docker/rcli" - "io" - "io/ioutil" - "log" - "os" - "os/exec" - "path" - "path/filepath" -) - -// Run docker in "simple mode": run a single command and return. -func SimpleMode(args []string) error { - var oldState *State - var err error - if IsTerminal(0) && os.Getenv("NORAW") == "" { - oldState, err = MakeRaw(0) - if err != nil { - return err - } - defer Restore(0, oldState) - } - // FIXME: we want to use unix sockets here, but net.UnixConn doesn't expose - // CloseWrite(), which we need to cleanly signal that stdin is closed without - // closing the connection. - // See http://code.google.com/p/go/issues/detail?id=3345 - conn, err := rcli.Call("tcp", "127.0.0.1:4242", args...) - if err != nil { - return err - } - receive_stdout := future.Go(func() error { - _, err := io.Copy(os.Stdout, conn) - return err - }) - send_stdin := future.Go(func() error { - _, err := io.Copy(conn, os.Stdin) - if err := conn.CloseWrite(); err != nil { - log.Printf("Couldn't send EOF: " + err.Error()) - } - return err - }) - if err := <-receive_stdout; err != nil { - return err - } - if oldState != nil { - Restore(0, oldState) - } - if !IsTerminal(0) { - if err := <-send_stdin; err != nil { - return err - } - } - return nil -} - -// Run docker in "interactive mode": run a bash-compatible shell capable of running docker commands. -func InteractiveMode(scripts ...string) error { - // Determine path of current docker binary - dockerPath, err := exec.LookPath(os.Args[0]) - if err != nil { - return err - } - dockerPath, err = filepath.Abs(dockerPath) - if err != nil { - return err - } - - // Create a temp directory - tmp, err := ioutil.TempDir("", "docker-shell") - if err != nil { - return err - } - defer os.RemoveAll(tmp) - - // For each command, create an alias in temp directory - // FIXME: generate this list dynamically with introspection of some sort - // It might make sense to merge docker and dockerd to keep that introspection - // within a single binary. - for _, cmd := range []string{ - "help", - "run", - "ps", - "pull", - "put", - "rm", - "kill", - "wait", - "stop", - "start", - "restart", - "logs", - "diff", - "commit", - "attach", - "info", - "tar", - "web", - "images", - "docker", - } { - if err := os.Symlink(dockerPath, path.Join(tmp, cmd)); err != nil { - return err - } - } - - // Run $SHELL with PATH set to temp directory - rcfile, err := ioutil.TempFile("", "docker-shell-rc") - if err != nil { - return err - } - defer os.Remove(rcfile.Name()) - io.WriteString(rcfile, "enable -n help\n") - 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 - shell.Stdout = os.Stdout - shell.Stderr = os.Stderr - if err := shell.Run(); err != nil { - return err - } - return nil -} diff --git a/components/engine/server/server.go b/components/engine/commands/commands.go similarity index 95% rename from components/engine/server/server.go rename to components/engine/commands/commands.go index 18dc578190..f8c97cdff0 100644 --- a/components/engine/server/server.go +++ b/components/engine/commands/commands.go @@ -1,4 +1,4 @@ -package server +package commands import ( "bufio" @@ -24,15 +24,6 @@ import ( const VERSION = "0.0.1" -func (srv *Server) ListenAndServe() error { - go rcli.ListenAndServeHTTP("127.0.0.1:8080", srv) - // FIXME: we want to use unix sockets here, but net.UnixConn doesn't expose - // CloseWrite(), which we need to cleanly signal that stdin is closed without - // closing the connection. - // See http://code.google.com/p/go/issues/detail?id=3345 - return rcli.ListenAndServe("tcp", "127.0.0.1:4242", srv) -} - func (srv *Server) Name() string { return "docker" } @@ -360,34 +351,36 @@ func (srv *Server) CmdPort(stdin io.ReadCloser, stdout io.Writer, args ...string } // 'docker rmi NAME' removes all images with the name NAME -// func (srv *Server) CmdRmi(stdin io.ReadCloser, stdout io.Writer, args ...string) error { -// cmd := rcli.Subcmd(stdout, "rmimage", "[OPTIONS] IMAGE", "Remove an image") -// fl_regexp := cmd.Bool("r", false, "Use IMAGE as a regular expression instead of an exact name") -// if err := cmd.Parse(args); err != nil { -// cmd.Usage() -// return nil -// } -// if cmd.NArg() < 1 { -// cmd.Usage() -// return nil -// } -// for _, name := range cmd.Args() { -// var err error -// if *fl_regexp { -// err = srv.images.DeleteMatch(name) -// } else { -// image := srv.images.Find(name) -// if image == nil { -// return errors.New("No such image: " + name) -// } -// err = srv.images.Delete(name) -// } -// if err != nil { -// return err -// } -// } -// return nil -// } +func (srv *Server) CmdRmi(stdin io.ReadCloser, stdout io.Writer, args ...string) error { + cmd := rcli.Subcmd(stdout, "rmimage", "[OPTIONS] IMAGE", "Remove an image") + fl_all := cmd.Bool("a", false, "Use IMAGE as a path and remove ALL images in this path") + if err := cmd.Parse(args); err != nil { + cmd.Usage() + return nil + } + if cmd.NArg() < 1 { + cmd.Usage() + return nil + } + for _, name := range cmd.Args() { + var err error + if *fl_all { + err = srv.images.RemoveInPath(name) + } else { + image, err := srv.images.Get(name) + if err != nil { + return err + } else if image == nil { + return errors.New("No such image: " + name) + } + err = srv.images.Remove(image) + } + if err != nil { + return err + } + } + return nil +} func (srv *Server) CmdRm(stdin io.ReadCloser, stdout io.Writer, args ...string) error { cmd := rcli.Subcmd(stdout, "rm", "[OPTIONS] CONTAINER", "Remove a container") diff --git a/components/engine/docker/docker.go b/components/engine/docker/docker.go index fa9011defa..ad121a8f60 100644 --- a/components/engine/docker/docker.go +++ b/components/engine/docker/docker.go @@ -2,28 +2,92 @@ package main import ( "flag" - "github.com/dotcloud/docker/client" + "github.com/dotcloud/docker" + "github.com/dotcloud/docker/commands" + "github.com/dotcloud/docker/future" + "github.com/dotcloud/docker/rcli" + "github.com/dotcloud/docker/term" + "io" "log" "os" - "path" ) func main() { - if cmd := path.Base(os.Args[0]); cmd == "docker" { - fl_shell := flag.Bool("i", false, "Interactive mode") - flag.Parse() - if *fl_shell { - if err := client.InteractiveMode(flag.Args()...); err != nil { - log.Fatal(err) - } - } else { - if err := client.SimpleMode(os.Args[1:]); err != nil { - log.Fatal(err) - } + if docker.SelfPath() == "/sbin/init" { + // Running in init mode + docker.SysInit() + return + } + fl_daemon := flag.Bool("d", false, "Daemon mode") + flag.Parse() + if *fl_daemon { + if flag.NArg() != 0 { + flag.Usage() + return + } + if err := daemon(); err != nil { + log.Fatal(err) } } else { - if err := client.SimpleMode(append([]string{cmd}, os.Args[1:]...)); err != nil { + if err := runCommand(flag.Args()); err != nil { log.Fatal(err) } } } + +func daemon() error { + service, err := commands.New() + if err != nil { + return err + } + return rcli.ListenAndServe("tcp", "127.0.0.1:4242", service) +} + +func runCommand(args []string) error { + var oldState *term.State + var err error + if term.IsTerminal(0) && os.Getenv("NORAW") == "" { + oldState, err = term.MakeRaw(0) + if err != nil { + return err + } + defer term.Restore(0, oldState) + } + // FIXME: we want to use unix sockets here, but net.UnixConn doesn't expose + // CloseWrite(), which we need to cleanly signal that stdin is closed without + // closing the connection. + // See http://code.google.com/p/go/issues/detail?id=3345 + if conn, err := rcli.Call("tcp", "127.0.0.1:4242", args...); err == nil { + receive_stdout := future.Go(func() error { + _, err := io.Copy(os.Stdout, conn) + return err + }) + send_stdin := future.Go(func() error { + _, err := io.Copy(conn, os.Stdin) + if err := conn.CloseWrite(); err != nil { + log.Printf("Couldn't send EOF: " + err.Error()) + } + return err + }) + if err := <-receive_stdout; err != nil { + return err + } + if !term.IsTerminal(0) { + if err := <-send_stdin; err != nil { + return err + } + } + } else { + service, err := commands.New() + if err != nil { + return err + } + if err := rcli.LocalCall(service, os.Stdin, os.Stdout, args...); err != nil { + return err + } + } + if oldState != nil { + term.Restore(0, oldState) + } + return nil +} diff --git a/components/engine/dockerd/dockerd.go b/components/engine/dockerd/dockerd.go deleted file mode 100644 index b2337d342c..0000000000 --- a/components/engine/dockerd/dockerd.go +++ /dev/null @@ -1,24 +0,0 @@ -package main - -import ( - "flag" - "github.com/dotcloud/docker" - "github.com/dotcloud/docker/server" - "log" -) - -func main() { - if docker.SelfPath() == "/sbin/init" { - // Running in init mode - docker.SysInit() - return - } - flag.Parse() - d, err := server.New() - if err != nil { - log.Fatal(err) - } - if err := d.ListenAndServe(); err != nil { - log.Fatal(err) - } -} diff --git a/components/engine/fs/store.go b/components/engine/fs/store.go index 41c940d7ed..1086e4a981 100644 --- a/components/engine/fs/store.go +++ b/components/engine/fs/store.go @@ -91,6 +91,24 @@ func (store *Store) Paths() ([]string, error) { return paths, nil } +func (store *Store) RemoveInPath(pth string) error { + images, err := store.List(pth) + if err != nil { + return err + } + for _, img := range images { + if err = store.Remove(img); err != nil { + return err + } + } + return nil +} + +func (store *Store) Remove(img *Image) error { + _, err := store.orm.Delete(img) + return err +} + func (store *Store) List(pth string) ([]*Image, error) { pth = path.Clean(pth) images, err := store.orm.Select(Image{}, "select images.* from images, paths where Path=? and paths.Image=images.Id", pth) diff --git a/components/engine/rcli/types.go b/components/engine/rcli/types.go index 52079291b6..2600fe240d 100644 --- a/components/engine/rcli/types.go +++ b/components/engine/rcli/types.go @@ -25,7 +25,12 @@ type Service interface { type Cmd func(io.ReadCloser, io.Writer, ...string) error type CmdMethod func(Service, io.ReadCloser, io.Writer, ...string) error +// FIXME: For reverse compatibility func call(service Service, stdin io.ReadCloser, stdout io.Writer, args ...string) error { + return LocalCall(service, stdin, stdout, args...) +} + +func LocalCall(service Service, stdin io.ReadCloser, stdout io.Writer, args ...string) error { if len(args) == 0 { args = []string{"help"} } diff --git a/components/engine/client/term.go b/components/engine/term/term.go similarity index 99% rename from components/engine/client/term.go rename to components/engine/term/term.go index a988d0d796..fdbe3b9984 100644 --- a/components/engine/client/term.go +++ b/components/engine/term/term.go @@ -1,4 +1,4 @@ -package client +package term import ( "syscall" diff --git a/components/engine/client/termios_darwin.go b/components/engine/term/termios_darwin.go similarity index 85% rename from components/engine/client/termios_darwin.go rename to components/engine/term/termios_darwin.go index 185687920c..26d17b3bb8 100644 --- a/components/engine/client/termios_darwin.go +++ b/components/engine/term/termios_darwin.go @@ -1,4 +1,4 @@ -package client +package term import "syscall" diff --git a/components/engine/client/termios_linux.go b/components/engine/term/termios_linux.go similarity index 85% rename from components/engine/client/termios_linux.go rename to components/engine/term/termios_linux.go index 36957c44a1..1ea26fa5c4 100644 --- a/components/engine/client/termios_linux.go +++ b/components/engine/term/termios_linux.go @@ -1,4 +1,4 @@ -package client +package term import "syscall"