This commit is contained in:
Andrea Luzzardi 2013-02-13 14:19:35 -08:00
commit ec21a2d364
11 changed files with 527 additions and 79 deletions

View file

@ -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
Vagrantfile vendored Normal file
View file

@ -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

View file

@ -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
}

View file

@ -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
}
img, err := srv.images.Import(args[0], resp.Body, stdout, nil)
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(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 {

View file

@ -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
}

View file

@ -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

View file

@ -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"
}

View file

@ -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"
}
}

View file

@ -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

View file

@ -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

View file

@ -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)
}