merge
This commit is contained in:
commit
ec21a2d364
11 changed files with 527 additions and 79 deletions
82
README.md
82
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
Vagrantfile
vendored
Normal file
100
Vagrantfile
vendored
Normal 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
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
puppet/manifests/quantal64.pp
Normal file
17
puppet/manifests/quantal64.pp
Normal 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"
|
||||
|
||||
}
|
56
puppet/modules/docker/manifests/init.pp
Normal file
56
puppet/modules/docker/manifests/init.pp
Normal 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"
|
||||
}
|
||||
|
||||
|
||||
}
|
11
puppet/modules/docker/templates/dockerd.conf
Normal file
11
puppet/modules/docker/templates/dockerd.conf
Normal 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
|
18
rcli/tcp.go
18
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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue