Andrea Luzzardi 12 лет назад
Родитель
Сommit
ec21a2d364

+ 82 - 0
README.md

@@ -78,3 +78,85 @@ Client installation
 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`)
 5. Extract the contents of the tar file `tar -xf docker.tar.gz`
 6. You can now use the docker client binary `./docker`. Consider adding it to your `PATH` for simplicity.
+
+Vagrant Usage
+-------------
+
+1. Install Vagrant from http://vagrantup.com
+2. Run `vagrant up`. This will take a few minutes as it does the following:
+    - Download Quantal64 base box
+    - Kick off Puppet to do:
+        - Download & untar most recent docker binary tarball to vagrant homedir.
+        - Debootstrap to /var/lib/docker/images/ubuntu.
+        - Install & run dockerd as service.
+        - Put docker in /usr/local/bin.
+        - Put latest Go toolchain in /usr/local/go.
+
+Sample run output:
+
+```bash
+$ vagrant up
+[default] Importing base box 'quantal64'...
+[default] Matching MAC address for NAT networking...
+[default] Clearing any previously set forwarded ports...
+[default] Forwarding ports...
+[default] -- 22 => 2222 (adapter 1)
+[default] Creating shared folders metadata...
+[default] Clearing any previously set network interfaces...
+[default] Booting VM...
+[default] Waiting for VM to boot. This can take a few minutes.
+[default] VM booted and ready for use!
+[default] Mounting shared folders...
+[default] -- v-root: /vagrant
+[default] -- manifests: /tmp/vagrant-puppet/manifests
+[default] -- v-pp-m0: /tmp/vagrant-puppet/modules-0
+[default] Running provisioner: Vagrant::Provisioners::Puppet...
+[default] Running Puppet with /tmp/vagrant-puppet/manifests/quantal64.pp...
+stdin: is not a tty
+notice: /Stage[main]//Node[default]/Exec[apt_update]/returns: executed successfully
+
+notice: /Stage[main]/Docker/Exec[fetch-docker]/returns: executed successfully
+notice: /Stage[main]/Docker/Package[lxc]/ensure: ensure changed 'purged' to 'present'
+notice: /Stage[main]/Docker/Exec[fetch-go]/returns: executed successfully
+
+notice: /Stage[main]/Docker/Exec[copy-docker-bin]/returns: executed successfully
+notice: /Stage[main]/Docker/Exec[debootstrap]/returns: executed successfully
+notice: /Stage[main]/Docker/File[/etc/init/dockerd.conf]/ensure: defined content as '{md5}78a593d38dd9919af14d8f0545ac95e9'
+
+notice: /Stage[main]/Docker/Service[dockerd]/ensure: ensure changed 'stopped' to 'running'
+
+notice: Finished catalog run in 329.74 seconds
+```
+
+When this has successfully completed, you should be able to get into your new system with `vagrant ssh` and use `docker`:
+
+```bash
+$ vagrant ssh
+Welcome to Ubuntu 12.10 (GNU/Linux 3.5.0-17-generic x86_64)
+
+ * Documentation:  https://help.ubuntu.com/
+
+Last login: Sun Feb  3 19:37:37 2013
+vagrant@vagrant-ubuntu-12:~$ DOCKER=localhost:4242 docker help
+Usage: docker COMMAND [arg...]
+
+A self-sufficient runtime for linux containers.
+
+Commands:
+    run       Run a command in a container
+    ps        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
+    attach    Attach to a running container
+```
+    

+ 100 - 0
Vagrantfile

@@ -0,0 +1,100 @@
+# -*- mode: ruby -*-
+# vi: set ft=ruby :
+
+Vagrant::Config.run do |config|
+  # All Vagrant configuration is done here. The most common configuration
+  # options are documented and commented below. For a complete reference,
+  # please see the online documentation at vagrantup.com.
+
+  # Every Vagrant virtual environment requires a box to build off of.
+  config.vm.box = "quantal64"
+
+  # The url from where the 'config.vm.box' box will be fetched if it
+  # doesn't already exist on the user's system.
+  config.vm.box_url = "http://unworkable.org/~niallo/quantal64.box"
+
+  # Boot with a GUI so you can see the screen. (Default is headless)
+  # config.vm.boot_mode = :gui
+
+  # Assign this VM to a host-only network IP, allowing you to access it
+  # via the IP. Host-only networks can talk to the host machine as well as
+  # any other machines on the same network, but cannot be accessed (through this
+  # network interface) by any external networks.
+  # config.vm.network :hostonly, "192.168.33.10"
+
+  # Assign this VM to a bridged network, allowing you to connect directly to a
+  # network using the host's network device. This makes the VM appear as another
+  # physical device on your network.
+  # config.vm.network :bridged
+
+  # Forward a port from the guest to the host, which allows for outside
+  # computers to access the VM, whereas host only networking does not.
+  # config.vm.forward_port 80, 8080
+
+  # Share an additional folder to the guest VM. The first argument is
+  # an identifier, the second is the path on the guest to mount the
+  # folder, and the third is the path on the host to the actual folder.
+  # config.vm.share_folder "v-data", "/vagrant_data", "../data"
+
+  # Enable provisioning with Puppet stand alone.  Puppet manifests
+  # are contained in a directory path relative to this Vagrantfile.
+  # You will need to create the manifests directory and a manifest in
+  # the file quantal64.pp in the manifests_path directory.
+  #
+  # An example Puppet manifest to provision the message of the day:
+  #
+  # # group { "puppet":
+  # #   ensure => "present",
+  # # }
+  # #
+  # # File { owner => 0, group => 0, mode => 0644 }
+  # #
+  # # file { '/etc/motd':
+  # #   content => "Welcome to your Vagrant-built virtual machine!
+  # #               Managed by Puppet.\n"
+  # # }
+  #
+  config.vm.provision :puppet do |puppet|
+    puppet.manifests_path = "puppet/manifests"
+    puppet.manifest_file  = "quantal64.pp"
+    puppet.module_path = "puppet/modules"
+  end
+
+  # Enable provisioning with chef solo, specifying a cookbooks path, roles
+  # path, and data_bags path (all relative to this Vagrantfile), and adding 
+  # some recipes and/or roles.
+  #
+  # config.vm.provision :chef_solo do |chef|
+  #   chef.cookbooks_path = "../my-recipes/cookbooks"
+  #   chef.roles_path = "../my-recipes/roles"
+  #   chef.data_bags_path = "../my-recipes/data_bags"
+  #   chef.add_recipe "mysql"
+  #   chef.add_role "web"
+  #
+  #   # You may also specify custom JSON attributes:
+  #   chef.json = { :mysql_password => "foo" }
+  # end
+
+  # Enable provisioning with chef server, specifying the chef server URL,
+  # and the path to the validation key (relative to this Vagrantfile).
+  #
+  # The Opscode Platform uses HTTPS. Substitute your organization for
+  # ORGNAME in the URL and validation key.
+  #
+  # If you have your own Chef Server, use the appropriate URL, which may be
+  # HTTP instead of HTTPS depending on your configuration. Also change the
+  # validation key to validation.pem.
+  #
+  # config.vm.provision :chef_client do |chef|
+  #   chef.chef_server_url = "https://api.opscode.com/organizations/ORGNAME"
+  #   chef.validation_key_path = "ORGNAME-validator.pem"
+  # end
+  #
+  # If you're using the Opscode platform, your validator client is
+  # ORGNAME-validator, replacing ORGNAME with your organization name.
+  #
+  # IF you have your own Chef Server, the default validation client name is
+  # chef-validator, unless you changed the configuration.
+  #
+  #   chef.validation_client_name = "ORGNAME-validator"
+end

+ 93 - 6
docker/docker.go

@@ -4,11 +4,15 @@ import (
 	"github.com/dotcloud/docker/rcli"
 	"github.com/dotcloud/docker/future"
 	"io"
+	"io/ioutil"
 	"log"
 	"os"
+	"os/exec"
 	"syscall"
 	"unsafe"
-	"fmt"
+	"path"
+	"path/filepath"
+	"flag"
 )
 
 
@@ -160,11 +164,24 @@ func Fatal(err error) {
 
 
 func main() {
-	var err error
-	if os.Getenv("DOCKER") == "" {
-		fmt.Printf("Can't connect. Please set environment variable DOCKER to ip:port, eg. 'localhost:4242'.\n")
-		os.Exit(1)
+	if cmd := path.Base(os.Args[0]); cmd == "docker" {
+		fl_shell := flag.Bool("i", false, "Interactive mode")
+		flag.Parse()
+		if *fl_shell {
+			if err := InteractiveMode(flag.Args()...); err != nil {
+				log.Fatal(err)
+			}
+		} else {
+			SimpleMode(os.Args[1:])
+		}
+	} else {
+		SimpleMode(append([]string{cmd}, os.Args[1:]...))
 	}
+}
+
+// Run docker in "simple mode": run a single command and return.
+func SimpleMode(args []string) {
+	var err error
 	if IsTerminal(0) && os.Getenv("NORAW") == "" {
 		oldState, err = MakeRaw(0)
 		if err != nil {
@@ -172,7 +189,11 @@ func main() {
 		}
 		defer Restore(0, oldState)
 	}
-	conn, err := rcli.CallTCP(os.Getenv("DOCKER"), os.Args[1:]...)
+	// 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 {
 		Fatal(err)
 	}
@@ -199,3 +220,69 @@ func main() {
 		}
 	}
 }
+
+// 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",
+		"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
+	}
+	io.WriteString(rcfile, "enable -n help\n")
+	os.Setenv("PATH", tmp)
+	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
+}

+ 127 - 63
dockerd/dockerd.go

@@ -14,7 +14,9 @@ import (
 	"io"
 	"log"
 	"net/http"
+	"net/url"
 	"os"
+	"path"
 	"strings"
 	"sync"
 	"text/tabwriter"
@@ -35,6 +37,7 @@ func (srv *Server) Help() string {
 		{"pull", "Download a tarball and create a container from it"},
 		{"put", "Upload a tarball and create a container from it"},
 		{"rm", "Remove containers"},
+		{"kill", "Kill a running container"},
 		{"wait", "Wait for the state of a container to change"},
 		{"stop", "Stop a running container"},
 		{"logs", "Fetch the logs of a container"},
@@ -44,7 +47,7 @@ func (srv *Server) Help() string {
 		{"info", "Display system-wide information"},
 		{"tar", "Stream the contents of a container as a tar archive"},
 		{"web", "Generate a web UI"},
-		{"attach", "Attach to a running container"},
+		{"images", "List images"},
 	} {
 		help += fmt.Sprintf("    %-10.10s%s\n", cmd...)
 	}
@@ -254,11 +257,11 @@ func (srv *Server) CmdRmi(stdin io.ReadCloser, stdout io.Writer, args ...string)
 }
 
 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 {
+	cmd := rcli.Subcmd(stdout, "rm", "[OPTIONS] CONTAINER", "Remove a container")
+	if err := cmd.Parse(args); err != nil {
 		return nil
 	}
-	for _, name := range flags.Args() {
+	for _, name := range cmd.Args() {
 		container := srv.containers.Get(name)
 		if container == nil {
 			return errors.New("No such container: " + name)
@@ -270,15 +273,59 @@ func (srv *Server) CmdRm(stdin io.ReadCloser, stdout io.Writer, args ...string)
 	return nil
 }
 
+// 'docker kill NAME' kills a running container
+func (srv *Server) CmdKill(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
+	cmd := rcli.Subcmd(stdout, "kill", "[OPTIONS] CONTAINER [CONTAINER...]", "Kill a running container")
+	if err := cmd.Parse(args); err != nil {
+		return nil
+	}
+	for _, name := range cmd.Args() {
+		container := srv.containers.Get(name)
+		if container == nil {
+			return errors.New("No such container: " + name)
+		}
+		if err := container.Kill(); err != nil {
+			fmt.Fprintln(stdout, "Error killing container "+name+": "+err.Error())
+		}
+	}
+	return nil
+}
+
 func (srv *Server) CmdPull(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
-	if len(args) < 1 {
+	cmd := rcli.Subcmd(stdout, "pull", "[OPTIONS] NAME", "Download a new image from a remote location")
+	fl_bzip2 := cmd.Bool("j", false, "Bzip2 compression")
+	fl_gzip := cmd.Bool("z", false, "Gzip compression")
+	if err := cmd.Parse(args); err != nil {
+		return nil
+	}
+	var compression image.Compression
+	if *fl_bzip2 {
+		compression = image.Bzip2
+	} else if *fl_gzip {
+		compression = image.Gzip
+	}
+	name := cmd.Arg(0)
+	if name == "" {
 		return errors.New("Not enough arguments")
 	}
-	resp, err := http.Get(args[0])
+	u, err := url.Parse(name)
+	if err != nil {
+		return err
+	}
+	if u.Scheme == "" {
+		u.Scheme = "http"
+	}
+	// FIXME: hardcode a mirror URL that does not depend on a single provider.
+	if u.Host == "" {
+		u.Host = "s3.amazonaws.com"
+		u.Path = path.Join("/docker.io/images", u.Path)
+	}
+	fmt.Fprintf(stdout, "Downloading %s from %s...\n", name, u.String())
+	resp, err := http.Get(u.String())
 	if err != nil {
 		return err
 	}
-	img, err := srv.images.Import(args[0], resp.Body, stdout, nil)
+	img, err := srv.images.Import(name, resp.Body, stdout, nil, compression)
 	if err != nil {
 		return err
 	}
@@ -287,10 +334,23 @@ func (srv *Server) CmdPull(stdin io.ReadCloser, stdout io.Writer, args ...string
 }
 
 func (srv *Server) CmdPut(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
-	if len(args) < 1 {
+	cmd := rcli.Subcmd(stdout, "put", "[OPTIONS] NAME", "Import a new image from a local archive.")
+	fl_bzip2 := cmd.Bool("j", false, "Bzip2 compression")
+	fl_gzip := cmd.Bool("z", false, "Gzip compression")
+	if err := cmd.Parse(args); err != nil {
+		return nil
+	}
+	var compression image.Compression
+	if *fl_bzip2 {
+		compression = image.Bzip2
+	} else if *fl_gzip {
+		compression = image.Gzip
+	}
+	name := cmd.Arg(0)
+	if name == "" {
 		return errors.New("Not enough arguments")
 	}
-	img, err := srv.images.Import(args[0], stdin, stdout, nil)
+	img, err := srv.images.Import(name, stdin, stdout, nil, compression)
 	if err != nil {
 		return err
 	}
@@ -299,17 +359,17 @@ func (srv *Server) CmdPut(stdin io.ReadCloser, stdout io.Writer, args ...string)
 }
 
 func (srv *Server) CmdImages(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
-	flags := rcli.Subcmd(stdout, "images", "[OPTIONS] [NAME]", "List images")
-	limit := flags.Int("l", 0, "Only show the N most recent versions of each image")
-	quiet := flags.Bool("q", false, "only show numeric IDs")
-	flags.Parse(args)
-	if flags.NArg() > 1 {
-		flags.Usage()
+	cmd := rcli.Subcmd(stdout, "images", "[OPTIONS] [NAME]", "List images")
+	limit := cmd.Int("l", 0, "Only show the N most recent versions of each image")
+	quiet := cmd.Bool("q", false, "only show numeric IDs")
+	cmd.Parse(args)
+	if cmd.NArg() > 1 {
+		cmd.Usage()
 		return nil
 	}
 	var nameFilter string
-	if flags.NArg() == 1 {
-		nameFilter = flags.Arg(0)
+	if cmd.NArg() == 1 {
+		nameFilter = cmd.Arg(0)
 	}
 	w := tabwriter.NewWriter(stdout, 20, 1, 3, ' ', 0)
 	if !*quiet {
@@ -402,10 +462,10 @@ func (srv *Server) CmdPs(stdin io.ReadCloser, stdout io.Writer, args ...string)
 }
 
 func (srv *Server) CmdLayers(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
-	flags := rcli.Subcmd(stdout,
+	cmd := rcli.Subcmd(stdout,
 		"layers", "[OPTIONS]",
 		"List filesystem layers (debug only)")
-	if err := flags.Parse(args); err != nil {
+	if err := cmd.Parse(args); err != nil {
 		return nil
 	}
 	for _, layer := range srv.images.Layers.List() {
@@ -415,13 +475,13 @@ func (srv *Server) CmdLayers(stdin io.ReadCloser, stdout io.Writer, args ...stri
 }
 
 func (srv *Server) CmdCp(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
-	flags := rcli.Subcmd(stdout,
+	cmd := rcli.Subcmd(stdout,
 		"cp", "[OPTIONS] IMAGE NAME",
 		"Create a copy of IMAGE and call it NAME")
-	if err := flags.Parse(args); err != nil {
+	if err := cmd.Parse(args); err != nil {
 		return nil
 	}
-	if newImage, err := srv.images.Copy(flags.Arg(0), flags.Arg(1)); err != nil {
+	if newImage, err := srv.images.Copy(cmd.Arg(0), cmd.Arg(1)); err != nil {
 		return err
 	} else {
 		fmt.Fprintln(stdout, newImage.Id)
@@ -430,15 +490,15 @@ func (srv *Server) CmdCp(stdin io.ReadCloser, stdout io.Writer, args ...string)
 }
 
 func (srv *Server) CmdCommit(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
-	flags := rcli.Subcmd(stdout,
+	cmd := rcli.Subcmd(stdout,
 		"commit", "[OPTIONS] CONTAINER [DEST]",
 		"Create a new image from a container's changes")
-	if err := flags.Parse(args); err != nil {
+	if err := cmd.Parse(args); err != nil {
 		return nil
 	}
-	containerName, imgName := flags.Arg(0), flags.Arg(1)
+	containerName, imgName := cmd.Arg(0), cmd.Arg(1)
 	if containerName == "" || imgName == "" {
-		flags.Usage()
+		cmd.Usage()
 		return nil
 	}
 	if container := srv.containers.Get(containerName); container != nil {
@@ -449,7 +509,7 @@ func (srv *Server) CmdCommit(stdin io.ReadCloser, stdout io.Writer, args ...stri
 		}
 		// Create a new image from the container's base layers + a new layer from container changes
 		parentImg := srv.images.Find(container.GetUserData("image"))
-		img, err := srv.images.Import(imgName, rwTar, stdout, parentImg)
+		img, err := srv.images.Import(imgName, rwTar, stdout, parentImg, image.Uncompressed)
 		if err != nil {
 			return err
 		}
@@ -460,17 +520,17 @@ func (srv *Server) CmdCommit(stdin io.ReadCloser, stdout io.Writer, args ...stri
 }
 
 func (srv *Server) CmdTar(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
-	flags := rcli.Subcmd(stdout,
+	cmd := rcli.Subcmd(stdout,
 		"tar", "CONTAINER",
 		"Stream the contents of a container as a tar archive")
-	fl_sparse := flags.Bool("s", false, "Generate a sparse tar stream (top layer + reference to bottom layers)")
-	if err := flags.Parse(args); err != nil {
+	fl_sparse := cmd.Bool("s", false, "Generate a sparse tar stream (top layer + reference to bottom layers)")
+	if err := cmd.Parse(args); err != nil {
 		return nil
 	}
 	if *fl_sparse {
 		return errors.New("Sparse mode not yet implemented") // FIXME
 	}
-	name := flags.Arg(0)
+	name := cmd.Arg(0)
 	if container := srv.containers.Get(name); container != nil {
 		data, err := container.Filesystem.Tar()
 		if err != nil {
@@ -486,16 +546,16 @@ func (srv *Server) CmdTar(stdin io.ReadCloser, stdout io.Writer, args ...string)
 }
 
 func (srv *Server) CmdDiff(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
-	flags := rcli.Subcmd(stdout,
+	cmd := rcli.Subcmd(stdout,
 		"diff", "CONTAINER [OPTIONS]",
 		"Inspect changes on a container's filesystem")
-	if err := flags.Parse(args); err != nil {
+	if err := cmd.Parse(args); err != nil {
 		return nil
 	}
-	if flags.NArg() < 1 {
+	if cmd.NArg() < 1 {
 		return errors.New("Not enough arguments")
 	}
-	if container := srv.containers.Get(flags.Arg(0)); container == nil {
+	if container := srv.containers.Get(cmd.Arg(0)); container == nil {
 		return errors.New("No such container")
 	} else {
 		changes, err := container.Filesystem.Changes()
@@ -510,16 +570,16 @@ func (srv *Server) CmdDiff(stdin io.ReadCloser, stdout io.Writer, args ...string
 }
 
 func (srv *Server) CmdReset(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
-	flags := rcli.Subcmd(stdout,
+	cmd := rcli.Subcmd(stdout,
 		"reset", "CONTAINER [OPTIONS]",
 		"Reset changes to a container's filesystem")
-	if err := flags.Parse(args); err != nil {
+	if err := cmd.Parse(args); err != nil {
 		return nil
 	}
-	if flags.NArg() < 1 {
+	if cmd.NArg() < 1 {
 		return errors.New("Not enough arguments")
 	}
-	for _, name := range flags.Args() {
+	for _, name := range cmd.Args() {
 		if container := srv.containers.Get(name); container != nil {
 			if err := container.Filesystem.Reset(); err != nil {
 				return errors.New("Reset " + container.Id + ": " + err.Error())
@@ -530,15 +590,15 @@ func (srv *Server) CmdReset(stdin io.ReadCloser, stdout io.Writer, args ...strin
 }
 
 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 {
+	cmd := rcli.Subcmd(stdout, "logs", "[OPTIONS] CONTAINER", "Fetch the logs of a container")
+	if err := cmd.Parse(args); err != nil {
 		return nil
 	}
-	if flags.NArg() != 1 {
-		flags.Usage()
+	if cmd.NArg() != 1 {
+		cmd.Usage()
 		return nil
 	}
-	name := flags.Arg(0)
+	name := cmd.Arg(0)
 	if container := srv.containers.Get(name); container != nil {
 		if _, err := io.Copy(stdout, container.StdoutLog()); err != nil {
 			return err
@@ -548,7 +608,7 @@ func (srv *Server) CmdLogs(stdin io.ReadCloser, stdout io.Writer, args ...string
 		}
 		return nil
 	}
-	return errors.New("No such container: " + flags.Arg(0))
+	return errors.New("No such container: " + cmd.Arg(0))
 }
 
 func (srv *Server) CreateContainer(img *image.Image, tty bool, openStdin bool, comment string, cmd string, args ...string) (*docker.Container, error) {
@@ -616,29 +676,29 @@ func (srv *Server) CmdAttach(stdin io.ReadCloser, stdout io.Writer, args ...stri
 }
 
 func (srv *Server) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
-	flags := rcli.Subcmd(stdout, "run", "[OPTIONS] IMAGE COMMAND [ARG...]", "Run a command in a new container")
-	fl_attach := flags.Bool("a", false, "Attach stdin and stdout")
-	fl_stdin := flags.Bool("i", false, "Keep stdin open even if not attached")
-	fl_tty := flags.Bool("t", false, "Allocate a pseudo-tty")
-	fl_comment := flags.String("c", "", "Comment")
-	if err := flags.Parse(args); err != nil {
+	cmd := rcli.Subcmd(stdout, "run", "[OPTIONS] IMAGE COMMAND [ARG...]", "Run a command in a new container")
+	fl_attach := cmd.Bool("a", false, "Attach stdin and stdout")
+	fl_stdin := cmd.Bool("i", false, "Keep stdin open even if not attached")
+	fl_tty := cmd.Bool("t", false, "Allocate a pseudo-tty")
+	fl_comment := cmd.String("c", "", "Comment")
+	if err := cmd.Parse(args); err != nil {
 		return nil
 	}
-	name := flags.Arg(0)
-	var cmd []string
-	if len(flags.Args()) >= 2 {
-		cmd = flags.Args()[1:]
+	name := cmd.Arg(0)
+	var cmdline []string
+	if len(cmd.Args()) >= 2 {
+		cmdline = cmd.Args()[1:]
 	}
 	// Choose a default image if needed
 	if name == "" {
 		name = "base"
 	}
 	// Choose a default command if needed
-	if len(cmd) == 0 {
+	if len(cmdline) == 0 {
 		*fl_stdin = true
 		*fl_tty = true
 		*fl_attach = true
-		cmd = []string{"/bin/bash", "-i"}
+		cmdline = []string{"/bin/bash", "-i"}
 	}
 	// Find the image
 	img := srv.images.Find(name)
@@ -646,7 +706,7 @@ func (srv *Server) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...string)
 		return errors.New("No such image: " + name)
 	}
 	// Create new container
-	container, err := srv.CreateContainer(img, *fl_tty, *fl_stdin, *fl_comment, cmd[0], cmd[1:]...)
+	container, err := srv.CreateContainer(img, *fl_tty, *fl_stdin, *fl_comment, cmdline[0], cmdline[1:]...)
 	if err != nil {
 		return errors.New("Error creating container: " + err.Error())
 	}
@@ -717,11 +777,15 @@ func main() {
 		log.Fatal(err)
 	}
 	go func() {
-		if err := rcli.ListenAndServeHTTP(":8080", d); err != nil {
+		if err := rcli.ListenAndServeHTTP("127.0.0.1:8080", d); err != nil {
 			log.Fatal(err)
 		}
 	}()
-	if err := rcli.ListenAndServeTCP(":4242", d); err != nil {
+	// 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 err := rcli.ListenAndServe("tcp", "127.0.0.1:4242", d); err != nil {
 		log.Fatal(err)
 	}
 }
@@ -764,9 +828,9 @@ func (srv *Server) CmdDebug(stdin io.ReadCloser, stdout io.Writer, args ...strin
 }
 
 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 {
+	cmd := rcli.Subcmd(stdout, "web", "[OPTIONS]", "A web UI for docker")
+	showurl := cmd.Bool("u", false, "Return the URL of the web UI")
+	if err := cmd.Parse(args); err != nil {
 		return nil
 	}
 	if *showurl {

+ 9 - 2
image/image.go

@@ -41,9 +41,16 @@ func New(root string) (*Store, error) {
 	}, nil
 }
 
+type Compression uint32
 
-func (store *Store) Import(name string, archive io.Reader, stderr io.Writer, parent *Image) (*Image, error) {
-	layer, err := store.Layers.AddLayer(archive, stderr)
+const (
+	Uncompressed	Compression = iota
+	Bzip2
+	Gzip
+)
+
+func (store *Store) Import(name string, archive io.Reader, stderr io.Writer, parent *Image, compression Compression) (*Image, error) {
+	layer, err := store.Layers.AddLayer(archive, stderr, compression)
 	if err != nil {
 		return nil, err
 	}

+ 8 - 2
image/layers.go

@@ -82,13 +82,19 @@ func (store *LayerStore) layerPath(id string) string {
 }
 
 
-func (store *LayerStore) AddLayer(archive io.Reader, stderr io.Writer) (string, error) {
+func (store *LayerStore) AddLayer(archive io.Reader, stderr io.Writer, compression Compression) (string, error) {
 	tmp, err := store.Mktemp()
 	defer os.RemoveAll(tmp)
 	if err != nil {
 		return "", err
 	}
-	untarCmd := exec.Command("tar", "-C", tmp, "-x")
+	extractFlags := "-x"
+	if compression == Bzip2 {
+		extractFlags += "j"
+	} else if compression == Gzip {
+		extractFlags += "z"
+	}
+	untarCmd := exec.Command("tar", "-C", tmp, extractFlags)
 	untarW, err := untarCmd.StdinPipe()
 	if err != nil {
 		return "", err

+ 17 - 0
puppet/manifests/quantal64.pp

@@ -0,0 +1,17 @@
+node default {
+    exec {
+        "apt_update" : 
+            command => "/usr/bin/apt-get update"
+    }
+
+    Package {
+        require => Exec['apt_update']
+    }
+
+    group { "puppet":
+        ensure => "present"
+    }
+
+    include "docker"
+
+}

+ 56 - 0
puppet/modules/docker/manifests/init.pp

@@ -0,0 +1,56 @@
+class docker {
+
+    # update this with latest docker binary distro
+    $docker_url = "https://dl.dropbox.com/u/20637798/docker.tar.gz"
+    # update this with latest go binary distry
+    $go_url = "http://go.googlecode.com/files/go1.0.3.linux-amd64.tar.gz"
+
+
+    Package { ensure => "installed" }
+
+    package { ["lxc", "debootstrap", "wget"]: }
+
+    exec { "debootstrap" :
+        require => Package["debootstrap"],
+        command => "/usr/sbin/debootstrap --arch=amd64 quantal /var/lib/docker/images/ubuntu",
+        creates => "/var/lib/docker/images/ubuntu",
+        timeout => 0
+    }
+
+    exec { "fetch-go":
+        require => Package["wget"],
+        command => "/usr/bin/wget -O - $go_url | /bin/tar xz -C /usr/local",
+        creates => "/usr/local/go/bin/go",
+    }
+
+    exec { "fetch-docker" :
+        require => Package["wget"],
+        command => "/usr/bin/wget -O - $docker_url | /bin/tar xz -C /home/vagrant",
+        creates => "/home/vagrant/docker/dockerd"
+    }
+
+    file { "/etc/init/dockerd.conf":
+        mode => 600,
+        owner => "root",
+        group => "root",
+        content => template("docker/dockerd.conf"),
+        require => [Exec["fetch-docker"], Exec["debootstrap"]]
+    }
+
+    exec { "copy-docker-bin" :
+        require => Exec["fetch-docker"],
+        command => "/bin/cp /home/vagrant/docker/docker /usr/local/bin",
+        creates => "/usr/local/bin/docker"
+    }
+
+    service { "dockerd" :
+        ensure => "running",
+        start => "/sbin/initctl start dockerd",
+        stop => "/sbin/initctl stop dockerd",
+        require => File["/etc/init/dockerd.conf"],
+        name => "dockerd",
+        provider => "base"
+    }
+
+
+}

+ 11 - 0
puppet/modules/docker/templates/dockerd.conf

@@ -0,0 +1,11 @@
+description     "Run dockerd"
+
+stop on runlevel [!2345]
+start on runlevel [3]
+
+# if you want it to automatically restart if it crashes, leave the next line in
+respawn
+
+script
+    /home/vagrant/dockerd/dockerd
+end script

+ 13 - 5
rcli/tcp.go

@@ -10,12 +10,15 @@ import (
 	"bufio"
 )
 
-func CallTCP(addr string, args ...string) (*net.TCPConn, error) {
+// Connect to a remote endpoint using protocol `proto` and address `addr`,
+// issue a single call, and return the result.
+// `proto` may be "tcp", "unix", etc. See the `net` package for available protocols.
+func Call(proto, addr string, args ...string) (*net.TCPConn, error) {
 	cmd, err := json.Marshal(args)
 	if err != nil {
 		return nil, err
 	}
-	conn, err := net.Dial("tcp", addr)
+	conn, err := net.Dial(proto, addr)
 	if err != nil {
 		return nil, err
 	}
@@ -25,12 +28,14 @@ func CallTCP(addr string, args ...string) (*net.TCPConn, error) {
 	return conn.(*net.TCPConn), nil
 }
 
-func ListenAndServeTCP(addr string, service Service) error {
-	listener, err := net.Listen("tcp", addr)
+// Listen on `addr`, using protocol `proto`, for incoming rcli calls,
+// and pass them to `service`.
+func ListenAndServe(proto, addr string, service Service) error {
+	listener, err := net.Listen(proto, addr)
 	if err != nil {
 		return err
 	}
-	log.Printf("Listening for RCLI/TCP on %s\n", addr)
+	log.Printf("Listening for RCLI/%s on %s\n", proto, addr)
 	defer listener.Close()
 	for {
 		if conn, err := listener.Accept(); err != nil {
@@ -48,6 +53,9 @@ func ListenAndServeTCP(addr string, service Service) error {
 	return nil
 }
 
+
+// Parse an rcli call on a new connection, and pass it to `service` if it
+// is valid.
 func Serve(conn io.ReadWriter, service Service) error {
 	r := bufio.NewReader(conn)
 	var args []string

+ 11 - 1
rcli/types.go

@@ -1,5 +1,12 @@
 package rcli
 
+// rcli (Remote Command-Line Interface) is a simple protocol for...
+// serving command-line interfaces remotely.
+//
+// rcli can be used over any transport capable of a) sending binary streams in
+// both directions, and b) capable of half-closing a connection. TCP and Unix sockets
+// are the usual suspects.
+
 import (
 	"fmt"
 	"io"
@@ -20,6 +27,9 @@ type CmdMethod func(Service, io.ReadCloser, io.Writer, ...string) error
 
 
 func call(service Service, stdin io.ReadCloser, stdout io.Writer, args ...string) error {
+	if len(args) == 0 {
+		args = []string{"help"}
+	}
 	flags := flag.NewFlagSet("main", flag.ContinueOnError)
 	flags.SetOutput(stdout)
 	flags.Usage = func() { stdout.Write([]byte(service.Help())) }
@@ -33,7 +43,7 @@ func call(service Service, stdin io.ReadCloser, stdout io.Writer, args ...string
 	}
 	method := getMethod(service, cmd)
 	if method != nil {
-		return method(stdin, stdout, args[1:]...)
+		return method(stdin, stdout, flags.Args()[1:]...)
 	}
 	return errors.New("No such command: " + cmd)
 }