From bbaa975ec8ea44f7df4f96a910360abc43b4d5f7 Mon Sep 17 00:00:00 2001 From: Daniel Mizyrycki Date: Tue, 9 Apr 2013 14:30:10 -0700 Subject: [PATCH 001/138] testing: Add buildbot VM --- Makefile | 3 ++ buildbot/README.rst | 20 ++++++++++++ buildbot/Vagrantfile | 28 ++++++++++++++++ buildbot/buildbot-cfg/buildbot-cfg.sh | 43 +++++++++++++++++++++++++ buildbot/buildbot-cfg/buildbot.conf | 18 +++++++++++ buildbot/buildbot-cfg/master.cfg | 46 +++++++++++++++++++++++++++ buildbot/buildbot-cfg/post-commit | 21 ++++++++++++ buildbot/buildbot.pp | 32 +++++++++++++++++++ buildbot/requirements.txt | 6 ++++ 9 files changed, 217 insertions(+) create mode 100644 buildbot/README.rst create mode 100644 buildbot/Vagrantfile create mode 100755 buildbot/buildbot-cfg/buildbot-cfg.sh create mode 100644 buildbot/buildbot-cfg/buildbot.conf create mode 100644 buildbot/buildbot-cfg/master.cfg create mode 100755 buildbot/buildbot-cfg/post-commit create mode 100644 buildbot/buildbot.pp create mode 100644 buildbot/requirements.txt diff --git a/Makefile b/Makefile index a6eb61383..94d8d4443 100644 --- a/Makefile +++ b/Makefile @@ -49,3 +49,6 @@ test: all fmt: @gofmt -s -l -w . + +hack: + @(cd $(CURDIR)/buildbot; vagrant up) diff --git a/buildbot/README.rst b/buildbot/README.rst new file mode 100644 index 000000000..a52b9769e --- /dev/null +++ b/buildbot/README.rst @@ -0,0 +1,20 @@ +Buildbot +======== + +Buildbot is a continuous integration system designed to automate the +build/test cycle. By automatically rebuilding and testing the tree each time +something has changed, build problems are pinpointed quickly, before other +developers are inconvenienced by the failure. + +When running 'make hack' at the docker root directory, it spawns a virtual +machine in the background running a buildbot instance and adds a git +post-commit hook that automatically run docker tests for you. + +You can check your buildbot instance at http://192.168.33.21:8010/waterfall + + +Buildbot dependencies +--------------------- + +vagrant, virtualbox packages and python package requests + diff --git a/buildbot/Vagrantfile b/buildbot/Vagrantfile new file mode 100644 index 000000000..ea027f066 --- /dev/null +++ b/buildbot/Vagrantfile @@ -0,0 +1,28 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +$BUILDBOT_IP = '192.168.33.21' + +def v10(config) + config.vm.box = "quantal64_3.5.0-25" + config.vm.box_url = "http://get.docker.io/vbox/ubuntu/12.10/quantal64_3.5.0-25.box" + config.vm.share_folder 'v-data', '/data/docker', File.dirname(__FILE__) + '/..' + config.vm.network :hostonly, $BUILDBOT_IP + + # Ensure puppet is installed on the instance + config.vm.provision :shell, :inline => 'apt-get -qq update; apt-get install -y puppet' + + config.vm.provision :puppet do |puppet| + puppet.manifests_path = '.' + puppet.manifest_file = 'buildbot.pp' + puppet.options = ['--templatedir','.'] + end +end + +Vagrant::VERSION < '1.1.0' and Vagrant::Config.run do |config| + v10(config) +end + +Vagrant::VERSION >= '1.1.0' and Vagrant.configure('1') do |config| + v10(config) +end diff --git a/buildbot/buildbot-cfg/buildbot-cfg.sh b/buildbot/buildbot-cfg/buildbot-cfg.sh new file mode 100755 index 000000000..5e4e7432f --- /dev/null +++ b/buildbot/buildbot-cfg/buildbot-cfg.sh @@ -0,0 +1,43 @@ +#!/bin/bash + +# Auto setup of buildbot configuration. Package installation is being done +# on buildbot.pp +# Dependencies: buildbot, buildbot-slave, supervisor + +SLAVE_NAME='buildworker' +SLAVE_SOCKET='localhost:9989' +BUILDBOT_PWD='pass-docker' +USER='vagrant' +ROOT_PATH='/data/buildbot' +DOCKER_PATH='/data/docker' +BUILDBOT_CFG="$DOCKER_PATH/buildbot/buildbot-cfg" +IP=$(grep BUILDBOT_IP /data/docker/buildbot/Vagrantfile | awk -F "'" '{ print $2; }') + +function run { su $USER -c "$1"; } + +export PATH=/bin:sbin:/usr/bin:/usr/sbin:/usr/local/bin + +# Exit if buildbot has already been installed +[ -d "$ROOT_PATH" ] && exit 0 + +# Setup buildbot +run "mkdir -p ${ROOT_PATH}" +cd ${ROOT_PATH} +run "buildbot create-master master" +run "cp $BUILDBOT_CFG/master.cfg master" +run "sed -i 's/localhost/$IP/' master/master.cfg" +run "buildslave create-slave slave $SLAVE_SOCKET $SLAVE_NAME $BUILDBOT_PWD" + +# Allow buildbot subprocesses (docker tests) to properly run in containers, +# in particular with docker -u +run "sed -i 's/^umask = None/umask = 000/' ${ROOT_PATH}/slave/buildbot.tac" + +# Setup supervisor +cp $BUILDBOT_CFG/buildbot.conf /etc/supervisor/conf.d/buildbot.conf +sed -i "s/^chmod=0700.*0700./chmod=0770\nchown=root:$USER/" /etc/supervisor/supervisord.conf +kill -HUP `pgrep -f "/usr/bin/python /usr/bin/supervisord"` + +# Add git hook +cp $BUILDBOT_CFG/post-commit $DOCKER_PATH/.git/hooks +sed -i "s/localhost/$IP/" $DOCKER_PATH/.git/hooks/post-commit + diff --git a/buildbot/buildbot-cfg/buildbot.conf b/buildbot/buildbot-cfg/buildbot.conf new file mode 100644 index 000000000..b162f4e7c --- /dev/null +++ b/buildbot/buildbot-cfg/buildbot.conf @@ -0,0 +1,18 @@ +[program:buildmaster] +command=su vagrant -c "buildbot start master" +directory=/data/buildbot +chown= root:root +redirect_stderr=true +stdout_logfile=/var/log/supervisor/buildbot-master.log +stderr_logfile=/var/log/supervisor/buildbot-master.log + +[program:buildworker] +command=buildslave start slave +directory=/data/buildbot +chown= root:root +redirect_stderr=true +stdout_logfile=/var/log/supervisor/buildbot-slave.log +stderr_logfile=/var/log/supervisor/buildbot-slave.log + +[group:buildbot] +programs=buildmaster,buildworker diff --git a/buildbot/buildbot-cfg/master.cfg b/buildbot/buildbot-cfg/master.cfg new file mode 100644 index 000000000..c786e418e --- /dev/null +++ b/buildbot/buildbot-cfg/master.cfg @@ -0,0 +1,46 @@ +import os +from buildbot.buildslave import BuildSlave +from buildbot.schedulers.forcesched import ForceScheduler +from buildbot.config import BuilderConfig +from buildbot.process.factory import BuildFactory +from buildbot.steps.shell import ShellCommand +from buildbot.status import html +from buildbot.status.web import authz, auth + +PORT_WEB = 8010 # Buildbot webserver port +PORT_MASTER = 9989 # Port where buildbot master listen buildworkers +TEST_USER = 'buildbot' # Credential to authenticate build triggers +TEST_PWD = 'docker' # Credential to authenticate build triggers +BUILDER_NAME = 'docker' +BUILDPASSWORD = 'pass-docker' # Credential to authenticate buildworkers +DOCKER_PATH = '/data/docker' + + +c = BuildmasterConfig = {} + +c['title'] = "Docker" +c['titleURL'] = "waterfall" +c['buildbotURL'] = "http://localhost:{0}/".format(PORT_WEB) +c['db'] = {'db_url':"sqlite:///state.sqlite"} +c['slaves'] = [BuildSlave('buildworker', BUILDPASSWORD)] +c['slavePortnum'] = PORT_MASTER + +c['schedulers'] = [ForceScheduler(name='trigger',builderNames=[BUILDER_NAME])] + +# Docker test command +test_cmd = """( + cd {0}/..; rm -rf docker-tmp; git clone docker docker-tmp; + cd docker-tmp; make test; exit_status=$?; + cd ..; rm -rf docker-tmp; exit $exit_status)""".format(DOCKER_PATH) + +# Builder +factory = BuildFactory() +factory.addStep(ShellCommand(description='Docker',logEnviron=False, + usePTY=True,command=test_cmd)) +c['builders'] = [BuilderConfig(name=BUILDER_NAME,slavenames=['buildworker'], + factory=factory)] + +# Status +authz_cfg=authz.Authz(auth=auth.BasicAuth([(TEST_USER,TEST_PWD)]), + forceBuild='auth') +c['status'] = [html.WebStatus(http_port=PORT_WEB, authz=authz_cfg)] diff --git a/buildbot/buildbot-cfg/post-commit b/buildbot/buildbot-cfg/post-commit new file mode 100755 index 000000000..8c5a06bf3 --- /dev/null +++ b/buildbot/buildbot-cfg/post-commit @@ -0,0 +1,21 @@ +#!/usr/bin/python + +'''Trigger buildbot docker test build + + post-commit git hook designed to automatically trigger buildbot on + the provided vagrant docker VM.''' + +import requests + +USERNAME = 'buildbot' +PASSWORD = 'docker' +BASE_URL = 'http://localhost:8010' +path = lambda s: BASE_URL + '/' + s + +try: + session = requests.session() + session.post(path('login'),data={'username':USERNAME,'passwd':PASSWORD}) + session.post(path('builders/docker/force'), + data={'forcescheduler':'trigger','reason':'Test commit'}) +except: + pass diff --git a/buildbot/buildbot.pp b/buildbot/buildbot.pp new file mode 100644 index 000000000..8109cdc2a --- /dev/null +++ b/buildbot/buildbot.pp @@ -0,0 +1,32 @@ +node default { + $USER = 'vagrant' + $ROOT_PATH = '/data/buildbot' + $DOCKER_PATH = '/data/docker' + + exec {'apt_update': command => '/usr/bin/apt-get update' } + Package { require => Exec['apt_update'] } + group {'puppet': ensure => 'present'} + + # Install dependencies + Package { ensure => 'installed' } + package { ['python-dev','python-pip','supervisor','lxc','bsdtar','git','golang']: } + + file{[ '/data' ]: + owner => $USER, group => $USER, ensure => 'directory' } + + file {'/var/tmp/requirements.txt': + content => template('requirements.txt') } + + exec {'requirements': + require => [ Package['python-dev'], Package['python-pip'], + File['/var/tmp/requirements.txt'] ], + cwd => '/var/tmp', + command => "/bin/sh -c '(/usr/bin/pip install -r requirements.txt; + rm /var/tmp/requirements.txt)'" } + + exec {'buildbot-cfg-sh': + require => [ Package['supervisor'], Exec['requirements']], + path => '/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin', + cwd => '/data', + command => "$DOCKER_PATH/buildbot/buildbot-cfg/buildbot-cfg.sh" } +} diff --git a/buildbot/requirements.txt b/buildbot/requirements.txt new file mode 100644 index 000000000..0e451b017 --- /dev/null +++ b/buildbot/requirements.txt @@ -0,0 +1,6 @@ +sqlalchemy<=0.7.9 +sqlalchemy-migrate>=0.7.2 +buildbot==0.8.7p1 +buildbot_slave==0.8.7p1 +nose==1.2.1 +requests==1.1.0 From b7cda3288ed857e4e2155846090b17a2d8fccdbf Mon Sep 17 00:00:00 2001 From: Julien Barbier Date: Tue, 9 Apr 2013 19:07:50 -0700 Subject: [PATCH 002/138] Fix the Makefile, rule=hack to make it work on Windows --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 94d8d4443..dea53206a 100644 --- a/Makefile +++ b/Makefile @@ -51,4 +51,4 @@ fmt: @gofmt -s -l -w . hack: - @(cd $(CURDIR)/buildbot; vagrant up) + cd $(CURDIR)/buildbot && vagrant up From d2c1850fb5a210d59b87dcb8504c8c24b60cca3c Mon Sep 17 00:00:00 2001 From: Daniel Mizyrycki Date: Wed, 10 Apr 2013 11:23:56 -0700 Subject: [PATCH 003/138] testing: Make postcommit more generic --- buildbot/buildbot-cfg/post-commit | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildbot/buildbot-cfg/post-commit b/buildbot/buildbot-cfg/post-commit index 8c5a06bf3..0173fe504 100755 --- a/buildbot/buildbot-cfg/post-commit +++ b/buildbot/buildbot-cfg/post-commit @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python '''Trigger buildbot docker test build From 45809e9a055defd073bde794026aa611333b7023 Mon Sep 17 00:00:00 2001 From: Shawn Siefkas Date: Fri, 12 Apr 2013 13:20:03 -0500 Subject: [PATCH 004/138] Issue #405: Documentation for Arch Linux Install --- docs/sources/installation/archlinux.rst | 43 +++++++++++++++++++++++++ docs/sources/installation/index.rst | 1 + 2 files changed, 44 insertions(+) create mode 100644 docs/sources/installation/archlinux.rst diff --git a/docs/sources/installation/archlinux.rst b/docs/sources/installation/archlinux.rst new file mode 100644 index 000000000..c6de247d6 --- /dev/null +++ b/docs/sources/installation/archlinux.rst @@ -0,0 +1,43 @@ +.. _arch_linux: + +Arch Linux +========== + +Installing on Arch Linux is not officially supported but can be handled via +either of the following AUR packages: + +* `dotcloud-docker `_ +* `dotcloud-docker-git `_ + +The dotcloud-docker package will install the latest tagged version of docker. +The dotcloud-docker-git package will build from the current master branch. + +Dependencies +------------ + +Docker depends on several packages which will be installed automatically with +either AUR package. + +* aufs3 +* bridge-utils +* go +* iproute2 +* linux-aufs_friendly + +Installation +------------ + +The instructions here assume **yaourt** is installed. See +`Arch User Repository `_ +for information on building and installing packages from the AUR if you have not +done so before. + +Keep in mind that if **linux-aufs_friendly** is not already installed that a +new kernel will be compiled and this can take quite a while. + +:: + + yaourt -S dotcloud-docker-git + +Prior to starting docker modify your bootloader to use the +**linux-aufs_friendly** kernel and reboot your system. diff --git a/docs/sources/installation/index.rst b/docs/sources/installation/index.rst index b02e9c83a..b2718c72b 100644 --- a/docs/sources/installation/index.rst +++ b/docs/sources/installation/index.rst @@ -13,6 +13,7 @@ Contents: :maxdepth: 1 ubuntulinux + archlinux macos windows amazon From ac49a797b469bda89384dc1cd31d349761340693 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 15 Apr 2013 12:05:46 +0200 Subject: [PATCH 005/138] try to load aufs module, disply error on failure --- image.go | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/image.go b/image.go index 83bf9481a..f9595bd97 100644 --- a/image.go +++ b/image.go @@ -7,7 +7,9 @@ import ( "fmt" "io" "io/ioutil" + "log" "os" + "os/exec" "path" "strings" "time" @@ -92,7 +94,19 @@ func MountAUFS(ro []string, rw string, target string) error { roBranches += fmt.Sprintf("%v=ro:", layer) } branches := fmt.Sprintf("br:%v:%v", rwBranch, roBranches) - return mount("none", target, "aufs", 0, branches) + + //if error, try to load aufs kernel module + if err := mount("none", target, "aufs", 0, branches); err != nil { + log.Printf("Kernel does not support AUFS, trying to load the AUFS module with modprobe...") + if err := exec.Command("modprobe", "aufs").Run(); err != nil { + return fmt.Errorf("Unable to load the AUFS module") + } + log.Printf("...module loaded.") + if err := mount("none", target, "aufs", 0, branches); err != nil { + return fmt.Errorf("Unable to mount using aufs") + } + } + return nil } func (image *Image) Mount(root, rw string) error { From 468fb901172f2534972cc44cd4459f14b3745a98 Mon Sep 17 00:00:00 2001 From: "Kevin J. Lynagh" Date: Mon, 15 Apr 2013 08:49:48 -0700 Subject: [PATCH 006/138] install.sh script's dockerd.conf should set docker daemon environment's LANG to en_US.UTF-8. See #355. --- contrib/install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/install.sh b/contrib/install.sh index b0a998332..d7c6e6646 100755 --- a/contrib/install.sh +++ b/contrib/install.sh @@ -45,7 +45,7 @@ then echo "Upstart script already exists." else echo "Creating /etc/init/dockerd.conf..." - echo "exec /usr/local/bin/docker -d" > /etc/init/dockerd.conf + echo "exec env LANG=\"en_US.UTF-8\" /usr/local/bin/docker -d" > /etc/init/dockerd.conf fi echo "Starting dockerd..." From fc72a809c1c60569fb1d70a95f5296feaa5220f8 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Tue, 16 Apr 2013 12:10:16 -0700 Subject: [PATCH 007/138] Remove unneeded dependencies from README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c186d9a06..4ba9222f8 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ Installing on Ubuntu 12.04 and 12.10 1. Install dependencies: ```bash - sudo apt-get install lxc wget bsdtar curl + sudo apt-get install lxc bsdtar sudo apt-get install linux-image-extra-`uname -r` ``` From 7b0e96f1f4639f869703ed32f885cec9b666b127 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Tue, 16 Apr 2013 00:25:55 -0700 Subject: [PATCH 008/138] Manually pass the env to docker-init instead of relying on lxc to pass it --- container.go | 27 ++++++++++++++------------- sysinit.go | 8 +++++--- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/container.go b/container.go index 74706a407..9f175e42a 100644 --- a/container.go +++ b/container.go @@ -390,21 +390,26 @@ func (container *Container) Start() error { params = append(params, "-u", container.Config.User) } + if container.Config.Tty { + params = append(params, "-e", "TERM=xterm") + } + + // Setup environment + params = append(params, + "-e", "HOME=/", + "-e", "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + ) + + for _, elem := range container.Config.Env { + params = append(params, "-e", elem) + } + // Program params = append(params, "--", container.Path) params = append(params, container.Args...) container.cmd = exec.Command("lxc-start", params...) - // Setup environment - container.cmd.Env = append( - []string{ - "HOME=/", - "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", - }, - container.Config.Env..., - ) - // Setup logging of stdout and stderr to disk if err := container.runtime.LogToDisk(container.stdout, container.logPath("stdout")); err != nil { return err @@ -415,10 +420,6 @@ func (container *Container) Start() error { var err error if container.Config.Tty { - container.cmd.Env = append( - []string{"TERM=xterm"}, - container.cmd.Env..., - ) err = container.startPty() } else { err = container.start() diff --git a/sysinit.go b/sysinit.go index 2c1106db1..4b2d6c303 100644 --- a/sysinit.go +++ b/sysinit.go @@ -53,8 +53,7 @@ func changeUser(u string) { } // Clear environment pollution introduced by lxc-start -func cleanupEnv() { - env := os.Environ() +func cleanupEnv(env ListOpts) { os.Clearenv() for _, kv := range env { parts := strings.SplitN(kv, "=", 2) @@ -91,10 +90,13 @@ func SysInit() { var u = flag.String("u", "", "username or uid") var gw = flag.String("g", "", "gateway address") + var flEnv ListOpts + flag.Var(&flEnv, "e", "Set environment variables") + flag.Parse() + cleanupEnv(flEnv) setupNetworking(*gw) - cleanupEnv() changeUser(*u) executeProgram(flag.Arg(0), flag.Args()) } From c4cd224d901ece4d9a2f15d10a80998f2b970c07 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Tue, 16 Apr 2013 15:20:04 -0700 Subject: [PATCH 009/138] improve the crashTest script --- contrib/crashTest.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/contrib/crashTest.go b/contrib/crashTest.go index 34749b52d..fa9cda605 100644 --- a/contrib/crashTest.go +++ b/contrib/crashTest.go @@ -11,6 +11,7 @@ import ( const DOCKER_PATH = "/home/creack/dotcloud/docker/docker/docker" func runDaemon() (*exec.Cmd, error) { + os.Remove("/var/run/docker.pid") cmd := exec.Command(DOCKER_PATH, "-d") outPipe, err := cmd.StdoutPipe() if err != nil { @@ -42,10 +43,12 @@ func crashTest() error { if err != nil { return err } - time.Sleep(5000 * time.Millisecond) + // time.Sleep(5000 * time.Millisecond) + var stop bool go func() error { - for i := 0; i < 100; i++ { - go func() error { + stop = false + for i := 0; i < 100 && !stop; i++ { + func() error { cmd := exec.Command(DOCKER_PATH, "run", "base", "echo", "hello", "world") log.Printf("%d", i) outPipe, err := cmd.StdoutPipe() @@ -74,12 +77,11 @@ func crashTest() error { outPipe.Close() return nil }() - time.Sleep(250 * time.Millisecond) } return nil }() - time.Sleep(20 * time.Second) + stop = true if err := daemon.Process.Kill(); err != nil { return err } From 1615bb08c7c3fc6c4b22db0a633edda516f97cf0 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 16 Apr 2013 18:43:44 +0200 Subject: [PATCH 010/138] added -t in docker stop and restart to choose grace period --- commands.go | 8 +++++--- container.go | 10 +++++----- runtime.go | 2 +- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/commands.go b/commands.go index 82a0ce103..2b0c91a58 100644 --- a/commands.go +++ b/commands.go @@ -223,7 +223,8 @@ func (srv *Server) CmdInfo(stdin io.ReadCloser, stdout io.Writer, args ...string } func (srv *Server) CmdStop(stdin io.ReadCloser, stdout io.Writer, args ...string) error { - cmd := rcli.Subcmd(stdout, "stop", "CONTAINER [CONTAINER...]", "Stop a running container") + cmd := rcli.Subcmd(stdout, "stop", "[OPTIONS] CONTAINER [CONTAINER...]", "Stop a running container") + nSeconds := cmd.Int("t", 10, "wait t seconds before killing the container") if err := cmd.Parse(args); err != nil { return nil } @@ -233,7 +234,7 @@ func (srv *Server) CmdStop(stdin io.ReadCloser, stdout io.Writer, args ...string } for _, name := range cmd.Args() { if container := srv.runtime.Get(name); container != nil { - if err := container.Stop(); err != nil { + if err := container.Stop(*nSeconds); err != nil { return err } fmt.Fprintln(stdout, container.ShortId()) @@ -246,6 +247,7 @@ func (srv *Server) CmdStop(stdin io.ReadCloser, stdout io.Writer, args ...string func (srv *Server) CmdRestart(stdin io.ReadCloser, stdout io.Writer, args ...string) error { cmd := rcli.Subcmd(stdout, "restart", "CONTAINER [CONTAINER...]", "Restart a running container") + nSeconds := cmd.Int("t", 10, "wait t seconds before killing the container") if err := cmd.Parse(args); err != nil { return nil } @@ -255,7 +257,7 @@ func (srv *Server) CmdRestart(stdin io.ReadCloser, stdout io.Writer, args ...str } for _, name := range cmd.Args() { if container := srv.runtime.Get(name); container != nil { - if err := container.Restart(); err != nil { + if err := container.Restart(*nSeconds); err != nil { return err } fmt.Fprintln(stdout, container.ShortId()) diff --git a/container.go b/container.go index 74706a407..2bd56180a 100644 --- a/container.go +++ b/container.go @@ -599,7 +599,7 @@ func (container *Container) Kill() error { return container.kill() } -func (container *Container) Stop() error { +func (container *Container) Stop(seconds int) error { container.State.lock() defer container.State.unlock() if !container.State.Running { @@ -619,8 +619,8 @@ func (container *Container) Stop() error { } // 2. Wait for the process to exit on its own - if err := container.WaitTimeout(10 * time.Second); err != nil { - log.Printf("Container %v failed to exit within 10 seconds of SIGTERM - using the force", container.Id) + if err := container.WaitTimeout(time.Duration(seconds) * time.Second); err != nil { + log.Printf("Container %v failed to exit within %d seconds of SIGTERM - using the force", container.Id, seconds) if err := container.kill(); err != nil { return err } @@ -628,8 +628,8 @@ func (container *Container) Stop() error { return nil } -func (container *Container) Restart() error { - if err := container.Stop(); err != nil { +func (container *Container) Restart(seconds int) error { + if err := container.Stop(seconds); err != nil { return err } if err := container.Start(); err != nil { diff --git a/runtime.go b/runtime.go index 3fe07c7ea..0955eb870 100644 --- a/runtime.go +++ b/runtime.go @@ -217,7 +217,7 @@ func (runtime *Runtime) Destroy(container *Container) error { return fmt.Errorf("Container %v not found - maybe it was already destroyed?", container.Id) } - if err := container.Stop(); err != nil { + if err := container.Stop(10); err != nil { return err } if mounted, err := container.Mounted(); err != nil { From ca6cd5b557279facb4831e8380be4eb40ac38362 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Wed, 17 Apr 2013 11:32:13 -0700 Subject: [PATCH 011/138] Keep a cache of the unit-tests image. So I can code in conferences with crappy wifi. --- runtime_test.go | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/runtime_test.go b/runtime_test.go index b990deaf3..20e7ee140 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -12,11 +12,9 @@ import ( "time" ) -// FIXME: this is no longer needed -const testLayerPath string = "/var/lib/docker/docker-ut.tar" const unitTestImageName string = "docker-ut" -var unitTestStoreBase string +const unitTestStoreBase string = "/var/lib/docker/unit-tests" func nuke(runtime *Runtime) error { var wg sync.WaitGroup @@ -62,15 +60,8 @@ func init() { panic("docker tests needs to be run as root") } - // Create a temp directory - root, err := ioutil.TempDir("", "docker-test") - if err != nil { - panic(err) - } - unitTestStoreBase = root - // Make it our Store root - runtime, err := NewRuntimeFromDirectory(root) + runtime, err := NewRuntimeFromDirectory(unitTestStoreBase) if err != nil { panic(err) } From 13d9e26edd62228597cf8f060ed397f7ae00298a Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Wed, 17 Apr 2013 16:35:22 -0700 Subject: [PATCH 012/138] Fix the behavior of Graph.Register so that it can be interrupted without side effect --- graph.go | 2 +- graph_test.go | 27 +++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/graph.go b/graph.go index e7044c25a..afb89cd02 100644 --- a/graph.go +++ b/graph.go @@ -111,7 +111,7 @@ func (graph *Graph) Register(layerData Archive, img *Image) error { if graph.Exists(img.Id) { return fmt.Errorf("Image %s already exists", img.Id) } - tmp, err := graph.Mktemp(img.Id) + tmp, err := graph.Mktemp("") defer os.RemoveAll(tmp) if err != nil { return fmt.Errorf("Mktemp failed: %s", err) diff --git a/graph_test.go b/graph_test.go index 7c40330aa..8f2898349 100644 --- a/graph_test.go +++ b/graph_test.go @@ -3,6 +3,7 @@ package docker import ( "archive/tar" "bytes" + "errors" "io" "io/ioutil" "os" @@ -26,6 +27,32 @@ func TestInit(t *testing.T) { } } +// Test that Register can be interrupted cleanly without side effects +func TestInterruptedRegister(t *testing.T) { + graph := tempGraph(t) + defer os.RemoveAll(graph.Root) + badArchive, w := io.Pipe() // Use a pipe reader as a fake archive which never yields data + image := &Image{ + Id: GenerateId(), + Comment: "testing", + Created: time.Now(), + } + go graph.Register(badArchive, image) + time.Sleep(200 * time.Millisecond) + w.CloseWithError(errors.New("But I'm not a tarball!")) // (Nobody's perfect, darling) + if _, err := graph.Get(image.Id); err == nil { + t.Fatal("Image should not exist after Register is interrupted") + } + // Registering the same image again should succeed if the first register was interrupted + goodArchive, err := fakeTar() + if err != nil { + t.Fatal(err) + } + if err := graph.Register(goodArchive, image); err != nil { + t.Fatal(err) + } +} + // FIXME: Do more extensive tests (ex: create multiple, delete, recreate; // create multiple, check the amount of images and paths, etc..) func TestGraphCreate(t *testing.T) { From e34e44e8e6097e0a54bc0a96581f79909f7d9a42 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Wed, 17 Apr 2013 17:12:08 -0700 Subject: [PATCH 013/138] Bumped version to 0.1.5 --- commands.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commands.go b/commands.go index 82a0ce103..cfb1d0300 100644 --- a/commands.go +++ b/commands.go @@ -18,7 +18,7 @@ import ( "unicode" ) -const VERSION = "0.1.4" +const VERSION = "0.1.5" var ( GIT_COMMIT string From 4ef2d5c1e6e65b1e214071388618fc9fa4345be9 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Wed, 17 Apr 2013 19:58:17 -0700 Subject: [PATCH 014/138] Added 'author' field to the image format --- commands.go | 4 ++-- container_test.go | 2 +- graph.go | 3 ++- graph_test.go | 12 ++++++------ image.go | 1 + runtime.go | 4 ++-- 6 files changed, 14 insertions(+), 12 deletions(-) diff --git a/commands.go b/commands.go index cfb1d0300..cfc4b714c 100644 --- a/commands.go +++ b/commands.go @@ -472,7 +472,7 @@ func (srv *Server) CmdImport(stdin io.ReadCloser, stdout rcli.DockerConn, args . } archive = ProgressReader(resp.Body, int(resp.ContentLength), stdout) } - img, err := srv.runtime.graph.Create(archive, nil, "Imported from "+src) + img, err := srv.runtime.graph.Create(archive, nil, "Imported from "+src, "") if err != nil { return err } @@ -727,7 +727,7 @@ func (srv *Server) CmdCommit(stdin io.ReadCloser, stdout io.Writer, args ...stri cmd.Usage() return nil } - img, err := srv.runtime.Commit(containerName, repository, tag, *flComment) + img, err := srv.runtime.Commit(containerName, repository, tag, *flComment, "") if err != nil { return err } diff --git a/container_test.go b/container_test.go index d5f3694c5..ff2dc4c4c 100644 --- a/container_test.go +++ b/container_test.go @@ -182,7 +182,7 @@ func TestCommitRun(t *testing.T) { if err != nil { t.Error(err) } - img, err := runtime.graph.Create(rwTar, container1, "unit test commited image") + img, err := runtime.graph.Create(rwTar, container1, "unit test commited image", "") if err != nil { t.Error(err) } diff --git a/graph.go b/graph.go index afb89cd02..b7dbf2e11 100644 --- a/graph.go +++ b/graph.go @@ -83,12 +83,13 @@ func (graph *Graph) Get(name string) (*Image, error) { } // Create creates a new image and registers it in the graph. -func (graph *Graph) Create(layerData Archive, container *Container, comment string) (*Image, error) { +func (graph *Graph) Create(layerData Archive, container *Container, comment, author string) (*Image, error) { img := &Image{ Id: GenerateId(), Comment: comment, Created: time.Now(), DockerVersion: VERSION, + Author: author, } if container != nil { img.Parent = container.Image diff --git a/graph_test.go b/graph_test.go index 8f2898349..1bd05aaa9 100644 --- a/graph_test.go +++ b/graph_test.go @@ -62,7 +62,7 @@ func TestGraphCreate(t *testing.T) { if err != nil { t.Fatal(err) } - image, err := graph.Create(archive, nil, "Testing") + image, err := graph.Create(archive, nil, "Testing", "") if err != nil { t.Fatal(err) } @@ -122,7 +122,7 @@ func TestMount(t *testing.T) { if err != nil { t.Fatal(err) } - image, err := graph.Create(archive, nil, "Testing") + image, err := graph.Create(archive, nil, "Testing", "") if err != nil { t.Fatal(err) } @@ -166,7 +166,7 @@ func createTestImage(graph *Graph, t *testing.T) *Image { if err != nil { t.Fatal(err) } - img, err := graph.Create(archive, nil, "Test image") + img, err := graph.Create(archive, nil, "Test image", "") if err != nil { t.Fatal(err) } @@ -181,7 +181,7 @@ func TestDelete(t *testing.T) { t.Fatal(err) } assertNImages(graph, t, 0) - img, err := graph.Create(archive, nil, "Bla bla") + img, err := graph.Create(archive, nil, "Bla bla", "") if err != nil { t.Fatal(err) } @@ -192,11 +192,11 @@ func TestDelete(t *testing.T) { assertNImages(graph, t, 0) // Test 2 create (same name) / 1 delete - img1, err := graph.Create(archive, nil, "Testing") + img1, err := graph.Create(archive, nil, "Testing", "") if err != nil { t.Fatal(err) } - if _, err = graph.Create(archive, nil, "Testing"); err != nil { + if _, err = graph.Create(archive, nil, "Testing", ""); err != nil { t.Fatal(err) } assertNImages(graph, t, 2) diff --git a/image.go b/image.go index f9595bd97..9369fc3f4 100644 --- a/image.go +++ b/image.go @@ -23,6 +23,7 @@ type Image struct { Container string `json:"container,omitempty"` ContainerConfig Config `json:"container_config,omitempty"` DockerVersion string `json:"docker_version,omitempty"` + Author string `json:"author,omitempty"` graph *Graph } diff --git a/runtime.go b/runtime.go index 3fe07c7ea..72de9f847 100644 --- a/runtime.go +++ b/runtime.go @@ -238,7 +238,7 @@ func (runtime *Runtime) Destroy(container *Container) error { // Commit creates a new filesystem image from the current state of a container. // The image can optionally be tagged into a repository -func (runtime *Runtime) Commit(id, repository, tag, comment string) (*Image, error) { +func (runtime *Runtime) Commit(id, repository, tag, comment, author string) (*Image, error) { container := runtime.Get(id) if container == nil { return nil, fmt.Errorf("No such container: %s", id) @@ -250,7 +250,7 @@ func (runtime *Runtime) Commit(id, repository, tag, comment string) (*Image, err return nil, err } // Create a new image from the container's base layers + a new layer from container changes - img, err := runtime.graph.Create(rwTar, container, comment) + img, err := runtime.graph.Create(rwTar, container, comment, author) if err != nil { return nil, err } From 227a8142a3c2ea2fd3b085214ef39989ebd57fe1 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Wed, 17 Apr 2013 20:13:11 -0700 Subject: [PATCH 015/138] Record the author of an image with 'docker commit -author' --- commands.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/commands.go b/commands.go index cfc4b714c..d66eaa839 100644 --- a/commands.go +++ b/commands.go @@ -719,6 +719,7 @@ func (srv *Server) CmdCommit(stdin io.ReadCloser, stdout io.Writer, args ...stri "commit", "[OPTIONS] CONTAINER [REPOSITORY [TAG]]", "Create a new image from a container's changes") flComment := cmd.String("m", "", "Commit message") + flAuthor := cmd.String("author", "", "Author (eg. \"John Hannibal Smith \"") if err := cmd.Parse(args); err != nil { return nil } @@ -727,7 +728,7 @@ func (srv *Server) CmdCommit(stdin io.ReadCloser, stdout io.Writer, args ...stri cmd.Usage() return nil } - img, err := srv.runtime.Commit(containerName, repository, tag, *flComment, "") + img, err := srv.runtime.Commit(containerName, repository, tag, *flComment, *flAuthor) if err != nil { return err } From ee82870ff78e6d9e0f0ce7674d6a065f9d02f67a Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Wed, 17 Apr 2013 20:18:35 -0700 Subject: [PATCH 016/138] Bumped version to 0.1.6 to mark image format change (author field) --- commands.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commands.go b/commands.go index d66eaa839..ba501dd5e 100644 --- a/commands.go +++ b/commands.go @@ -18,7 +18,7 @@ import ( "unicode" ) -const VERSION = "0.1.5" +const VERSION = "0.1.6" var ( GIT_COMMIT string From fd39af7f859b5bd1c202b4c0ddee53de9c82437d Mon Sep 17 00:00:00 2001 From: Daniel Mizyrycki Date: Fri, 12 Apr 2013 11:42:35 -0700 Subject: [PATCH 017/138] packaging-ubuntu: move original files in place for update --- packaging/ubuntu/{debian => }/changelog | 0 packaging/ubuntu/{debian => }/compat | 0 packaging/ubuntu/{debian => }/control | 0 packaging/ubuntu/{debian => }/copyright | 0 packaging/ubuntu/{etc => }/docker.upstart | 0 packaging/ubuntu/{debian => }/docs | 0 packaging/ubuntu/{debian => }/rules | 0 packaging/ubuntu/{debian => }/source/format | 0 8 files changed, 0 insertions(+), 0 deletions(-) rename packaging/ubuntu/{debian => }/changelog (100%) rename packaging/ubuntu/{debian => }/compat (100%) rename packaging/ubuntu/{debian => }/control (100%) rename packaging/ubuntu/{debian => }/copyright (100%) rename packaging/ubuntu/{etc => }/docker.upstart (100%) rename packaging/ubuntu/{debian => }/docs (100%) rename packaging/ubuntu/{debian => }/rules (100%) rename packaging/ubuntu/{debian => }/source/format (100%) diff --git a/packaging/ubuntu/debian/changelog b/packaging/ubuntu/changelog similarity index 100% rename from packaging/ubuntu/debian/changelog rename to packaging/ubuntu/changelog diff --git a/packaging/ubuntu/debian/compat b/packaging/ubuntu/compat similarity index 100% rename from packaging/ubuntu/debian/compat rename to packaging/ubuntu/compat diff --git a/packaging/ubuntu/debian/control b/packaging/ubuntu/control similarity index 100% rename from packaging/ubuntu/debian/control rename to packaging/ubuntu/control diff --git a/packaging/ubuntu/debian/copyright b/packaging/ubuntu/copyright similarity index 100% rename from packaging/ubuntu/debian/copyright rename to packaging/ubuntu/copyright diff --git a/packaging/ubuntu/etc/docker.upstart b/packaging/ubuntu/docker.upstart similarity index 100% rename from packaging/ubuntu/etc/docker.upstart rename to packaging/ubuntu/docker.upstart diff --git a/packaging/ubuntu/debian/docs b/packaging/ubuntu/docs similarity index 100% rename from packaging/ubuntu/debian/docs rename to packaging/ubuntu/docs diff --git a/packaging/ubuntu/debian/rules b/packaging/ubuntu/rules similarity index 100% rename from packaging/ubuntu/debian/rules rename to packaging/ubuntu/rules diff --git a/packaging/ubuntu/debian/source/format b/packaging/ubuntu/source/format similarity index 100% rename from packaging/ubuntu/debian/source/format rename to packaging/ubuntu/source/format From 523cd8e29cb871271a8e2a55ab009aa980ae5572 Mon Sep 17 00:00:00 2001 From: Daniel Mizyrycki Date: Mon, 15 Apr 2013 18:01:54 -0700 Subject: [PATCH 018/138] packaging-ubuntu, issue #30: streamline building and uploading to PPA --- packaging/ubuntu/Makefile | 120 ++++---- packaging/ubuntu/README.ubuntu | 37 +++ packaging/ubuntu/Vagrantfile | 12 + packaging/ubuntu/changelog | 19 +- packaging/ubuntu/control | 22 +- packaging/ubuntu/copyright | 440 ++++++++++++++------------- packaging/ubuntu/docker.upstart | 2 +- packaging/ubuntu/lxc-docker.postinst | 4 + packaging/ubuntu/lxc-docker.prerm | 4 + packaging/ubuntu/maintainer.ubuntu | 34 +++ 10 files changed, 407 insertions(+), 287 deletions(-) create mode 100644 packaging/ubuntu/README.ubuntu create mode 100644 packaging/ubuntu/Vagrantfile create mode 100644 packaging/ubuntu/lxc-docker.postinst create mode 100644 packaging/ubuntu/lxc-docker.prerm create mode 100644 packaging/ubuntu/maintainer.ubuntu diff --git a/packaging/ubuntu/Makefile b/packaging/ubuntu/Makefile index beec903fc..0443d8b3e 100644 --- a/packaging/ubuntu/Makefile +++ b/packaging/ubuntu/Makefile @@ -1,73 +1,57 @@ +# Ubuntu package Makefile +# +# Dependencies: debhelper autotools-dev devscripts golang +# Notes: +# Use 'make ubuntu' to create the ubuntu package +# GPG_KEY environment variable needs to contain a GPG private key for package to be signed +# and uploaded to docker PPA. +# If GPG_KEY is not defined, make ubuntu will create docker package and exit with +# status code 2 + PKG_NAME=lxc-docker -PKG_ARCH=amd64 -PKG_VERSION=1 -ROOT_PATH:=$(PWD) -BUILD_PATH=build # Do not change, decided by dpkg-buildpackage -BUILD_SRC=build_src -GITHUB_PATH=src/github.com/dotcloud/docker -INSDIR=usr/bin -SOURCE_PACKAGE=$(PKG_NAME)_$(PKG_VERSION).orig.tar.gz -DEB_PACKAGE=$(PKG_NAME)_$(PKG_VERSION)_$(PKG_ARCH).deb -EXTRA_GO_PKG=./auth +VERSION=$(shell head -1 changelog | sed 's/^.\+(\(.\+\)..).\+$$/\1/') +GITHUB_PATH=github.com/dotcloud/docker +DOCKER_VERSION=${PKG_NAME}_${VERSION} +DOCKER_FVERSION=${PKG_NAME}_$(shell head -1 changelog | sed 's/^.\+(\(.\+\)).\+$$/\1/') +BUILD_SRC=${CURDIR}/../../build_src -TMPDIR=$(shell mktemp -d -t XXXXXX) +all: + # Compile docker. Used by dpkg-buildpackage. + cd src/${GITHUB_PATH}/docker; GOPATH=${CURDIR} go build - -# Build a debian source package -all: clean build_in_deb - -build_in_deb: - echo "GOPATH = " $(ROOT_PATH) - mkdir bin - cd $(GITHUB_PATH)/docker; GOPATH=$(ROOT_PATH) go build -o $(ROOT_PATH)/bin/docker - -# DESTDIR provided by Debian packaging install: - # Call this from a go environment (as packaged for deb source package) - mkdir -p $(DESTDIR)/$(INSDIR) - mkdir -p $(DESTDIR)/etc/init - install -m 0755 bin/docker $(DESTDIR)/$(INSDIR) - install -o root -m 0755 etc/docker.upstart $(DESTDIR)/etc/init/docker.conf + # Used by dpkg-buildpackage + mkdir -p ${DESTDIR}/usr/bin + mkdir -p ${DESTDIR}/etc/init + install -m 0755 src/${GITHUB_PATH}/docker/docker ${DESTDIR}/usr/bin + install -o root -m 0755 debian/docker.upstart ${DESTDIR}/etc/init/docker.conf -$(BUILD_SRC): clean - # Copy ourselves into $BUILD_SRC to comply with unusual golang constraints - tar --exclude=*.tar.gz --exclude=checkout.tgz -f checkout.tgz -cz * - mkdir -p $(BUILD_SRC)/$(GITHUB_PATH) - tar -f checkout.tgz -C $(BUILD_SRC)/$(GITHUB_PATH) -xz - cd $(BUILD_SRC)/$(GITHUB_PATH)/docker; GOPATH=$(ROOT_PATH)/$(BUILD_SRC) go get -d - for d in `find $(BUILD_SRC) -name '.git*'`; do rm -rf $$d; done - # Populate source build with debian stuff - cp -R -L ./deb/* $(BUILD_SRC) - -$(SOURCE_PACKAGE): $(BUILD_SRC) - rm -f $(SOURCE_PACKAGE) - # Create the debian source package - tar -f $(SOURCE_PACKAGE) -C ${ROOT_PATH}/${BUILD_SRC} -cz . - -# Build deb package fetching go dependencies and cleaning up git repositories -deb: $(DEB_PACKAGE) - -$(DEB_PACKAGE): $(SOURCE_PACKAGE) - # dpkg-buildpackage looks for source package tarball in ../ - cd $(BUILD_SRC); dpkg-buildpackage - rm -rf $(BUILD_PATH) debian/$(PKG_NAME)* debian/files - -debsrc: $(SOURCE_PACKAGE) - -# Build local sources -#$(PKG_NAME): build_local - -build_local: - -@mkdir -p bin - cd docker && go build -o ../bin/docker - -gotest: - @echo "\033[36m[Testing]\033[00m docker..." - @sudo -E GOPATH=$(ROOT_PATH)/$(BUILD_SRC) go test -v . $(EXTRA_GO_PKG) && \ - echo -n "\033[32m[OK]\033[00m" || \ - echo -n "\033[31m[FAIL]\033[00m"; \ - echo " docker" - @sudo rm -rf /tmp/docker-* - -clean: - rm -rf $(BUILD_PATH) debian/$(PKG_NAME)* debian/files $(BUILD_SRC) checkout.tgz bin +ubuntu: + # This Makefile will compile the github master branch of dotcloud/docker + # Retrieve docker project and its go structure from internet + rm -rf ${BUILD_SRC} + GOPATH=${BUILD_SRC} go get ${GITHUB_PATH} + # Add debianization + mkdir ${BUILD_SRC}/debian + cp Makefile ${BUILD_SRC} + cp -r * ${BUILD_SRC}/debian + cp ../../README.md ${BUILD_SRC} + # Cleanup + for d in `find ${BUILD_SRC} -name '.git*'`; do rm -rf $$d; done + rm -rf ${BUILD_SRC}/../${DOCKER_VERSION}.orig.tar.gz + rm -rf ${BUILD_SRC}/pkg + # Create docker debian files + cd ${BUILD_SRC}; tar czf ../${DOCKER_VERSION}.orig.tar.gz . + cd ${BUILD_SRC}; dpkg-buildpackage -us -uc + rm -rf ${BUILD_SRC} + # Sign package and upload it to PPA if GPG_KEY environment variable + # holds a private GPG KEY + if /usr/bin/test "$${GPG_KEY}" == ""; then exit 2; fi + mkdir ${BUILD_SRC} + # Import gpg signing key + echo "$${GPG_KEY}" | gpg --allow-secret-key-import --import + # Sign the package + cd ${BUILD_SRC}; dpkg-source -x ${BUILD_SRC}/../${DOCKER_FVERSION}.dsc + cd ${BUILD_SRC}/${PKG_NAME}-${VERSION}; debuild -S -sa + cd ${BUILD_SRC};dput ppa:dotcloud/lxc-docker ${DOCKER_FVERSION}_source.changes + rm -rf ${BUILD_SRC} diff --git a/packaging/ubuntu/README.ubuntu b/packaging/ubuntu/README.ubuntu new file mode 100644 index 000000000..286a6f8d5 --- /dev/null +++ b/packaging/ubuntu/README.ubuntu @@ -0,0 +1,37 @@ +Docker on Ubuntu +================ + +The easiest way to get docker up and running natively on Ubuntu is installing +it from its official PPA:: + + sudo sh -c "echo 'deb http://ppa.launchpad.net/dotcloud/lxc-docker/ubuntu precise main' >>/etc/apt/sources.list" + sudo apt-get update + sudo apt-get install lxc-docker + + +Building docker package +~~~~~~~~~~~~~~~~~~~~~~~ + +The building process is shared by both, developers and maintainers. If you are +a developer, the Makefile will stop with exit status 2 right before signing +the built packages. + +Assuming you are working on an Ubuntu 12.04 TLS system :: + + # Download a fresh copy of the docker project + git clone https://github.com/dotcloud/docker.git + cd docker + + # Get building dependencies + sudo apt-get update; sudo apt-get install -y debhelper autotools-dev devscripts golang + + # Make the ubuntu package + (cd packaging/ubuntu; make ubuntu) + + +Install docker built package +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:: + + sudo dpkg -i lxc-docker_*_amd64.deb; sudo apt-get install -f -y diff --git a/packaging/ubuntu/Vagrantfile b/packaging/ubuntu/Vagrantfile new file mode 100644 index 000000000..2c9018ab9 --- /dev/null +++ b/packaging/ubuntu/Vagrantfile @@ -0,0 +1,12 @@ +BUILDBOT_IP = '192.168.33.32' + +Vagrant::Config.run do |config| + config.vm.box = 'quantal64_3.5.0-25' + config.vm.box_url = 'http://get.docker.io/vbox/ubuntu/12.10/quantal64_3.5.0-25.box' + config.vm.share_folder 'v-data', '/data/docker', "#{File.dirname(__FILE__)}/../.." + config.vm.network :hostonly,BUILDBOT_IP + + # Install ubuntu packaging dependencies and create ubuntu packages + config.vm.provision :shell, :inline => 'export DEBIAN_FRONTEND=noninteractive; apt-get -qq update; apt-get install -qq -y debhelper autotools-dev devscripts golang' + config.vm.provision :shell, :inline => "export GPG_KEY='#{ENV['GPG_KEY']}'; cd /data/docker/packaging/ubuntu; make ubuntu" +end diff --git a/packaging/ubuntu/changelog b/packaging/ubuntu/changelog index d8932885e..515a927f5 100644 --- a/packaging/ubuntu/changelog +++ b/packaging/ubuntu/changelog @@ -1,4 +1,21 @@ -lxc-docker (1) precise; urgency=low +lxc-docker (0.1.4.1-1) precise; urgency=low + + Improvements [+], Updates [*], Bug fixes [-]: + * Test PPA + + -- dotCloud Fri, 15 Apr 2013 12:14:50 -0700 + + +lxc-docker (0.1.4-1) precise; urgency=low + + Improvements [+], Updates [*], Bug fixes [-]: + * Changed default bridge interface do 'docker0' + - Fix a race condition when running the port allocator + + -- dotCloud Fri, 12 Apr 2013 12:20:06 -0700 + + +lxc-docker (0.1.0-1) unstable; urgency=low * Initial release diff --git a/packaging/ubuntu/control b/packaging/ubuntu/control index 1ad913854..c52303a88 100644 --- a/packaging/ubuntu/control +++ b/packaging/ubuntu/control @@ -1,19 +1,19 @@ Source: lxc-docker Section: misc Priority: extra -Homepage: http://docker.io Maintainer: Daniel Mizyrycki -Build-Depends: debhelper (>= 8.0.0), pkg-config, git, golang, libsqlite3-dev -Vcs-Git: http://github.com/dotcloud/docker.git +Build-Depends: debhelper,autotools-dev,devscripts,golang Standards-Version: 3.9.3 +Homepage: http://github.com/dotcloud/docker Package: lxc-docker -Architecture: amd64 -Depends: ${shlibs:Depends}, ${misc:Depends}, lxc, wget, bsdtar, curl, sqlite3 +Architecture: linux-any +Depends: ${misc:Depends},${shlibs:Depends},lxc,bsdtar Conflicts: docker -Description: A process manager with superpowers - It encapsulates heterogeneous payloads in Standard Containers, and runs - them on any server with strong guarantees of isolation and repeatability. - Is is a great building block for automating distributed systems: - large-scale web deployments, database clusters, continuous deployment - systems, private PaaS, service-oriented architectures, etc. +Description: lxc-docker is a Linux container runtime + Docker complements LXC with a high-level API which operates at the process + level. It runs unix processes with strong guarantees of isolation and + repeatability across servers. + Docker is a great building block for automating distributed systems: + large-scale web deployments, database clusters, continuous deployment systems, + private PaaS, service-oriented architectures, etc. diff --git a/packaging/ubuntu/copyright b/packaging/ubuntu/copyright index c6c97190a..668c8635e 100644 --- a/packaging/ubuntu/copyright +++ b/packaging/ubuntu/copyright @@ -1,209 +1,237 @@ -Format: http://dep.debian.net/deps/dep5 +Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: docker -Source: https://github.com/dotcloud/docker +Upstream-Contact: DotCloud Inc +Source: http://github.com/dotcloud/docker Files: * -Copyright: 2012 DotCloud Inc (opensource@dotcloud.com) -License: Apache License Version 2.0 - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2012 DotCloud Inc (opensource@dotcloud.com) - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +Copyright: 2012, DotCloud Inc +License: Apache-2.0 + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2012 DotCloud Inc + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +Files: src/github.com/kr/pty/* +Copyright: Copyright (c) 2011 Keith Rarick +License: Expat + Copyright (c) 2011 Keith Rarick + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated + documentation files (the "Software"), to deal in the + Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, + sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall + be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY + KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS + OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR + OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packaging/ubuntu/docker.upstart b/packaging/ubuntu/docker.upstart index 6cfe9d261..4e49a3fa9 100644 --- a/packaging/ubuntu/docker.upstart +++ b/packaging/ubuntu/docker.upstart @@ -5,6 +5,6 @@ stop on starting rc RUNLEVEL=[016] respawn script - test -f /etc/default/locale && . /etc/default/locale || true + /usr/bin/test -f /etc/default/locale && . /etc/default/locale || true LANG=$LANG LC_ALL=$LANG /usr/bin/docker -d end script diff --git a/packaging/ubuntu/lxc-docker.postinst b/packaging/ubuntu/lxc-docker.postinst new file mode 100644 index 000000000..5d04c5b55 --- /dev/null +++ b/packaging/ubuntu/lxc-docker.postinst @@ -0,0 +1,4 @@ +#!/bin/sh + +# Start docker +/sbin/start docker diff --git a/packaging/ubuntu/lxc-docker.prerm b/packaging/ubuntu/lxc-docker.prerm new file mode 100644 index 000000000..824f15cff --- /dev/null +++ b/packaging/ubuntu/lxc-docker.prerm @@ -0,0 +1,4 @@ +#!/bin/sh + +# Stop docker +/sbin/stop docker diff --git a/packaging/ubuntu/maintainer.ubuntu b/packaging/ubuntu/maintainer.ubuntu new file mode 100644 index 000000000..d69ba462a --- /dev/null +++ b/packaging/ubuntu/maintainer.ubuntu @@ -0,0 +1,34 @@ +Maintainer duty +=============== + +Ubuntu allows developers to use their PPA (Personal Package Archive) +repository. This is very convenient for the users as they just need to add +the PPA address, update their package database and use the apt-get tool. + +The official lxc-docker package is located on launchpad and can be accessed +adding the following line to /etc/apt/sources.list :: + + deb http://ppa.launchpad.net/dotcloud/lxc-docker/ubuntu precise main + + +Realeasing a new package +~~~~~~~~~~~~~~~~~~~~~~~~ + +The most relevant information to update is the changelog file: +Each new release should create a new first paragraph with new release version, +changes, and the maintainer information. + +Assuming your PPA GPG signing key is on /media/usbdrive/docker.key, load it +into the GPG_KEY environment variable with:: + + export GPG_KEY=`cat /media/usbdrive/docker.key` + + +After this is done and you are ready to upload the package to the PPA, you have +a couple of choices: + +* Follow README.debian to generate the actual source packages and upload them + to the PPA +* Let vagrant do all the work for you:: + + ( cd docker/packaging/ubuntu; vagrant up ) From 8e6ba343bfb93ed7782d50a3ac1b581ae0e1fb00 Mon Sep 17 00:00:00 2001 From: Daniel Mizyrycki Date: Wed, 17 Apr 2013 21:10:53 -0700 Subject: [PATCH 019/138] packaging-ubuntu, issue #30: Ensure docker package installs and passes tests on official vagrant Ubuntu 12.04 box --- packaging/ubuntu/Makefile | 3 +++ packaging/ubuntu/Vagrantfile | 6 +++--- packaging/ubuntu/changelog | 10 +++++++++- packaging/ubuntu/docker.upstart | 4 ++-- packaging/ubuntu/maintainer.ubuntu | 9 +++++---- 5 files changed, 22 insertions(+), 10 deletions(-) diff --git a/packaging/ubuntu/Makefile b/packaging/ubuntu/Makefile index 0443d8b3e..8e0634870 100644 --- a/packaging/ubuntu/Makefile +++ b/packaging/ubuntu/Makefile @@ -23,8 +23,11 @@ install: # Used by dpkg-buildpackage mkdir -p ${DESTDIR}/usr/bin mkdir -p ${DESTDIR}/etc/init + mkdir -p ${DESTDIR}/DEBIAN install -m 0755 src/${GITHUB_PATH}/docker/docker ${DESTDIR}/usr/bin install -o root -m 0755 debian/docker.upstart ${DESTDIR}/etc/init/docker.conf + install debian/lxc-docker.prerm ${DESTDIR}/DEBIAN/prerm + install debian/lxc-docker.postinst ${DESTDIR}/DEBIAN/postinst ubuntu: # This Makefile will compile the github master branch of dotcloud/docker diff --git a/packaging/ubuntu/Vagrantfile b/packaging/ubuntu/Vagrantfile index 2c9018ab9..0689eea1c 100644 --- a/packaging/ubuntu/Vagrantfile +++ b/packaging/ubuntu/Vagrantfile @@ -1,12 +1,12 @@ BUILDBOT_IP = '192.168.33.32' Vagrant::Config.run do |config| - config.vm.box = 'quantal64_3.5.0-25' - config.vm.box_url = 'http://get.docker.io/vbox/ubuntu/12.10/quantal64_3.5.0-25.box' + config.vm.box = 'precise64' + config.vm.box_url = 'http://files.vagrantup.com/precise64.box' config.vm.share_folder 'v-data', '/data/docker', "#{File.dirname(__FILE__)}/../.." config.vm.network :hostonly,BUILDBOT_IP # Install ubuntu packaging dependencies and create ubuntu packages - config.vm.provision :shell, :inline => 'export DEBIAN_FRONTEND=noninteractive; apt-get -qq update; apt-get install -qq -y debhelper autotools-dev devscripts golang' + config.vm.provision :shell, :inline => 'export DEBIAN_FRONTEND=noninteractive; apt-get -qq update; apt-get install -qq -y git debhelper autotools-dev devscripts golang' config.vm.provision :shell, :inline => "export GPG_KEY='#{ENV['GPG_KEY']}'; cd /data/docker/packaging/ubuntu; make ubuntu" end diff --git a/packaging/ubuntu/changelog b/packaging/ubuntu/changelog index 515a927f5..aa5ea6cc8 100644 --- a/packaging/ubuntu/changelog +++ b/packaging/ubuntu/changelog @@ -1,9 +1,17 @@ +lxc-docker (0.1.6-1) precise; urgency=low + + Improvements [+], Updates [*], Bug fixes [-]: + + Multiple improvements, updates and bug fixes + + -- dotCloud Wed, 17 Apr 2013 20:43:43 -0700 + + lxc-docker (0.1.4.1-1) precise; urgency=low Improvements [+], Updates [*], Bug fixes [-]: * Test PPA - -- dotCloud Fri, 15 Apr 2013 12:14:50 -0700 + -- dotCloud Mon, 15 Apr 2013 12:14:50 -0700 lxc-docker (0.1.4-1) precise; urgency=low diff --git a/packaging/ubuntu/docker.upstart b/packaging/ubuntu/docker.upstart index 4e49a3fa9..07e7e8a89 100644 --- a/packaging/ubuntu/docker.upstart +++ b/packaging/ubuntu/docker.upstart @@ -5,6 +5,6 @@ stop on starting rc RUNLEVEL=[016] respawn script - /usr/bin/test -f /etc/default/locale && . /etc/default/locale || true - LANG=$LANG LC_ALL=$LANG /usr/bin/docker -d + # FIXME: docker should not depend on the system having en_US.UTF-8 + LC_ALL='en_US.UTF-8' /usr/bin/docker -d end script diff --git a/packaging/ubuntu/maintainer.ubuntu b/packaging/ubuntu/maintainer.ubuntu index d69ba462a..406498eba 100644 --- a/packaging/ubuntu/maintainer.ubuntu +++ b/packaging/ubuntu/maintainer.ubuntu @@ -5,14 +5,15 @@ Ubuntu allows developers to use their PPA (Personal Package Archive) repository. This is very convenient for the users as they just need to add the PPA address, update their package database and use the apt-get tool. -The official lxc-docker package is located on launchpad and can be accessed -adding the following line to /etc/apt/sources.list :: +For now, the official lxc-docker package is located on launchpad and can be +accessed adding the following line to /etc/apt/sources.list :: + deb http://ppa.launchpad.net/dotcloud/lxc-docker/ubuntu precise main -Realeasing a new package -~~~~~~~~~~~~~~~~~~~~~~~~ +Releasing a new package +~~~~~~~~~~~~~~~~~~~~~~~ The most relevant information to update is the changelog file: Each new release should create a new first paragraph with new release version, From 90602ab62a4dddadff6671d80815465556a267ee Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 18 Apr 2013 16:03:50 +0200 Subject: [PATCH 020/138] fixed test --- container_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/container_test.go b/container_test.go index d5f3694c5..9e1739011 100644 --- a/container_test.go +++ b/container_test.go @@ -97,7 +97,7 @@ func TestMultipleAttachRestart(t *testing.T) { t.Fatalf("Unexpected output. Expected [%s], received [%s]", "hello", l3) } - if err := container.Stop(); err != nil { + if err := container.Stop(10); err != nil { t.Fatal(err) } From 84c13a3dcf8a0f884d3b95b8f3515e0040816691 Mon Sep 17 00:00:00 2001 From: Shawn Siefkas Date: Thu, 18 Apr 2013 09:17:31 -0500 Subject: [PATCH 021/138] Adding archlinux packaging documentation --- packaging/archlinux/README.archlinux | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 packaging/archlinux/README.archlinux diff --git a/packaging/archlinux/README.archlinux b/packaging/archlinux/README.archlinux new file mode 100644 index 000000000..f20d2d25b --- /dev/null +++ b/packaging/archlinux/README.archlinux @@ -0,0 +1,25 @@ +Docker on Arch +============== + +The AUR lxc-docker and lxc-docker-git packages handle building docker on Arch +linux. The PKGBUILD specifies all dependencies, build, and packaging steps. + +Dependencies +============ + +The only buildtime dependencies are git and go which are available via pacman. +The -s flag can be used on makepkg commands below to automatically install +these dependencies. + +Building Package +================ + +Download the tarball for either AUR packaged to a local directory. In that +directory makepkg can be run to build the package. + +# Build the binary package +makepkg + +# Build an updated source tarball +makepkg --source + From 7eda9c64b80ff43af5e90b37a3830615034fefee Mon Sep 17 00:00:00 2001 From: Shawn Siefkas Date: Thu, 18 Apr 2013 09:17:57 -0500 Subject: [PATCH 022/138] Updating the arch linux installation docs New AUR package name Adding systemd service unit info --- docs/sources/installation/archlinux.rst | 28 +++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/docs/sources/installation/archlinux.rst b/docs/sources/installation/archlinux.rst index c6de247d6..e80997405 100644 --- a/docs/sources/installation/archlinux.rst +++ b/docs/sources/installation/archlinux.rst @@ -6,16 +6,16 @@ Arch Linux Installing on Arch Linux is not officially supported but can be handled via either of the following AUR packages: -* `dotcloud-docker `_ -* `dotcloud-docker-git `_ +* `lxc-docker `_ +* `lxc-docker-git `_ -The dotcloud-docker package will install the latest tagged version of docker. -The dotcloud-docker-git package will build from the current master branch. +The lxc-docker package will install the latest tagged version of docker. +The lxc-docker-git package will build from the current master branch. Dependencies ------------ -Docker depends on several packages which will be installed automatically with +Docker depends on several packages which are specified as dependencies in either AUR package. * aufs3 @@ -23,6 +23,7 @@ either AUR package. * go * iproute2 * linux-aufs_friendly +* lxc Installation ------------ @@ -37,7 +38,22 @@ new kernel will be compiled and this can take quite a while. :: - yaourt -S dotcloud-docker-git + yaourt -S lxc-docker-git + +Starting Docker +--------------- Prior to starting docker modify your bootloader to use the **linux-aufs_friendly** kernel and reboot your system. + +There is a systemd service unit created for docker. To start the docker service: + +:: + + sudo systemctl start docker + +To start on system boot: + +:: + + sudo systemctl enable docker From deb55e416e068a9b51c5973dfeeb2bb2ce6fd1be Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Thu, 18 Apr 2013 14:46:17 -0700 Subject: [PATCH 023/138] contrib/docker-build: don't remove the base image if the first build step fails --- contrib/docker-build/docker-build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/docker-build/docker-build b/contrib/docker-build/docker-build index 934e47ec8..f2fc34068 100755 --- a/contrib/docker-build/docker-build +++ b/contrib/docker-build/docker-build @@ -96,7 +96,7 @@ def main(): else: print "Skipping uknown op " + op except: - docker(["rmi"] + steps) + docker(["rmi"] + steps[1:]) raise print base From 6de5ca1e64407582debe01f17377cdb7bb8d70c5 Mon Sep 17 00:00:00 2001 From: Thatcher Peskens Date: Thu, 18 Apr 2013 16:00:18 -0700 Subject: [PATCH 024/138] Added redirect from old location of documentation (/documentation), these was the location when we were on github. --- docs/Makefile | 1 + docs/sources/nginx.conf | 4 ++++ 2 files changed, 5 insertions(+) create mode 100644 docs/sources/nginx.conf diff --git a/docs/Makefile b/docs/Makefile index f74bf5705..77f14ee92 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -51,6 +51,7 @@ docs: cp sources/dotcloud.yml $(BUILDDIR)/html/ cp sources/CNAME $(BUILDDIR)/html/ cp sources/.nojekyll $(BUILDDIR)/html/ + cp sources/nginx.conf $(BUILDDIR)/html/ @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." diff --git a/docs/sources/nginx.conf b/docs/sources/nginx.conf new file mode 100644 index 000000000..cbc954318 --- /dev/null +++ b/docs/sources/nginx.conf @@ -0,0 +1,4 @@ + +# rule to redirect original links created when hosted on github pages +rewrite ^/documentation/(.*).html http://docs.docker.io/en/latest/$1/ permanent; + From 003622c8b6587814744a9903f3286dc1b07554c2 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 18 Apr 2013 20:47:24 -0700 Subject: [PATCH 025/138] Check kernel version and display warning if too low --- runtime.go | 19 ++++++++++- utils.go | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+), 1 deletion(-) diff --git a/runtime.go b/runtime.go index 72de9f847..d8c6d4259 100644 --- a/runtime.go +++ b/runtime.go @@ -6,6 +6,7 @@ import ( "github.com/dotcloud/docker/auth" "io" "io/ioutil" + "log" "os" "os/exec" "path" @@ -23,6 +24,7 @@ type Runtime struct { repositories *TagStore authConfig *auth.AuthConfig idIndex *TruncIndex + kernelVersion *KernelVersionInfo } var sysInitPath string @@ -282,7 +284,22 @@ func (runtime *Runtime) restore() error { // FIXME: harmonize with NewGraph() func NewRuntime() (*Runtime, error) { - return NewRuntimeFromDirectory("/var/lib/docker") + runtime, err := NewRuntimeFromDirectory("/var/lib/docker") + if err != nil { + return nil, err + } + + k, err := GetKernelVersion() + if err != nil { + return nil, err + } + runtime.kernelVersion = k + + if CompareKernelVersion(k, &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0}) < 0 { + log.Printf("WARNING: You are running linux kernel version %s, which might be unstable running docker. Please upgrade your kernel to 3.8.0.", k.String()) + } + + return runtime, nil } func NewRuntimeFromDirectory(root string) (*Runtime, error) { diff --git a/utils.go b/utils.go index 68e12b20b..8daf40448 100644 --- a/utils.go +++ b/utils.go @@ -13,8 +13,10 @@ import ( "os/exec" "path/filepath" "runtime" + "strconv" "strings" "sync" + "syscall" "time" ) @@ -384,3 +386,95 @@ func CopyEscapable(dst io.Writer, src io.ReadCloser) (written int64, err error) } return written, err } + +type KernelVersionInfo struct { + Kernel int + Major int + Minor int + Specific int +} + +func GetKernelVersion() (*KernelVersionInfo, error) { + var uts syscall.Utsname + + if err := syscall.Uname(&uts); err != nil { + return nil, err + } + + release := make([]byte, len(uts.Release)) + + i := 0 + for _, c := range uts.Release { + release[i] = byte(c) + i++ + } + + tmp := strings.SplitN(string(release), "-", 2) + if len(tmp) != 2 { + return nil, fmt.Errorf("Unrecognized kernel version") + } + tmp2 := strings.SplitN(tmp[0], ".", 3) + if len(tmp2) != 3 { + return nil, fmt.Errorf("Unrecognized kernel version") + } + + kernel, err := strconv.Atoi(tmp2[0]) + if err != nil { + return nil, err + } + + major, err := strconv.Atoi(tmp2[1]) + if err != nil { + return nil, err + } + + minor, err := strconv.Atoi(tmp2[2]) + if err != nil { + return nil, err + } + + specific, err := strconv.Atoi(strings.Split(tmp[1], "-")[0]) + if err != nil { + return nil, err + } + + return &KernelVersionInfo{ + Kernel: kernel, + Major: major, + Minor: minor, + Specific: specific, + }, nil +} + +func (k *KernelVersionInfo) String() string { + return fmt.Sprintf("%d.%d.%d-%d", k.Kernel, k.Major, k.Minor, k.Specific) +} + +// Compare two KernelVersionInfo struct. +// Returns -1 if a < b, = if a == b, 1 it a > b +func CompareKernelVersion(a, b *KernelVersionInfo) int { + if a.Kernel < b.Kernel { + return -1 + } else if a.Kernel > b.Kernel { + return 1 + } + + if a.Major < b.Major { + return -1 + } else if a.Major > b.Major { + return 1 + } + + if a.Minor < b.Minor { + return -1 + } else if a.Minor > b.Minor { + return 1 + } + + if a.Specific < b.Specific { + return -1 + } else if a.Specific > b.Specific { + return 1 + } + return 0 +} From 640efc2ed2244dc16e161cc954af59d20a5e6ed2 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 18 Apr 2013 20:55:41 -0700 Subject: [PATCH 026/138] Add capabilities check to allow docker to run on kernel that does not have all options --- commands.go | 2 +- container.go | 12 +++++++++--- runtime.go | 14 ++++++++++++++ 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/commands.go b/commands.go index ba501dd5e..6ab164d64 100644 --- a/commands.go +++ b/commands.go @@ -907,7 +907,7 @@ func (srv *Server) CmdTag(stdin io.ReadCloser, stdout io.Writer, args ...string) } func (srv *Server) CmdRun(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error { - config, err := ParseRun(args, stdout) + config, err := ParseRun(args, stdout, srv.runtime.capabilities) if err != nil { return err } diff --git a/container.go b/container.go index 9f175e42a..0d3427d9c 100644 --- a/container.go +++ b/container.go @@ -66,7 +66,7 @@ type Config struct { Image string // Name of the image as it was passed by the operator (eg. could be symbolic) } -func ParseRun(args []string, stdout io.Writer) (*Config, error) { +func ParseRun(args []string, stdout io.Writer, capabilities *Capabilities) (*Config, error) { cmd := rcli.Subcmd(stdout, "run", "[OPTIONS] IMAGE COMMAND [ARG...]", "Run a command in a new container") if len(args) > 0 && args[0] != "--help" { cmd.SetOutput(ioutil.Discard) @@ -81,8 +81,8 @@ func ParseRun(args []string, stdout io.Writer) (*Config, error) { flTty := cmd.Bool("t", false, "Allocate a pseudo-tty") flMemory := cmd.Int64("m", 0, "Memory limit (in bytes)") - if *flMemory > 0 && NO_MEMORY_LIMIT { - fmt.Fprintf(stdout, "WARNING: This version of docker has been compiled without memory limit support. Discarding -m.") + if *flMemory > 0 && !capabilities.MemoryLimit { + fmt.Fprintf(stdout, "WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n") *flMemory = 0 } @@ -135,6 +135,12 @@ func ParseRun(args []string, stdout io.Writer) (*Config, error) { Dns: flDns, Image: image, } + + if *flMemory > 0 && !capabilities.SwapLimit { + fmt.Fprintf(stdout, "WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.\n") + config.MemorySwap = -1 + } + // When allocating stdin in attached mode, close stdin at client disconnect if config.OpenStdin && config.AttachStdin { config.StdinOnce = true diff --git a/runtime.go b/runtime.go index d8c6d4259..43b1a7815 100644 --- a/runtime.go +++ b/runtime.go @@ -15,6 +15,11 @@ import ( "time" ) +type Capabilities struct { + MemoryLimit bool + SwapLimit bool +} + type Runtime struct { root string repository string @@ -24,6 +29,7 @@ type Runtime struct { repositories *TagStore authConfig *auth.AuthConfig idIndex *TruncIndex + capabilities *Capabilities kernelVersion *KernelVersionInfo } @@ -299,6 +305,13 @@ func NewRuntime() (*Runtime, error) { log.Printf("WARNING: You are running linux kernel version %s, which might be unstable running docker. Please upgrade your kernel to 3.8.0.", k.String()) } + _, err1 := ioutil.ReadFile("/sys/fs/cgroup/memory/memory.limit_in_bytes") + _, err2 := ioutil.ReadFile("/sys/fs/cgroup/memory/memory.soft_limit_in_bytes") + runtime.capabilities.MemoryLimit = err1 == nil && err2 == nil + + _, err = ioutil.ReadFile("/sys/fs/cgroup/memory/memeory.memsw.limit_in_bytes") + runtime.capabilities.SwapLimit = err == nil + return runtime, nil } @@ -338,6 +351,7 @@ func NewRuntimeFromDirectory(root string) (*Runtime, error) { repositories: repositories, authConfig: authConfig, idIndex: NewTruncIndex(), + capabilities: &Capabilities{}, } if err := runtime.restore(); err != nil { From f68d107a1368b3d4f3342456a2b0675659688354 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 18 Apr 2013 21:08:20 -0700 Subject: [PATCH 027/138] Remove the NO_MEMORY_LIMIT constant --- Makefile | 5 +---- commands.go | 3 +-- container.go | 9 +++++++-- docker/docker.go | 7 +------ runtime_test.go | 2 -- 5 files changed, 10 insertions(+), 16 deletions(-) diff --git a/Makefile b/Makefile index c89f0f33b..a6eb61383 100644 --- a/Makefile +++ b/Makefile @@ -13,10 +13,7 @@ endif GIT_COMMIT = $(shell git rev-parse --short HEAD) GIT_STATUS = $(shell test -n "`git status --porcelain`" && echo "+CHANGES") -NO_MEMORY_LIMIT ?= 0 -export NO_MEMORY_LIMIT - -BUILD_OPTIONS = -ldflags "-X main.GIT_COMMIT $(GIT_COMMIT)$(GIT_STATUS) -X main.NO_MEMORY_LIMIT $(NO_MEMORY_LIMIT)" +BUILD_OPTIONS = -ldflags "-X main.GIT_COMMIT $(GIT_COMMIT)$(GIT_STATUS)" SRC_DIR := $(GOPATH)/src diff --git a/commands.go b/commands.go index 6ab164d64..274acf499 100644 --- a/commands.go +++ b/commands.go @@ -21,8 +21,7 @@ import ( const VERSION = "0.1.6" var ( - GIT_COMMIT string - NO_MEMORY_LIMIT bool + GIT_COMMIT string ) func (srv *Server) Name() string { diff --git a/container.go b/container.go index 0d3427d9c..c23578875 100644 --- a/container.go +++ b/container.go @@ -373,10 +373,15 @@ func (container *Container) Start() error { return err } - if container.Config.Memory > 0 && NO_MEMORY_LIMIT { - log.Printf("WARNING: This version of docker has been compiled without memory limit support. Discarding the limit.") + // Make sure the config is compatible with the current kernel + if container.Config.Memory > 0 && !container.runtime.capabilities.MemoryLimit { + log.Printf("WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n") container.Config.Memory = 0 } + if container.Config.Memory > 0 && !container.runtime.capabilities.SwapLimit { + log.Printf("WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.\n") + container.Config.MemorySwap = -1 + } if err := container.generateLXCConfig(); err != nil { return err diff --git a/docker/docker.go b/docker/docker.go index 83c47c6f1..411e4d0c9 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -14,8 +14,7 @@ import ( ) var ( - GIT_COMMIT string - NO_MEMORY_LIMIT string + GIT_COMMIT string ) func main() { @@ -39,15 +38,11 @@ func main() { os.Setenv("DEBUG", "1") } docker.GIT_COMMIT = GIT_COMMIT - docker.NO_MEMORY_LIMIT = NO_MEMORY_LIMIT == "1" if *flDaemon { if flag.NArg() != 0 { flag.Usage() return } - if NO_MEMORY_LIMIT == "1" { - log.Printf("WARNING: This version of docker has been compiled without memory limit support.") - } if err := daemon(*pidfile); err != nil { log.Fatal(err) } diff --git a/runtime_test.go b/runtime_test.go index 20e7ee140..48786fd5b 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -46,8 +46,6 @@ func layerArchive(tarfile string) (io.Reader, error) { } func init() { - NO_MEMORY_LIMIT = os.Getenv("NO_MEMORY_LIMIT") == "1" - // Hack to run sys init during unit testing if SelfPath() == "/sbin/init" { SysInit() From 2d32ac8cffe08b9c5d562e6ea30c796ae32b8fe1 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 18 Apr 2013 21:08:33 -0700 Subject: [PATCH 028/138] Improve the docker version output --- commands.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/commands.go b/commands.go index 274acf499..2a73da7f6 100644 --- a/commands.go +++ b/commands.go @@ -183,10 +183,14 @@ func (srv *Server) CmdWait(stdin io.ReadCloser, stdout io.Writer, args ...string // 'docker version': show version information func (srv *Server) CmdVersion(stdin io.ReadCloser, stdout io.Writer, args ...string) error { - fmt.Fprintf(stdout, "Version:%s\n", VERSION) - fmt.Fprintf(stdout, "Git Commit:%s\n", GIT_COMMIT) - if NO_MEMORY_LIMIT { - fmt.Fprintf(stdout, "Memory limit disabled\n") + fmt.Fprintf(stdout, "Version: %s\n", VERSION) + fmt.Fprintf(stdout, "Git Commit: %s\n", GIT_COMMIT) + fmt.Fprintf(stdout, "Kernel: %s\n", srv.runtime.kernelVersion) + if !srv.runtime.capabilities.MemoryLimit { + fmt.Fprintf(stdout, "WARNING: No memory limit support\n") + } + if !srv.runtime.capabilities.SwapLimit { + fmt.Fprintf(stdout, "WARNING: No swap limit support\n") } return nil } From c42a4179fc6954a2363b181969978641553955c4 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 18 Apr 2013 21:34:34 -0700 Subject: [PATCH 029/138] Add unit tests for CompareKernelVersion --- utils_test.go | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/utils_test.go b/utils_test.go index c15084f61..1ee223ee3 100644 --- a/utils_test.go +++ b/utils_test.go @@ -228,3 +228,36 @@ func assertIndexGet(t *testing.T, index *TruncIndex, input, expectedResult strin t.Fatalf("Getting '%s' returned '%s' instead of '%s'", input, result, expectedResult) } } + +func assertKernelVersion(t *testing.T, a, b *KernelVersionInfo, result int) { + if r := CompareKernelVersion(a, b); r != result { + t.Fatalf("Unepected kernel version comparaison result. Found %d, expected %d", r, result) + } +} + +func TestCompareKernelVersion(t *testing.T) { + assertKernelVersion(t, + &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0, Specific: 0}, + &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0, Specific: 0}, + 0) + assertKernelVersion(t, + &KernelVersionInfo{Kernel: 2, Major: 6, Minor: 0, Specific: 0}, + &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0, Specific: 0}, + -1) + assertKernelVersion(t, + &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0, Specific: 0}, + &KernelVersionInfo{Kernel: 2, Major: 6, Minor: 0, Specific: 0}, + 1) + assertKernelVersion(t, + &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0, Specific: 0}, + &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0, Specific: 16}, + -1) + assertKernelVersion(t, + &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 5, Specific: 0}, + &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0, Specific: 0}, + 1) + assertKernelVersion(t, + &KernelVersionInfo{Kernel: 3, Major: 0, Minor: 20, Specific: 25}, + &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0, Specific: 0}, + -1) +} From f3e89fae287778cb8b7056e228170e0073c9a046 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 18 Apr 2013 21:57:58 -0700 Subject: [PATCH 030/138] Use mount to determine the cgroup mountpoint --- runtime.go | 11 ++++++++--- utils.go | 18 ++++++++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/runtime.go b/runtime.go index 43b1a7815..ca850d347 100644 --- a/runtime.go +++ b/runtime.go @@ -305,11 +305,16 @@ func NewRuntime() (*Runtime, error) { log.Printf("WARNING: You are running linux kernel version %s, which might be unstable running docker. Please upgrade your kernel to 3.8.0.", k.String()) } - _, err1 := ioutil.ReadFile("/sys/fs/cgroup/memory/memory.limit_in_bytes") - _, err2 := ioutil.ReadFile("/sys/fs/cgroup/memory/memory.soft_limit_in_bytes") + cgroupMemoryMountpoint, err := FindCgroupMountpoint("memory") + if err != nil { + return nil, err + } + + _, err1 := ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "/memory.limit_in_bytes")) + _, err2 := ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.soft_limit_in_bytes")) runtime.capabilities.MemoryLimit = err1 == nil && err2 == nil - _, err = ioutil.ReadFile("/sys/fs/cgroup/memory/memeory.memsw.limit_in_bytes") + _, err = ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memeory.memsw.limit_in_bytes")) runtime.capabilities.SwapLimit = err == nil return runtime, nil diff --git a/utils.go b/utils.go index 8daf40448..8763a4393 100644 --- a/utils.go +++ b/utils.go @@ -12,6 +12,7 @@ import ( "os" "os/exec" "path/filepath" + "regexp" "runtime" "strconv" "strings" @@ -478,3 +479,20 @@ func CompareKernelVersion(a, b *KernelVersionInfo) int { } return 0 } + +func FindCgroupMountpoint(cgroupType string) (string, error) { + output, err := exec.Command("mount").CombinedOutput() + if err != nil { + return "", err + } + + reg := regexp.MustCompile(`^cgroup on (.*) type cgroup \(.*` + cgroupType + `[,\)]`) + for _, line := range strings.Split(string(output), "\n") { + r := reg.FindStringSubmatch(line) + if len(r) == 2 { + return r[1], nil + } + fmt.Printf("line: %s (%d)\n", line, len(r)) + } + return "", fmt.Errorf("cgroup mountpoint not found") +} From 3ae5c45d9a75befdd144d689a70c32180b0a16c6 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Thu, 18 Apr 2013 22:22:00 -0700 Subject: [PATCH 031/138] Fix examples in README to no longer rely on standalone mode --- README.md | 25 +++++++++++++++++----- docs/sources/examples/running_examples.rst | 21 +++++------------- 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 4ba9222f8..2c4065842 100644 --- a/README.md +++ b/README.md @@ -122,8 +122,26 @@ Some streamlined (but possibly outdated) installation paths' are available from Usage examples ============== -Running an interactive shell ----------------------------- +First run the docker daemon +--------------------------- + +All the examples assume your machine is running the docker daemon. To run the docker daemon in the background, simply type: + + .. code-block:: bash + + sudo docker -d & + +Now you can run docker in client mode: all commands will be forwarded to the docker daemon, so the client +can run from any account. + + .. code-block:: bash + + # now you can run docker commands from any account. + docker help + + +Throwaway shell in a base ubuntu image +-------------------------------------- ```bash # Download a base image @@ -145,9 +163,6 @@ Starting a long-running worker process -------------------------------------- ```bash -# Run docker in daemon mode -(docker -d || echo "Docker daemon already running") & - # Start a very useful long-running process JOB=$(docker run -d base /bin/sh -c "while true; do echo Hello world; sleep 1; done") diff --git a/docs/sources/examples/running_examples.rst b/docs/sources/examples/running_examples.rst index 4042add48..3d2593c71 100644 --- a/docs/sources/examples/running_examples.rst +++ b/docs/sources/examples/running_examples.rst @@ -7,27 +7,16 @@ Running The Examples -------------------- -There are two ways to run docker, daemon mode and standalone mode. - -When you run the docker command it will first check if there is a docker daemon running in the background it can connect to. - -* If it exists it will use that daemon to run all of the commands. -* If it does not exist docker will run in standalone mode (docker will exit after each command). - -Docker needs to be run from a privileged account (root). - -1. The most common (and recommended) way is to run a docker daemon as root in the background, and then connect to it from the docker client from any account. +All the examples assume your machine is running the docker daemon. To run the docker daemon in the background, simply type: .. code-block:: bash - # starting docker daemon in the background sudo docker -d & - # now you can run docker commands from any account. - docker - -2. Standalone: You need to run every command as root, or using sudo +Now you can run docker in client mode: all commands will be forwarded to the docker daemon, so the client +can run from any account. .. code-block:: bash - sudo docker + # now you can run docker commands from any account. + docker help From 79a78d37e7102f0aadfa49d89b84b095e86824d1 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Thu, 18 Apr 2013 22:24:29 -0700 Subject: [PATCH 032/138] Add examples to the README --- README.md | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 2c4065842..a3fe1313a 100644 --- a/README.md +++ b/README.md @@ -144,12 +144,11 @@ Throwaway shell in a base ubuntu image -------------------------------------- ```bash -# Download a base image -docker pull base +docker pull ubuntu:12.10 -# Run an interactive shell in the base image, +# Run an interactive shell # allocate a tty, attach stdin and stdout -docker run -i -t base /bin/bash +docker run -i -t ubuntu:12.10 /bin/bash ``` Detaching from the interactive shell @@ -164,7 +163,7 @@ Starting a long-running worker process ```bash # Start a very useful long-running process -JOB=$(docker run -d base /bin/sh -c "while true; do echo Hello world; sleep 1; done") +JOB=$(docker run -d ubuntu /bin/sh -c "while true; do echo Hello world; sleep 1; done") # Collect the output of the job so far docker logs $JOB @@ -173,21 +172,27 @@ docker logs $JOB docker kill $JOB ``` - -Listing all running containers ------------------------------- +Run an irc bouncer +------------------ ```bash -docker ps +BOUNCER_ID=$(docker run -d -p 6667 -u irc shykes/znc $USER $PASSWORD) +echo "Configure your irc client to connect to port $(port $BOUNCER_ID 6667) of this machine" ``` +Run Redis +--------- + +```bash +REDIS_ID=$(docker run -d -p 6379 shykes/redis redis-server) +echo "Configure your redis client to connect to port $(port $REDIS_ID 6379) of this machine" +``` Share your own image! --------------------- ```bash -docker pull base -CONTAINER=$(docker run -d base apt-get install -y curl) +CONTAINER=$(docker run -d ubuntu:12.10 apt-get install -y curl) docker commit -m "Installed curl" $CONTAINER $USER/betterbase docker push $USER/betterbase ``` From 3d2fd8a65024a503456fc8cd64d4d2565c73e304 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Thu, 18 Apr 2013 22:24:52 -0700 Subject: [PATCH 033/138] Small wording fix in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a3fe1313a..420c79500 100644 --- a/README.md +++ b/README.md @@ -154,7 +154,7 @@ docker run -i -t ubuntu:12.10 /bin/bash Detaching from the interactive shell ------------------------------------ ``` -# In order to detach without killing the shell, you can use the escape sequence Ctrl-p + Ctrl-q +# To detach without killing the shell, you can use the escape sequence Ctrl-p + Ctrl-q # Note: this works only in tty mode (run with -t option). ``` From e8a67f632ea2e83c24f32c243b72fd7653e94d4b Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Thu, 18 Apr 2013 22:37:45 -0700 Subject: [PATCH 034/138] Cleanup examples on README --- README.md | 37 +++++++++++++++---------------------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 420c79500..54fe0a22b 100644 --- a/README.md +++ b/README.md @@ -127,17 +127,17 @@ First run the docker daemon All the examples assume your machine is running the docker daemon. To run the docker daemon in the background, simply type: - .. code-block:: bash +```bash +# On a production system you want this running in an init script +sudo docker -d & +``` - sudo docker -d & +Now you can run docker in client mode: all commands will be forwarded to the docker daemon, so the client can run from any account. -Now you can run docker in client mode: all commands will be forwarded to the docker daemon, so the client -can run from any account. - - .. code-block:: bash - - # now you can run docker commands from any account. - docker help +```bash +# Now you can run docker commands from any account. +docker help +``` Throwaway shell in a base ubuntu image @@ -146,18 +146,11 @@ Throwaway shell in a base ubuntu image ```bash docker pull ubuntu:12.10 -# Run an interactive shell -# allocate a tty, attach stdin and stdout +# Run an interactive shell, allocate a tty, attach stdin and stdout +# To detach the tty without exiting the shell, use the escape sequence Ctrl-p + Ctrl-q docker run -i -t ubuntu:12.10 /bin/bash ``` -Detaching from the interactive shell ------------------------------------- -``` -# To detach without killing the shell, you can use the escape sequence Ctrl-p + Ctrl-q -# Note: this works only in tty mode (run with -t option). -``` - Starting a long-running worker process -------------------------------------- @@ -172,16 +165,16 @@ docker logs $JOB docker kill $JOB ``` -Run an irc bouncer ------------------- +Running an irc bouncer +---------------------- ```bash BOUNCER_ID=$(docker run -d -p 6667 -u irc shykes/znc $USER $PASSWORD) echo "Configure your irc client to connect to port $(port $BOUNCER_ID 6667) of this machine" ``` -Run Redis ---------- +Running Redis +------------- ```bash REDIS_ID=$(docker run -d -p 6379 shykes/redis redis-server) From 152a9f77b4475bcec95ac58229c5db0e24b42557 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Fri, 19 Apr 2013 12:39:40 -0700 Subject: [PATCH 035/138] Fix ubuntu packaging to build from a clean checkout of the correct git tag --- packaging/ubuntu/Makefile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packaging/ubuntu/Makefile b/packaging/ubuntu/Makefile index 8e0634870..dbdf1af7a 100644 --- a/packaging/ubuntu/Makefile +++ b/packaging/ubuntu/Makefile @@ -14,6 +14,7 @@ GITHUB_PATH=github.com/dotcloud/docker DOCKER_VERSION=${PKG_NAME}_${VERSION} DOCKER_FVERSION=${PKG_NAME}_$(shell head -1 changelog | sed 's/^.\+(\(.\+\)).\+$$/\1/') BUILD_SRC=${CURDIR}/../../build_src +VERSION_TAG=v$(shell head -1 changelog | sed 's/^.\+(\(.\+\)-[0-9]\+).\+$$/\1/') all: # Compile docker. Used by dpkg-buildpackage. @@ -33,7 +34,8 @@ ubuntu: # This Makefile will compile the github master branch of dotcloud/docker # Retrieve docker project and its go structure from internet rm -rf ${BUILD_SRC} - GOPATH=${BUILD_SRC} go get ${GITHUB_PATH} + git clone $(shell git rev-parse --show-toplevel) ${BUILD_SRC}/${GITHUB_PATH} + cd ${BUILD_SRC}/${GITHUB_PATH}; git checkout ${VERSION_TAG} && GOPATH=${BUILD_SRC} go get -d # Add debianization mkdir ${BUILD_SRC}/debian cp Makefile ${BUILD_SRC} From e81ddb2dc76ab2e502ad35363a436dd823289598 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Fri, 19 Apr 2013 12:55:17 -0700 Subject: [PATCH 036/138] Fixed 'hack' rule in Makefile --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index a4096b1bb..ab5682a09 100644 --- a/Makefile +++ b/Makefile @@ -26,7 +26,7 @@ DOCKER_MAIN := $(DOCKER_DIR)/docker DOCKER_BIN_RELATIVE := bin/docker DOCKER_BIN := $(CURDIR)/$(DOCKER_BIN_RELATIVE) -.PHONY: all clean test +.PHONY: all clean test hack all: $(DOCKER_BIN) From d8416539b3b068a6a1a17f6dba441913b648a774 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Fri, 19 Apr 2013 15:55:34 -0700 Subject: [PATCH 037/138] contrib/vagrant-docker: a placeholder to centralize collaboration on an official docker provider for vagrant --- contrib/vagrant-docker/README.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 contrib/vagrant-docker/README.md diff --git a/contrib/vagrant-docker/README.md b/contrib/vagrant-docker/README.md new file mode 100644 index 000000000..5852ea192 --- /dev/null +++ b/contrib/vagrant-docker/README.md @@ -0,0 +1,3 @@ +# Vagrant-docker + +This is a placeholder for the official vagrant-docker, a plugin for Vagrant (http://vagrantup.com) which exposes Docker as a provider. From e49af5b6def4e69ae234549fbcd2dd2987949cab Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Fri, 19 Apr 2013 16:33:25 -0700 Subject: [PATCH 038/138] Use aufs to handle parents whitouts instead of doing it manually --- image.go | 27 +-------------------------- 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/image.go b/image.go index 9369fc3f4..3b5b4be6e 100644 --- a/image.go +++ b/image.go @@ -92,7 +92,7 @@ func MountAUFS(ro []string, rw string, target string) error { rwBranch := fmt.Sprintf("%v=rw", rw) roBranches := "" for _, layer := range ro { - roBranches += fmt.Sprintf("%v=ro:", layer) + roBranches += fmt.Sprintf("%v=ro+wh:", layer) } branches := fmt.Sprintf("br:%v:%v", rwBranch, roBranches) @@ -127,34 +127,9 @@ func (image *Image) Mount(root, rw string) error { if err := os.Mkdir(rw, 0755); err != nil && !os.IsExist(err) { return err } - // FIXME: @creack shouldn't we do this after going over changes? if err := MountAUFS(layers, rw, root); err != nil { return err } - // FIXME: Create tests for deletion - // FIXME: move this part to change.go - // Retrieve the changeset from the parent and apply it to the container - // - Retrieve the changes - changes, err := Changes(layers, layers[0]) - if err != nil { - return err - } - // Iterate on changes - for _, c := range changes { - // If there is a delete - if c.Kind == ChangeDelete { - // Make sure the directory exists - file_path, file_name := path.Dir(c.Path), path.Base(c.Path) - if err := os.MkdirAll(path.Join(rw, file_path), 0755); err != nil { - return err - } - // And create the whiteout (we just need to create empty file, discard the return) - if _, err := os.Create(path.Join(path.Join(rw, file_path), - ".wh."+path.Base(file_name))); err != nil { - return err - } - } - } return nil } From 3bfb70db243d35b969eba9781dec619b7c52be98 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Fri, 19 Apr 2013 18:06:13 -0700 Subject: [PATCH 039/138] Wait for the container terminate at the end of CmdRun Fixes the race condition between docker run and docker logs from #428. --- commands.go | 1 + 1 file changed, 1 insertion(+) diff --git a/commands.go b/commands.go index 2ca026eb8..4fe91bb8e 100644 --- a/commands.go +++ b/commands.go @@ -975,6 +975,7 @@ func (srv *Server) CmdRun(stdin io.ReadCloser, stdout rcli.DockerConn, args ...s } Debugf("Waiting for attach to return\n") <-attachErr + container.Wait() // Expecting I/O pipe error, discarding return nil } From cc5a044a8c140e32868bb0f312f3780161515f0b Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Fri, 19 Apr 2013 17:51:41 -0700 Subject: [PATCH 040/138] update TestRunDisconnectTty to reflect the correct behavior of CmdRun --- commands_test.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/commands_test.go b/commands_test.go index 1be4a2abe..9615e877e 100644 --- a/commands_test.go +++ b/commands_test.go @@ -239,11 +239,8 @@ func TestRunDisconnectTty(t *testing.T) { t.Fatal(err) } - // as the pipes are close, we expect the process to die, - // therefore CmdRun to unblock. Wait for CmdRun - setTimeout(t, "Waiting for CmdRun timed out", 2*time.Second, func() { - <-c1 - }) + // In tty mode, we expect the process to stay alive even after client's stdin closes. + // Do not wait for run to finish // Client disconnect after run -i should keep stdin out in TTY mode container := runtime.List()[0] From 931ca464a7db038fa78ee51a3f5c4cbe85db1e21 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Fri, 19 Apr 2013 19:29:13 -0700 Subject: [PATCH 041/138] 'docker ps' shows port mappings --- commands.go | 3 ++- container.go | 12 ++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/commands.go b/commands.go index 4fe91bb8e..7f8277569 100644 --- a/commands.go +++ b/commands.go @@ -677,7 +677,7 @@ func (srv *Server) CmdPs(stdin io.ReadCloser, stdout io.Writer, args ...string) } w := tabwriter.NewWriter(stdout, 12, 1, 3, ' ', 0) if !*quiet { - fmt.Fprintln(w, "ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tCOMMENT") + fmt.Fprintln(w, "ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tCOMMENT\tPORTS") } for i, container := range srv.runtime.List() { if !container.State.Running && !*flAll && *nLast == -1 { @@ -698,6 +698,7 @@ func (srv *Server) CmdPs(stdin io.ReadCloser, stdout io.Writer, args ...string) /* CREATED */ HumanDuration(time.Now().Sub(container.Created)) + " ago", /* STATUS */ container.State.String(), /* COMMENT */ "", + /* PORTS */ container.NetworkSettings.PortMappingHuman(), } { if idx == 0 { w.Write([]byte(field)) diff --git a/container.go b/container.go index dc04a9160..4719e9f1c 100644 --- a/container.go +++ b/container.go @@ -11,7 +11,9 @@ import ( "os" "os/exec" "path" + "sort" "strconv" + "strings" "syscall" "time" ) @@ -150,6 +152,16 @@ type NetworkSettings struct { PortMapping map[string]string } +// String returns a human-readable description of the port mapping defined in the settings +func (settings *NetworkSettings) PortMappingHuman() string { + var mapping []string + for private, public := range settings.PortMapping { + mapping = append(mapping, fmt.Sprintf("%s->%s", public, private)) + } + sort.Strings(mapping) + return strings.Join(mapping, ", ") +} + func (container *Container) Cmd() *exec.Cmd { return container.cmd } From 61259ab4b4bfe3404e75dd811a2da7c88e7c7133 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Fri, 19 Apr 2013 19:32:32 -0700 Subject: [PATCH 042/138] Exclude loopback-to-loopback connections from DNAT rules, to allow userland proxying --- network.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/network.go b/network.go index 706c31fa4..85c608331 100644 --- a/network.go +++ b/network.go @@ -188,7 +188,8 @@ type PortMapper struct { func (mapper *PortMapper) cleanup() error { // Ignore errors - This could mean the chains were never set up iptables("-t", "nat", "-D", "PREROUTING", "-m", "addrtype", "--dst-type", "LOCAL", "-j", "DOCKER") - iptables("-t", "nat", "-D", "OUTPUT", "-m", "addrtype", "--dst-type", "LOCAL", "-j", "DOCKER") + iptables("-t", "nat", "-D", "OUTPUT", "-m", "addrtype", "--dst-type", "LOCAL", "!", "--dst", "127.0.0.0/8", "-j", "DOCKER") + iptables("-t", "nat", "-D", "OUTPUT", "-m", "addrtype", "--dst-type", "LOCAL", "-j", "DOCKER") // Created in versions <= 0.1.6 // Also cleanup rules created by older versions, or -X might fail. iptables("-t", "nat", "-D", "PREROUTING", "-j", "DOCKER") iptables("-t", "nat", "-D", "OUTPUT", "-j", "DOCKER") @@ -205,7 +206,7 @@ func (mapper *PortMapper) setup() error { if err := iptables("-t", "nat", "-A", "PREROUTING", "-m", "addrtype", "--dst-type", "LOCAL", "-j", "DOCKER"); err != nil { return fmt.Errorf("Failed to inject docker in PREROUTING chain: %s", err) } - if err := iptables("-t", "nat", "-A", "OUTPUT", "-m", "addrtype", "--dst-type", "LOCAL", "-j", "DOCKER"); err != nil { + if err := iptables("-t", "nat", "-A", "OUTPUT", "-m", "addrtype", "--dst-type", "LOCAL", "!", "--dst", "127.0.0.0/8", "-j", "DOCKER"); err != nil { return fmt.Errorf("Failed to inject docker in OUTPUT chain: %s", err) } return nil From 930e9a7e430a3d78e09a95bb32d9fb6052e7dae1 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Fri, 19 Apr 2013 19:35:44 -0700 Subject: [PATCH 043/138] Emulate DNAT in userland for loopback-to-loopback connections. This makes container ports available from localhost. --- network.go | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/network.go b/network.go index 85c608331..54b8dbfe3 100644 --- a/network.go +++ b/network.go @@ -4,6 +4,7 @@ import ( "encoding/binary" "errors" "fmt" + "io" "log" "net" "os/exec" @@ -221,10 +222,55 @@ func (mapper *PortMapper) Map(port int, dest net.TCPAddr) error { if err := mapper.iptablesForward("-A", port, dest); err != nil { return err } + mapper.mapping[port] = dest + listener, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port)) + if err != nil { + mapper.Unmap(port) + return err + } + // FIXME: store the listener so we can close it at Unmap + go proxy(listener, "tcp", dest.String()) return nil } +// proxy listens for socket connections on `listener`, and forwards them unmodified +// to `proto:address` +func proxy(listener net.Listener, proto, address string) error { + Debugf("proxying to %s:%s", proto, address) + defer Debugf("Done proxying to %s:%s", proto, address) + for { + Debugf("Listening on %s", listener) + src, err := listener.Accept() + if err != nil { + return err + } + Debugf("Connecting to %s:%s", proto, address) + dst, err := net.Dial(proto, address) + if err != nil { + log.Printf("Error connecting to %s:%s: %s", proto, address, err) + src.Close() + continue + } + Debugf("Connected to backend, splicing") + splice(src, dst) + } + return nil +} + +func halfSplice(dst, src net.Conn) error { + _, err := io.Copy(dst, src) + // FIXME: on EOF from a tcp connection, pass WriteClose() + dst.Close() + src.Close() + return err +} + +func splice(a, b net.Conn) { + go halfSplice(a, b) + go halfSplice(b, a) +} + func (mapper *PortMapper) Unmap(port int) error { dest, ok := mapper.mapping[port] if !ok { From 7f1a32b9ff31bd931e9495acc1d5ccdef4bd51b6 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Fri, 19 Apr 2013 20:44:25 -0700 Subject: [PATCH 044/138] Shutdown loopback-to-loopback proxy when unmapping a port --- network.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/network.go b/network.go index 54b8dbfe3..373625d59 100644 --- a/network.go +++ b/network.go @@ -184,6 +184,7 @@ func getIfaceAddr(name string) (net.Addr, error) { // It keeps track of all mappings and is able to unmap at will type PortMapper struct { mapping map[int]net.TCPAddr + proxies map[int]net.Listener } func (mapper *PortMapper) cleanup() error { @@ -197,6 +198,7 @@ func (mapper *PortMapper) cleanup() error { iptables("-t", "nat", "-F", "DOCKER") iptables("-t", "nat", "-X", "DOCKER") mapper.mapping = make(map[int]net.TCPAddr) + mapper.proxies = make(map[int]net.Listener) return nil } @@ -229,7 +231,7 @@ func (mapper *PortMapper) Map(port int, dest net.TCPAddr) error { mapper.Unmap(port) return err } - // FIXME: store the listener so we can close it at Unmap + mapper.proxies[port] = listener go proxy(listener, "tcp", dest.String()) return nil } @@ -276,6 +278,10 @@ func (mapper *PortMapper) Unmap(port int) error { if !ok { return errors.New("Port is not mapped") } + if proxy, exists := mapper.proxies[port]; exists { + proxy.Close() + delete(mapper.proxies, port) + } if err := mapper.iptablesForward("-D", port, dest); err != nil { return err } From 911925b54a42fffa0fed8bac2a3eeba14bf3ec4b Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Fri, 19 Apr 2013 20:46:07 -0700 Subject: [PATCH 045/138] Add a test for allocating tcp ports and reaching them on localhost --- runtime_test.go | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/runtime_test.go b/runtime_test.go index 20e7ee140..eef8db3a8 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -1,9 +1,11 @@ package docker import ( + "fmt" "github.com/dotcloud/docker/rcli" "io" "io/ioutil" + "net" "os" "os/exec" "os/user" @@ -254,6 +256,47 @@ func TestGet(t *testing.T) { } +// Run a container with a TCP port allocated, and test that it can receive connections on localhost +func TestAllocatePortLocalhost(t *testing.T) { + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + container, err := runtime.Create(&Config{ + Image: GetTestImage(runtime).Id, + Cmd: []string{"sh", "-c", "echo well hello there | nc -l -p 5555"}, + PortSpecs: []string{"5555"}, + }, + ) + if err != nil { + t.Fatal(err) + } + if err := container.Start(); err != nil { + t.Fatal(err) + } + defer container.Kill() + time.Sleep(300 * time.Millisecond) // Wait for the container to run + conn, err := net.Dial("tcp", + fmt.Sprintf( + "localhost:%s", container.NetworkSettings.PortMapping["5555"], + ), + ) + if err != nil { + t.Fatal(err) + } + defer conn.Close() + output, err := ioutil.ReadAll(conn) + if err != nil { + t.Fatal(err) + } + if string(output) != "well hello there\n" { + t.Fatalf("Received wrong output from network connection: should be '%s', not '%s'", + "well hello there\n", + string(output), + ) + } +} + func TestRestore(t *testing.T) { root, err := ioutil.TempDir("", "docker-test") From 8ecde8f9a5dcad23afea013a62d373cef303fa84 Mon Sep 17 00:00:00 2001 From: Thatcher Peskens Date: Fri, 19 Apr 2013 20:57:50 -0700 Subject: [PATCH 046/138] Updated documentation and fixed Vagrantfile --- Vagrantfile | 51 +++++++++++++----- docs/sources/installation/binaries.rst | 56 ++++++++++++++++++++ docs/sources/installation/index.rst | 2 +- docs/sources/installation/vagrant.rst | 73 ++++++++++++++++++++++++++ docs/sources/installation/windows.rst | 4 +- docs/sources/nginx.conf | 2 + 6 files changed, 172 insertions(+), 16 deletions(-) create mode 100644 docs/sources/installation/binaries.rst create mode 100644 docs/sources/installation/vagrant.rst diff --git a/Vagrantfile b/Vagrantfile index 48b3ef567..f49e78156 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -2,19 +2,13 @@ # vi: set ft=ruby : def v10(config) - config.vm.box = "quantal64_3.5.0-25" - config.vm.box_url = "http://get.docker.io/vbox/ubuntu/12.10/quantal64_3.5.0-25.box" + config.vm.box = 'precise64' + config.vm.box_url = 'http://files.vagrantup.com/precise64.box' - config.vm.share_folder "v-data", "/opt/go/src/github.com/dotcloud/docker", File.dirname(__FILE__) + # Install ubuntu packaging dependencies and create ubuntu packages + config.vm.provision :shell, :inline => "echo 'deb http://ppa.launchpad.net/dotcloud/lxc-docker/ubuntu precise main' >>/etc/apt/sources.list" + config.vm.provision :shell, :inline => 'export DEBIAN_FRONTEND=noninteractive; apt-get -qq update; apt-get install -qq -y --force-yes lxc-docker' - # Ensure puppet is installed on the instance - config.vm.provision :shell, :inline => "apt-get -qq update; apt-get install -y puppet" - - config.vm.provision :puppet do |puppet| - puppet.manifests_path = "puppet/manifests" - puppet.manifest_file = "quantal64.pp" - puppet.module_path = "puppet/modules" - end end Vagrant::VERSION < "1.1.0" and Vagrant::Config.run do |config| @@ -30,11 +24,11 @@ Vagrant::VERSION >= "1.1.0" and Vagrant.configure("2") do |config| config.vm.box = "dummy" config.vm.box_url = "https://github.com/mitchellh/vagrant-aws/raw/master/dummy.box" aws.access_key_id = ENV["AWS_ACCESS_KEY_ID"] - aws.secret_access_key = ENV["AWS_SECRET_ACCESS_KEY"] + aws.secret_access_key = ENV["AWS_SECRET_ACCESS_KEY"] aws.keypair_name = ENV["AWS_KEYPAIR_NAME"] aws.ssh_private_key_path = ENV["AWS_SSH_PRIVKEY"] aws.region = "us-east-1" - aws.ami = "ami-ae9806c7" + aws.ami = "ami-d0f89fb9" aws.ssh_username = "ubuntu" aws.instance_type = "t1.micro" end @@ -55,3 +49,34 @@ Vagrant::VERSION >= "1.1.0" and Vagrant.configure("2") do |config| config.vm.box_url = "http://get.docker.io/vbox/ubuntu/12.10/quantal64_3.5.0-25.box" end end + +Vagrant::VERSION >= "1.2.0" and Vagrant.configure("2") do |config| + config.vm.provider :aws do |aws, override| + config.vm.box = "dummy" + config.vm.box_url = "https://github.com/mitchellh/vagrant-aws/raw/master/dummy.box" + aws.access_key_id = ENV["AWS_ACCESS_KEY_ID"] + aws.secret_access_key = ENV["AWS_SECRET_ACCESS_KEY"] + aws.keypair_name = ENV["AWS_KEYPAIR_NAME"] + override.ssh.private_key_path = ENV["AWS_SSH_PRIVKEY"] + override.ssh.username = "ubuntu" + aws.region = "us-east-1" + aws.ami = "ami-d0f89fb9" + aws.instance_type = "t1.micro" + end + + config.vm.provider :rackspace do |rs| + config.vm.box = "dummy" + config.vm.box_url = "https://github.com/mitchellh/vagrant-rackspace/raw/master/dummy.box" + config.ssh.private_key_path = ENV["RS_PRIVATE_KEY"] + rs.username = ENV["RS_USERNAME"] + rs.api_key = ENV["RS_API_KEY"] + rs.public_key_path = ENV["RS_PUBLIC_KEY"] + rs.flavor = /512MB/ + rs.image = /Ubuntu/ + end + + config.vm.provider :virtualbox do |vb| + config.vm.box = "quantal64_3.5.0-25" + config.vm.box_url = "http://get.docker.io/vbox/ubuntu/12.10/quantal64_3.5.0-25.box" + end +end diff --git a/docs/sources/installation/binaries.rst b/docs/sources/installation/binaries.rst new file mode 100644 index 000000000..bf83a5bc8 --- /dev/null +++ b/docs/sources/installation/binaries.rst @@ -0,0 +1,56 @@ +.. _ubuntu_linux: + +Ubuntu Linux +============ + + **Please note this project is currently under heavy development. It should not be used in production.** + + + +Installing on Ubuntu 12.04 and 12.10 + +Right now, the officially supported distributions are: + +Ubuntu 12.04 (precise LTS) +Ubuntu 12.10 (quantal) +Docker probably works on other distributions featuring a recent kernel, the AUFS patch, and up-to-date lxc. However this has not been tested. + +Install dependencies: +--------------------- + +:: + + sudo apt-get install lxc wget bsdtar curl + sudo apt-get install linux-image-extra-`uname -r` + +The linux-image-extra package is needed on standard Ubuntu EC2 AMIs in order to install the aufs kernel module. + +Install the latest docker binary: + +:: + + wget http://get.docker.io/builds/$(uname -s)/$(uname -m)/docker-master.tgz + tar -xf docker-master.tgz + +Run your first container! + +:: + + cd docker-master + +:: + + sudo ./docker run -i -t base /bin/bash + + +To run docker as a daemon, in the background, and allow non-root users to run ``docker`` start +docker -d + +:: + + sudo ./docker -d & + + +Consider adding docker to your PATH for simplicity. + +Continue with the :ref:`hello_world` example. \ No newline at end of file diff --git a/docs/sources/installation/index.rst b/docs/sources/installation/index.rst index b02e9c83a..ae1125887 100644 --- a/docs/sources/installation/index.rst +++ b/docs/sources/installation/index.rst @@ -13,7 +13,7 @@ Contents: :maxdepth: 1 ubuntulinux - macos + vagrant windows amazon upgrading diff --git a/docs/sources/installation/vagrant.rst b/docs/sources/installation/vagrant.rst new file mode 100644 index 000000000..5b5772142 --- /dev/null +++ b/docs/sources/installation/vagrant.rst @@ -0,0 +1,73 @@ + +.. _install_using_vagrant: + +Install using Vagrant +===================== + + Please note this is a community contributed installation path. The only 'official' installation is using the :ref:`ubuntu_linux` installation path. This version + may be out of date because it depends on some binaries to be updated and published + +**requirements** +This guide will setup a new virtual machine on your computer. This works on most operating systems, +including MacOX, Windows, Linux, FreeBSD and others. If you can +install these and have at least 400Mb RAM to spare you should be good. + + +Install Vagrant, Virtualbox and Git +----------------------------------- + +We currently rely on some Ubuntu-linux specific packages, this will change in the future, but for now we provide a +streamlined path to install Virtualbox with a Ubuntu 12.10 image using Vagrant. + +1. Install virtualbox from https://www.virtualbox.org/ (or use your package manager) +2. Install vagrant from http://www.vagrantup.com/ (or use your package manager) +3. Install git if you had not installed it before, check if it is installed by running + ``git`` in a terminal window + +We recommend having at least about 2Gb of free disk space and 2Gb RAM (or more). + +Spin up your machine +-------------------- + +1. Fetch the docker sources + +.. code-block:: bash + + git clone https://github.com/dotcloud/docker.git + +2. Run vagrant from the sources directory + +.. code-block:: bash + + vagrant up + +Vagrant will: + +* Download the Quantal64 base ubuntu virtual machine image from get.docker.io/ +* Boot this image in virtualbox + +Then it will use Puppet to perform an initial setup in this machine: + +* Download & untar the 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. + +You now have a Ubuntu Virtual Machine running with docker pre-installed. + +To access the VM and use Docker, Run ``vagrant ssh`` from the same directory as where you ran +``vagrant up``. Vagrant will make sure to connect you to the correct VM. + +.. code-block:: bash + + vagrant ssh + +Now you are in the VM, run docker + +.. code-block:: bash + + docker + + +Continue with the :ref:`hello_world` example. diff --git a/docs/sources/installation/windows.rst b/docs/sources/installation/windows.rst index 6091d6bac..a89d3a901 100644 --- a/docs/sources/installation/windows.rst +++ b/docs/sources/installation/windows.rst @@ -3,8 +3,8 @@ :keywords: Docker, Docker documentation, Windows, requirements, virtualbox, vagrant, git, ssh, putty, cygwin -Windows -========= +Windows (with Vagrant) +====================== Please note this is a community contributed installation path. The only 'official' installation is using the :ref:`ubuntu_linux` installation path. This version may be out of date because it depends on some binaries to be updated and published diff --git a/docs/sources/nginx.conf b/docs/sources/nginx.conf index cbc954318..97ffd2c0e 100644 --- a/docs/sources/nginx.conf +++ b/docs/sources/nginx.conf @@ -2,3 +2,5 @@ # rule to redirect original links created when hosted on github pages rewrite ^/documentation/(.*).html http://docs.docker.io/en/latest/$1/ permanent; +# rewrite the stuff which was on the current page +rewrite ^/gettingstarted.html$ /gettingstarted/ permanent; From 0731d1a582b44a18e9bfdf0764d232b46218346b Mon Sep 17 00:00:00 2001 From: Thatcher Peskens Date: Fri, 19 Apr 2013 20:59:43 -0700 Subject: [PATCH 047/138] Updated ubuntu install --- docs/sources/installation/ubuntulinux.rst | 88 ++++++++++------------- 1 file changed, 37 insertions(+), 51 deletions(-) diff --git a/docs/sources/installation/ubuntulinux.rst b/docs/sources/installation/ubuntulinux.rst index bf83a5bc8..a822242ce 100644 --- a/docs/sources/installation/ubuntulinux.rst +++ b/docs/sources/installation/ubuntulinux.rst @@ -1,56 +1,42 @@ -.. _ubuntu_linux: +Docker on Ubuntu +================ -Ubuntu Linux -============ +Docker is now available as a Ubuntu PPA (Personal Package Archive), which makes installing Docker on Ubuntu super easy! - **Please note this project is currently under heavy development. It should not be used in production.** +**The Requirements** + +* Ubuntu 12.04 (LTS) or Ubuntu 12.10 +* **64-bit Operating system** + + +Add the custom package sources to your apt sources list. Copy and paste both the following lines at once. + +.. code-block:: bash + + sudo sh -c "echo 'deb http://ppa.launchpad.net/dotcloud/lxc-docker/ubuntu precise main' \ + >> /etc/apt/sources.list" + + +Update your sources. You will see a warning that GPG signatures cannot be verified + +.. code-block:: bash + + sudo apt-get update + + +Now install it, you will see another warning that the package cannot be authenticated. + +.. code-block:: bash + + sudo apt-get install lxc-docker + + +**Run!** + +.. code-block:: bash + + docker -Installing on Ubuntu 12.04 and 12.10 - -Right now, the officially supported distributions are: - -Ubuntu 12.04 (precise LTS) -Ubuntu 12.10 (quantal) -Docker probably works on other distributions featuring a recent kernel, the AUFS patch, and up-to-date lxc. However this has not been tested. - -Install dependencies: ---------------------- - -:: - - sudo apt-get install lxc wget bsdtar curl - sudo apt-get install linux-image-extra-`uname -r` - -The linux-image-extra package is needed on standard Ubuntu EC2 AMIs in order to install the aufs kernel module. - -Install the latest docker binary: - -:: - - wget http://get.docker.io/builds/$(uname -s)/$(uname -m)/docker-master.tgz - tar -xf docker-master.tgz - -Run your first container! - -:: - - cd docker-master - -:: - - sudo ./docker run -i -t base /bin/bash - - -To run docker as a daemon, in the background, and allow non-root users to run ``docker`` start -docker -d - -:: - - sudo ./docker -d & - - -Consider adding docker to your PATH for simplicity. - -Continue with the :ref:`hello_world` example. \ No newline at end of file +Probably you would like to continue with the :ref:`hello_world` example. \ No newline at end of file From c40f01319f741ef895090334ffa637f099c529b3 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Sat, 20 Apr 2013 17:26:50 -0700 Subject: [PATCH 048/138] Cleaned up install instructions in the README * Addded quick install on ubuntu as the 1st install option * Grouped other binary installs under "binary installs" * Removed duplicate binary ubuntu installs (linked to the docs) * Improved "build from source" instructions --- README.md | 47 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 33 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 54fe0a22b..774e107a5 100644 --- a/README.md +++ b/README.md @@ -53,29 +53,48 @@ Under the hood, Docker is built on the following components: Install instructions ================== -Building from source --------------------- +Quick install on Ubuntu 12.04 and 12.10 +--------------------------------------- -1. Make sure you have a [Go language](http://golang.org) compiler. +```bash +curl get.docker.io | sh -x +``` - On a Debian/wheezy or Ubuntu 12.10 install the package: +Binary installs +---------------- - ```bash +Docker supports the following binary installation methods. +Note that some methods are community contributions and not yet officially supported. - $ sudo apt-get install golang-go - ``` +* [Ubuntu 12.04 and 12.10 (officially supported)](http://docs.docker.io/en/latest/installation/ubuntulinux/) +* [Arch Linux](http://docs.docker.io/en/latest/installation/archlinux/) +* [MacOS X (with Vagrant)](http://docs.docker.io/en/latest/installation/macos/) +* [Windows (with Vagrant)](http://docs.docker.io/en/latest/installation/windows/) +* [Amazon EC2 (with Vagrant)](http://docs.docker.io/en/latest/installation/amazon/) -2. Execute ``make`` +Installing from source +---------------------- - This command will install all necessary dependencies and build the - executable that you can find in ``bin/docker`` +1. Make sure you have a [Go language](http://golang.org/doc/install) compiler and [git](http://git-scm.com) installed. -3. Should you like to see what's happening, run ``make`` with ``VERBOSE=1`` parameter: +2. Checkout the source code - ```bash + ```bash + git clone http://github.com/dotcloud/docker + ``` - $ make VERBOSE=1 - ``` +3. Build the docker binary + + ```bash + cd docker + make VERBOSE=1 + ``` + +4. Execute the docker daemon + + ```bash + sudo ./bin/docker -d + ``` Installing on Ubuntu 12.04 and 12.10 ------------------------------------ From 70cf467fdf8c4809429089ceae0f3ac1b3a6f729 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Sat, 20 Apr 2013 17:29:12 -0700 Subject: [PATCH 049/138] Removed duplicate ubuntu binary install instructions from README --- README.md | 49 +------------------------------------------------ 1 file changed, 1 insertion(+), 48 deletions(-) diff --git a/README.md b/README.md index 774e107a5..d11257563 100644 --- a/README.md +++ b/README.md @@ -88,56 +88,9 @@ Installing from source ```bash cd docker make VERBOSE=1 + sudo cp ./bin/docker /usr/local/bin/docker ``` -4. Execute the docker daemon - - ```bash - sudo ./bin/docker -d - ``` - -Installing on Ubuntu 12.04 and 12.10 ------------------------------------- - -1. Install dependencies: - - ```bash - sudo apt-get install lxc bsdtar - sudo apt-get install linux-image-extra-`uname -r` - ``` - - The `linux-image-extra` package is needed on standard Ubuntu EC2 AMIs in order to install the aufs kernel module. - -2. Install the latest docker binary: - - ```bash - wget http://get.docker.io/builds/$(uname -s)/$(uname -m)/docker-master.tgz - tar -xf docker-master.tgz - ``` - -3. Run your first container! - - ```bash - cd docker-master - sudo ./docker pull base - sudo ./docker run -i -t base /bin/bash - ``` - - Consider adding docker to your `PATH` for simplicity. - -Installing on other Linux distributions ---------------------------------------- - -Right now, the officially supported distributions are: - -* Ubuntu 12.04 (precise LTS) -* Ubuntu 12.10 (quantal) - -Docker probably works on other distributions featuring a recent kernel, the AUFS patch, and up-to-date lxc. However this has not been tested. - -Some streamlined (but possibly outdated) installation paths' are available from the website: http://docker.io/documentation/ - - Usage examples ============== From 28831a412fa51094124f17d242908f308a2a973a Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Sat, 20 Apr 2013 17:29:41 -0700 Subject: [PATCH 050/138] Link to public images list in the README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index d11257563..22359c926 100644 --- a/README.md +++ b/README.md @@ -162,6 +162,7 @@ docker commit -m "Installed curl" $CONTAINER $USER/betterbase docker push $USER/betterbase ``` +A list of publicly available images is [available here](https://github.com/dotcloud/docker/wiki/Public-docker-images). Expose a service on a TCP port ------------------------------ From 4a9c3a92e1552fab5702eeddc0307f7f87f96496 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Sat, 20 Apr 2013 17:30:33 -0700 Subject: [PATCH 051/138] Formatting fix in ubuntu install doc --- docs/sources/installation/ubuntulinux.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/sources/installation/ubuntulinux.rst b/docs/sources/installation/ubuntulinux.rst index bf83a5bc8..4d777d52a 100644 --- a/docs/sources/installation/ubuntulinux.rst +++ b/docs/sources/installation/ubuntulinux.rst @@ -11,9 +11,8 @@ Installing on Ubuntu 12.04 and 12.10 Right now, the officially supported distributions are: -Ubuntu 12.04 (precise LTS) -Ubuntu 12.10 (quantal) -Docker probably works on other distributions featuring a recent kernel, the AUFS patch, and up-to-date lxc. However this has not been tested. +* Ubuntu 12.04 (precise LTS) +* Ubuntu 12.10 (quantal) Install dependencies: --------------------- From 1201c418cd6f02ca8c206cfdd4659b8ec2d2f856 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Sat, 20 Apr 2013 17:31:49 -0700 Subject: [PATCH 052/138] Fix Ubuntu install doc --- docs/sources/installation/ubuntulinux.rst | 37 ++++++++++++----------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/docs/sources/installation/ubuntulinux.rst b/docs/sources/installation/ubuntulinux.rst index 4d777d52a..306e43813 100644 --- a/docs/sources/installation/ubuntulinux.rst +++ b/docs/sources/installation/ubuntulinux.rst @@ -19,37 +19,38 @@ Install dependencies: :: - sudo apt-get install lxc wget bsdtar curl + sudo apt-get install lxc bsdtar sudo apt-get install linux-image-extra-`uname -r` The linux-image-extra package is needed on standard Ubuntu EC2 AMIs in order to install the aufs kernel module. -Install the latest docker binary: +Install the docker binary +------------------------- :: - wget http://get.docker.io/builds/$(uname -s)/$(uname -m)/docker-master.tgz + wget http://get.docker.io/builds/Linux/x86_64/docker-master.tgz tar -xf docker-master.tgz + sudo cp ./docker-master /usr/local/bin + +Note: docker currently only supports 64-bit Linux hosts. + + +Run the docker daemon +--------------------- + +:: + + sudo docker -d & Run your first container! +------------------------- :: - - cd docker-master - -:: - - sudo ./docker run -i -t base /bin/bash + docker run -i -t ubuntu /bin/bash -To run docker as a daemon, in the background, and allow non-root users to run ``docker`` start -docker -d - -:: - - sudo ./docker -d & - - -Consider adding docker to your PATH for simplicity. +Check out more examples +----------------------- Continue with the :ref:`hello_world` example. \ No newline at end of file From d49a273071eaa579f59799b679a9f7b316e28fb8 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Sat, 20 Apr 2013 17:33:02 -0700 Subject: [PATCH 053/138] Moved 'under the hood' below install instructions and examples in README --- README.md | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 22359c926..058004c07 100644 --- a/README.md +++ b/README.md @@ -33,23 +33,6 @@ Notable features * Interactive shell: docker can allocate a pseudo-tty and attach to the standard input of any container, for example to run a throwaway interactive shell. - - -Under the hood --------------- - -Under the hood, Docker is built on the following components: - - -* The [cgroup](http://blog.dotcloud.com/kernel-secrets-from-the-paas-garage-part-24-c) and [namespacing](http://blog.dotcloud.com/under-the-hood-linux-kernels-on-dotcloud-part) capabilities of the Linux kernel; - -* [AUFS](http://aufs.sourceforge.net/aufs.html), a powerful union filesystem with copy-on-write capabilities; - -* The [Go](http://golang.org) programming language; - -* [lxc](http://lxc.sourceforge.net/), a set of convenience scripts to simplify the creation of linux containers. - - Install instructions ================== @@ -183,6 +166,22 @@ echo hello world | nc $IP $PORT echo "Daemon received: $(docker logs $JOB)" ``` +Under the hood +-------------- + +Under the hood, Docker is built on the following components: + + +* The [cgroup](http://blog.dotcloud.com/kernel-secrets-from-the-paas-garage-part-24-c) and [namespacing](http://blog.dotcloud.com/under-the-hood-linux-kernels-on-dotcloud-part) capabilities of the Linux kernel; + +* [AUFS](http://aufs.sourceforge.net/aufs.html), a powerful union filesystem with copy-on-write capabilities; + +* The [Go](http://golang.org) programming language; + +* [lxc](http://lxc.sourceforge.net/), a set of convenience scripts to simplify the creation of linux containers. + + + Contributing to Docker ====================== From 3b6c540fe8313035de0f0b00de52d7197f8bc665 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Sat, 20 Apr 2013 17:35:00 -0700 Subject: [PATCH 054/138] Bumped version to 0.1.7 --- commands.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commands.go b/commands.go index 7f8277569..fea1c109d 100644 --- a/commands.go +++ b/commands.go @@ -18,7 +18,7 @@ import ( "unicode" ) -const VERSION = "0.1.6" +const VERSION = "0.1.7" var ( GIT_COMMIT string From 52cedb8a05182c7d6f5e5918453cf356b1efafc8 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Sat, 20 Apr 2013 18:26:15 -0700 Subject: [PATCH 055/138] Better title in ubuntu install doc --- docs/sources/installation/ubuntulinux.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/sources/installation/ubuntulinux.rst b/docs/sources/installation/ubuntulinux.rst index 306e43813..6149a26f2 100644 --- a/docs/sources/installation/ubuntulinux.rst +++ b/docs/sources/installation/ubuntulinux.rst @@ -1,7 +1,7 @@ .. _ubuntu_linux: -Ubuntu Linux -============ +Installing on Ubuntu Linux +========================== **Please note this project is currently under heavy development. It should not be used in production.** @@ -53,4 +53,4 @@ Run your first container! Check out more examples ----------------------- -Continue with the :ref:`hello_world` example. \ No newline at end of file +Continue with the :ref:`hello_world` example. From baacae8345febd688579ac29832c200c41602ed2 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Sun, 21 Apr 2013 14:23:55 -0700 Subject: [PATCH 056/138] 'docker push' buffers filesystem archives on disk instead of memory. --- archive.go | 36 ++++++++++++++++++++++++++++++++++++ graph.go | 24 +++++++++++++++++++++++- image.go | 9 +++++++++ registry.go | 21 +++++++++------------ 4 files changed, 77 insertions(+), 13 deletions(-) diff --git a/archive.go b/archive.go index d09d3d6b9..8a011eb6e 100644 --- a/archive.go +++ b/archive.go @@ -4,6 +4,7 @@ import ( "errors" "io" "io/ioutil" + "os" "os/exec" ) @@ -86,3 +87,38 @@ func CmdStream(cmd *exec.Cmd) (io.Reader, error) { } return pipeR, nil } + +// NewTempArchive reads the content of src into a temporary file, and returns the contents +// of that file as an archive. The archive can only be read once - as soon as reading completes, +// the file will be deleted. +func NewTempArchive(src Archive, dir string) (*TempArchive, error) { + f, err := ioutil.TempFile(dir, "") + if err != nil { + return nil, err + } + if _, err := io.Copy(f, src); err != nil { + return nil, err + } + if _, err := f.Seek(0, 0); err != nil { + return nil, err + } + st, err := f.Stat() + if err != nil { + return nil, err + } + size := st.Size() + return &TempArchive{f, size}, nil +} + +type TempArchive struct { + *os.File + Size int64 // Pre-computed from Stat().Size() as a convenience +} + +func (archive *TempArchive) Read(data []byte) (int, error) { + n, err := archive.File.Read(data) + if err != nil { + os.Remove(archive.File.Name()) + } + return n, err +} diff --git a/graph.go b/graph.go index b7dbf2e11..d2692fc82 100644 --- a/graph.go +++ b/graph.go @@ -129,12 +129,30 @@ func (graph *Graph) Register(layerData Archive, img *Image) error { return nil } +// TempLayerArchive creates a temporary archive of the given image's filesystem layer. +// The archive is stored on disk and will be automatically deleted as soon as has been read. +func (graph *Graph) TempLayerArchive(id string, compression Compression) (*TempArchive, error) { + image, err := graph.Get(id) + if err != nil { + return nil, err + } + tmp, err := graph.tmp() + if err != nil { + return nil, err + } + archive, err := image.TarLayer(compression) + if err != nil { + return nil, err + } + return NewTempArchive(archive, tmp.Root) +} + // Mktemp creates a temporary sub-directory inside the graph's filesystem. func (graph *Graph) Mktemp(id string) (string, error) { if id == "" { id = GenerateId() } - tmp, err := NewGraph(path.Join(graph.Root, ":tmp:")) + tmp, err := graph.tmp() if err != nil { return "", fmt.Errorf("Couldn't create temp: %s", err) } @@ -144,6 +162,10 @@ func (graph *Graph) Mktemp(id string) (string, error) { return tmp.imageRoot(id), nil } +func (graph *Graph) tmp() (*Graph, error) { + return NewGraph(path.Join(graph.Root, ":tmp:")) +} + // Check if given error is "not empty". // Note: this is the way golang does it internally with os.IsNotExists. func isNotEmpty(err error) bool { diff --git a/image.go b/image.go index 9369fc3f4..403731d6e 100644 --- a/image.go +++ b/image.go @@ -110,6 +110,15 @@ func MountAUFS(ro []string, rw string, target string) error { return nil } +// TarLayer returns a tar archive of the image's filesystem layer. +func (image *Image) TarLayer(compression Compression) (Archive, error) { + layerPath, err := image.layer() + if err != nil { + return nil, err + } + return Tar(layerPath, compression) +} + func (image *Image) Mount(root, rw string) error { if mounted, err := Mounted(root); err != nil { return err diff --git a/registry.go b/registry.go index 428db1b96..2f461cc8e 100644 --- a/registry.go +++ b/registry.go @@ -7,6 +7,7 @@ import ( "io" "io/ioutil" "net/http" + "os" "path" "strings" ) @@ -269,24 +270,20 @@ func (graph *Graph) PushImage(stdout io.Writer, imgOrig *Image, authConfig *auth return fmt.Errorf("Failed to retrieve layer upload location: %s", err) } - // FIXME: Don't do this :D. Check the S3 requierement and implement chunks of 5MB - // FIXME2: I won't stress it enough, DON'T DO THIS! very high priority - layerData2, err := Tar(path.Join(graph.Root, img.Id, "layer"), Xz) - tmp, err := ioutil.ReadAll(layerData2) + // FIXME: stream the archive directly to the registry instead of buffering it on disk. This requires either: + // a) Implementing S3's proprietary streaming logic, or + // b) Stream directly to the registry instead of S3. + // I prefer option b. because it doesn't lock us into a proprietary cloud service. + tmpLayer, err := graph.TempLayerArchive(img.Id, Xz) if err != nil { return err } - layerLength := len(tmp) - - layerData, err := Tar(path.Join(graph.Root, img.Id, "layer"), Xz) - if err != nil { - return fmt.Errorf("Failed to generate layer archive: %s", err) - } - req3, err := http.NewRequest("PUT", url.String(), ProgressReader(layerData.(io.ReadCloser), layerLength, stdout)) + defer os.Remove(tmpLayer.Name()) + req3, err := http.NewRequest("PUT", url.String(), ProgressReader(tmpLayer, int(tmpLayer.Size), stdout)) if err != nil { return err } - req3.ContentLength = int64(layerLength) + req3.ContentLength = int64(tmpLayer.Size) req3.TransferEncoding = []string{"none"} res3, err := client.Do(req3) From 965e8a02d24a35aa7a7f64f2baf2b652a04f5983 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Sun, 21 Apr 2013 15:29:26 -0700 Subject: [PATCH 057/138] 'docker push' shows an additional progress bar while it buffers the archive to disk. Fixes #451. --- commands.go | 2 +- graph.go | 7 +++++-- registry.go | 6 +++--- utils.go | 31 +++++++++++++++++++++---------- 4 files changed, 30 insertions(+), 16 deletions(-) diff --git a/commands.go b/commands.go index 2feb648c3..1f951ff33 100644 --- a/commands.go +++ b/commands.go @@ -475,7 +475,7 @@ func (srv *Server) CmdImport(stdin io.ReadCloser, stdout rcli.DockerConn, args . if err != nil { return err } - archive = ProgressReader(resp.Body, int(resp.ContentLength), stdout) + archive = ProgressReader(resp.Body, int(resp.ContentLength), stdout, "Importing %v/%v (%v)") } img, err := srv.runtime.graph.Create(archive, nil, "Imported from "+src, "") if err != nil { diff --git a/graph.go b/graph.go index d2692fc82..c0e500091 100644 --- a/graph.go +++ b/graph.go @@ -2,6 +2,7 @@ package docker import ( "fmt" + "io" "io/ioutil" "os" "path" @@ -131,7 +132,9 @@ func (graph *Graph) Register(layerData Archive, img *Image) error { // TempLayerArchive creates a temporary archive of the given image's filesystem layer. // The archive is stored on disk and will be automatically deleted as soon as has been read. -func (graph *Graph) TempLayerArchive(id string, compression Compression) (*TempArchive, error) { +// If output is not nil, a human-readable progress bar will be written to it. +// FIXME: does this belong in Graph? How about MktempFile, let the caller use it for archives? +func (graph *Graph) TempLayerArchive(id string, compression Compression, output io.Writer) (*TempArchive, error) { image, err := graph.Get(id) if err != nil { return nil, err @@ -144,7 +147,7 @@ func (graph *Graph) TempLayerArchive(id string, compression Compression) (*TempA if err != nil { return nil, err } - return NewTempArchive(archive, tmp.Root) + return NewTempArchive(ProgressReader(ioutil.NopCloser(archive), 0, output, "Buffering to disk %v/%v (%v)"), tmp.Root) } // Mktemp creates a temporary sub-directory inside the graph's filesystem. diff --git a/registry.go b/registry.go index 2f461cc8e..74b166906 100644 --- a/registry.go +++ b/registry.go @@ -136,7 +136,7 @@ func (graph *Graph) getRemoteImage(stdout io.Writer, imgId string, authConfig *a if err != nil { return nil, nil, err } - return img, ProgressReader(res.Body, int(res.ContentLength), stdout), nil + return img, ProgressReader(res.Body, int(res.ContentLength), stdout, "Downloading %v/%v (%v)"), nil } func (graph *Graph) PullImage(stdout io.Writer, imgId string, authConfig *auth.AuthConfig) error { @@ -274,12 +274,12 @@ func (graph *Graph) PushImage(stdout io.Writer, imgOrig *Image, authConfig *auth // a) Implementing S3's proprietary streaming logic, or // b) Stream directly to the registry instead of S3. // I prefer option b. because it doesn't lock us into a proprietary cloud service. - tmpLayer, err := graph.TempLayerArchive(img.Id, Xz) + tmpLayer, err := graph.TempLayerArchive(img.Id, Xz, stdout) if err != nil { return err } defer os.Remove(tmpLayer.Name()) - req3, err := http.NewRequest("PUT", url.String(), ProgressReader(tmpLayer, int(tmpLayer.Size), stdout)) + req3, err := http.NewRequest("PUT", url.String(), ProgressReader(tmpLayer, int(tmpLayer.Size), stdout, "Uploading %v/%v (%v)")) if err != nil { return err } diff --git a/utils.go b/utils.go index 8763a4393..bb891b18a 100644 --- a/utils.go +++ b/utils.go @@ -72,23 +72,30 @@ type progressReader struct { readTotal int // Expected stream length (bytes) readProgress int // How much has been read so far (bytes) lastUpdate int // How many bytes read at least update + template string // Template to print. Default "%v/%v (%v)" } func (r *progressReader) Read(p []byte) (n int, err error) { read, err := io.ReadCloser(r.reader).Read(p) r.readProgress += read - // Only update progress for every 1% read - updateEvery := int(0.01 * float64(r.readTotal)) - if r.readProgress-r.lastUpdate > updateEvery || r.readProgress == r.readTotal { - fmt.Fprintf(r.output, "%d/%d (%.0f%%)\r", - r.readProgress, - r.readTotal, - float64(r.readProgress)/float64(r.readTotal)*100) + updateEvery := 4096 + if r.readTotal > 0 { + // Only update progress for every 1% read + if increment := int(0.01 * float64(r.readTotal)); increment > updateEvery { + updateEvery = increment + } + } + if r.readProgress-r.lastUpdate > updateEvery || err != nil { + if r.readTotal > 0 { + fmt.Fprintf(r.output, r.template+"\r", r.readProgress, r.readTotal, fmt.Sprintf("%.0f%%", float64(r.readProgress)/float64(r.readTotal)*100)) + } else { + fmt.Fprintf(r.output, r.template+"\r", r.readProgress, "?", "n/a") + } r.lastUpdate = r.readProgress } // Send newline when complete - if err == io.EOF { + if err != nil { fmt.Fprintf(r.output, "\n") } @@ -97,8 +104,11 @@ func (r *progressReader) Read(p []byte) (n int, err error) { func (r *progressReader) Close() error { return io.ReadCloser(r.reader).Close() } -func ProgressReader(r io.ReadCloser, size int, output io.Writer) *progressReader { - return &progressReader{r, output, size, 0, 0} +func ProgressReader(r io.ReadCloser, size int, output io.Writer, template string) *progressReader { + if template == "" { + template = "%v/%v (%v)" + } + return &progressReader{r, output, size, 0, 0, template} } // HumanDuration returns a human-readable approximation of a duration @@ -395,6 +405,7 @@ type KernelVersionInfo struct { Specific int } +// FIXME: this doens't build on Darwin func GetKernelVersion() (*KernelVersionInfo, error) { var uts syscall.Utsname From 1f65c6bf4c9dcfc513eeb9da34a7cac60c6bc4ff Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Sun, 21 Apr 2013 19:19:38 -0600 Subject: [PATCH 058/138] Update utils.go to not enforce extra constraints on the kernel "flavor" (such as being integral or even comparable one to another) This is especially to fix the current docker on kernels such as gentoo-sources, where the "flavor" is the string "gentoo", and that obviously fails to be converted to an integer. --- utils.go | 28 ++++++++++------------------ utils_test.go | 26 +++++++++++++------------- 2 files changed, 23 insertions(+), 31 deletions(-) diff --git a/utils.go b/utils.go index bb891b18a..c6a8c9465 100644 --- a/utils.go +++ b/utils.go @@ -399,10 +399,10 @@ func CopyEscapable(dst io.Writer, src io.ReadCloser) (written int64, err error) } type KernelVersionInfo struct { - Kernel int - Major int - Minor int - Specific int + Kernel int + Major int + Minor int + Flavor string } // FIXME: this doens't build on Darwin @@ -445,21 +445,18 @@ func GetKernelVersion() (*KernelVersionInfo, error) { return nil, err } - specific, err := strconv.Atoi(strings.Split(tmp[1], "-")[0]) - if err != nil { - return nil, err - } + flavor := tmp[1] return &KernelVersionInfo{ - Kernel: kernel, - Major: major, - Minor: minor, - Specific: specific, + Kernel: kernel, + Major: major, + Minor: minor, + Flavor: flavor, }, nil } func (k *KernelVersionInfo) String() string { - return fmt.Sprintf("%d.%d.%d-%d", k.Kernel, k.Major, k.Minor, k.Specific) + return fmt.Sprintf("%d.%d.%d-%s", k.Kernel, k.Major, k.Minor, k.Flavor) } // Compare two KernelVersionInfo struct. @@ -483,11 +480,6 @@ func CompareKernelVersion(a, b *KernelVersionInfo) int { return 1 } - if a.Specific < b.Specific { - return -1 - } else if a.Specific > b.Specific { - return 1 - } return 0 } diff --git a/utils_test.go b/utils_test.go index 1ee223ee3..aa2a1b968 100644 --- a/utils_test.go +++ b/utils_test.go @@ -237,27 +237,27 @@ func assertKernelVersion(t *testing.T, a, b *KernelVersionInfo, result int) { func TestCompareKernelVersion(t *testing.T) { assertKernelVersion(t, - &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0, Specific: 0}, - &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0, Specific: 0}, + &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0}, + &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0}, 0) assertKernelVersion(t, - &KernelVersionInfo{Kernel: 2, Major: 6, Minor: 0, Specific: 0}, - &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0, Specific: 0}, + &KernelVersionInfo{Kernel: 2, Major: 6, Minor: 0}, + &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0}, -1) assertKernelVersion(t, - &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0, Specific: 0}, - &KernelVersionInfo{Kernel: 2, Major: 6, Minor: 0, Specific: 0}, + &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0}, + &KernelVersionInfo{Kernel: 2, Major: 6, Minor: 0}, 1) assertKernelVersion(t, - &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0, Specific: 0}, - &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0, Specific: 16}, - -1) + &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0, Flavor: "0"}, + &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0, Flavor: "16"}, + 0) assertKernelVersion(t, - &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 5, Specific: 0}, - &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0, Specific: 0}, + &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 5}, + &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0}, 1) assertKernelVersion(t, - &KernelVersionInfo{Kernel: 3, Major: 0, Minor: 20, Specific: 25}, - &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0, Specific: 0}, + &KernelVersionInfo{Kernel: 3, Major: 0, Minor: 20, Flavor: "25"}, + &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0, Flavor: "0"}, -1) } From 71b580661451c35f01ee3824506f572c52d86ac8 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Mon, 22 Apr 2013 00:44:57 -0400 Subject: [PATCH 059/138] Do not stop execution if cgroup mountpoint is not found --- runtime.go | 26 +++++++++++++++----------- utils.go | 3 +-- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/runtime.go b/runtime.go index a12d0c92d..b894a2cda 100644 --- a/runtime.go +++ b/runtime.go @@ -305,18 +305,22 @@ func NewRuntime() (*Runtime, error) { log.Printf("WARNING: You are running linux kernel version %s, which might be unstable running docker. Please upgrade your kernel to 3.8.0.", k.String()) } - cgroupMemoryMountpoint, err := FindCgroupMountpoint("memory") - if err != nil { - return nil, err + if cgroupMemoryMountpoint, err := FindCgroupMountpoint("memory"); err != nil { + log.Printf("WARNING: %s\n", err) + } else { + _, err1 := ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.limit_in_bytes")) + _, err2 := ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.soft_limit_in_bytes")) + runtime.capabilities.MemoryLimit = err1 == nil && err2 == nil + if !runtime.capabilities.MemoryLimit { + log.Printf("WARNING: Your kernel does not support cgroup memory limit.") + } + + _, err = ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.memsw.limit_in_bytes")) + runtime.capabilities.SwapLimit = err == nil + if !runtime.capabilities.SwapLimit { + log.Printf("WARNING: Your kernel does not support cgroup swap limit.") + } } - - _, err1 := ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "/memory.limit_in_bytes")) - _, err2 := ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.soft_limit_in_bytes")) - runtime.capabilities.MemoryLimit = err1 == nil && err2 == nil - - _, err = ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memeory.memsw.limit_in_bytes")) - runtime.capabilities.SwapLimit = err == nil - return runtime, nil } diff --git a/utils.go b/utils.go index bb891b18a..ff918d1ee 100644 --- a/utils.go +++ b/utils.go @@ -503,7 +503,6 @@ func FindCgroupMountpoint(cgroupType string) (string, error) { if len(r) == 2 { return r[1], nil } - fmt.Printf("line: %s (%d)\n", line, len(r)) } - return "", fmt.Errorf("cgroup mountpoint not found") + return "", fmt.Errorf("cgroup mountpoint not found for %s", cgroupType) } From acb546cd1bf45a248f4bb51637c795ef63feb6cb Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Mon, 22 Apr 2013 11:16:32 -0700 Subject: [PATCH 060/138] Fix race within TestRunDisconnectTty --- commands.go | 7 ++++++- commands_test.go | 17 +++++++++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/commands.go b/commands.go index 1f951ff33..b0440a976 100644 --- a/commands.go +++ b/commands.go @@ -979,8 +979,13 @@ func (srv *Server) CmdRun(stdin io.ReadCloser, stdout rcli.DockerConn, args ...s } Debugf("Waiting for attach to return\n") <-attachErr - container.Wait() // Expecting I/O pipe error, discarding + + // If we are in stdinonce mode, wait for the process to end + // otherwise, simply return + if config.StdinOnce && !config.Tty { + container.Wait() + } return nil } diff --git a/commands_test.go b/commands_test.go index 9615e877e..a64b4f4dc 100644 --- a/commands_test.go +++ b/commands_test.go @@ -228,6 +228,21 @@ func TestRunDisconnectTty(t *testing.T) { close(c1) }() + setTimeout(t, "Waiting for the container to be started timed out", 2*time.Second, func() { + for { + // Client disconnect after run -i should keep stdin out in TTY mode + l := runtime.List() + if len(l) == 1 && l[0].State.Running { + break + } + + time.Sleep(10 * time.Millisecond) + } + }) + + // Client disconnect after run -i should keep stdin out in TTY mode + container := runtime.List()[0] + setTimeout(t, "Read/Write assertion timed out", 2*time.Second, func() { if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil { t.Fatal(err) @@ -242,8 +257,6 @@ func TestRunDisconnectTty(t *testing.T) { // In tty mode, we expect the process to stay alive even after client's stdin closes. // Do not wait for run to finish - // Client disconnect after run -i should keep stdin out in TTY mode - container := runtime.List()[0] // Give some time to monitor to do his thing container.WaitTimeout(500 * time.Millisecond) if !container.State.Running { From 3514e47edc2d3cdaae2d92a78cc5c618d9549f13 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Mon, 22 Apr 2013 11:26:34 -0700 Subject: [PATCH 061/138] Do not prevent docker from running when kernel detection fails --- runtime.go | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/runtime.go b/runtime.go index b894a2cda..1dfc22d63 100644 --- a/runtime.go +++ b/runtime.go @@ -295,14 +295,13 @@ func NewRuntime() (*Runtime, error) { return nil, err } - k, err := GetKernelVersion() - if err != nil { - return nil, err - } - runtime.kernelVersion = k - - if CompareKernelVersion(k, &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0}) < 0 { - log.Printf("WARNING: You are running linux kernel version %s, which might be unstable running docker. Please upgrade your kernel to 3.8.0.", k.String()) + if k, err := GetKernelVersion(); err != nil { + log.Printf("WARNING: %s\n", err) + } else { + runtime.kernelVersion = k + if CompareKernelVersion(k, &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0}) < 0 { + log.Printf("WARNING: You are running linux kernel version %s, which might be unstable running docker. Please upgrade your kernel to 3.8.0.", k.String()) + } } if cgroupMemoryMountpoint, err := FindCgroupMountpoint("memory"); err != nil { From 4ac3b803b9b7d0db1840a12f3332034938eb9550 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Mon, 22 Apr 2013 11:39:56 -0700 Subject: [PATCH 062/138] Make the kernel version detection more generic --- utils.go | 43 ++++++++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/utils.go b/utils.go index a039ca6eb..8f9e84f2b 100644 --- a/utils.go +++ b/utils.go @@ -407,7 +407,12 @@ type KernelVersionInfo struct { // FIXME: this doens't build on Darwin func GetKernelVersion() (*KernelVersionInfo, error) { - var uts syscall.Utsname + var ( + uts syscall.Utsname + flavor string + kernel, major, minor int + err error + ) if err := syscall.Uname(&uts); err != nil { return nil, err @@ -422,31 +427,35 @@ func GetKernelVersion() (*KernelVersionInfo, error) { } tmp := strings.SplitN(string(release), "-", 2) - if len(tmp) != 2 { - return nil, fmt.Errorf("Unrecognized kernel version") - } tmp2 := strings.SplitN(tmp[0], ".", 3) - if len(tmp2) != 3 { - return nil, fmt.Errorf("Unrecognized kernel version") + + if len(tmp2) > 0 { + kernel, err = strconv.Atoi(tmp2[0]) + if err != nil { + return nil, err + } } - kernel, err := strconv.Atoi(tmp2[0]) - if err != nil { - return nil, err + if len(tmp2) > 1 { + major, err = strconv.Atoi(tmp2[1]) + if err != nil { + return nil, err + } } - major, err := strconv.Atoi(tmp2[1]) - if err != nil { - return nil, err + if len(tmp2) > 2 { + minor, err = strconv.Atoi(tmp2[2]) + if err != nil { + return nil, err + } } - minor, err := strconv.Atoi(tmp2[2]) - if err != nil { - return nil, err + if len(tmp) == 2 { + flavor = tmp[1] + } else { + flavor = "" } - flavor := tmp[1] - return &KernelVersionInfo{ Kernel: kernel, Major: major, From 16aeb77d5155cf33d1637073b9ca56728d472b49 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Mon, 22 Apr 2013 12:08:59 -0700 Subject: [PATCH 063/138] Move the kernel detection to arch specific files --- getKernelVersion_darwin.go | 5 +++ getKernelVersion_linux.go | 65 ++++++++++++++++++++++++++++++++++++++ utils.go | 58 +--------------------------------- 3 files changed, 71 insertions(+), 57 deletions(-) create mode 100644 getKernelVersion_darwin.go create mode 100644 getKernelVersion_linux.go diff --git a/getKernelVersion_darwin.go b/getKernelVersion_darwin.go new file mode 100644 index 000000000..36a959b8d --- /dev/null +++ b/getKernelVersion_darwin.go @@ -0,0 +1,5 @@ +package docker + +func getKernelVersion() (*KernelVersionInfo, error) { + return nil, fmt.Errorf("Kernel version detection is not available on darwin") +} diff --git a/getKernelVersion_linux.go b/getKernelVersion_linux.go new file mode 100644 index 000000000..bbf5a4fad --- /dev/null +++ b/getKernelVersion_linux.go @@ -0,0 +1,65 @@ +package docker + +import ( + "strconv" + "strings" + "syscall" +) + +func getKernelVersion() (*KernelVersionInfo, error) { + var ( + uts syscall.Utsname + flavor string + kernel, major, minor int + err error + ) + + if err := syscall.Uname(&uts); err != nil { + return nil, err + } + + release := make([]byte, len(uts.Release)) + + i := 0 + for _, c := range uts.Release { + release[i] = byte(c) + i++ + } + + tmp := strings.SplitN(string(release), "-", 2) + tmp2 := strings.SplitN(tmp[0], ".", 3) + + if len(tmp2) > 0 { + kernel, err = strconv.Atoi(tmp2[0]) + if err != nil { + return nil, err + } + } + + if len(tmp2) > 1 { + major, err = strconv.Atoi(tmp2[1]) + if err != nil { + return nil, err + } + } + + if len(tmp2) > 2 { + minor, err = strconv.Atoi(tmp2[2]) + if err != nil { + return nil, err + } + } + + if len(tmp) == 2 { + flavor = tmp[1] + } else { + flavor = "" + } + + return &KernelVersionInfo{ + Kernel: kernel, + Major: major, + Minor: minor, + Flavor: flavor, + }, nil +} diff --git a/utils.go b/utils.go index 8f9e84f2b..5974b7df3 100644 --- a/utils.go +++ b/utils.go @@ -14,10 +14,8 @@ import ( "path/filepath" "regexp" "runtime" - "strconv" "strings" "sync" - "syscall" "time" ) @@ -407,61 +405,7 @@ type KernelVersionInfo struct { // FIXME: this doens't build on Darwin func GetKernelVersion() (*KernelVersionInfo, error) { - var ( - uts syscall.Utsname - flavor string - kernel, major, minor int - err error - ) - - if err := syscall.Uname(&uts); err != nil { - return nil, err - } - - release := make([]byte, len(uts.Release)) - - i := 0 - for _, c := range uts.Release { - release[i] = byte(c) - i++ - } - - tmp := strings.SplitN(string(release), "-", 2) - tmp2 := strings.SplitN(tmp[0], ".", 3) - - if len(tmp2) > 0 { - kernel, err = strconv.Atoi(tmp2[0]) - if err != nil { - return nil, err - } - } - - if len(tmp2) > 1 { - major, err = strconv.Atoi(tmp2[1]) - if err != nil { - return nil, err - } - } - - if len(tmp2) > 2 { - minor, err = strconv.Atoi(tmp2[2]) - if err != nil { - return nil, err - } - } - - if len(tmp) == 2 { - flavor = tmp[1] - } else { - flavor = "" - } - - return &KernelVersionInfo{ - Kernel: kernel, - Major: major, - Minor: minor, - Flavor: flavor, - }, nil + return getKernelVersion() } func (k *KernelVersionInfo) String() string { From 6c8dcd5cbbff2b33f878a32ae3b93abc0d7b9dae Mon Sep 17 00:00:00 2001 From: Thatcher Peskens Date: Mon, 22 Apr 2013 13:10:32 -0700 Subject: [PATCH 064/138] Updated Vagrantfile and documentation to reflect new installation path using Ubuntu's PPA, also switched everything to use Ubuntu 12.04 by default. --- Vagrantfile | 10 +++---- docs/sources/installation/ubuntulinux.rst | 8 +++-- docs/sources/installation/upgrading.rst | 3 +- docs/sources/installation/vagrant.rst | 36 ++++++++--------------- 4 files changed, 25 insertions(+), 32 deletions(-) diff --git a/Vagrantfile b/Vagrantfile index f49e78156..01cfd1427 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -8,7 +8,6 @@ def v10(config) # Install ubuntu packaging dependencies and create ubuntu packages config.vm.provision :shell, :inline => "echo 'deb http://ppa.launchpad.net/dotcloud/lxc-docker/ubuntu precise main' >>/etc/apt/sources.list" config.vm.provision :shell, :inline => 'export DEBIAN_FRONTEND=noninteractive; apt-get -qq update; apt-get install -qq -y --force-yes lxc-docker' - end Vagrant::VERSION < "1.1.0" and Vagrant::Config.run do |config| @@ -45,8 +44,8 @@ Vagrant::VERSION >= "1.1.0" and Vagrant.configure("2") do |config| end config.vm.provider :virtualbox do |vb| - config.vm.box = "quantal64_3.5.0-25" - config.vm.box_url = "http://get.docker.io/vbox/ubuntu/12.10/quantal64_3.5.0-25.box" + config.vm.box = 'precise64' + config.vm.box_url = 'http://files.vagrantup.com/precise64.box' end end @@ -76,7 +75,8 @@ Vagrant::VERSION >= "1.2.0" and Vagrant.configure("2") do |config| end config.vm.provider :virtualbox do |vb| - config.vm.box = "quantal64_3.5.0-25" - config.vm.box_url = "http://get.docker.io/vbox/ubuntu/12.10/quantal64_3.5.0-25.box" + config.vm.box = 'precise64' + config.vm.box_url = 'http://files.vagrantup.com/precise64.box' end + end diff --git a/docs/sources/installation/ubuntulinux.rst b/docs/sources/installation/ubuntulinux.rst index a822242ce..94786f95d 100644 --- a/docs/sources/installation/ubuntulinux.rst +++ b/docs/sources/installation/ubuntulinux.rst @@ -1,7 +1,9 @@ Docker on Ubuntu ================ -Docker is now available as a Ubuntu PPA (Personal Package Archive), which makes installing Docker on Ubuntu super easy! +Docker is now available as a Ubuntu PPA (Personal Package Archive), +`hosted on launchpad `_ +which makes installing Docker on Ubuntu very easy. **The Requirements** @@ -17,14 +19,14 @@ Add the custom package sources to your apt sources list. Copy and paste both the >> /etc/apt/sources.list" -Update your sources. You will see a warning that GPG signatures cannot be verified +Update your sources. You will see a warning that GPG signatures cannot be verified. .. code-block:: bash sudo apt-get update -Now install it, you will see another warning that the package cannot be authenticated. +Now install it, you will see another warning that the package cannot be authenticated. Confirm install. .. code-block:: bash diff --git a/docs/sources/installation/upgrading.rst b/docs/sources/installation/upgrading.rst index 4a1de88a7..66825ac64 100644 --- a/docs/sources/installation/upgrading.rst +++ b/docs/sources/installation/upgrading.rst @@ -3,7 +3,8 @@ Upgrading ============ - We assume you are upgrading from within the operating system which runs your docker daemon. +These instructions are for upgrading your Docker binary for when you had a custom (non package manager) installation. +If you istalled docker using apt-get, use that to upgrade. Get the latest docker binary: diff --git a/docs/sources/installation/vagrant.rst b/docs/sources/installation/vagrant.rst index 5b5772142..a8249961a 100644 --- a/docs/sources/installation/vagrant.rst +++ b/docs/sources/installation/vagrant.rst @@ -4,32 +4,28 @@ Install using Vagrant ===================== - Please note this is a community contributed installation path. The only 'official' installation is using the :ref:`ubuntu_linux` installation path. This version - may be out of date because it depends on some binaries to be updated and published + Please note this is a community contributed installation path. The only 'official' installation is using the + :ref:`ubuntu_linux` installation path. This version may sometimes be out of date. **requirements** -This guide will setup a new virtual machine on your computer. This works on most operating systems, -including MacOX, Windows, Linux, FreeBSD and others. If you can -install these and have at least 400Mb RAM to spare you should be good. +This guide will setup a new virtual machine with docker installed on your computer. This works on most operating +systems, including MacOX, Windows, Linux, FreeBSD and others. If you can install these and have at least 400Mb RAM +to spare you should be good. -Install Vagrant, Virtualbox and Git ------------------------------------ - -We currently rely on some Ubuntu-linux specific packages, this will change in the future, but for now we provide a -streamlined path to install Virtualbox with a Ubuntu 12.10 image using Vagrant. +Install Vagrant and Virtualbox +------------------------------ 1. Install virtualbox from https://www.virtualbox.org/ (or use your package manager) 2. Install vagrant from http://www.vagrantup.com/ (or use your package manager) 3. Install git if you had not installed it before, check if it is installed by running ``git`` in a terminal window -We recommend having at least about 2Gb of free disk space and 2Gb RAM (or more). Spin up your machine -------------------- -1. Fetch the docker sources +1. Fetch the docker sources (this includes the instructions for machine setup). .. code-block:: bash @@ -43,21 +39,16 @@ Spin up your machine Vagrant will: -* Download the Quantal64 base ubuntu virtual machine image from get.docker.io/ +* Download the 'official' Precise64 base ubuntu virtual machine image from vagrantup.com * Boot this image in virtualbox - -Then it will use Puppet to perform an initial setup in this machine: - -* Download & untar the 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. +* Add the `Docker PPA sources `_ to /etc/apt/sources.lst +* Update your sources +* Install lxc-docker You now have a Ubuntu Virtual Machine running with docker pre-installed. To access the VM and use Docker, Run ``vagrant ssh`` from the same directory as where you ran -``vagrant up``. Vagrant will make sure to connect you to the correct VM. +``vagrant up``. Vagrant will connect you to the correct VM. .. code-block:: bash @@ -69,5 +60,4 @@ Now you are in the VM, run docker docker - Continue with the :ref:`hello_world` example. From 038e1d174bed0d73bcf4ba197f60651907aa1116 Mon Sep 17 00:00:00 2001 From: Alexey Shamrin Date: Tue, 23 Apr 2013 00:27:23 +0400 Subject: [PATCH 065/138] README.md: `docker port` instead of just `port` --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 058004c07..13ec817e2 100644 --- a/README.md +++ b/README.md @@ -125,7 +125,7 @@ Running an irc bouncer ```bash BOUNCER_ID=$(docker run -d -p 6667 -u irc shykes/znc $USER $PASSWORD) -echo "Configure your irc client to connect to port $(port $BOUNCER_ID 6667) of this machine" +echo "Configure your irc client to connect to port $(docker port $BOUNCER_ID 6667) of this machine" ``` Running Redis @@ -133,7 +133,7 @@ Running Redis ```bash REDIS_ID=$(docker run -d -p 6379 shykes/redis redis-server) -echo "Configure your redis client to connect to port $(port $REDIS_ID 6379) of this machine" +echo "Configure your redis client to connect to port $(docker port $REDIS_ID 6379) of this machine" ``` Share your own image! From 690e1186704eff65ff72404eb2a686b2b205ee7c Mon Sep 17 00:00:00 2001 From: Thatcher Peskens Date: Mon, 22 Apr 2013 13:36:00 -0700 Subject: [PATCH 066/138] Updated gettingstarted with quicker install. --- docs/sources/gettingstarted/index.html | 34 ++++++++++++++++---------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/docs/sources/gettingstarted/index.html b/docs/sources/gettingstarted/index.html index b86e9bbdd..102287907 100644 --- a/docs/sources/gettingstarted/index.html +++ b/docs/sources/gettingstarted/index.html @@ -71,34 +71,42 @@

Installing on Ubuntu

+ Requirements +
    +
  • Ubuntu 12.04 (LTS) or Ubuntu 12.10
  • +
  • 64-bit Operating system
  • +
  1. -

    Install dependencies:

    +

    Add the Ubuntu PPA (Personal Package Archive) sources to your apt sources list. Copy and + paste the following lines at once.

    -
    sudo apt-get install lxc wget bsdtar curl
    -
    sudo apt-get install linux-image-extra-`uname -r`
    +
    sudo sh -c "echo 'deb http://ppa.launchpad.net/dotcloud/lxc-docker/ubuntu precise main' >> /etc/apt/sources.list"
    -

    The linux-image-extra package is needed on standard Ubuntu EC2 AMIs in order to install the aufs kernel module.

  2. -

    Install the latest docker binary:

    +

    Update your sources. You will see a warning that GPG signatures cannot be verified.

    -
    wget http://get.docker.io/builds/$(uname -s)/$(uname -m)/docker-master.tgz
    -
    tar -xf docker-master.tgz
    +
    sudo apt-get update
  3. -

    Run your first container!

    +

    Now install it, you will see another warning that the package cannot be authenticated. Confirm install.

    -
    cd docker-master
    -
    sudo ./docker run -i -t base /bin/bash
    +
    +
    sudo apt-get install lxc-docker
    -

    Done!

    -

    Consider adding docker to your PATH for simplicity.

  4. +
  5. +

    Run!

    + +
    +
    docker
    +
    +
  6. Continue with the Hello world example.
@@ -117,7 +125,7 @@ vagrant and an Ubuntu virtual machine.

From 0b60829df795bc5178feb8f266b1f7e35a91f6da Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Mon, 22 Apr 2013 14:41:30 -0700 Subject: [PATCH 067/138] Add initial changelog --- CHANGELOG.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..66d052e6e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,32 @@ +# Changelog + +## 0.2.0 (dev) + - Fix Vagrant in windows and OSX + - Fix TTY behavior + - Fix attach/detach/run behavior + - Fix memory/fds leaks + - Fix various race conditions + - Fix `docker diff` for removed files + - Fix `docker stop` for ghost containers + - Fix lxc 0.9 compatibility + - Implement an escape sequence `C-p C-q` in order to detach containers in tty mode + - Implement `-a stdin` in order to write on container's stdin while retrieving its ID + - Implement the possiblity to choose the publicly exposed port + - Implement progress bar for registry push/pull + - Improve documentation + - Improve `docker rmi` in order to remove images by name + - Shortened containers and images IDs + - Add cgroup capabilities detection + - Automatically try to load AUFS module + - Automatically create and configure a bridge `dockbr0` + - Remove the standalone mode + +## 0.1.0 (03/23/2013) + - Open-source the project + - Implement registry in order to push/pull images + - Fix termcaps on Linux + - Add the documentation + - Add Vagrant support with Vagrantfile + - Add unit tests + - Add repository/tags to ease the image management + - Improve the layer implementation From ffe16e32243da4f02d325dae1bf08f71181ed439 Mon Sep 17 00:00:00 2001 From: Evan Wies Date: Mon, 22 Apr 2013 18:37:06 -0400 Subject: [PATCH 068/138] Fix typo (ghot -> ghost) --- container.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/container.go b/container.go index 7c6f7614f..c2c6fddd4 100644 --- a/container.go +++ b/container.go @@ -630,7 +630,7 @@ func (container *Container) Stop(seconds int) error { return nil } if container.State.Ghost { - return fmt.Errorf("Can't stop ghot container") + return fmt.Errorf("Can't stop ghost container") } // 1. Send a SIGTERM From f079fbe3fa90d58d414341bed5246539c89b61b2 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Mon, 22 Apr 2013 15:57:31 -0700 Subject: [PATCH 069/138] Check that the pid in pidfile exists before preventing docker to start --- docker/docker.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/docker/docker.go b/docker/docker.go index 411e4d0c9..f2194c06c 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -7,9 +7,11 @@ import ( "github.com/dotcloud/docker/rcli" "github.com/dotcloud/docker/term" "io" + "io/ioutil" "log" "os" "os/signal" + "strconv" "syscall" ) @@ -54,8 +56,13 @@ func main() { } func createPidFile(pidfile string) error { - if _, err := os.Stat(pidfile); err == nil { - return fmt.Errorf("pid file found, ensure docker is not running or delete %s", pidfile) + if pidString, err := ioutil.ReadFile(pidfile); err == nil { + pid, err := strconv.Atoi(string(pidString)) + if err == nil { + if _, err := os.Stat(fmt.Sprintf("/proc/%d/", pid)); err == nil { + return fmt.Errorf("pid file found, ensure docker is not running or delete %s", pidfile) + } + } } file, err := os.Create(pidfile) From 97535e5a6466dcad6ed9767960e869eadf68204f Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Mon, 22 Apr 2013 17:51:09 -0700 Subject: [PATCH 070/138] Add unit test for file deletion --- container_test.go | 76 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/container_test.go b/container_test.go index e6525f0a7..fef5331e3 100644 --- a/container_test.go +++ b/container_test.go @@ -150,6 +150,82 @@ func TestMultipleAttachRestart(t *testing.T) { } } +func TestDiff(t *testing.T) { + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) + + // Create a container and remove a file + container1, err := runtime.Create( + &Config{ + Image: GetTestImage(runtime).Id, + Cmd: []string{"/bin/rm", "/etc/passwd"}, + }, + ) + if err != nil { + t.Fatal(err) + } + defer runtime.Destroy(container1) + + if err := container1.Run(); err != nil { + t.Fatal(err) + } + + // Check the changelog + c, err := container1.Changes() + if err != nil { + t.Fatal(err) + } + success := false + for _, elem := range c { + if elem.Path == "/etc/passwd" && elem.Kind == 2 { + success = true + } + } + if !success { + t.Fatalf("/etc/passwd as been removed but is not present in the diff") + } + + // Commit the container + rwTar, err := container1.ExportRw() + if err != nil { + t.Error(err) + } + img, err := runtime.graph.Create(rwTar, container1, "unit test commited image - diff", "") + if err != nil { + t.Error(err) + } + + // Create a new container from the commited image + container2, err := runtime.Create( + &Config{ + Image: img.Id, + Cmd: []string{"cat", "/etc/passwd"}, + }, + ) + if err != nil { + t.Fatal(err) + } + defer runtime.Destroy(container2) + + if err := container2.Run(); err != nil { + t.Fatal(err) + } + + // Check the changelog + c, err = container2.Changes() + if err != nil { + t.Fatal(err) + } + for _, elem := range c { + if elem.Path == "/etc/passwd" { + t.Fatalf("/etc/passwd should not be present in the diff after commit.") + } + } +} + func TestCommitRun(t *testing.T) { runtime, err := newTestRuntime() if err != nil { From 82848d415866d08121bb52857e7d7b1d2a952c0c Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Fri, 19 Apr 2013 12:08:43 -0700 Subject: [PATCH 071/138] Allow to wait on container even after docker server restarts using lxc-info --- container.go | 34 ++++++++++++++++++++++++++++++---- runtime.go | 15 +++++++++------ 2 files changed, 39 insertions(+), 10 deletions(-) diff --git a/container.go b/container.go index c2c6fddd4..91c5806ec 100644 --- a/container.go +++ b/container.go @@ -530,16 +530,42 @@ func (container *Container) releaseNetwork() { container.NetworkSettings = &NetworkSettings{} } +// FIXME: replace this with a control socket within docker-init +func (container *Container) waitLxc() error { + for { + if output, err := exec.Command("lxc-info", "-n", container.Id).CombinedOutput(); err != nil { + return err + } else { + if !strings.Contains(string(output), "RUNNING") { + return nil + } + } + time.Sleep(500 * time.Millisecond) + } + return nil +} + func (container *Container) monitor() { // Wait for the program to exit Debugf("Waiting for process") - if err := container.cmd.Wait(); err != nil { - // Discard the error as any signals or non 0 returns will generate an error - Debugf("%s: Process: %s", container.Id, err) + + // If the command does not exists, try to wait via lxc + if container.cmd == nil { + if err := container.waitLxc(); err != nil { + Debugf("%s: Process: %s", container.Id, err) + } + } else { + if err := container.cmd.Wait(); err != nil { + // Discard the error as any signals or non 0 returns will generate an error + Debugf("%s: Process: %s", container.Id, err) + } } Debugf("Process finished") - exitCode := container.cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus() + var exitCode int = -1 + if container.cmd != nil { + exitCode = container.cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus() + } // Cleanup container.releaseNetwork() diff --git a/runtime.go b/runtime.go index b894a2cda..1b4fceced 100644 --- a/runtime.go +++ b/runtime.go @@ -184,12 +184,6 @@ func (runtime *Runtime) Register(container *Container) error { } } - // If the container is not running or just has been flagged not running - // then close the wait lock chan (will be reset upon start) - if !container.State.Running { - close(container.waitLock) - } - // Even if not running, we init the lock (prevents races in start/stop/kill) container.State.initLock() @@ -207,6 +201,15 @@ func (runtime *Runtime) Register(container *Container) error { // done runtime.containers.PushBack(container) runtime.idIndex.Add(container.Id) + + // If the container is not running or just has been flagged not running + // then close the wait lock chan (will be reset upon start) + if !container.State.Running { + close(container.waitLock) + } else { + container.allocateNetwork() + go container.monitor() + } return nil } From d440782e17d384d52c813f93cd53d2d8a15fd13a Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Fri, 19 Apr 2013 12:12:30 -0700 Subject: [PATCH 072/138] Allow to kill container after docker server restarts --- container.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/container.go b/container.go index 91c5806ec..1f60e08a9 100644 --- a/container.go +++ b/container.go @@ -614,7 +614,7 @@ func (container *Container) monitor() { } func (container *Container) kill() error { - if !container.State.Running || container.cmd == nil { + if !container.State.Running { return nil } @@ -626,6 +626,9 @@ func (container *Container) kill() error { // 2. Wait for the process to die, in last resort, try to kill the process directly if err := container.WaitTimeout(10 * time.Second); err != nil { + if container.cmd == nil { + return fmt.Errorf("lxc-kill failed, impossible to kill the container %s", container.Id) + } log.Printf("Container %s failed to exit within 10 seconds of lxc SIGKILL - trying direct SIGKILL", container.Id) if err := container.cmd.Process.Kill(); err != nil { return err From f926ed182f908aba20980202becfcdbe2d1e9c34 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Fri, 19 Apr 2013 12:21:39 -0700 Subject: [PATCH 073/138] Allow to kill/stop ghosts --- container.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/container.go b/container.go index 1f60e08a9..bac0951da 100644 --- a/container.go +++ b/container.go @@ -646,9 +646,6 @@ func (container *Container) Kill() error { if !container.State.Running { return nil } - if container.State.Ghost { - return fmt.Errorf("Can't kill ghost container") - } return container.kill() } @@ -658,9 +655,6 @@ func (container *Container) Stop(seconds int) error { if !container.State.Running { return nil } - if container.State.Ghost { - return fmt.Errorf("Can't stop ghost container") - } // 1. Send a SIGTERM if output, err := exec.Command("lxc-kill", "-n", container.Id, "15").CombinedOutput(); err != nil { From b76d63cb0c97aacec6d81c07108d2a573fb8af05 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Fri, 19 Apr 2013 14:18:03 -0700 Subject: [PATCH 074/138] Forbid attach to ghost --- commands.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/commands.go b/commands.go index b0440a976..de814d664 100644 --- a/commands.go +++ b/commands.go @@ -836,6 +836,10 @@ func (srv *Server) CmdAttach(stdin io.ReadCloser, stdout rcli.DockerConn, args . return fmt.Errorf("No such container: %s", name) } + if container.State.Ghost { + return fmt.Errorf("Impossible to attach to a ghost container") + } + if container.Config.Tty { stdout.SetOptionRawTerminal() } From c05c91ca3bb69d2c9faa0a2b15bc23b7c2a7bd8a Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Mon, 22 Apr 2013 13:19:50 -0700 Subject: [PATCH 075/138] Make kernel detection work without suffix --- getKernelVersion_darwin.go | 4 ++++ getKernelVersion_linux.go | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/getKernelVersion_darwin.go b/getKernelVersion_darwin.go index 36a959b8d..be3b733b6 100644 --- a/getKernelVersion_darwin.go +++ b/getKernelVersion_darwin.go @@ -1,5 +1,9 @@ package docker +import ( + "fmt" +) + func getKernelVersion() (*KernelVersionInfo, error) { return nil, fmt.Errorf("Kernel version detection is not available on darwin") } diff --git a/getKernelVersion_linux.go b/getKernelVersion_linux.go index bbf5a4fad..04bb1edcb 100644 --- a/getKernelVersion_linux.go +++ b/getKernelVersion_linux.go @@ -1,6 +1,7 @@ package docker import ( + "bytes" "strconv" "strings" "syscall" @@ -26,6 +27,9 @@ func getKernelVersion() (*KernelVersionInfo, error) { i++ } + // Remove the \x00 from the release for Atoi to parse correctly + release = release[:bytes.IndexByte(release, 0)] + tmp := strings.SplitN(string(release), "-", 2) tmp2 := strings.SplitN(tmp[0], ".", 3) From 4031a01af1658225344af04be0dbd4225893c242 Mon Sep 17 00:00:00 2001 From: Thatcher Peskens Date: Mon, 22 Apr 2013 18:38:42 -0700 Subject: [PATCH 076/138] Merged changes --- .mailmap | 3 + AUTHORS | 7 + Makefile | 5 +- README.md | 181 +++++++------- SPECS/data-volumes.md | 71 ++++++ archive.go | 36 +++ buildbot/README.rst | 20 ++ buildbot/Vagrantfile | 28 +++ buildbot/buildbot-cfg/buildbot-cfg.sh | 43 ++++ buildbot/buildbot-cfg/buildbot.conf | 18 ++ buildbot/buildbot-cfg/master.cfg | 46 ++++ buildbot/buildbot-cfg/post-commit | 21 ++ buildbot/buildbot.pp | 32 +++ buildbot/requirements.txt | 6 + commands.go | 118 ++++++--- commands_test.go | 41 +++- container.go | 116 ++++++--- container_test.go | 60 ++++- contrib/crashTest.go | 96 ++++++++ contrib/docker-build/README | 68 ++++++ contrib/docker-build/docker-build | 104 ++++++++ contrib/docker-build/example.changefile | 11 + contrib/install.sh | 2 +- contrib/vagrant-docker/README.md | 3 + deb/Makefile | 1 - deb/Makefile.deb | 73 ------ deb/README.md | 1 - deb/debian/changelog | 5 - deb/debian/control | 20 -- deb/debian/copyright | 209 ---------------- deb/etc/docker-dev.upstart | 10 - docker/docker.go | 58 ++++- docs/sources/examples/running_examples.rst | 21 +- docs/sources/installation/amazon.rst | 7 +- docs/sources/installation/archlinux.rst | 64 +++++ docs/sources/installation/index.rst | 1 + docs/sources/installation/ubuntulinux.rst | 17 +- docs/sources/installation/vagrant.rst | 8 +- graph.go | 32 ++- graph_test.go | 39 ++- hack/README.md | 1 + hack/fmt-check.hook | 46 ++++ image.go | 26 +- lxc_template.go | 2 +- network.go | 59 ++++- packaging/README.rst | 8 + packaging/archlinux/README.archlinux | 25 ++ packaging/debian/Makefile | 35 +++ packaging/debian/README.debian | 31 +++ packaging/debian/Vagrantfile | 22 ++ packaging/debian/changelog | 14 ++ packaging/debian/compat | 1 + packaging/debian/control | 19 ++ packaging/debian/copyright | 237 +++++++++++++++++++ packaging/debian/docker.initd | 49 ++++ {deb => packaging}/debian/docs | 0 packaging/debian/lxc-docker.postinst | 13 + packaging/debian/maintainer.rst | 16 ++ packaging/debian/rules | 13 + {deb => packaging}/debian/source/format | 0 packaging/ubuntu/Makefile | 62 +++++ packaging/ubuntu/README.ubuntu | 37 +++ packaging/ubuntu/Vagrantfile | 12 + packaging/ubuntu/changelog | 30 +++ {deb/debian => packaging/ubuntu}/compat | 0 packaging/ubuntu/control | 19 ++ packaging/ubuntu/copyright | 237 +++++++++++++++++++ {deb/etc => packaging/ubuntu}/docker.upstart | 4 +- packaging/ubuntu/docs | 1 + packaging/ubuntu/lxc-docker.postinst | 4 + packaging/ubuntu/lxc-docker.prerm | 4 + packaging/ubuntu/maintainer.ubuntu | 35 +++ {deb/debian => packaging/ubuntu}/rules | 0 packaging/ubuntu/source/format | 1 + rcli/tcp.go | 6 +- registry.go | 43 ++-- runtime.go | 81 ++++++- runtime_test.go | 57 ++++- state.go | 4 + sysinit.go | 11 +- utils.go | 134 ++++++++++- utils_test.go | 33 +++ 82 files changed, 2525 insertions(+), 609 deletions(-) create mode 100644 SPECS/data-volumes.md create mode 100644 buildbot/README.rst create mode 100644 buildbot/Vagrantfile create mode 100755 buildbot/buildbot-cfg/buildbot-cfg.sh create mode 100644 buildbot/buildbot-cfg/buildbot.conf create mode 100644 buildbot/buildbot-cfg/master.cfg create mode 100755 buildbot/buildbot-cfg/post-commit create mode 100644 buildbot/buildbot.pp create mode 100644 buildbot/requirements.txt create mode 100644 contrib/crashTest.go create mode 100644 contrib/docker-build/README create mode 100755 contrib/docker-build/docker-build create mode 100644 contrib/docker-build/example.changefile create mode 100644 contrib/vagrant-docker/README.md delete mode 120000 deb/Makefile delete mode 100644 deb/Makefile.deb delete mode 120000 deb/README.md delete mode 100644 deb/debian/changelog delete mode 100644 deb/debian/control delete mode 100644 deb/debian/copyright delete mode 100644 deb/etc/docker-dev.upstart create mode 100644 docs/sources/installation/archlinux.rst create mode 100644 hack/README.md create mode 100644 hack/fmt-check.hook create mode 100644 packaging/README.rst create mode 100644 packaging/archlinux/README.archlinux create mode 100644 packaging/debian/Makefile create mode 100644 packaging/debian/README.debian create mode 100644 packaging/debian/Vagrantfile create mode 100644 packaging/debian/changelog create mode 100644 packaging/debian/compat create mode 100644 packaging/debian/control create mode 100644 packaging/debian/copyright create mode 100644 packaging/debian/docker.initd rename {deb => packaging}/debian/docs (100%) create mode 100644 packaging/debian/lxc-docker.postinst create mode 100644 packaging/debian/maintainer.rst create mode 100755 packaging/debian/rules rename {deb => packaging}/debian/source/format (100%) create mode 100644 packaging/ubuntu/Makefile create mode 100644 packaging/ubuntu/README.ubuntu create mode 100644 packaging/ubuntu/Vagrantfile create mode 100644 packaging/ubuntu/changelog rename {deb/debian => packaging/ubuntu}/compat (100%) create mode 100644 packaging/ubuntu/control create mode 100644 packaging/ubuntu/copyright rename {deb/etc => packaging/ubuntu}/docker.upstart (50%) create mode 100644 packaging/ubuntu/docs create mode 100644 packaging/ubuntu/lxc-docker.postinst create mode 100644 packaging/ubuntu/lxc-docker.prerm create mode 100644 packaging/ubuntu/maintainer.ubuntu rename {deb/debian => packaging/ubuntu}/rules (100%) create mode 100644 packaging/ubuntu/source/format diff --git a/.mailmap b/.mailmap index 2570683f8..83c18fa29 100644 --- a/.mailmap +++ b/.mailmap @@ -14,3 +14,6 @@ Joffrey F Tim Terhorst Andy Smith + + + diff --git a/AUTHORS b/AUTHORS index fefd74842..e8979aac6 100644 --- a/AUTHORS +++ b/AUTHORS @@ -10,6 +10,8 @@ Daniel Robinson Dominik Honnef Don Spaulding ezbercih +Flavio Castelli +Francisco Souza Frederick F. Kautz IV Guillaume J. Charmes Hunter Blanks @@ -23,10 +25,13 @@ Jérôme Petazzoni Ken Cochrane Kevin J. Lynagh Louis Opter +Maxim Treskin Mikhail Sobolev Nelson Chen Niall O'Higgins +Paul Hammond Piotr Bogdan +Robert Obryk Sam Alba Shawn Siefkas Silas Sewell @@ -35,4 +40,6 @@ Sridhar Ratnakumar Thatcher Peskens Tim Terhorst Troy Howard +unclejack +Victor Vieux Vivek Agarwal diff --git a/Makefile b/Makefile index a6eb61383..c3e2f7820 100644 --- a/Makefile +++ b/Makefile @@ -23,7 +23,7 @@ DOCKER_MAIN := $(DOCKER_DIR)/docker DOCKER_BIN_RELATIVE := bin/docker DOCKER_BIN := $(CURDIR)/$(DOCKER_BIN_RELATIVE) -.PHONY: all clean test +.PHONY: all clean test hack all: $(DOCKER_BIN) @@ -49,3 +49,6 @@ test: all fmt: @gofmt -s -l -w . + +hack: + cd $(CURDIR)/buildbot && vagrant up diff --git a/README.md b/README.md index c186d9a06..13ec817e2 100644 --- a/README.md +++ b/README.md @@ -33,123 +33,85 @@ Notable features * Interactive shell: docker can allocate a pseudo-tty and attach to the standard input of any container, for example to run a throwaway interactive shell. - - -Under the hood --------------- - -Under the hood, Docker is built on the following components: - - -* The [cgroup](http://blog.dotcloud.com/kernel-secrets-from-the-paas-garage-part-24-c) and [namespacing](http://blog.dotcloud.com/under-the-hood-linux-kernels-on-dotcloud-part) capabilities of the Linux kernel; - -* [AUFS](http://aufs.sourceforge.net/aufs.html), a powerful union filesystem with copy-on-write capabilities; - -* The [Go](http://golang.org) programming language; - -* [lxc](http://lxc.sourceforge.net/), a set of convenience scripts to simplify the creation of linux containers. - - Install instructions ================== -Building from source --------------------- - -1. Make sure you have a [Go language](http://golang.org) compiler. - - On a Debian/wheezy or Ubuntu 12.10 install the package: - - ```bash - - $ sudo apt-get install golang-go - ``` - -2. Execute ``make`` - - This command will install all necessary dependencies and build the - executable that you can find in ``bin/docker`` - -3. Should you like to see what's happening, run ``make`` with ``VERBOSE=1`` parameter: - - ```bash - - $ make VERBOSE=1 - ``` - -Installing on Ubuntu 12.04 and 12.10 ------------------------------------- - -1. Install dependencies: - - ```bash - sudo apt-get install lxc wget bsdtar curl - sudo apt-get install linux-image-extra-`uname -r` - ``` - - The `linux-image-extra` package is needed on standard Ubuntu EC2 AMIs in order to install the aufs kernel module. - -2. Install the latest docker binary: - - ```bash - wget http://get.docker.io/builds/$(uname -s)/$(uname -m)/docker-master.tgz - tar -xf docker-master.tgz - ``` - -3. Run your first container! - - ```bash - cd docker-master - sudo ./docker pull base - sudo ./docker run -i -t base /bin/bash - ``` - - Consider adding docker to your `PATH` for simplicity. - -Installing on other Linux distributions +Quick install on Ubuntu 12.04 and 12.10 --------------------------------------- -Right now, the officially supported distributions are: +```bash +curl get.docker.io | sh -x +``` -* Ubuntu 12.04 (precise LTS) -* Ubuntu 12.10 (quantal) +Binary installs +---------------- -Docker probably works on other distributions featuring a recent kernel, the AUFS patch, and up-to-date lxc. However this has not been tested. +Docker supports the following binary installation methods. +Note that some methods are community contributions and not yet officially supported. -Some streamlined (but possibly outdated) installation paths' are available from the website: http://docker.io/documentation/ +* [Ubuntu 12.04 and 12.10 (officially supported)](http://docs.docker.io/en/latest/installation/ubuntulinux/) +* [Arch Linux](http://docs.docker.io/en/latest/installation/archlinux/) +* [MacOS X (with Vagrant)](http://docs.docker.io/en/latest/installation/macos/) +* [Windows (with Vagrant)](http://docs.docker.io/en/latest/installation/windows/) +* [Amazon EC2 (with Vagrant)](http://docs.docker.io/en/latest/installation/amazon/) +Installing from source +---------------------- + +1. Make sure you have a [Go language](http://golang.org/doc/install) compiler and [git](http://git-scm.com) installed. + +2. Checkout the source code + + ```bash + git clone http://github.com/dotcloud/docker + ``` + +3. Build the docker binary + + ```bash + cd docker + make VERBOSE=1 + sudo cp ./bin/docker /usr/local/bin/docker + ``` Usage examples ============== -Running an interactive shell ----------------------------- +First run the docker daemon +--------------------------- + +All the examples assume your machine is running the docker daemon. To run the docker daemon in the background, simply type: ```bash -# Download a base image -docker pull base - -# Run an interactive shell in the base image, -# allocate a tty, attach stdin and stdout -docker run -i -t base /bin/bash +# On a production system you want this running in an init script +sudo docker -d & ``` -Detaching from the interactive shell ------------------------------------- +Now you can run docker in client mode: all commands will be forwarded to the docker daemon, so the client can run from any account. + +```bash +# Now you can run docker commands from any account. +docker help ``` -# In order to detach without killing the shell, you can use the escape sequence Ctrl-p + Ctrl-q -# Note: this works only in tty mode (run with -t option). + + +Throwaway shell in a base ubuntu image +-------------------------------------- + +```bash +docker pull ubuntu:12.10 + +# Run an interactive shell, allocate a tty, attach stdin and stdout +# To detach the tty without exiting the shell, use the escape sequence Ctrl-p + Ctrl-q +docker run -i -t ubuntu:12.10 /bin/bash ``` Starting a long-running worker process -------------------------------------- ```bash -# Run docker in daemon mode -(docker -d || echo "Docker daemon already running") & - # Start a very useful long-running process -JOB=$(docker run -d base /bin/sh -c "while true; do echo Hello world; sleep 1; done") +JOB=$(docker run -d ubuntu /bin/sh -c "while true; do echo Hello world; sleep 1; done") # Collect the output of the job so far docker logs $JOB @@ -158,25 +120,32 @@ docker logs $JOB docker kill $JOB ``` - -Listing all running containers ------------------------------- +Running an irc bouncer +---------------------- ```bash -docker ps +BOUNCER_ID=$(docker run -d -p 6667 -u irc shykes/znc $USER $PASSWORD) +echo "Configure your irc client to connect to port $(docker port $BOUNCER_ID 6667) of this machine" ``` +Running Redis +------------- + +```bash +REDIS_ID=$(docker run -d -p 6379 shykes/redis redis-server) +echo "Configure your redis client to connect to port $(docker port $REDIS_ID 6379) of this machine" +``` Share your own image! --------------------- ```bash -docker pull base -CONTAINER=$(docker run -d base apt-get install -y curl) +CONTAINER=$(docker run -d ubuntu:12.10 apt-get install -y curl) docker commit -m "Installed curl" $CONTAINER $USER/betterbase docker push $USER/betterbase ``` +A list of publicly available images is [available here](https://github.com/dotcloud/docker/wiki/Public-docker-images). Expose a service on a TCP port ------------------------------ @@ -197,6 +166,22 @@ echo hello world | nc $IP $PORT echo "Daemon received: $(docker logs $JOB)" ``` +Under the hood +-------------- + +Under the hood, Docker is built on the following components: + + +* The [cgroup](http://blog.dotcloud.com/kernel-secrets-from-the-paas-garage-part-24-c) and [namespacing](http://blog.dotcloud.com/under-the-hood-linux-kernels-on-dotcloud-part) capabilities of the Linux kernel; + +* [AUFS](http://aufs.sourceforge.net/aufs.html), a powerful union filesystem with copy-on-write capabilities; + +* The [Go](http://golang.org) programming language; + +* [lxc](http://lxc.sourceforge.net/), a set of convenience scripts to simplify the creation of linux containers. + + + Contributing to Docker ====================== diff --git a/SPECS/data-volumes.md b/SPECS/data-volumes.md new file mode 100644 index 000000000..d800656af --- /dev/null +++ b/SPECS/data-volumes.md @@ -0,0 +1,71 @@ + +## Spec for data volumes + +Spec owner: Solomon Hykes + +Data volumes (issue #111) are a much-requested feature which trigger much discussion and debate. Below is the current authoritative spec for implementing data volumes. +This spec will be deprecated once the feature is fully implemented. + +Discussion, requests, trolls, demands, offerings, threats and other forms of supplications concerning this spec should be addressed to Solomon here: https://github.com/dotcloud/docker/issues/111 + + +### 1. Creating data volumes + +At container creation, parts of a container's filesystem can be mounted as separate data volumes. Volumes are defined with the -v flag. + +For example: + +```bash +$ docker run -v /var/lib/postgres -v /var/log postgres /usr/bin/postgres +``` + +In this example, a new container is created from the 'postgres' image. At the same time, docker creates 2 new data volumes: one will be mapped to the container at /var/lib/postgres, the other at /var/log. + +2 important notes: + +1) Volumes don't have top-level names. At no point does the user provide a name, or is a name given to him. Volumes are identified by the path at which they are mounted inside their container. + +2) The user doesn't choose the source of the volume. Docker only mounts volumes it created itself, in the same way that it only runs containers that it created itself. That is by design. + + +### 2. Sharing data volumes + +Instead of creating its own volumes, a container can share another container's volumes. For example: + +```bash +$ docker run --volumes-from $OTHER_CONTAINER_ID postgres /usr/local/bin/postgres-backup +``` + +In this example, a new container is created from the 'postgres' example. At the same time, docker will *re-use* the 2 data volumes created in the previous example. One volume will be mounted on the /var/lib/postgres of *both* containers, and the other will be mounted on the /var/log of both containers. + +### 3. Under the hood + +Docker stores volumes in /var/lib/docker/volumes. Each volume receives a globally unique ID at creation, and is stored at /var/lib/docker/volumes/ID. + +At creation, volumes are attached to a single container - the source of truth for this mapping will be the container's configuration. + +Mounting a volume consists of calling "mount --bind" from the volume's directory to the appropriate sub-directory of the container mountpoint. This may be done by Docker itself, or farmed out to lxc (which supports mount-binding) if possible. + + +### 4. Backups, transfers and other volume operations + +Volumes sometimes need to be backed up, transfered between hosts, synchronized, etc. These operations typically are application-specific or site-specific, eg. rsync vs. S3 upload vs. replication vs... + +Rather than attempting to implement all these scenarios directly, Docker will allow for custom implementations using an extension mechanism. + +### 5. Custom volume handlers + +Docker allows for arbitrary code to be executed against a container's volumes, to implement any custom action: backup, transfer, synchronization across hosts, etc. + +Here's an example: + +```bash +$ DB=$(docker run -d -v /var/lib/postgres -v /var/log postgres /usr/bin/postgres) + +$ BACKUP_JOB=$(docker run -d --volumes-from $DB shykes/backuper /usr/local/bin/backup-postgres --s3creds=$S3CREDS) + +$ docker wait $BACKUP_JOB +``` + +Congratulations, you just implemented a custom volume handler, using Docker's built-in ability to 1) execute arbitrary code and 2) share volumes between containers. + diff --git a/archive.go b/archive.go index d09d3d6b9..8a011eb6e 100644 --- a/archive.go +++ b/archive.go @@ -4,6 +4,7 @@ import ( "errors" "io" "io/ioutil" + "os" "os/exec" ) @@ -86,3 +87,38 @@ func CmdStream(cmd *exec.Cmd) (io.Reader, error) { } return pipeR, nil } + +// NewTempArchive reads the content of src into a temporary file, and returns the contents +// of that file as an archive. The archive can only be read once - as soon as reading completes, +// the file will be deleted. +func NewTempArchive(src Archive, dir string) (*TempArchive, error) { + f, err := ioutil.TempFile(dir, "") + if err != nil { + return nil, err + } + if _, err := io.Copy(f, src); err != nil { + return nil, err + } + if _, err := f.Seek(0, 0); err != nil { + return nil, err + } + st, err := f.Stat() + if err != nil { + return nil, err + } + size := st.Size() + return &TempArchive{f, size}, nil +} + +type TempArchive struct { + *os.File + Size int64 // Pre-computed from Stat().Size() as a convenience +} + +func (archive *TempArchive) Read(data []byte) (int, error) { + n, err := archive.File.Read(data) + if err != nil { + os.Remove(archive.File.Name()) + } + return n, err +} diff --git a/buildbot/README.rst b/buildbot/README.rst new file mode 100644 index 000000000..a52b9769e --- /dev/null +++ b/buildbot/README.rst @@ -0,0 +1,20 @@ +Buildbot +======== + +Buildbot is a continuous integration system designed to automate the +build/test cycle. By automatically rebuilding and testing the tree each time +something has changed, build problems are pinpointed quickly, before other +developers are inconvenienced by the failure. + +When running 'make hack' at the docker root directory, it spawns a virtual +machine in the background running a buildbot instance and adds a git +post-commit hook that automatically run docker tests for you. + +You can check your buildbot instance at http://192.168.33.21:8010/waterfall + + +Buildbot dependencies +--------------------- + +vagrant, virtualbox packages and python package requests + diff --git a/buildbot/Vagrantfile b/buildbot/Vagrantfile new file mode 100644 index 000000000..ea027f066 --- /dev/null +++ b/buildbot/Vagrantfile @@ -0,0 +1,28 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +$BUILDBOT_IP = '192.168.33.21' + +def v10(config) + config.vm.box = "quantal64_3.5.0-25" + config.vm.box_url = "http://get.docker.io/vbox/ubuntu/12.10/quantal64_3.5.0-25.box" + config.vm.share_folder 'v-data', '/data/docker', File.dirname(__FILE__) + '/..' + config.vm.network :hostonly, $BUILDBOT_IP + + # Ensure puppet is installed on the instance + config.vm.provision :shell, :inline => 'apt-get -qq update; apt-get install -y puppet' + + config.vm.provision :puppet do |puppet| + puppet.manifests_path = '.' + puppet.manifest_file = 'buildbot.pp' + puppet.options = ['--templatedir','.'] + end +end + +Vagrant::VERSION < '1.1.0' and Vagrant::Config.run do |config| + v10(config) +end + +Vagrant::VERSION >= '1.1.0' and Vagrant.configure('1') do |config| + v10(config) +end diff --git a/buildbot/buildbot-cfg/buildbot-cfg.sh b/buildbot/buildbot-cfg/buildbot-cfg.sh new file mode 100755 index 000000000..5e4e7432f --- /dev/null +++ b/buildbot/buildbot-cfg/buildbot-cfg.sh @@ -0,0 +1,43 @@ +#!/bin/bash + +# Auto setup of buildbot configuration. Package installation is being done +# on buildbot.pp +# Dependencies: buildbot, buildbot-slave, supervisor + +SLAVE_NAME='buildworker' +SLAVE_SOCKET='localhost:9989' +BUILDBOT_PWD='pass-docker' +USER='vagrant' +ROOT_PATH='/data/buildbot' +DOCKER_PATH='/data/docker' +BUILDBOT_CFG="$DOCKER_PATH/buildbot/buildbot-cfg" +IP=$(grep BUILDBOT_IP /data/docker/buildbot/Vagrantfile | awk -F "'" '{ print $2; }') + +function run { su $USER -c "$1"; } + +export PATH=/bin:sbin:/usr/bin:/usr/sbin:/usr/local/bin + +# Exit if buildbot has already been installed +[ -d "$ROOT_PATH" ] && exit 0 + +# Setup buildbot +run "mkdir -p ${ROOT_PATH}" +cd ${ROOT_PATH} +run "buildbot create-master master" +run "cp $BUILDBOT_CFG/master.cfg master" +run "sed -i 's/localhost/$IP/' master/master.cfg" +run "buildslave create-slave slave $SLAVE_SOCKET $SLAVE_NAME $BUILDBOT_PWD" + +# Allow buildbot subprocesses (docker tests) to properly run in containers, +# in particular with docker -u +run "sed -i 's/^umask = None/umask = 000/' ${ROOT_PATH}/slave/buildbot.tac" + +# Setup supervisor +cp $BUILDBOT_CFG/buildbot.conf /etc/supervisor/conf.d/buildbot.conf +sed -i "s/^chmod=0700.*0700./chmod=0770\nchown=root:$USER/" /etc/supervisor/supervisord.conf +kill -HUP `pgrep -f "/usr/bin/python /usr/bin/supervisord"` + +# Add git hook +cp $BUILDBOT_CFG/post-commit $DOCKER_PATH/.git/hooks +sed -i "s/localhost/$IP/" $DOCKER_PATH/.git/hooks/post-commit + diff --git a/buildbot/buildbot-cfg/buildbot.conf b/buildbot/buildbot-cfg/buildbot.conf new file mode 100644 index 000000000..b162f4e7c --- /dev/null +++ b/buildbot/buildbot-cfg/buildbot.conf @@ -0,0 +1,18 @@ +[program:buildmaster] +command=su vagrant -c "buildbot start master" +directory=/data/buildbot +chown= root:root +redirect_stderr=true +stdout_logfile=/var/log/supervisor/buildbot-master.log +stderr_logfile=/var/log/supervisor/buildbot-master.log + +[program:buildworker] +command=buildslave start slave +directory=/data/buildbot +chown= root:root +redirect_stderr=true +stdout_logfile=/var/log/supervisor/buildbot-slave.log +stderr_logfile=/var/log/supervisor/buildbot-slave.log + +[group:buildbot] +programs=buildmaster,buildworker diff --git a/buildbot/buildbot-cfg/master.cfg b/buildbot/buildbot-cfg/master.cfg new file mode 100644 index 000000000..c786e418e --- /dev/null +++ b/buildbot/buildbot-cfg/master.cfg @@ -0,0 +1,46 @@ +import os +from buildbot.buildslave import BuildSlave +from buildbot.schedulers.forcesched import ForceScheduler +from buildbot.config import BuilderConfig +from buildbot.process.factory import BuildFactory +from buildbot.steps.shell import ShellCommand +from buildbot.status import html +from buildbot.status.web import authz, auth + +PORT_WEB = 8010 # Buildbot webserver port +PORT_MASTER = 9989 # Port where buildbot master listen buildworkers +TEST_USER = 'buildbot' # Credential to authenticate build triggers +TEST_PWD = 'docker' # Credential to authenticate build triggers +BUILDER_NAME = 'docker' +BUILDPASSWORD = 'pass-docker' # Credential to authenticate buildworkers +DOCKER_PATH = '/data/docker' + + +c = BuildmasterConfig = {} + +c['title'] = "Docker" +c['titleURL'] = "waterfall" +c['buildbotURL'] = "http://localhost:{0}/".format(PORT_WEB) +c['db'] = {'db_url':"sqlite:///state.sqlite"} +c['slaves'] = [BuildSlave('buildworker', BUILDPASSWORD)] +c['slavePortnum'] = PORT_MASTER + +c['schedulers'] = [ForceScheduler(name='trigger',builderNames=[BUILDER_NAME])] + +# Docker test command +test_cmd = """( + cd {0}/..; rm -rf docker-tmp; git clone docker docker-tmp; + cd docker-tmp; make test; exit_status=$?; + cd ..; rm -rf docker-tmp; exit $exit_status)""".format(DOCKER_PATH) + +# Builder +factory = BuildFactory() +factory.addStep(ShellCommand(description='Docker',logEnviron=False, + usePTY=True,command=test_cmd)) +c['builders'] = [BuilderConfig(name=BUILDER_NAME,slavenames=['buildworker'], + factory=factory)] + +# Status +authz_cfg=authz.Authz(auth=auth.BasicAuth([(TEST_USER,TEST_PWD)]), + forceBuild='auth') +c['status'] = [html.WebStatus(http_port=PORT_WEB, authz=authz_cfg)] diff --git a/buildbot/buildbot-cfg/post-commit b/buildbot/buildbot-cfg/post-commit new file mode 100755 index 000000000..0173fe504 --- /dev/null +++ b/buildbot/buildbot-cfg/post-commit @@ -0,0 +1,21 @@ +#!/usr/bin/env python + +'''Trigger buildbot docker test build + + post-commit git hook designed to automatically trigger buildbot on + the provided vagrant docker VM.''' + +import requests + +USERNAME = 'buildbot' +PASSWORD = 'docker' +BASE_URL = 'http://localhost:8010' +path = lambda s: BASE_URL + '/' + s + +try: + session = requests.session() + session.post(path('login'),data={'username':USERNAME,'passwd':PASSWORD}) + session.post(path('builders/docker/force'), + data={'forcescheduler':'trigger','reason':'Test commit'}) +except: + pass diff --git a/buildbot/buildbot.pp b/buildbot/buildbot.pp new file mode 100644 index 000000000..8109cdc2a --- /dev/null +++ b/buildbot/buildbot.pp @@ -0,0 +1,32 @@ +node default { + $USER = 'vagrant' + $ROOT_PATH = '/data/buildbot' + $DOCKER_PATH = '/data/docker' + + exec {'apt_update': command => '/usr/bin/apt-get update' } + Package { require => Exec['apt_update'] } + group {'puppet': ensure => 'present'} + + # Install dependencies + Package { ensure => 'installed' } + package { ['python-dev','python-pip','supervisor','lxc','bsdtar','git','golang']: } + + file{[ '/data' ]: + owner => $USER, group => $USER, ensure => 'directory' } + + file {'/var/tmp/requirements.txt': + content => template('requirements.txt') } + + exec {'requirements': + require => [ Package['python-dev'], Package['python-pip'], + File['/var/tmp/requirements.txt'] ], + cwd => '/var/tmp', + command => "/bin/sh -c '(/usr/bin/pip install -r requirements.txt; + rm /var/tmp/requirements.txt)'" } + + exec {'buildbot-cfg-sh': + require => [ Package['supervisor'], Exec['requirements']], + path => '/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin', + cwd => '/data', + command => "$DOCKER_PATH/buildbot/buildbot-cfg/buildbot-cfg.sh" } +} diff --git a/buildbot/requirements.txt b/buildbot/requirements.txt new file mode 100644 index 000000000..0e451b017 --- /dev/null +++ b/buildbot/requirements.txt @@ -0,0 +1,6 @@ +sqlalchemy<=0.7.9 +sqlalchemy-migrate>=0.7.2 +buildbot==0.8.7p1 +buildbot_slave==0.8.7p1 +nose==1.2.1 +requests==1.1.0 diff --git a/commands.go b/commands.go index 20d6b45c9..b0440a976 100644 --- a/commands.go +++ b/commands.go @@ -18,9 +18,11 @@ import ( "unicode" ) -const VERSION = "0.1.4" +const VERSION = "0.1.7" -var GIT_COMMIT string +var ( + GIT_COMMIT string +) func (srv *Server) Name() string { return "docker" @@ -79,7 +81,7 @@ func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout rcli.DockerConn, args .. n, err := stdin.Read(char) if n > 0 { if char[0] == '\r' || char[0] == '\n' { - stdout.Write([]byte{'\n'}) + stdout.Write([]byte{'\r', '\n'}) break } else if char[0] == 127 || char[0] == '\b' { if i > 0 { @@ -99,7 +101,7 @@ func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout rcli.DockerConn, args .. } if err != nil { if err != io.EOF { - fmt.Fprintf(stdout, "Read error: %v\n", err) + fmt.Fprintf(stdout, "Read error: %v\r\n", err) } break } @@ -149,7 +151,7 @@ func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout rcli.DockerConn, args .. newAuthConfig := auth.NewAuthConfig(username, password, email, srv.runtime.root) status, err := auth.Login(newAuthConfig) if err != nil { - fmt.Fprintln(stdout, "Error:", err) + fmt.Fprintf(stdout, "Error: %s\r\n", err) } else { srv.runtime.authConfig = newAuthConfig } @@ -161,7 +163,7 @@ func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout rcli.DockerConn, args .. // 'docker wait': block until a container stops func (srv *Server) CmdWait(stdin io.ReadCloser, stdout io.Writer, args ...string) error { - cmd := rcli.Subcmd(stdout, "wait", "[OPTIONS] NAME", "Block until a container stops, then print its exit code.") + cmd := rcli.Subcmd(stdout, "wait", "CONTAINER [CONTAINER...]", "Block until a container stops, then print its exit code.") if err := cmd.Parse(args); err != nil { return nil } @@ -181,8 +183,15 @@ func (srv *Server) CmdWait(stdin io.ReadCloser, stdout io.Writer, args ...string // 'docker version': show version information func (srv *Server) CmdVersion(stdin io.ReadCloser, stdout io.Writer, args ...string) error { - fmt.Fprintf(stdout, "Version:%s\n", VERSION) - fmt.Fprintf(stdout, "Git Commit:%s\n", GIT_COMMIT) + fmt.Fprintf(stdout, "Version: %s\n", VERSION) + fmt.Fprintf(stdout, "Git Commit: %s\n", GIT_COMMIT) + fmt.Fprintf(stdout, "Kernel: %s\n", srv.runtime.kernelVersion) + if !srv.runtime.capabilities.MemoryLimit { + fmt.Fprintf(stdout, "WARNING: No memory limit support\n") + } + if !srv.runtime.capabilities.SwapLimit { + fmt.Fprintf(stdout, "WARNING: No swap limit support\n") + } return nil } @@ -217,7 +226,8 @@ func (srv *Server) CmdInfo(stdin io.ReadCloser, stdout io.Writer, args ...string } func (srv *Server) CmdStop(stdin io.ReadCloser, stdout io.Writer, args ...string) error { - cmd := rcli.Subcmd(stdout, "stop", "[OPTIONS] NAME", "Stop a running container") + cmd := rcli.Subcmd(stdout, "stop", "[OPTIONS] CONTAINER [CONTAINER...]", "Stop a running container") + nSeconds := cmd.Int("t", 10, "wait t seconds before killing the container") if err := cmd.Parse(args); err != nil { return nil } @@ -227,7 +237,7 @@ func (srv *Server) CmdStop(stdin io.ReadCloser, stdout io.Writer, args ...string } for _, name := range cmd.Args() { if container := srv.runtime.Get(name); container != nil { - if err := container.Stop(); err != nil { + if err := container.Stop(*nSeconds); err != nil { return err } fmt.Fprintln(stdout, container.ShortId()) @@ -239,7 +249,8 @@ func (srv *Server) CmdStop(stdin io.ReadCloser, stdout io.Writer, args ...string } func (srv *Server) CmdRestart(stdin io.ReadCloser, stdout io.Writer, args ...string) error { - cmd := rcli.Subcmd(stdout, "restart", "[OPTIONS] NAME", "Restart a running container") + cmd := rcli.Subcmd(stdout, "restart", "CONTAINER [CONTAINER...]", "Restart a running container") + nSeconds := cmd.Int("t", 10, "wait t seconds before killing the container") if err := cmd.Parse(args); err != nil { return nil } @@ -249,7 +260,7 @@ func (srv *Server) CmdRestart(stdin io.ReadCloser, stdout io.Writer, args ...str } for _, name := range cmd.Args() { if container := srv.runtime.Get(name); container != nil { - if err := container.Restart(); err != nil { + if err := container.Restart(*nSeconds); err != nil { return err } fmt.Fprintln(stdout, container.ShortId()) @@ -261,7 +272,7 @@ func (srv *Server) CmdRestart(stdin io.ReadCloser, stdout io.Writer, args ...str } func (srv *Server) CmdStart(stdin io.ReadCloser, stdout io.Writer, args ...string) error { - cmd := rcli.Subcmd(stdout, "start", "[OPTIONS] NAME", "Start a stopped container") + cmd := rcli.Subcmd(stdout, "start", "CONTAINER [CONTAINER...]", "Start a stopped container") if err := cmd.Parse(args); err != nil { return nil } @@ -283,7 +294,7 @@ func (srv *Server) CmdStart(stdin io.ReadCloser, stdout io.Writer, args ...strin } func (srv *Server) CmdInspect(stdin io.ReadCloser, stdout io.Writer, args ...string) error { - cmd := rcli.Subcmd(stdout, "inspect", "[OPTIONS] CONTAINER", "Return low-level information on a container") + cmd := rcli.Subcmd(stdout, "inspect", "CONTAINER", "Return low-level information on a container") if err := cmd.Parse(args); err != nil { return nil } @@ -318,7 +329,7 @@ func (srv *Server) CmdInspect(stdin io.ReadCloser, stdout io.Writer, args ...str } func (srv *Server) CmdPort(stdin io.ReadCloser, stdout io.Writer, args ...string) error { - cmd := rcli.Subcmd(stdout, "port", "[OPTIONS] CONTAINER PRIVATE_PORT", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT") + cmd := rcli.Subcmd(stdout, "port", "CONTAINER PRIVATE_PORT", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT") if err := cmd.Parse(args); err != nil { return nil } @@ -340,9 +351,9 @@ func (srv *Server) CmdPort(stdin io.ReadCloser, stdout io.Writer, args ...string return nil } -// 'docker rmi NAME' removes all images with the name NAME +// 'docker rmi IMAGE' removes all images with the name IMAGE func (srv *Server) CmdRmi(stdin io.ReadCloser, stdout io.Writer, args ...string) (err error) { - cmd := rcli.Subcmd(stdout, "rmimage", "[OPTIONS] IMAGE", "Remove an image") + cmd := rcli.Subcmd(stdout, "rmimage", "IMAGE [IMAGE...]", "Remove an image") if err := cmd.Parse(args); err != nil { return nil } @@ -351,7 +362,11 @@ func (srv *Server) CmdRmi(stdin io.ReadCloser, stdout io.Writer, args ...string) return nil } for _, name := range cmd.Args() { - if err := srv.runtime.graph.Delete(name); err != nil { + img, err := srv.runtime.repositories.LookupImage(name) + if err != nil { + return err + } + if err := srv.runtime.graph.Delete(img.Id); err != nil { return err } } @@ -359,7 +374,7 @@ func (srv *Server) CmdRmi(stdin io.ReadCloser, stdout io.Writer, args ...string) } func (srv *Server) CmdHistory(stdin io.ReadCloser, stdout io.Writer, args ...string) error { - cmd := rcli.Subcmd(stdout, "history", "[OPTIONS] IMAGE", "Show the history of an image") + cmd := rcli.Subcmd(stdout, "history", "IMAGE", "Show the history of an image") if err := cmd.Parse(args); err != nil { return nil } @@ -385,10 +400,14 @@ func (srv *Server) CmdHistory(stdin io.ReadCloser, stdout io.Writer, args ...str } func (srv *Server) CmdRm(stdin io.ReadCloser, stdout io.Writer, args ...string) error { - cmd := rcli.Subcmd(stdout, "rm", "[OPTIONS] CONTAINER", "Remove a container") + cmd := rcli.Subcmd(stdout, "rm", "CONTAINER [CONTAINER...]", "Remove a container") if err := cmd.Parse(args); err != nil { return nil } + if cmd.NArg() < 1 { + cmd.Usage() + return nil + } for _, name := range cmd.Args() { container := srv.runtime.Get(name) if container == nil { @@ -403,10 +422,14 @@ func (srv *Server) CmdRm(stdin io.ReadCloser, stdout io.Writer, args ...string) // '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") + cmd := rcli.Subcmd(stdout, "kill", "CONTAINER [CONTAINER...]", "Kill a running container") if err := cmd.Parse(args); err != nil { return nil } + if cmd.NArg() < 1 { + cmd.Usage() + return nil + } for _, name := range cmd.Args() { container := srv.runtime.Get(name) if container == nil { @@ -421,17 +444,19 @@ func (srv *Server) CmdKill(stdin io.ReadCloser, stdout io.Writer, args ...string func (srv *Server) CmdImport(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error { stdout.Flush() - cmd := rcli.Subcmd(stdout, "import", "[OPTIONS] URL|- [REPOSITORY [TAG]]", "Create a new filesystem image from the contents of a tarball") + cmd := rcli.Subcmd(stdout, "import", "URL|- [REPOSITORY [TAG]]", "Create a new filesystem image from the contents of a tarball") var archive io.Reader var resp *http.Response if err := cmd.Parse(args); err != nil { return nil } + if cmd.NArg() < 1 { + cmd.Usage() + return nil + } src := cmd.Arg(0) - if src == "" { - return fmt.Errorf("Not enough arguments") - } else if src == "-" { + if src == "-" { archive = stdin } else { u, err := url.Parse(src) @@ -450,9 +475,9 @@ func (srv *Server) CmdImport(stdin io.ReadCloser, stdout rcli.DockerConn, args . if err != nil { return err } - archive = ProgressReader(resp.Body, int(resp.ContentLength), stdout) + archive = ProgressReader(resp.Body, int(resp.ContentLength), stdout, "Importing %v/%v (%v)") } - img, err := srv.runtime.graph.Create(archive, nil, "Imported from "+src) + img, err := srv.runtime.graph.Create(archive, nil, "Imported from "+src, "") if err != nil { return err } @@ -569,7 +594,7 @@ func (srv *Server) CmdImages(stdin io.ReadCloser, stdout io.Writer, args ...stri } w := tabwriter.NewWriter(stdout, 20, 1, 3, ' ', 0) if !*quiet { - fmt.Fprintln(w, "REPOSITORY\tTAG\tID\tCREATED\tPARENT") + fmt.Fprintln(w, "REPOSITORY\tTAG\tID\tCREATED") } var allImages map[string]*Image var err error @@ -598,7 +623,6 @@ func (srv *Server) CmdImages(stdin io.ReadCloser, stdout io.Writer, args ...stri /* TAG */ tag, /* ID */ TruncateId(id), /* CREATED */ HumanDuration(time.Now().Sub(image.Created)) + " ago", - /* PARENT */ srv.runtime.repositories.ImageName(image.Parent), } { if idx == 0 { w.Write([]byte(field)) @@ -621,7 +645,6 @@ func (srv *Server) CmdImages(stdin io.ReadCloser, stdout io.Writer, args ...stri /* TAG */ "", /* ID */ TruncateId(id), /* CREATED */ HumanDuration(time.Now().Sub(image.Created)) + " ago", - /* PARENT */ srv.runtime.repositories.ImageName(image.Parent), } { if idx == 0 { w.Write([]byte(field)) @@ -647,17 +670,25 @@ func (srv *Server) CmdPs(stdin io.ReadCloser, stdout io.Writer, args ...string) quiet := cmd.Bool("q", false, "Only display numeric IDs") flAll := cmd.Bool("a", false, "Show all containers. Only running containers are shown by default.") flFull := cmd.Bool("notrunc", false, "Don't truncate output") + latest := cmd.Bool("l", false, "Show only the latest created container, include non-running ones.") + nLast := cmd.Int("n", -1, "Show n last created containers, include non-running ones.") if err := cmd.Parse(args); err != nil { return nil } + if *nLast == -1 && *latest { + *nLast = 1 + } w := tabwriter.NewWriter(stdout, 12, 1, 3, ' ', 0) if !*quiet { - fmt.Fprintln(w, "ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tCOMMENT") + fmt.Fprintln(w, "ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tCOMMENT\tPORTS") } - for _, container := range srv.runtime.List() { - if !container.State.Running && !*flAll { + for i, container := range srv.runtime.List() { + if !container.State.Running && !*flAll && *nLast == -1 { continue } + if i == *nLast { + break + } if !*quiet { command := fmt.Sprintf("%s %s", container.Path, strings.Join(container.Args, " ")) if !*flFull { @@ -670,6 +701,7 @@ func (srv *Server) CmdPs(stdin io.ReadCloser, stdout io.Writer, args ...string) /* CREATED */ HumanDuration(time.Now().Sub(container.Created)) + " ago", /* STATUS */ container.State.String(), /* COMMENT */ "", + /* PORTS */ container.NetworkSettings.PortMappingHuman(), } { if idx == 0 { w.Write([]byte(field)) @@ -693,6 +725,7 @@ func (srv *Server) CmdCommit(stdin io.ReadCloser, stdout io.Writer, args ...stri "commit", "[OPTIONS] CONTAINER [REPOSITORY [TAG]]", "Create a new image from a container's changes") flComment := cmd.String("m", "", "Commit message") + flAuthor := cmd.String("author", "", "Author (eg. \"John Hannibal Smith \"") if err := cmd.Parse(args); err != nil { return nil } @@ -701,7 +734,7 @@ func (srv *Server) CmdCommit(stdin io.ReadCloser, stdout io.Writer, args ...stri cmd.Usage() return nil } - img, err := srv.runtime.Commit(containerName, repository, tag, *flComment) + img, err := srv.runtime.Commit(containerName, repository, tag, *flComment, *flAuthor) if err != nil { return err } @@ -733,13 +766,14 @@ func (srv *Server) CmdExport(stdin io.ReadCloser, stdout io.Writer, args ...stri func (srv *Server) CmdDiff(stdin io.ReadCloser, stdout io.Writer, args ...string) error { cmd := rcli.Subcmd(stdout, - "diff", "CONTAINER [OPTIONS]", + "diff", "CONTAINER", "Inspect changes on a container's filesystem") if err := cmd.Parse(args); err != nil { return nil } if cmd.NArg() < 1 { - return fmt.Errorf("Not enough arguments") + cmd.Usage() + return nil } if container := srv.runtime.Get(cmd.Arg(0)); container == nil { return fmt.Errorf("No such container") @@ -756,7 +790,7 @@ func (srv *Server) CmdDiff(stdin io.ReadCloser, stdout io.Writer, args ...string } func (srv *Server) CmdLogs(stdin io.ReadCloser, stdout io.Writer, args ...string) error { - cmd := rcli.Subcmd(stdout, "logs", "[OPTIONS] CONTAINER", "Fetch the logs of a container") + cmd := rcli.Subcmd(stdout, "logs", "CONTAINER", "Fetch the logs of a container") if err := cmd.Parse(args); err != nil { return nil } @@ -879,7 +913,7 @@ func (srv *Server) CmdTag(stdin io.ReadCloser, stdout io.Writer, args ...string) } func (srv *Server) CmdRun(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error { - config, err := ParseRun(args, stdout) + config, err := ParseRun(args, stdout, srv.runtime.capabilities) if err != nil { return err } @@ -904,7 +938,7 @@ func (srv *Server) CmdRun(stdin io.ReadCloser, stdout rcli.DockerConn, args ...s if err != nil { // If container not found, try to pull it if srv.runtime.graph.IsNotExist(err) { - fmt.Fprintf(stdout, "Image %s not found, trying to pull it from registry.\n", config.Image) + fmt.Fprintf(stdout, "Image %s not found, trying to pull it from registry.\r\n", config.Image) if err = srv.CmdPull(stdin, stdout, config.Image); err != nil { return err } @@ -946,6 +980,12 @@ func (srv *Server) CmdRun(stdin io.ReadCloser, stdout rcli.DockerConn, args ...s Debugf("Waiting for attach to return\n") <-attachErr // Expecting I/O pipe error, discarding + + // If we are in stdinonce mode, wait for the process to end + // otherwise, simply return + if config.StdinOnce && !config.Tty { + container.Wait() + } return nil } diff --git a/commands_test.go b/commands_test.go index 30e2579d2..a64b4f4dc 100644 --- a/commands_test.go +++ b/commands_test.go @@ -59,6 +59,20 @@ func assertPipe(input, output string, r io.Reader, w io.Writer, count int) error return nil } +func cmdWait(srv *Server, container *Container) error { + stdout, stdoutPipe := io.Pipe() + + go func() { + srv.CmdWait(nil, stdoutPipe, container.Id) + }() + + if _, err := bufio.NewReader(stdout).ReadString('\n'); err != nil { + return err + } + // Cleanup pipes + return closeWrap(stdout, stdoutPipe) +} + // TestRunHostname checks that 'docker run -h' correctly sets a custom hostname func TestRunHostname(t *testing.T) { runtime, err := newTestRuntime() @@ -89,7 +103,9 @@ func TestRunHostname(t *testing.T) { setTimeout(t, "CmdRun timed out", 2*time.Second, func() { <-c + cmdWait(srv, srv.runtime.List()[0]) }) + } func TestRunExit(t *testing.T) { @@ -129,6 +145,7 @@ func TestRunExit(t *testing.T) { // as the process exited, CmdRun must finish and unblock. Wait for it setTimeout(t, "Waiting for CmdRun timed out", 2*time.Second, func() { <-c1 + cmdWait(srv, container) }) // Make sure that the client has been disconnected @@ -211,6 +228,21 @@ func TestRunDisconnectTty(t *testing.T) { close(c1) }() + setTimeout(t, "Waiting for the container to be started timed out", 2*time.Second, func() { + for { + // Client disconnect after run -i should keep stdin out in TTY mode + l := runtime.List() + if len(l) == 1 && l[0].State.Running { + break + } + + time.Sleep(10 * time.Millisecond) + } + }) + + // Client disconnect after run -i should keep stdin out in TTY mode + container := runtime.List()[0] + setTimeout(t, "Read/Write assertion timed out", 2*time.Second, func() { if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil { t.Fatal(err) @@ -222,14 +254,9 @@ func TestRunDisconnectTty(t *testing.T) { t.Fatal(err) } - // as the pipes are close, we expect the process to die, - // therefore CmdRun to unblock. Wait for CmdRun - setTimeout(t, "Waiting for CmdRun timed out", 2*time.Second, func() { - <-c1 - }) + // In tty mode, we expect the process to stay alive even after client's stdin closes. + // Do not wait for run to finish - // Client disconnect after run -i should keep stdin out in TTY mode - container := runtime.List()[0] // Give some time to monitor to do his thing container.WaitTimeout(500 * time.Millisecond) if !container.State.Running { diff --git a/container.go b/container.go index f180c7559..c2c6fddd4 100644 --- a/container.go +++ b/container.go @@ -11,7 +11,9 @@ import ( "os" "os/exec" "path" + "sort" "strconv" + "strings" "syscall" "time" ) @@ -33,13 +35,14 @@ type Container struct { network *NetworkInterface NetworkSettings *NetworkSettings - SysInitPath string - cmd *exec.Cmd - stdout *writeBroadcaster - stderr *writeBroadcaster - stdin io.ReadCloser - stdinPipe io.WriteCloser + SysInitPath string + ResolvConfPath string + cmd *exec.Cmd + stdout *writeBroadcaster + stderr *writeBroadcaster + stdin io.ReadCloser + stdinPipe io.WriteCloser ptyMaster io.Closer runtime *Runtime @@ -61,10 +64,11 @@ type Config struct { StdinOnce bool // If true, close stdin after the 1 attached client disconnects. Env []string Cmd []string + Dns []string Image string // Name of the image as it was passed by the operator (eg. could be symbolic) } -func ParseRun(args []string, stdout io.Writer) (*Config, error) { +func ParseRun(args []string, stdout io.Writer, capabilities *Capabilities) (*Config, error) { cmd := rcli.Subcmd(stdout, "run", "[OPTIONS] IMAGE COMMAND [ARG...]", "Run a command in a new container") if len(args) > 0 && args[0] != "--help" { cmd.SetOutput(ioutil.Discard) @@ -79,12 +83,20 @@ func ParseRun(args []string, stdout io.Writer) (*Config, error) { flTty := cmd.Bool("t", false, "Allocate a pseudo-tty") flMemory := cmd.Int64("m", 0, "Memory limit (in bytes)") + if *flMemory > 0 && !capabilities.MemoryLimit { + fmt.Fprintf(stdout, "WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n") + *flMemory = 0 + } + var flPorts ListOpts cmd.Var(&flPorts, "p", "Expose a container's port to the host (use 'docker port' to see the actual mapping)") var flEnv ListOpts cmd.Var(&flEnv, "e", "Set environment variables") + var flDns ListOpts + cmd.Var(&flDns, "dns", "Set custom dns servers") + if err := cmd.Parse(args); err != nil { return nil, err } @@ -122,8 +134,15 @@ func ParseRun(args []string, stdout io.Writer) (*Config, error) { AttachStderr: flAttach.Get("stderr"), Env: flEnv, Cmd: runCmd, + Dns: flDns, Image: image, } + + if *flMemory > 0 && !capabilities.SwapLimit { + fmt.Fprintf(stdout, "WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.\n") + config.MemorySwap = -1 + } + // When allocating stdin in attached mode, close stdin at client disconnect if config.OpenStdin && config.AttachStdin { config.StdinOnce = true @@ -139,6 +158,16 @@ type NetworkSettings struct { PortMapping map[string]string } +// String returns a human-readable description of the port mapping defined in the settings +func (settings *NetworkSettings) PortMappingHuman() string { + var mapping []string + for private, public := range settings.PortMapping { + mapping = append(mapping, fmt.Sprintf("%s->%s", public, private)) + } + sort.Strings(mapping) + return strings.Join(mapping, ", ") +} + func (container *Container) Cmd() *exec.Cmd { return container.cmd } @@ -355,6 +384,17 @@ func (container *Container) Start() error { if err := container.allocateNetwork(); err != nil { return err } + + // Make sure the config is compatible with the current kernel + if container.Config.Memory > 0 && !container.runtime.capabilities.MemoryLimit { + log.Printf("WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n") + container.Config.Memory = 0 + } + if container.Config.Memory > 0 && !container.runtime.capabilities.SwapLimit { + log.Printf("WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.\n") + container.Config.MemorySwap = -1 + } + if err := container.generateLXCConfig(); err != nil { return err } @@ -373,21 +413,26 @@ func (container *Container) Start() error { params = append(params, "-u", container.Config.User) } + if container.Config.Tty { + params = append(params, "-e", "TERM=xterm") + } + + // Setup environment + params = append(params, + "-e", "HOME=/", + "-e", "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + ) + + for _, elem := range container.Config.Env { + params = append(params, "-e", elem) + } + // Program params = append(params, "--", container.Path) params = append(params, container.Args...) container.cmd = exec.Command("lxc-start", params...) - // Setup environment - container.cmd.Env = append( - []string{ - "HOME=/", - "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", - }, - container.Config.Env..., - ) - // Setup logging of stdout and stderr to disk if err := container.runtime.LogToDisk(container.stdout, container.logPath("stdout")); err != nil { return err @@ -398,10 +443,6 @@ func (container *Container) Start() error { var err error if container.Config.Tty { - container.cmd.Env = append( - []string{"TERM=xterm"}, - container.cmd.Env..., - ) err = container.startPty() } else { err = container.start() @@ -550,9 +591,21 @@ func (container *Container) kill() error { if !container.State.Running || container.cmd == nil { return nil } - if err := container.cmd.Process.Kill(); err != nil { - return err + + // Sending SIGKILL to the process via lxc + output, err := exec.Command("lxc-kill", "-n", container.Id, "9").CombinedOutput() + if err != nil { + log.Printf("error killing container %s (%s, %s)", container.Id, output, err) } + + // 2. Wait for the process to die, in last resort, try to kill the process directly + if err := container.WaitTimeout(10 * time.Second); err != nil { + log.Printf("Container %s failed to exit within 10 seconds of lxc SIGKILL - trying direct SIGKILL", container.Id) + if err := container.cmd.Process.Kill(); err != nil { + return err + } + } + // Wait for the container to be actually stopped container.Wait() return nil @@ -561,15 +614,24 @@ func (container *Container) kill() error { func (container *Container) Kill() error { container.State.lock() defer container.State.unlock() + if !container.State.Running { + return nil + } + if container.State.Ghost { + return fmt.Errorf("Can't kill ghost container") + } return container.kill() } -func (container *Container) Stop() error { +func (container *Container) Stop(seconds int) error { container.State.lock() defer container.State.unlock() if !container.State.Running { return nil } + if container.State.Ghost { + return fmt.Errorf("Can't stop ghost container") + } // 1. Send a SIGTERM if output, err := exec.Command("lxc-kill", "-n", container.Id, "15").CombinedOutput(); err != nil { @@ -581,8 +643,8 @@ func (container *Container) Stop() error { } // 2. Wait for the process to exit on its own - if err := container.WaitTimeout(10 * time.Second); err != nil { - log.Printf("Container %v failed to exit within 10 seconds of SIGTERM - using the force", container.Id) + if err := container.WaitTimeout(time.Duration(seconds) * time.Second); err != nil { + log.Printf("Container %v failed to exit within %d seconds of SIGTERM - using the force", container.Id, seconds) if err := container.kill(); err != nil { return err } @@ -590,8 +652,8 @@ func (container *Container) Stop() error { return nil } -func (container *Container) Restart() error { - if err := container.Stop(); err != nil { +func (container *Container) Restart(seconds int) error { + if err := container.Stop(seconds); err != nil { return err } if err := container.Start(); err != nil { diff --git a/container_test.go b/container_test.go index ac47f84bf..e6525f0a7 100644 --- a/container_test.go +++ b/container_test.go @@ -97,7 +97,7 @@ func TestMultipleAttachRestart(t *testing.T) { t.Fatalf("Unexpected output. Expected [%s], received [%s]", "hello", l3) } - if err := container.Stop(); err != nil { + if err := container.Stop(10); err != nil { t.Fatal(err) } @@ -182,7 +182,7 @@ func TestCommitRun(t *testing.T) { if err != nil { t.Error(err) } - img, err := runtime.graph.Create(rwTar, container1, "unit test commited image") + img, err := runtime.graph.Create(rwTar, container1, "unit test commited image", "") if err != nil { t.Error(err) } @@ -324,6 +324,54 @@ func TestOutput(t *testing.T) { } } +func TestKillDifferentUser(t *testing.T) { + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) + container, err := runtime.Create(&Config{ + Image: GetTestImage(runtime).Id, + Cmd: []string{"tail", "-f", "/etc/resolv.conf"}, + User: "daemon", + }, + ) + if err != nil { + t.Fatal(err) + } + defer runtime.Destroy(container) + + if container.State.Running { + t.Errorf("Container shouldn't be running") + } + if err := container.Start(); err != nil { + t.Fatal(err) + } + + // Give some time to lxc to spawn the process (setuid might take some time) + container.WaitTimeout(500 * time.Millisecond) + + if !container.State.Running { + t.Errorf("Container should be running") + } + + if err := container.Kill(); err != nil { + t.Fatal(err) + } + + if container.State.Running { + t.Errorf("Container shouldn't be running") + } + container.Wait() + if container.State.Running { + t.Errorf("Container shouldn't be running") + } + // Try stopping twice + if err := container.Kill(); err != nil { + t.Fatal(err) + } +} + func TestKill(t *testing.T) { runtime, err := newTestRuntime() if err != nil { @@ -346,6 +394,10 @@ func TestKill(t *testing.T) { if err := container.Start(); err != nil { t.Fatal(err) } + + // Give some time to lxc to spawn the process + container.WaitTimeout(500 * time.Millisecond) + if !container.State.Running { t.Errorf("Container should be running") } @@ -657,6 +709,10 @@ func TestMultipleContainers(t *testing.T) { t.Fatal(err) } + // Make sure they are running before trying to kill them + container1.WaitTimeout(250 * time.Millisecond) + container2.WaitTimeout(250 * time.Millisecond) + // If we are here, both containers should be running if !container1.State.Running { t.Fatal("Container not running") diff --git a/contrib/crashTest.go b/contrib/crashTest.go new file mode 100644 index 000000000..fa9cda605 --- /dev/null +++ b/contrib/crashTest.go @@ -0,0 +1,96 @@ +package main + +import ( + "io" + "log" + "os" + "os/exec" + "time" +) + +const DOCKER_PATH = "/home/creack/dotcloud/docker/docker/docker" + +func runDaemon() (*exec.Cmd, error) { + os.Remove("/var/run/docker.pid") + cmd := exec.Command(DOCKER_PATH, "-d") + outPipe, err := cmd.StdoutPipe() + if err != nil { + return nil, err + } + errPipe, err := cmd.StderrPipe() + if err != nil { + return nil, err + } + if err := cmd.Start(); err != nil { + return nil, err + } + go func() { + io.Copy(os.Stdout, outPipe) + }() + go func() { + io.Copy(os.Stderr, errPipe) + }() + return cmd, nil +} + +func crashTest() error { + if err := exec.Command("/bin/bash", "-c", "while true; do true; done").Start(); err != nil { + return err + } + + for { + daemon, err := runDaemon() + if err != nil { + return err + } + // time.Sleep(5000 * time.Millisecond) + var stop bool + go func() error { + stop = false + for i := 0; i < 100 && !stop; i++ { + func() error { + cmd := exec.Command(DOCKER_PATH, "run", "base", "echo", "hello", "world") + log.Printf("%d", i) + outPipe, err := cmd.StdoutPipe() + if err != nil { + return err + } + inPipe, err := cmd.StdinPipe() + if err != nil { + return err + } + if err := cmd.Start(); err != nil { + return err + } + go func() { + io.Copy(os.Stdout, outPipe) + }() + // Expecting error, do not check + inPipe.Write([]byte("hello world!!!!!\n")) + go inPipe.Write([]byte("hello world!!!!!\n")) + go inPipe.Write([]byte("hello world!!!!!\n")) + inPipe.Close() + + if err := cmd.Wait(); err != nil { + return err + } + outPipe.Close() + return nil + }() + } + return nil + }() + time.Sleep(20 * time.Second) + stop = true + if err := daemon.Process.Kill(); err != nil { + return err + } + } + return nil +} + +func main() { + if err := crashTest(); err != nil { + log.Println(err) + } +} diff --git a/contrib/docker-build/README b/contrib/docker-build/README new file mode 100644 index 000000000..f648753b9 --- /dev/null +++ b/contrib/docker-build/README @@ -0,0 +1,68 @@ +# docker-build: build your software with docker + +## Description + +docker-build is a script to build docker images from source. It will be deprecated once the 'build' feature is incorporated into docker itself (See https://github.com/dotcloud/docker/issues/278) + +Author: Solomon Hykes + + +## Install + +docker-builder requires: + +1) A reasonably recent Python setup (tested on 2.7.2). + +2) A running docker daemon at version 0.1.4 or more recent (http://www.docker.io/gettingstarted) + + +## Usage + +First create a valid Changefile, which defines a sequence of changes to apply to a base image. + + $ cat Changefile + # Start build from a know base image + from base:ubuntu-12.10 + # Update ubuntu sources + run echo 'deb http://archive.ubuntu.com/ubuntu quantal main universe multiverse' > /etc/apt/sources.list + run apt-get update + # Install system packages + run DEBIAN_FRONTEND=noninteractive apt-get install -y -q git + run DEBIAN_FRONTEND=noninteractive apt-get install -y -q curl + run DEBIAN_FRONTEND=noninteractive apt-get install -y -q golang + # Insert files from the host (./myscript must be present in the current directory) + copy myscript /usr/local/bin/myscript + + +Run docker-build, and pass the contents of your Changefile as standard input. + + $ IMG=$(./docker-build < Changefile) + +This will take a while: for each line of the changefile, docker-build will: + +1. Create a new container to execute the given command or insert the given file +2. Wait for the container to complete execution +3. Commit the resulting changes as a new image +4. Use the resulting image as the input of the next step + + +If all the steps succeed, the result will be an image containing the combined results of each build step. +You can trace back those build steps by inspecting the image's history: + + $ docker history $IMG + ID CREATED CREATED BY + 1e9e2045de86 A few seconds ago /bin/sh -c cat > /usr/local/bin/myscript; chmod +x /usr/local/bin/git + 77db140aa62a A few seconds ago /bin/sh -c DEBIAN_FRONTEND=noninteractive apt-get install -y -q golang + 77db140aa62a A few seconds ago /bin/sh -c DEBIAN_FRONTEND=noninteractive apt-get install -y -q curl + 77db140aa62a A few seconds ago /bin/sh -c DEBIAN_FRONTEND=noninteractive apt-get install -y -q git + 83e85d155451 A few seconds ago /bin/sh -c apt-get update + bfd53b36d9d3 A few seconds ago /bin/sh -c echo 'deb http://archive.ubuntu.com/ubuntu quantal main universe multiverse' > /etc/apt/sources.list + base 2 weeks ago /bin/bash + 27cf78414709 2 weeks ago + + +Note that your build started from 'base', as instructed by your Changefile. But that base image itself seems to have been built in 2 steps - hence the extra step in the history. + + +You can use this build technique to create any image you want: a database, a web application, or anything else that can be build by a sequence of unix commands - in other words, anything else. + diff --git a/contrib/docker-build/docker-build b/contrib/docker-build/docker-build new file mode 100755 index 000000000..f2fc34068 --- /dev/null +++ b/contrib/docker-build/docker-build @@ -0,0 +1,104 @@ +#!/usr/bin/env python + +# docker-build is a script to build docker images from source. +# It will be deprecated once the 'build' feature is incorporated into docker itself. +# (See https://github.com/dotcloud/docker/issues/278) +# +# Author: Solomon Hykes + + + +# First create a valid Changefile, which defines a sequence of changes to apply to a base image. +# +# $ cat Changefile +# # Start build from a know base image +# from base:ubuntu-12.10 +# # Update ubuntu sources +# run echo 'deb http://archive.ubuntu.com/ubuntu quantal main universe multiverse' > /etc/apt/sources.list +# run apt-get update +# # Install system packages +# run DEBIAN_FRONTEND=noninteractive apt-get install -y -q git +# run DEBIAN_FRONTEND=noninteractive apt-get install -y -q curl +# run DEBIAN_FRONTEND=noninteractive apt-get install -y -q golang +# # Insert files from the host (./myscript must be present in the current directory) +# copy myscript /usr/local/bin/myscript +# +# +# Run docker-build, and pass the contents of your Changefile as standard input. +# +# $ IMG=$(./docker-build < Changefile) +# +# This will take a while: for each line of the changefile, docker-build will: +# +# 1. Create a new container to execute the given command or insert the given file +# 2. Wait for the container to complete execution +# 3. Commit the resulting changes as a new image +# 4. Use the resulting image as the input of the next step + + +import sys +import subprocess +import json +import hashlib + +def docker(args, stdin=None): + print "# docker " + " ".join(args) + p = subprocess.Popen(["docker"] + list(args), stdin=stdin, stdout=subprocess.PIPE) + return p.stdout + +def image_exists(img): + return docker(["inspect", img]).read().strip() != "" + +def run_and_commit(img_in, cmd, stdin=None): + run_id = docker(["run"] + (["-i", "-a", "stdin"] if stdin else ["-d"]) + [img_in, "/bin/sh", "-c", cmd], stdin=stdin).read().rstrip() + print "---> Waiting for " + run_id + result=int(docker(["wait", run_id]).read().rstrip()) + if result != 0: + print "!!! '{}' return non-zero exit code '{}'. Aborting.".format(cmd, result) + sys.exit(1) + return docker(["commit", run_id]).read().rstrip() + +def insert(base, src, dst): + print "COPY {} to {} in {}".format(src, dst, base) + if dst == "": + raise Exception("Missing destination path") + stdin = file(src) + stdin.seek(0) + return run_and_commit(base, "cat > {0}; chmod +x {0}".format(dst), stdin=stdin) + + +def main(): + base="" + steps = [] + try: + for line in sys.stdin.readlines(): + line = line.strip() + # Skip comments and empty lines + if line == "" or line[0] == "#": + continue + op, param = line.split(" ", 1) + if op == "from": + print "FROM " + param + base = param + steps.append(base) + elif op == "run": + print "RUN " + param + result = run_and_commit(base, param) + steps.append(result) + base = result + print "===> " + base + elif op == "copy": + src, dst = param.split(" ", 1) + result = insert(base, src, dst) + steps.append(result) + base = result + print "===> " + base + else: + print "Skipping uknown op " + op + except: + docker(["rmi"] + steps[1:]) + raise + print base + +if __name__ == "__main__": + main() diff --git a/contrib/docker-build/example.changefile b/contrib/docker-build/example.changefile new file mode 100644 index 000000000..19261de82 --- /dev/null +++ b/contrib/docker-build/example.changefile @@ -0,0 +1,11 @@ +# Start build from a know base image +from base:ubuntu-12.10 +# Update ubuntu sources +run echo 'deb http://archive.ubuntu.com/ubuntu quantal main universe multiverse' > /etc/apt/sources.list +run apt-get update +# Install system packages +run DEBIAN_FRONTEND=noninteractive apt-get install -y -q git +run DEBIAN_FRONTEND=noninteractive apt-get install -y -q curl +run DEBIAN_FRONTEND=noninteractive apt-get install -y -q golang +# Insert files from the host (./myscript must be present in the current directory) +copy myscript /usr/local/bin/myscript diff --git a/contrib/install.sh b/contrib/install.sh index b0a998332..d7c6e6646 100755 --- a/contrib/install.sh +++ b/contrib/install.sh @@ -45,7 +45,7 @@ then echo "Upstart script already exists." else echo "Creating /etc/init/dockerd.conf..." - echo "exec /usr/local/bin/docker -d" > /etc/init/dockerd.conf + echo "exec env LANG=\"en_US.UTF-8\" /usr/local/bin/docker -d" > /etc/init/dockerd.conf fi echo "Starting dockerd..." diff --git a/contrib/vagrant-docker/README.md b/contrib/vagrant-docker/README.md new file mode 100644 index 000000000..5852ea192 --- /dev/null +++ b/contrib/vagrant-docker/README.md @@ -0,0 +1,3 @@ +# Vagrant-docker + +This is a placeholder for the official vagrant-docker, a plugin for Vagrant (http://vagrantup.com) which exposes Docker as a provider. diff --git a/deb/Makefile b/deb/Makefile deleted file mode 120000 index d0b0e8e00..000000000 --- a/deb/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/deb/Makefile.deb b/deb/Makefile.deb deleted file mode 100644 index c954b0f5b..000000000 --- a/deb/Makefile.deb +++ /dev/null @@ -1,73 +0,0 @@ -PKG_NAME=dotcloud-docker -PKG_ARCH=amd64 -PKG_VERSION=1 -ROOT_PATH:=$(PWD) -BUILD_PATH=build # Do not change, decided by dpkg-buildpackage -BUILD_SRC=build_src -GITHUB_PATH=src/github.com/dotcloud/docker -INSDIR=usr/bin -SOURCE_PACKAGE=$(PKG_NAME)_$(PKG_VERSION).orig.tar.gz -DEB_PACKAGE=$(PKG_NAME)_$(PKG_VERSION)_$(PKG_ARCH).deb -EXTRA_GO_PKG=./auth - -TMPDIR=$(shell mktemp -d -t XXXXXX) - - -# Build a debian source package -all: clean build_in_deb - -build_in_deb: - echo "GOPATH = " $(ROOT_PATH) - mkdir bin - cd $(GITHUB_PATH)/docker; GOPATH=$(ROOT_PATH) go build -o $(ROOT_PATH)/bin/docker - -# DESTDIR provided by Debian packaging -install: - # Call this from a go environment (as packaged for deb source package) - mkdir -p $(DESTDIR)/$(INSDIR) - mkdir -p $(DESTDIR)/etc/init - install -m 0755 bin/docker $(DESTDIR)/$(INSDIR) - install -o root -m 0755 etc/docker.upstart $(DESTDIR)/etc/init/docker.conf - -$(BUILD_SRC): clean - # Copy ourselves into $BUILD_SRC to comply with unusual golang constraints - tar --exclude=*.tar.gz --exclude=checkout.tgz -f checkout.tgz -cz * - mkdir -p $(BUILD_SRC)/$(GITHUB_PATH) - tar -f checkout.tgz -C $(BUILD_SRC)/$(GITHUB_PATH) -xz - cd $(BUILD_SRC)/$(GITHUB_PATH)/docker; GOPATH=$(ROOT_PATH)/$(BUILD_SRC) go get -d - for d in `find $(BUILD_SRC) -name '.git*'`; do rm -rf $$d; done - # Populate source build with debian stuff - cp -R -L ./deb/* $(BUILD_SRC) - -$(SOURCE_PACKAGE): $(BUILD_SRC) - rm -f $(SOURCE_PACKAGE) - # Create the debian source package - tar -f $(SOURCE_PACKAGE) -C ${ROOT_PATH}/${BUILD_SRC} -cz . - -# Build deb package fetching go dependencies and cleaning up git repositories -deb: $(DEB_PACKAGE) - -$(DEB_PACKAGE): $(SOURCE_PACKAGE) - # dpkg-buildpackage looks for source package tarball in ../ - cd $(BUILD_SRC); dpkg-buildpackage - rm -rf $(BUILD_PATH) debian/$(PKG_NAME)* debian/files - -debsrc: $(SOURCE_PACKAGE) - -# Build local sources -#$(PKG_NAME): build_local - -build_local: - -@mkdir -p bin - cd docker && go build -o ../bin/docker - -gotest: - @echo "\033[36m[Testing]\033[00m docker..." - @sudo -E GOPATH=$(ROOT_PATH)/$(BUILD_SRC) go test -v . $(EXTRA_GO_PKG) && \ - echo -n "\033[32m[OK]\033[00m" || \ - echo -n "\033[31m[FAIL]\033[00m"; \ - echo " docker" - @sudo rm -rf /tmp/docker-* - -clean: - rm -rf $(BUILD_PATH) debian/$(PKG_NAME)* debian/files $(BUILD_SRC) checkout.tgz bin diff --git a/deb/README.md b/deb/README.md deleted file mode 120000 index 32d46ee88..000000000 --- a/deb/README.md +++ /dev/null @@ -1 +0,0 @@ -../README.md \ No newline at end of file diff --git a/deb/debian/changelog b/deb/debian/changelog deleted file mode 100644 index 76cc04bee..000000000 --- a/deb/debian/changelog +++ /dev/null @@ -1,5 +0,0 @@ -dotcloud-docker (1) precise; urgency=low - - * Initial release - - -- dotCloud Mon, 14 Mar 2013 04:43:21 -0700 diff --git a/deb/debian/control b/deb/debian/control deleted file mode 100644 index 5245d7e23..000000000 --- a/deb/debian/control +++ /dev/null @@ -1,20 +0,0 @@ -Source: dotcloud-docker -Section: misc -Priority: extra -Homepage: https://github.com/dotcloud/docker -Maintainer: Daniel Mizyrycki -Build-Depends: debhelper (>= 8.0.0), git, golang -Vcs-Git: https://github.com/dotcloud/docker.git -Standards-Version: 3.9.2 - -Package: dotcloud-docker -Architecture: amd64 -Provides: dotcloud-docker -Depends: lxc, wget, bsdtar, curl -Conflicts: docker -Description: A process manager with superpowers - It encapsulates heterogeneous payloads in Standard Containers, and runs - them on any server with strong guarantees of isolation and repeatability. - Is is a great building block for automating distributed systems: - large-scale web deployments, database clusters, continuous deployment - systems, private PaaS, service-oriented architectures, etc. diff --git a/deb/debian/copyright b/deb/debian/copyright deleted file mode 100644 index 6f3a66bbc..000000000 --- a/deb/debian/copyright +++ /dev/null @@ -1,209 +0,0 @@ -Format: http://dep.debian.net/deps/dep5 -Upstream-Name: dotcloud-docker -Source: https://github.com/dotcloud/docker - -Files: * -Copyright: 2012 DotCloud Inc (opensource@dotcloud.com) -License: Apache License Version 2.0 - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2012 DotCloud Inc (opensource@dotcloud.com) - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/deb/etc/docker-dev.upstart b/deb/etc/docker-dev.upstart deleted file mode 100644 index 6cfe9d261..000000000 --- a/deb/etc/docker-dev.upstart +++ /dev/null @@ -1,10 +0,0 @@ -description "Run docker" - -start on runlevel [2345] -stop on starting rc RUNLEVEL=[016] -respawn - -script - test -f /etc/default/locale && . /etc/default/locale || true - LANG=$LANG LC_ALL=$LANG /usr/bin/docker -d -end script diff --git a/docker/docker.go b/docker/docker.go index 1b1c21990..411e4d0c9 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -2,15 +2,20 @@ package main import ( "flag" + "fmt" "github.com/dotcloud/docker" "github.com/dotcloud/docker/rcli" "github.com/dotcloud/docker/term" "io" "log" "os" + "os/signal" + "syscall" ) -var GIT_COMMIT string +var ( + GIT_COMMIT string +) func main() { if docker.SelfPath() == "/sbin/init" { @@ -22,6 +27,7 @@ func main() { flDaemon := flag.Bool("d", false, "Daemon mode") flDebug := flag.Bool("D", false, "Debug mode") bridgeName := flag.String("b", "", "Attach containers to a pre-existing network bridge") + pidfile := flag.String("p", "/var/run/docker.pid", "File containing process PID") flag.Parse() if *bridgeName != "" { docker.NetworkBridgeIface = *bridgeName @@ -37,7 +43,7 @@ func main() { flag.Usage() return } - if err := daemon(); err != nil { + if err := daemon(*pidfile); err != nil { log.Fatal(err) } } else { @@ -47,7 +53,43 @@ func main() { } } -func daemon() error { +func createPidFile(pidfile string) error { + if _, err := os.Stat(pidfile); err == nil { + return fmt.Errorf("pid file found, ensure docker is not running or delete %s", pidfile) + } + + file, err := os.Create(pidfile) + if err != nil { + return err + } + + defer file.Close() + + _, err = fmt.Fprintf(file, "%d", os.Getpid()) + return err +} + +func removePidFile(pidfile string) { + if err := os.Remove(pidfile); err != nil { + log.Printf("Error removing %s: %s", pidfile, err) + } +} + +func daemon(pidfile string) error { + if err := createPidFile(pidfile); err != nil { + log.Fatal(err) + } + defer removePidFile(pidfile) + + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt, os.Kill, os.Signal(syscall.SIGTERM)) + go func() { + sig := <-c + log.Printf("Received signal '%v', exiting\n", sig) + removePidFile(pidfile) + os.Exit(0) + }() + service, err := docker.NewServer() if err != nil { return err @@ -91,15 +133,7 @@ func runCommand(args []string) error { } } } else { - service, err := docker.NewServer() - if err != nil { - return err - } - dockerConn := rcli.NewDockerLocalConn(os.Stdout) - defer dockerConn.Close() - if err := rcli.LocalCall(service, os.Stdin, dockerConn, args...); err != nil { - return err - } + return fmt.Errorf("Can't connect to docker daemon. Is 'docker -d' running on this host?") } return nil } diff --git a/docs/sources/examples/running_examples.rst b/docs/sources/examples/running_examples.rst index 4042add48..3d2593c71 100644 --- a/docs/sources/examples/running_examples.rst +++ b/docs/sources/examples/running_examples.rst @@ -7,27 +7,16 @@ Running The Examples -------------------- -There are two ways to run docker, daemon mode and standalone mode. - -When you run the docker command it will first check if there is a docker daemon running in the background it can connect to. - -* If it exists it will use that daemon to run all of the commands. -* If it does not exist docker will run in standalone mode (docker will exit after each command). - -Docker needs to be run from a privileged account (root). - -1. The most common (and recommended) way is to run a docker daemon as root in the background, and then connect to it from the docker client from any account. +All the examples assume your machine is running the docker daemon. To run the docker daemon in the background, simply type: .. code-block:: bash - # starting docker daemon in the background sudo docker -d & - # now you can run docker commands from any account. - docker - -2. Standalone: You need to run every command as root, or using sudo +Now you can run docker in client mode: all commands will be forwarded to the docker daemon, so the client +can run from any account. .. code-block:: bash - sudo docker + # now you can run docker commands from any account. + docker help diff --git a/docs/sources/installation/amazon.rst b/docs/sources/installation/amazon.rst index 5260b992b..012c78f40 100644 --- a/docs/sources/installation/amazon.rst +++ b/docs/sources/installation/amazon.rst @@ -1,8 +1,9 @@ Amazon EC2 ========== - Please note this is a community contributed installation path. The only 'official' installation is using the :ref:`ubuntu_linux` installation path. This version - may be out of date because it depends on some binaries to be updated and published + Please note this is a community contributed installation path. The only 'official' installation is using the + :ref:`ubuntu_linux` installation path. This version may sometimes be out of date. + Installation ------------ @@ -17,7 +18,7 @@ Docker can now be installed on Amazon EC2 with a single vagrant command. Vagrant vagrant plugin install vagrant-aws -3. Get the docker sources, this will give you the latest Vagrantfile and puppet manifests. +3. Get the docker sources, this will give you the latest Vagrantfile. :: diff --git a/docs/sources/installation/archlinux.rst b/docs/sources/installation/archlinux.rst new file mode 100644 index 000000000..ad9ab255e --- /dev/null +++ b/docs/sources/installation/archlinux.rst @@ -0,0 +1,64 @@ +.. _arch_linux: + +Arch Linux +========== + + Please note this is a community contributed installation path. The only 'official' installation is using the + :ref:`ubuntu_linux` installation path. This version may sometimes be out of date. + + +Installing on Arch Linux is not officially supported but can be handled via +either of the following AUR packages: + +* `lxc-docker `_ +* `lxc-docker-git `_ + +The lxc-docker package will install the latest tagged version of docker. +The lxc-docker-git package will build from the current master branch. + +Dependencies +------------ + +Docker depends on several packages which are specified as dependencies in +either AUR package. + +* aufs3 +* bridge-utils +* go +* iproute2 +* linux-aufs_friendly +* lxc + +Installation +------------ + +The instructions here assume **yaourt** is installed. See +`Arch User Repository `_ +for information on building and installing packages from the AUR if you have not +done so before. + +Keep in mind that if **linux-aufs_friendly** is not already installed that a +new kernel will be compiled and this can take quite a while. + +:: + + yaourt -S lxc-docker-git + +Starting Docker +--------------- + +Prior to starting docker modify your bootloader to use the +**linux-aufs_friendly** kernel and reboot your system. + +There is a systemd service unit created for docker. To start the docker service: + +:: + + sudo systemctl start docker + + +To start on system boot: + +:: + + sudo systemctl enable docker diff --git a/docs/sources/installation/index.rst b/docs/sources/installation/index.rst index ae1125887..f9a59b0ad 100644 --- a/docs/sources/installation/index.rst +++ b/docs/sources/installation/index.rst @@ -13,6 +13,7 @@ Contents: :maxdepth: 1 ubuntulinux + archlinux vagrant windows amazon diff --git a/docs/sources/installation/ubuntulinux.rst b/docs/sources/installation/ubuntulinux.rst index 94786f95d..5f1ab3922 100644 --- a/docs/sources/installation/ubuntulinux.rst +++ b/docs/sources/installation/ubuntulinux.rst @@ -1,5 +1,10 @@ -Docker on Ubuntu -================ +.. _ubuntu_linux: + +Ubuntu Linux +============ + + **Please note this project is currently under heavy development. It should not be used in production.** + Docker is now available as a Ubuntu PPA (Personal Package Archive), `hosted on launchpad `_ @@ -15,8 +20,7 @@ Add the custom package sources to your apt sources list. Copy and paste both the .. code-block:: bash - sudo sh -c "echo 'deb http://ppa.launchpad.net/dotcloud/lxc-docker/ubuntu precise main' \ - >> /etc/apt/sources.list" + sudo sh -c "echo 'deb http://ppa.launchpad.net/dotcloud/lxc-docker/ubuntu precise main' >> /etc/apt/sources.list" Update your sources. You will see a warning that GPG signatures cannot be verified. @@ -33,12 +37,11 @@ Now install it, you will see another warning that the package cannot be authenti sudo apt-get install lxc-docker -**Run!** +Verify it worked .. code-block:: bash docker - -Probably you would like to continue with the :ref:`hello_world` example. \ No newline at end of file +**Done!**, now continue with the :ref:`hello_world` example. diff --git a/docs/sources/installation/vagrant.rst b/docs/sources/installation/vagrant.rst index a8249961a..67d1f2281 100644 --- a/docs/sources/installation/vagrant.rst +++ b/docs/sources/installation/vagrant.rst @@ -7,7 +7,7 @@ Install using Vagrant Please note this is a community contributed installation path. The only 'official' installation is using the :ref:`ubuntu_linux` installation path. This version may sometimes be out of date. -**requirements** +**Requirements:** This guide will setup a new virtual machine with docker installed on your computer. This works on most operating systems, including MacOX, Windows, Linux, FreeBSD and others. If you can install these and have at least 400Mb RAM to spare you should be good. @@ -22,10 +22,10 @@ Install Vagrant and Virtualbox ``git`` in a terminal window -Spin up your machine --------------------- +Spin it up +---------- -1. Fetch the docker sources (this includes the instructions for machine setup). +1. Fetch the docker sources (this includes the Vagrantfile for machine setup). .. code-block:: bash diff --git a/graph.go b/graph.go index e7044c25a..c0e500091 100644 --- a/graph.go +++ b/graph.go @@ -2,6 +2,7 @@ package docker import ( "fmt" + "io" "io/ioutil" "os" "path" @@ -83,12 +84,13 @@ func (graph *Graph) Get(name string) (*Image, error) { } // Create creates a new image and registers it in the graph. -func (graph *Graph) Create(layerData Archive, container *Container, comment string) (*Image, error) { +func (graph *Graph) Create(layerData Archive, container *Container, comment, author string) (*Image, error) { img := &Image{ Id: GenerateId(), Comment: comment, Created: time.Now(), DockerVersion: VERSION, + Author: author, } if container != nil { img.Parent = container.Image @@ -111,7 +113,7 @@ func (graph *Graph) Register(layerData Archive, img *Image) error { if graph.Exists(img.Id) { return fmt.Errorf("Image %s already exists", img.Id) } - tmp, err := graph.Mktemp(img.Id) + tmp, err := graph.Mktemp("") defer os.RemoveAll(tmp) if err != nil { return fmt.Errorf("Mktemp failed: %s", err) @@ -128,12 +130,32 @@ func (graph *Graph) Register(layerData Archive, img *Image) error { return nil } +// TempLayerArchive creates a temporary archive of the given image's filesystem layer. +// The archive is stored on disk and will be automatically deleted as soon as has been read. +// If output is not nil, a human-readable progress bar will be written to it. +// FIXME: does this belong in Graph? How about MktempFile, let the caller use it for archives? +func (graph *Graph) TempLayerArchive(id string, compression Compression, output io.Writer) (*TempArchive, error) { + image, err := graph.Get(id) + if err != nil { + return nil, err + } + tmp, err := graph.tmp() + if err != nil { + return nil, err + } + archive, err := image.TarLayer(compression) + if err != nil { + return nil, err + } + return NewTempArchive(ProgressReader(ioutil.NopCloser(archive), 0, output, "Buffering to disk %v/%v (%v)"), tmp.Root) +} + // Mktemp creates a temporary sub-directory inside the graph's filesystem. func (graph *Graph) Mktemp(id string) (string, error) { if id == "" { id = GenerateId() } - tmp, err := NewGraph(path.Join(graph.Root, ":tmp:")) + tmp, err := graph.tmp() if err != nil { return "", fmt.Errorf("Couldn't create temp: %s", err) } @@ -143,6 +165,10 @@ func (graph *Graph) Mktemp(id string) (string, error) { return tmp.imageRoot(id), nil } +func (graph *Graph) tmp() (*Graph, error) { + return NewGraph(path.Join(graph.Root, ":tmp:")) +} + // Check if given error is "not empty". // Note: this is the way golang does it internally with os.IsNotExists. func isNotEmpty(err error) bool { diff --git a/graph_test.go b/graph_test.go index 7c40330aa..1bd05aaa9 100644 --- a/graph_test.go +++ b/graph_test.go @@ -3,6 +3,7 @@ package docker import ( "archive/tar" "bytes" + "errors" "io" "io/ioutil" "os" @@ -26,6 +27,32 @@ func TestInit(t *testing.T) { } } +// Test that Register can be interrupted cleanly without side effects +func TestInterruptedRegister(t *testing.T) { + graph := tempGraph(t) + defer os.RemoveAll(graph.Root) + badArchive, w := io.Pipe() // Use a pipe reader as a fake archive which never yields data + image := &Image{ + Id: GenerateId(), + Comment: "testing", + Created: time.Now(), + } + go graph.Register(badArchive, image) + time.Sleep(200 * time.Millisecond) + w.CloseWithError(errors.New("But I'm not a tarball!")) // (Nobody's perfect, darling) + if _, err := graph.Get(image.Id); err == nil { + t.Fatal("Image should not exist after Register is interrupted") + } + // Registering the same image again should succeed if the first register was interrupted + goodArchive, err := fakeTar() + if err != nil { + t.Fatal(err) + } + if err := graph.Register(goodArchive, image); err != nil { + t.Fatal(err) + } +} + // FIXME: Do more extensive tests (ex: create multiple, delete, recreate; // create multiple, check the amount of images and paths, etc..) func TestGraphCreate(t *testing.T) { @@ -35,7 +62,7 @@ func TestGraphCreate(t *testing.T) { if err != nil { t.Fatal(err) } - image, err := graph.Create(archive, nil, "Testing") + image, err := graph.Create(archive, nil, "Testing", "") if err != nil { t.Fatal(err) } @@ -95,7 +122,7 @@ func TestMount(t *testing.T) { if err != nil { t.Fatal(err) } - image, err := graph.Create(archive, nil, "Testing") + image, err := graph.Create(archive, nil, "Testing", "") if err != nil { t.Fatal(err) } @@ -139,7 +166,7 @@ func createTestImage(graph *Graph, t *testing.T) *Image { if err != nil { t.Fatal(err) } - img, err := graph.Create(archive, nil, "Test image") + img, err := graph.Create(archive, nil, "Test image", "") if err != nil { t.Fatal(err) } @@ -154,7 +181,7 @@ func TestDelete(t *testing.T) { t.Fatal(err) } assertNImages(graph, t, 0) - img, err := graph.Create(archive, nil, "Bla bla") + img, err := graph.Create(archive, nil, "Bla bla", "") if err != nil { t.Fatal(err) } @@ -165,11 +192,11 @@ func TestDelete(t *testing.T) { assertNImages(graph, t, 0) // Test 2 create (same name) / 1 delete - img1, err := graph.Create(archive, nil, "Testing") + img1, err := graph.Create(archive, nil, "Testing", "") if err != nil { t.Fatal(err) } - if _, err = graph.Create(archive, nil, "Testing"); err != nil { + if _, err = graph.Create(archive, nil, "Testing", ""); err != nil { t.Fatal(err) } assertNImages(graph, t, 2) diff --git a/hack/README.md b/hack/README.md new file mode 100644 index 000000000..06cdd5085 --- /dev/null +++ b/hack/README.md @@ -0,0 +1 @@ +This directory contains material helpful for hacking on docker. diff --git a/hack/fmt-check.hook b/hack/fmt-check.hook new file mode 100644 index 000000000..cd18a18bc --- /dev/null +++ b/hack/fmt-check.hook @@ -0,0 +1,46 @@ +#!/bin/sh + +# This pre-commit hook will abort if a committed file doesn't pass gofmt. +# By Even Shaw +# http://github.com/edsrzf/gofmt-git-hook + +test_fmt() { + hash gofmt 2>&- || { echo >&2 "gofmt not in PATH."; exit 1; } + IFS=' +' + for file in `git diff --cached --name-only --diff-filter=ACM | grep '\.go$'` + do + output=`git cat-file -p :$file | gofmt -l 2>&1` + if test $? -ne 0 + then + output=`echo "$output" | sed "s,,$file,"` + syntaxerrors="${list}${output}\n" + elif test -n "$output" + then + list="${list}${file}\n" + fi + done + exitcode=0 + if test -n "$syntaxerrors" + then + echo >&2 "gofmt found syntax errors:" + printf "$syntaxerrors" + exitcode=1 + fi + if test -n "$list" + then + echo >&2 "gofmt needs to format these files (run gofmt -w and git add):" + printf "$list" + exitcode=1 + fi + exit $exitcode +} + +case "$1" in + --about ) + echo "Check Go code formatting" + ;; + * ) + test_fmt + ;; +esac diff --git a/image.go b/image.go index 83bf9481a..403731d6e 100644 --- a/image.go +++ b/image.go @@ -7,7 +7,9 @@ import ( "fmt" "io" "io/ioutil" + "log" "os" + "os/exec" "path" "strings" "time" @@ -21,6 +23,7 @@ type Image struct { Container string `json:"container,omitempty"` ContainerConfig Config `json:"container_config,omitempty"` DockerVersion string `json:"docker_version,omitempty"` + Author string `json:"author,omitempty"` graph *Graph } @@ -92,7 +95,28 @@ func MountAUFS(ro []string, rw string, target string) error { roBranches += fmt.Sprintf("%v=ro:", layer) } branches := fmt.Sprintf("br:%v:%v", rwBranch, roBranches) - return mount("none", target, "aufs", 0, branches) + + //if error, try to load aufs kernel module + if err := mount("none", target, "aufs", 0, branches); err != nil { + log.Printf("Kernel does not support AUFS, trying to load the AUFS module with modprobe...") + if err := exec.Command("modprobe", "aufs").Run(); err != nil { + return fmt.Errorf("Unable to load the AUFS module") + } + log.Printf("...module loaded.") + if err := mount("none", target, "aufs", 0, branches); err != nil { + return fmt.Errorf("Unable to mount using aufs") + } + } + return nil +} + +// TarLayer returns a tar archive of the image's filesystem layer. +func (image *Image) TarLayer(compression Compression) (Archive, error) { + layerPath, err := image.layer() + if err != nil { + return nil, err + } + return Tar(layerPath, compression) } func (image *Image) Mount(root, rw string) error { diff --git a/lxc_template.go b/lxc_template.go index c6849cb0d..5ac62f52a 100644 --- a/lxc_template.go +++ b/lxc_template.go @@ -78,7 +78,7 @@ lxc.mount.entry = devpts {{$ROOTFS}}/dev/pts devpts newinstance,ptmxmode=0666,no lxc.mount.entry = {{.SysInitPath}} {{$ROOTFS}}/sbin/init none bind,ro 0 0 # In order to get a working DNS environment, mount bind (ro) the host's /etc/resolv.conf into the container -lxc.mount.entry = /etc/resolv.conf {{$ROOTFS}}/etc/resolv.conf none bind,ro 0 0 +lxc.mount.entry = {{.ResolvConfPath}} {{$ROOTFS}}/etc/resolv.conf none bind,ro 0 0 # drop linux capabilities (apply mainly to the user root in the container) diff --git a/network.go b/network.go index 9164c1d72..373625d59 100644 --- a/network.go +++ b/network.go @@ -4,6 +4,7 @@ import ( "encoding/binary" "errors" "fmt" + "io" "log" "net" "os/exec" @@ -183,18 +184,21 @@ func getIfaceAddr(name string) (net.Addr, error) { // It keeps track of all mappings and is able to unmap at will type PortMapper struct { mapping map[int]net.TCPAddr + proxies map[int]net.Listener } func (mapper *PortMapper) cleanup() error { // Ignore errors - This could mean the chains were never set up iptables("-t", "nat", "-D", "PREROUTING", "-m", "addrtype", "--dst-type", "LOCAL", "-j", "DOCKER") - iptables("-t", "nat", "-D", "OUTPUT", "-m", "addrtype", "--dst-type", "LOCAL", "-j", "DOCKER") + iptables("-t", "nat", "-D", "OUTPUT", "-m", "addrtype", "--dst-type", "LOCAL", "!", "--dst", "127.0.0.0/8", "-j", "DOCKER") + iptables("-t", "nat", "-D", "OUTPUT", "-m", "addrtype", "--dst-type", "LOCAL", "-j", "DOCKER") // Created in versions <= 0.1.6 // Also cleanup rules created by older versions, or -X might fail. iptables("-t", "nat", "-D", "PREROUTING", "-j", "DOCKER") iptables("-t", "nat", "-D", "OUTPUT", "-j", "DOCKER") iptables("-t", "nat", "-F", "DOCKER") iptables("-t", "nat", "-X", "DOCKER") mapper.mapping = make(map[int]net.TCPAddr) + mapper.proxies = make(map[int]net.Listener) return nil } @@ -205,7 +209,7 @@ func (mapper *PortMapper) setup() error { if err := iptables("-t", "nat", "-A", "PREROUTING", "-m", "addrtype", "--dst-type", "LOCAL", "-j", "DOCKER"); err != nil { return fmt.Errorf("Failed to inject docker in PREROUTING chain: %s", err) } - if err := iptables("-t", "nat", "-A", "OUTPUT", "-m", "addrtype", "--dst-type", "LOCAL", "-j", "DOCKER"); err != nil { + if err := iptables("-t", "nat", "-A", "OUTPUT", "-m", "addrtype", "--dst-type", "LOCAL", "!", "--dst", "127.0.0.0/8", "-j", "DOCKER"); err != nil { return fmt.Errorf("Failed to inject docker in OUTPUT chain: %s", err) } return nil @@ -220,15 +224,64 @@ func (mapper *PortMapper) Map(port int, dest net.TCPAddr) error { if err := mapper.iptablesForward("-A", port, dest); err != nil { return err } + mapper.mapping[port] = dest + listener, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port)) + if err != nil { + mapper.Unmap(port) + return err + } + mapper.proxies[port] = listener + go proxy(listener, "tcp", dest.String()) return nil } +// proxy listens for socket connections on `listener`, and forwards them unmodified +// to `proto:address` +func proxy(listener net.Listener, proto, address string) error { + Debugf("proxying to %s:%s", proto, address) + defer Debugf("Done proxying to %s:%s", proto, address) + for { + Debugf("Listening on %s", listener) + src, err := listener.Accept() + if err != nil { + return err + } + Debugf("Connecting to %s:%s", proto, address) + dst, err := net.Dial(proto, address) + if err != nil { + log.Printf("Error connecting to %s:%s: %s", proto, address, err) + src.Close() + continue + } + Debugf("Connected to backend, splicing") + splice(src, dst) + } + return nil +} + +func halfSplice(dst, src net.Conn) error { + _, err := io.Copy(dst, src) + // FIXME: on EOF from a tcp connection, pass WriteClose() + dst.Close() + src.Close() + return err +} + +func splice(a, b net.Conn) { + go halfSplice(a, b) + go halfSplice(b, a) +} + func (mapper *PortMapper) Unmap(port int) error { dest, ok := mapper.mapping[port] if !ok { return errors.New("Port is not mapped") } + if proxy, exists := mapper.proxies[port]; exists { + proxy.Close() + delete(mapper.proxies, port) + } if err := mapper.iptablesForward("-D", port, dest); err != nil { return err } @@ -293,7 +346,7 @@ func (alloc *PortAllocator) Acquire(port int) (int, error) { func newPortAllocator() (*PortAllocator, error) { allocator := &PortAllocator{ - inUse: make(map[int]struct{}), + inUse: make(map[int]struct{}), fountain: make(chan int), } go allocator.runFountain() diff --git a/packaging/README.rst b/packaging/README.rst new file mode 100644 index 000000000..7e927ccff --- /dev/null +++ b/packaging/README.rst @@ -0,0 +1,8 @@ +Docker packaging +================ + +This directory has one subdirectory per packaging distribution. +At minimum, each of these subdirectories should contain a +README.$DISTRIBUTION explaining how to create the native +docker package and how to install it. + diff --git a/packaging/archlinux/README.archlinux b/packaging/archlinux/README.archlinux new file mode 100644 index 000000000..f20d2d25b --- /dev/null +++ b/packaging/archlinux/README.archlinux @@ -0,0 +1,25 @@ +Docker on Arch +============== + +The AUR lxc-docker and lxc-docker-git packages handle building docker on Arch +linux. The PKGBUILD specifies all dependencies, build, and packaging steps. + +Dependencies +============ + +The only buildtime dependencies are git and go which are available via pacman. +The -s flag can be used on makepkg commands below to automatically install +these dependencies. + +Building Package +================ + +Download the tarball for either AUR packaged to a local directory. In that +directory makepkg can be run to build the package. + +# Build the binary package +makepkg + +# Build an updated source tarball +makepkg --source + diff --git a/packaging/debian/Makefile b/packaging/debian/Makefile new file mode 100644 index 000000000..75ff8f34f --- /dev/null +++ b/packaging/debian/Makefile @@ -0,0 +1,35 @@ +PKG_NAME=lxc-docker +DOCKER_VERSION=$(shell head -1 changelog | awk 'match($$0, /\(.+\)/) {print substr($$0, RSTART+1, RLENGTH-4)}') +GITHUB_PATH=github.com/dotcloud/docker +SOURCE_PKG=$(PKG_NAME)_$(DOCKER_VERSION).orig.tar.gz +BUILD_SRC=${CURDIR}/../../build_src + +all: + # Compile docker. Used by debian dpkg-buildpackage. + cd src/${GITHUB_PATH}/docker; GOPATH=${CURDIR} go build + +install: + # Used by debian dpkg-buildpackage + mkdir -p $(DESTDIR)/usr/bin + mkdir -p $(DESTDIR)/etc/init.d + install -m 0755 src/${GITHUB_PATH}/docker/docker $(DESTDIR)/usr/bin + install -o root -m 0755 debian/docker.initd $(DESTDIR)/etc/init.d/docker + +debian: + # This Makefile will compile the github master branch of dotcloud/docker + # Retrieve docker project and its go structure from internet + rm -rf ${BUILD_SRC} + GOPATH=${BUILD_SRC} go get ${GITHUB_PATH} + # Add debianization + mkdir ${BUILD_SRC}/debian + cp Makefile ${BUILD_SRC} + cp -r * ${BUILD_SRC}/debian + cp ../../README.md ${BUILD_SRC} + # Cleanup + for d in `find ${BUILD_SRC} -name '.git*'`; do rm -rf $$d; done + rm -rf ${BUILD_SRC}/../${SOURCE_PKG} + rm -rf ${BUILD_SRC}/pkg + # Create docker debian files + cd ${BUILD_SRC}; tar czf ../${SOURCE_PKG} . + cd ${BUILD_SRC}; dpkg-buildpackage + rm -rf ${BUILD_SRC} diff --git a/packaging/debian/README.debian b/packaging/debian/README.debian new file mode 100644 index 000000000..83dc42268 --- /dev/null +++ b/packaging/debian/README.debian @@ -0,0 +1,31 @@ +Docker on Debian +================ + +Docker has been built and tested on Wheezy. All docker functionality works +out of the box, except for memory limitation as the stock debian kernel +does not support it yet. + + +Building docker package +~~~~~~~~~~~~~~~~~~~~~~~ + +Building Dependencies: debhelper, autotools-dev and golang + + +Assuming you have a wheezy system up and running + +# Download a fresh copy of the docker project +git clone https://github.com/dotcloud/docker.git +cd docker + +# Get building dependencies +sudo apt-get update ; sudo apt-get install -y debhelper autotools-dev golang + +# Make the debian package, with no memory limitation support +(cd packaging/debian; make debian NO_MEMORY_LIMIT=1) + + +Install docker package +~~~~~~~~~~~~~~~~~~~~~~ + +sudo dpkg -i lxc-docker_0.1.4-1_amd64.deb; sudo apt-get install -f -y diff --git a/packaging/debian/Vagrantfile b/packaging/debian/Vagrantfile new file mode 100644 index 000000000..2da290060 --- /dev/null +++ b/packaging/debian/Vagrantfile @@ -0,0 +1,22 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +$BUILDBOT_IP = '192.168.33.31' + +def v10(config) + config.vm.box = 'debian' + config.vm.share_folder 'v-data', '/data/docker', File.dirname(__FILE__) + '/../..' + config.vm.network :hostonly, $BUILDBOT_IP + + # Install debian packaging dependencies and create debian packages + config.vm.provision :shell, :inline => 'apt-get -qq update; apt-get install -y debhelper autotools-dev golang' + config.vm.provision :shell, :inline => 'cd /data/docker/packaging/debian; make debian' +end + +Vagrant::VERSION < '1.1.0' and Vagrant::Config.run do |config| + v10(config) +end + +Vagrant::VERSION >= '1.1.0' and Vagrant.configure('1') do |config| + v10(config) +end diff --git a/packaging/debian/changelog b/packaging/debian/changelog new file mode 100644 index 000000000..761a879e8 --- /dev/null +++ b/packaging/debian/changelog @@ -0,0 +1,14 @@ +lxc-docker (0.1.4-1) unstable; urgency=low + + Improvements [+], Updates [*], Bug fixes [-]: + * Changed default bridge interface do 'docker0' + - Fix a race condition when running the port allocator + + -- Daniel Mizyrycki Wed, 10 Apr 2013 18:06:21 -0700 + + +lxc-docker (0.1.0-1) unstable; urgency=low + + * Initial release + + -- Daniel Mizyrycki Mon, 29 Mar 2013 18:09:55 -0700 diff --git a/packaging/debian/compat b/packaging/debian/compat new file mode 100644 index 000000000..ec635144f --- /dev/null +++ b/packaging/debian/compat @@ -0,0 +1 @@ +9 diff --git a/packaging/debian/control b/packaging/debian/control new file mode 100644 index 000000000..a09e9aee5 --- /dev/null +++ b/packaging/debian/control @@ -0,0 +1,19 @@ +Source: lxc-docker +Section: admin +Priority: optional +Maintainer: Daniel Mizyrycki +Build-Depends: debhelper (>= 9),autotools-dev,golang +Standards-Version: 3.9.3 +Homepage: http://github.com/dotcloud/docker + +Package: lxc-docker +Architecture: linux-any +Depends: ${misc:Depends},${shlibs:Depends},lxc,bsdtar +Conflicts: docker +Description: lxc-docker is a Linux container runtime + Docker complements LXC with a high-level API which operates at the process + level. It runs unix processes with strong guarantees of isolation and + repeatability across servers. + Docker is a great building block for automating distributed systems: + large-scale web deployments, database clusters, continuous deployment systems, + private PaaS, service-oriented architectures, etc. diff --git a/packaging/debian/copyright b/packaging/debian/copyright new file mode 100644 index 000000000..668c8635e --- /dev/null +++ b/packaging/debian/copyright @@ -0,0 +1,237 @@ +Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: docker +Upstream-Contact: DotCloud Inc +Source: http://github.com/dotcloud/docker + +Files: * +Copyright: 2012, DotCloud Inc +License: Apache-2.0 + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2012 DotCloud Inc + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +Files: src/github.com/kr/pty/* +Copyright: Copyright (c) 2011 Keith Rarick +License: Expat + Copyright (c) 2011 Keith Rarick + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated + documentation files (the "Software"), to deal in the + Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, + sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall + be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY + KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS + OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR + OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packaging/debian/docker.initd b/packaging/debian/docker.initd new file mode 100644 index 000000000..2b6a3c097 --- /dev/null +++ b/packaging/debian/docker.initd @@ -0,0 +1,49 @@ +#!/bin/sh + +### BEGIN INIT INFO +# Provides: docker +# Required-Start: $local_fs +# Required-Stop: $local_fs +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: docker +# Description: docker daemon +### END INIT INFO + +DOCKER=/usr/bin/docker +PIDFILE=/var/run/docker.pid + +# Check docker is present +[ -x $DOCKER ] || log_success_msg "Docker not present" + +# Get lsb functions +. /lib/lsb/init-functions + + +case "$1" in + start) + log_begin_msg "Starting docker..." + start-stop-daemon --start --background --exec "$DOCKER" -- -d + log_end_msg $? + ;; + stop) + log_begin_msg "Stopping docker..." + docker_pid=`pgrep -f "$DOCKER -d"` + [ -n "$docker_pid" ] && kill $docker_pid + log_end_msg $? + ;; + status) + docker_pid=`pgrep -f "$DOCKER -d"` + if [ -z "$docker_pid" ] ; then + echo "docker not running" + else + echo "docker running (pid $docker_pid)" + fi + ;; + *) + echo "Usage: /etc/init.d/docker {start|stop|status}" + exit 1 + ;; +esac + +exit 0 diff --git a/deb/debian/docs b/packaging/debian/docs similarity index 100% rename from deb/debian/docs rename to packaging/debian/docs diff --git a/packaging/debian/lxc-docker.postinst b/packaging/debian/lxc-docker.postinst new file mode 100644 index 000000000..91e251dc8 --- /dev/null +++ b/packaging/debian/lxc-docker.postinst @@ -0,0 +1,13 @@ +#!/bin/sh + +# Ensure cgroup is mounted +if [ -z "`/bin/egrep -e '^cgroup' /etc/fstab`" ]; then + /bin/echo 'cgroup /sys/fs/cgroup cgroup defaults 0 0' >>/etc/fstab +fi +if [ -z "`/bin/mount | /bin/egrep -e '^cgroup'`" ]; then + /bin/mount /sys/fs/cgroup +fi + +# Start docker +/usr/sbin/update-rc.d docker defaults +/etc/init.d/docker start diff --git a/packaging/debian/maintainer.rst b/packaging/debian/maintainer.rst new file mode 100644 index 000000000..111d4fcc3 --- /dev/null +++ b/packaging/debian/maintainer.rst @@ -0,0 +1,16 @@ +Maintainer duty +=============== + +The Debian project specifies the role of a 'maintainer' which is the person +making the Debian package of the program. This role requires an 'sponsor' to +upload the package. As a maintainer you should follow the guide +http://www.debian.org/doc/manuals/maint-guide . Your sponsor will be there +helping you succeed. + +The most relevant information to update is the changelog file: +Each new release should create a new first paragraph with new release version, +changes, and the maintainer information. + +After this is done, follow README.debian to generate the actual source +packages and talk with your sponsor to upload them into the official Debian +package archive. diff --git a/packaging/debian/rules b/packaging/debian/rules new file mode 100755 index 000000000..25f16f9c6 --- /dev/null +++ b/packaging/debian/rules @@ -0,0 +1,13 @@ +#!/usr/bin/make -f +# -*- makefile -*- +# Sample debian/rules that uses debhelper. +# This file was originally written by Joey Hess and Craig Small. +# As a special exception, when this file is copied by dh-make into a +# dh-make output file, you may use that output file without restriction. +# This special exception was added by Craig Small in version 0.37 of dh-make. + +# Uncomment this to turn on verbose mode. +#export DH_VERBOSE=1 + +%: + dh ${@} --with autotools_dev diff --git a/deb/debian/source/format b/packaging/debian/source/format similarity index 100% rename from deb/debian/source/format rename to packaging/debian/source/format diff --git a/packaging/ubuntu/Makefile b/packaging/ubuntu/Makefile new file mode 100644 index 000000000..dbdf1af7a --- /dev/null +++ b/packaging/ubuntu/Makefile @@ -0,0 +1,62 @@ +# Ubuntu package Makefile +# +# Dependencies: debhelper autotools-dev devscripts golang +# Notes: +# Use 'make ubuntu' to create the ubuntu package +# GPG_KEY environment variable needs to contain a GPG private key for package to be signed +# and uploaded to docker PPA. +# If GPG_KEY is not defined, make ubuntu will create docker package and exit with +# status code 2 + +PKG_NAME=lxc-docker +VERSION=$(shell head -1 changelog | sed 's/^.\+(\(.\+\)..).\+$$/\1/') +GITHUB_PATH=github.com/dotcloud/docker +DOCKER_VERSION=${PKG_NAME}_${VERSION} +DOCKER_FVERSION=${PKG_NAME}_$(shell head -1 changelog | sed 's/^.\+(\(.\+\)).\+$$/\1/') +BUILD_SRC=${CURDIR}/../../build_src +VERSION_TAG=v$(shell head -1 changelog | sed 's/^.\+(\(.\+\)-[0-9]\+).\+$$/\1/') + +all: + # Compile docker. Used by dpkg-buildpackage. + cd src/${GITHUB_PATH}/docker; GOPATH=${CURDIR} go build + +install: + # Used by dpkg-buildpackage + mkdir -p ${DESTDIR}/usr/bin + mkdir -p ${DESTDIR}/etc/init + mkdir -p ${DESTDIR}/DEBIAN + install -m 0755 src/${GITHUB_PATH}/docker/docker ${DESTDIR}/usr/bin + install -o root -m 0755 debian/docker.upstart ${DESTDIR}/etc/init/docker.conf + install debian/lxc-docker.prerm ${DESTDIR}/DEBIAN/prerm + install debian/lxc-docker.postinst ${DESTDIR}/DEBIAN/postinst + +ubuntu: + # This Makefile will compile the github master branch of dotcloud/docker + # Retrieve docker project and its go structure from internet + rm -rf ${BUILD_SRC} + git clone $(shell git rev-parse --show-toplevel) ${BUILD_SRC}/${GITHUB_PATH} + cd ${BUILD_SRC}/${GITHUB_PATH}; git checkout ${VERSION_TAG} && GOPATH=${BUILD_SRC} go get -d + # Add debianization + mkdir ${BUILD_SRC}/debian + cp Makefile ${BUILD_SRC} + cp -r * ${BUILD_SRC}/debian + cp ../../README.md ${BUILD_SRC} + # Cleanup + for d in `find ${BUILD_SRC} -name '.git*'`; do rm -rf $$d; done + rm -rf ${BUILD_SRC}/../${DOCKER_VERSION}.orig.tar.gz + rm -rf ${BUILD_SRC}/pkg + # Create docker debian files + cd ${BUILD_SRC}; tar czf ../${DOCKER_VERSION}.orig.tar.gz . + cd ${BUILD_SRC}; dpkg-buildpackage -us -uc + rm -rf ${BUILD_SRC} + # Sign package and upload it to PPA if GPG_KEY environment variable + # holds a private GPG KEY + if /usr/bin/test "$${GPG_KEY}" == ""; then exit 2; fi + mkdir ${BUILD_SRC} + # Import gpg signing key + echo "$${GPG_KEY}" | gpg --allow-secret-key-import --import + # Sign the package + cd ${BUILD_SRC}; dpkg-source -x ${BUILD_SRC}/../${DOCKER_FVERSION}.dsc + cd ${BUILD_SRC}/${PKG_NAME}-${VERSION}; debuild -S -sa + cd ${BUILD_SRC};dput ppa:dotcloud/lxc-docker ${DOCKER_FVERSION}_source.changes + rm -rf ${BUILD_SRC} diff --git a/packaging/ubuntu/README.ubuntu b/packaging/ubuntu/README.ubuntu new file mode 100644 index 000000000..286a6f8d5 --- /dev/null +++ b/packaging/ubuntu/README.ubuntu @@ -0,0 +1,37 @@ +Docker on Ubuntu +================ + +The easiest way to get docker up and running natively on Ubuntu is installing +it from its official PPA:: + + sudo sh -c "echo 'deb http://ppa.launchpad.net/dotcloud/lxc-docker/ubuntu precise main' >>/etc/apt/sources.list" + sudo apt-get update + sudo apt-get install lxc-docker + + +Building docker package +~~~~~~~~~~~~~~~~~~~~~~~ + +The building process is shared by both, developers and maintainers. If you are +a developer, the Makefile will stop with exit status 2 right before signing +the built packages. + +Assuming you are working on an Ubuntu 12.04 TLS system :: + + # Download a fresh copy of the docker project + git clone https://github.com/dotcloud/docker.git + cd docker + + # Get building dependencies + sudo apt-get update; sudo apt-get install -y debhelper autotools-dev devscripts golang + + # Make the ubuntu package + (cd packaging/ubuntu; make ubuntu) + + +Install docker built package +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:: + + sudo dpkg -i lxc-docker_*_amd64.deb; sudo apt-get install -f -y diff --git a/packaging/ubuntu/Vagrantfile b/packaging/ubuntu/Vagrantfile new file mode 100644 index 000000000..0689eea1c --- /dev/null +++ b/packaging/ubuntu/Vagrantfile @@ -0,0 +1,12 @@ +BUILDBOT_IP = '192.168.33.32' + +Vagrant::Config.run do |config| + config.vm.box = 'precise64' + config.vm.box_url = 'http://files.vagrantup.com/precise64.box' + config.vm.share_folder 'v-data', '/data/docker', "#{File.dirname(__FILE__)}/../.." + config.vm.network :hostonly,BUILDBOT_IP + + # Install ubuntu packaging dependencies and create ubuntu packages + config.vm.provision :shell, :inline => 'export DEBIAN_FRONTEND=noninteractive; apt-get -qq update; apt-get install -qq -y git debhelper autotools-dev devscripts golang' + config.vm.provision :shell, :inline => "export GPG_KEY='#{ENV['GPG_KEY']}'; cd /data/docker/packaging/ubuntu; make ubuntu" +end diff --git a/packaging/ubuntu/changelog b/packaging/ubuntu/changelog new file mode 100644 index 000000000..aa5ea6cc8 --- /dev/null +++ b/packaging/ubuntu/changelog @@ -0,0 +1,30 @@ +lxc-docker (0.1.6-1) precise; urgency=low + + Improvements [+], Updates [*], Bug fixes [-]: + + Multiple improvements, updates and bug fixes + + -- dotCloud Wed, 17 Apr 2013 20:43:43 -0700 + + +lxc-docker (0.1.4.1-1) precise; urgency=low + + Improvements [+], Updates [*], Bug fixes [-]: + * Test PPA + + -- dotCloud Mon, 15 Apr 2013 12:14:50 -0700 + + +lxc-docker (0.1.4-1) precise; urgency=low + + Improvements [+], Updates [*], Bug fixes [-]: + * Changed default bridge interface do 'docker0' + - Fix a race condition when running the port allocator + + -- dotCloud Fri, 12 Apr 2013 12:20:06 -0700 + + +lxc-docker (0.1.0-1) unstable; urgency=low + + * Initial release + + -- dotCloud Mon, 25 Mar 2013 05:51:12 -0700 diff --git a/deb/debian/compat b/packaging/ubuntu/compat similarity index 100% rename from deb/debian/compat rename to packaging/ubuntu/compat diff --git a/packaging/ubuntu/control b/packaging/ubuntu/control new file mode 100644 index 000000000..c52303a88 --- /dev/null +++ b/packaging/ubuntu/control @@ -0,0 +1,19 @@ +Source: lxc-docker +Section: misc +Priority: extra +Maintainer: Daniel Mizyrycki +Build-Depends: debhelper,autotools-dev,devscripts,golang +Standards-Version: 3.9.3 +Homepage: http://github.com/dotcloud/docker + +Package: lxc-docker +Architecture: linux-any +Depends: ${misc:Depends},${shlibs:Depends},lxc,bsdtar +Conflicts: docker +Description: lxc-docker is a Linux container runtime + Docker complements LXC with a high-level API which operates at the process + level. It runs unix processes with strong guarantees of isolation and + repeatability across servers. + Docker is a great building block for automating distributed systems: + large-scale web deployments, database clusters, continuous deployment systems, + private PaaS, service-oriented architectures, etc. diff --git a/packaging/ubuntu/copyright b/packaging/ubuntu/copyright new file mode 100644 index 000000000..668c8635e --- /dev/null +++ b/packaging/ubuntu/copyright @@ -0,0 +1,237 @@ +Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: docker +Upstream-Contact: DotCloud Inc +Source: http://github.com/dotcloud/docker + +Files: * +Copyright: 2012, DotCloud Inc +License: Apache-2.0 + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2012 DotCloud Inc + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +Files: src/github.com/kr/pty/* +Copyright: Copyright (c) 2011 Keith Rarick +License: Expat + Copyright (c) 2011 Keith Rarick + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated + documentation files (the "Software"), to deal in the + Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, + sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall + be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY + KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS + OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR + OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/deb/etc/docker.upstart b/packaging/ubuntu/docker.upstart similarity index 50% rename from deb/etc/docker.upstart rename to packaging/ubuntu/docker.upstart index 6cfe9d261..07e7e8a89 100644 --- a/deb/etc/docker.upstart +++ b/packaging/ubuntu/docker.upstart @@ -5,6 +5,6 @@ stop on starting rc RUNLEVEL=[016] respawn script - test -f /etc/default/locale && . /etc/default/locale || true - LANG=$LANG LC_ALL=$LANG /usr/bin/docker -d + # FIXME: docker should not depend on the system having en_US.UTF-8 + LC_ALL='en_US.UTF-8' /usr/bin/docker -d end script diff --git a/packaging/ubuntu/docs b/packaging/ubuntu/docs new file mode 100644 index 000000000..b43bf86b5 --- /dev/null +++ b/packaging/ubuntu/docs @@ -0,0 +1 @@ +README.md diff --git a/packaging/ubuntu/lxc-docker.postinst b/packaging/ubuntu/lxc-docker.postinst new file mode 100644 index 000000000..5d04c5b55 --- /dev/null +++ b/packaging/ubuntu/lxc-docker.postinst @@ -0,0 +1,4 @@ +#!/bin/sh + +# Start docker +/sbin/start docker diff --git a/packaging/ubuntu/lxc-docker.prerm b/packaging/ubuntu/lxc-docker.prerm new file mode 100644 index 000000000..824f15cff --- /dev/null +++ b/packaging/ubuntu/lxc-docker.prerm @@ -0,0 +1,4 @@ +#!/bin/sh + +# Stop docker +/sbin/stop docker diff --git a/packaging/ubuntu/maintainer.ubuntu b/packaging/ubuntu/maintainer.ubuntu new file mode 100644 index 000000000..406498eba --- /dev/null +++ b/packaging/ubuntu/maintainer.ubuntu @@ -0,0 +1,35 @@ +Maintainer duty +=============== + +Ubuntu allows developers to use their PPA (Personal Package Archive) +repository. This is very convenient for the users as they just need to add +the PPA address, update their package database and use the apt-get tool. + +For now, the official lxc-docker package is located on launchpad and can be +accessed adding the following line to /etc/apt/sources.list :: + + + deb http://ppa.launchpad.net/dotcloud/lxc-docker/ubuntu precise main + + +Releasing a new package +~~~~~~~~~~~~~~~~~~~~~~~ + +The most relevant information to update is the changelog file: +Each new release should create a new first paragraph with new release version, +changes, and the maintainer information. + +Assuming your PPA GPG signing key is on /media/usbdrive/docker.key, load it +into the GPG_KEY environment variable with:: + + export GPG_KEY=`cat /media/usbdrive/docker.key` + + +After this is done and you are ready to upload the package to the PPA, you have +a couple of choices: + +* Follow README.debian to generate the actual source packages and upload them + to the PPA +* Let vagrant do all the work for you:: + + ( cd docker/packaging/ubuntu; vagrant up ) diff --git a/deb/debian/rules b/packaging/ubuntu/rules similarity index 100% rename from deb/debian/rules rename to packaging/ubuntu/rules diff --git a/packaging/ubuntu/source/format b/packaging/ubuntu/source/format new file mode 100644 index 000000000..163aaf8d8 --- /dev/null +++ b/packaging/ubuntu/source/format @@ -0,0 +1 @@ +3.0 (quilt) diff --git a/rcli/tcp.go b/rcli/tcp.go index e9dba7f31..cf111cdf7 100644 --- a/rcli/tcp.go +++ b/rcli/tcp.go @@ -138,7 +138,8 @@ func ListenAndServe(proto, addr string, service Service) error { if err != nil { return err } - go func() { + go func(conn DockerConn) { + defer conn.Close() if DEBUG_FLAG { CLIENT_SOCKET = conn } @@ -146,8 +147,7 @@ func ListenAndServe(proto, addr string, service Service) error { log.Println("Error:", err.Error()) fmt.Fprintln(conn, "Error:", err.Error()) } - conn.Close() - }() + }(conn) } } return nil diff --git a/registry.go b/registry.go index 761fc335d..74b166906 100644 --- a/registry.go +++ b/registry.go @@ -7,6 +7,7 @@ import ( "io" "io/ioutil" "net/http" + "os" "path" "strings" ) @@ -97,7 +98,7 @@ func (graph *Graph) LookupRemoteImage(imgId string, authConfig *auth.AuthConfig) func (graph *Graph) getRemoteImage(stdout io.Writer, imgId string, authConfig *auth.AuthConfig) (*Image, Archive, error) { client := &http.Client{} - fmt.Fprintf(stdout, "Pulling %s metadata\n", imgId) + fmt.Fprintf(stdout, "Pulling %s metadata\r\n", imgId) // Get the Json req, err := http.NewRequest("GET", REGISTRY_ENDPOINT+"/images/"+imgId+"/json", nil) if err != nil { @@ -125,7 +126,7 @@ func (graph *Graph) getRemoteImage(stdout io.Writer, imgId string, authConfig *a img.Id = imgId // Get the layer - fmt.Fprintf(stdout, "Pulling %s fs layer\n", imgId) + fmt.Fprintf(stdout, "Pulling %s fs layer\r\n", imgId) req, err = http.NewRequest("GET", REGISTRY_ENDPOINT+"/images/"+imgId+"/layer", nil) if err != nil { return nil, nil, fmt.Errorf("Error while getting from the server: %s\n", err) @@ -135,7 +136,7 @@ func (graph *Graph) getRemoteImage(stdout io.Writer, imgId string, authConfig *a if err != nil { return nil, nil, err } - return img, ProgressReader(res.Body, int(res.ContentLength), stdout), nil + return img, ProgressReader(res.Body, int(res.ContentLength), stdout, "Downloading %v/%v (%v)"), nil } func (graph *Graph) PullImage(stdout io.Writer, imgId string, authConfig *auth.AuthConfig) error { @@ -164,7 +165,7 @@ func (graph *Graph) PullImage(stdout io.Writer, imgId string, authConfig *auth.A func (graph *Graph) PullRepository(stdout io.Writer, remote, askedTag string, repositories *TagStore, authConfig *auth.AuthConfig) error { client := &http.Client{} - fmt.Fprintf(stdout, "Pulling repository %s\n", remote) + fmt.Fprintf(stdout, "Pulling repository %s\r\n", remote) var repositoryTarget string // If we are asking for 'root' repository, lookup on the Library's registry @@ -196,7 +197,7 @@ func (graph *Graph) PullRepository(stdout io.Writer, remote, askedTag string, re return err } for tag, rev := range t { - fmt.Fprintf(stdout, "Pulling tag %s:%s\n", remote, tag) + fmt.Fprintf(stdout, "Pulling tag %s:%s\r\n", remote, tag) if err = graph.PullImage(stdout, rev, authConfig); err != nil { return err } @@ -223,7 +224,7 @@ func (graph *Graph) PushImage(stdout io.Writer, imgOrig *Image, authConfig *auth return fmt.Errorf("Error while retreiving the path for {%s}: %s", img.Id, err) } - fmt.Fprintf(stdout, "Pushing %s metadata\n", img.Id) + fmt.Fprintf(stdout, "Pushing %s metadata\r\n", img.Id) // FIXME: try json with UTF8 jsonData := strings.NewReader(string(jsonRaw)) @@ -253,7 +254,7 @@ func (graph *Graph) PushImage(stdout io.Writer, imgOrig *Image, authConfig *auth } } - fmt.Fprintf(stdout, "Pushing %s fs layer\n", img.Id) + fmt.Fprintf(stdout, "Pushing %s fs layer\r\n", img.Id) req2, err := http.NewRequest("PUT", REGISTRY_ENDPOINT+"/images/"+img.Id+"/layer", nil) req2.SetBasicAuth(authConfig.Username, authConfig.Password) res2, err := client.Do(req2) @@ -269,24 +270,20 @@ func (graph *Graph) PushImage(stdout io.Writer, imgOrig *Image, authConfig *auth return fmt.Errorf("Failed to retrieve layer upload location: %s", err) } - // FIXME: Don't do this :D. Check the S3 requierement and implement chunks of 5MB - // FIXME2: I won't stress it enough, DON'T DO THIS! very high priority - layerData2, err := Tar(path.Join(graph.Root, img.Id, "layer"), Xz) - tmp, err := ioutil.ReadAll(layerData2) + // FIXME: stream the archive directly to the registry instead of buffering it on disk. This requires either: + // a) Implementing S3's proprietary streaming logic, or + // b) Stream directly to the registry instead of S3. + // I prefer option b. because it doesn't lock us into a proprietary cloud service. + tmpLayer, err := graph.TempLayerArchive(img.Id, Xz, stdout) if err != nil { return err } - layerLength := len(tmp) - - layerData, err := Tar(path.Join(graph.Root, img.Id, "layer"), Xz) - if err != nil { - return fmt.Errorf("Failed to generate layer archive: %s", err) - } - req3, err := http.NewRequest("PUT", url.String(), ProgressReader(layerData.(io.ReadCloser), layerLength, stdout)) + defer os.Remove(tmpLayer.Name()) + req3, err := http.NewRequest("PUT", url.String(), ProgressReader(tmpLayer, int(tmpLayer.Size), stdout, "Uploading %v/%v (%v)")) if err != nil { return err } - req3.ContentLength = int64(layerLength) + req3.ContentLength = int64(tmpLayer.Size) req3.TransferEncoding = []string{"none"} res3, err := client.Do(req3) @@ -375,15 +372,15 @@ func (graph *Graph) pushPrimitive(stdout io.Writer, remote, tag, imgId string, a // Check if the local impage exists img, err := graph.Get(imgId) if err != nil { - fmt.Fprintf(stdout, "Skipping tag %s:%s: %s does not exist\n", remote, tag, imgId) + fmt.Fprintf(stdout, "Skipping tag %s:%s: %s does not exist\r\n", remote, tag, imgId) return nil } - fmt.Fprintf(stdout, "Pushing tag %s:%s\n", remote, tag) + fmt.Fprintf(stdout, "Pushing tag %s:%s\r\n", remote, tag) // Push the image if err = graph.PushImage(stdout, img, authConfig); err != nil { return err } - fmt.Fprintf(stdout, "Registering tag %s:%s\n", remote, tag) + fmt.Fprintf(stdout, "Registering tag %s:%s\r\n", remote, tag) // And then the tag if err = graph.pushTag(remote, imgId, tag, authConfig); err != nil { return err @@ -399,7 +396,7 @@ func (graph *Graph) PushRepository(stdout io.Writer, remote string, localRepo Re return fmt.Errorf("Permission denied on repository %s\n", remote) } - fmt.Fprintf(stdout, "Pushing repository %s (%d tags)\n", remote, len(localRepo)) + fmt.Fprintf(stdout, "Pushing repository %s (%d tags)\r\n", remote, len(localRepo)) // For each image within the repo, push them for tag, imgId := range localRepo { if err := graph.pushPrimitive(stdout, remote, tag, imgId, authConfig); err != nil { diff --git a/runtime.go b/runtime.go index 7971fe4f4..b894a2cda 100644 --- a/runtime.go +++ b/runtime.go @@ -6,6 +6,7 @@ import ( "github.com/dotcloud/docker/auth" "io" "io/ioutil" + "log" "os" "os/exec" "path" @@ -14,6 +15,11 @@ import ( "time" ) +type Capabilities struct { + MemoryLimit bool + SwapLimit bool +} + type Runtime struct { root string repository string @@ -23,6 +29,8 @@ type Runtime struct { repositories *TagStore authConfig *auth.AuthConfig idIndex *TruncIndex + capabilities *Capabilities + kernelVersion *KernelVersionInfo } var sysInitPath string @@ -82,6 +90,7 @@ func (runtime *Runtime) Create(config *Config) (*Container, error) { if config.Hostname == "" { config.Hostname = id[:12] } + container := &Container{ // FIXME: we should generate the ID here instead of receiving it as an argument Id: id, @@ -100,6 +109,24 @@ func (runtime *Runtime) Create(config *Config) (*Container, error) { if err := os.Mkdir(container.root, 0700); err != nil { return nil, err } + + // If custom dns exists, then create a resolv.conf for the container + if len(config.Dns) > 0 { + container.ResolvConfPath = path.Join(container.root, "resolv.conf") + f, err := os.Create(container.ResolvConfPath) + if err != nil { + return nil, err + } + defer f.Close() + for _, dns := range config.Dns { + if _, err := f.Write([]byte("nameserver " + dns + "\n")); err != nil { + return nil, err + } + } + } else { + container.ResolvConfPath = "/etc/resolv.conf" + } + // Step 2: save the container json if err := container.ToDisk(); err != nil { return nil, err @@ -119,6 +146,9 @@ func (runtime *Runtime) Load(id string) (*Container, error) { if container.Id != id { return container, fmt.Errorf("Container %s is stored at %s", container.Id, id) } + if container.State.Running { + container.State.Ghost = true + } if err := runtime.Register(container); err != nil { return nil, err } @@ -134,6 +164,9 @@ func (runtime *Runtime) Register(container *Container) error { return err } + // init the wait lock + container.waitLock = make(chan struct{}) + // FIXME: if the container is supposed to be running but is not, auto restart it? // if so, then we need to restart monitor and init a new lock // If the container is supposed to be running, make sure of it @@ -150,6 +183,14 @@ func (runtime *Runtime) Register(container *Container) error { } } } + + // If the container is not running or just has been flagged not running + // then close the wait lock chan (will be reset upon start) + if !container.State.Running { + close(container.waitLock) + } + + // Even if not running, we init the lock (prevents races in start/stop/kill) container.State.initLock() container.runtime = runtime @@ -184,7 +225,7 @@ func (runtime *Runtime) Destroy(container *Container) error { return fmt.Errorf("Container %v not found - maybe it was already destroyed?", container.Id) } - if err := container.Stop(); err != nil { + if err := container.Stop(10); err != nil { return err } if mounted, err := container.Mounted(); err != nil { @@ -205,7 +246,7 @@ func (runtime *Runtime) Destroy(container *Container) error { // Commit creates a new filesystem image from the current state of a container. // The image can optionally be tagged into a repository -func (runtime *Runtime) Commit(id, repository, tag, comment string) (*Image, error) { +func (runtime *Runtime) Commit(id, repository, tag, comment, author string) (*Image, error) { container := runtime.Get(id) if container == nil { return nil, fmt.Errorf("No such container: %s", id) @@ -217,7 +258,7 @@ func (runtime *Runtime) Commit(id, repository, tag, comment string) (*Image, err return nil, err } // Create a new image from the container's base layers + a new layer from container changes - img, err := runtime.graph.Create(rwTar, container, comment) + img, err := runtime.graph.Create(rwTar, container, comment, author) if err != nil { return nil, err } @@ -249,7 +290,38 @@ func (runtime *Runtime) restore() error { // FIXME: harmonize with NewGraph() func NewRuntime() (*Runtime, error) { - return NewRuntimeFromDirectory("/var/lib/docker") + runtime, err := NewRuntimeFromDirectory("/var/lib/docker") + if err != nil { + return nil, err + } + + k, err := GetKernelVersion() + if err != nil { + return nil, err + } + runtime.kernelVersion = k + + if CompareKernelVersion(k, &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0}) < 0 { + log.Printf("WARNING: You are running linux kernel version %s, which might be unstable running docker. Please upgrade your kernel to 3.8.0.", k.String()) + } + + if cgroupMemoryMountpoint, err := FindCgroupMountpoint("memory"); err != nil { + log.Printf("WARNING: %s\n", err) + } else { + _, err1 := ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.limit_in_bytes")) + _, err2 := ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.soft_limit_in_bytes")) + runtime.capabilities.MemoryLimit = err1 == nil && err2 == nil + if !runtime.capabilities.MemoryLimit { + log.Printf("WARNING: Your kernel does not support cgroup memory limit.") + } + + _, err = ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.memsw.limit_in_bytes")) + runtime.capabilities.SwapLimit = err == nil + if !runtime.capabilities.SwapLimit { + log.Printf("WARNING: Your kernel does not support cgroup swap limit.") + } + } + return runtime, nil } func NewRuntimeFromDirectory(root string) (*Runtime, error) { @@ -288,6 +360,7 @@ func NewRuntimeFromDirectory(root string) (*Runtime, error) { repositories: repositories, authConfig: authConfig, idIndex: NewTruncIndex(), + capabilities: &Capabilities{}, } if err := runtime.restore(); err != nil { diff --git a/runtime_test.go b/runtime_test.go index 9ab8b9b1e..c43e8641e 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -1,9 +1,11 @@ package docker import ( + "fmt" "github.com/dotcloud/docker/rcli" "io" "io/ioutil" + "net" "os" "os/exec" "os/user" @@ -12,12 +14,9 @@ import ( "time" ) -// FIXME: this is no longer needed -const testLayerPath string = "/var/lib/docker/docker-ut.tar" const unitTestImageName string = "docker-ut" -var unitTestStoreBase string -var srv *Server +const unitTestStoreBase string = "/var/lib/docker/unit-tests" func nuke(runtime *Runtime) error { var wg sync.WaitGroup @@ -61,15 +60,8 @@ func init() { panic("docker tests needs to be run as root") } - // Create a temp directory - root, err := ioutil.TempDir("", "docker-test") - if err != nil { - panic(err) - } - unitTestStoreBase = root - // Make it our Store root - runtime, err := NewRuntimeFromDirectory(root) + runtime, err := NewRuntimeFromDirectory(unitTestStoreBase) if err != nil { panic(err) } @@ -262,6 +254,47 @@ func TestGet(t *testing.T) { } +// Run a container with a TCP port allocated, and test that it can receive connections on localhost +func TestAllocatePortLocalhost(t *testing.T) { + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + container, err := runtime.Create(&Config{ + Image: GetTestImage(runtime).Id, + Cmd: []string{"sh", "-c", "echo well hello there | nc -l -p 5555"}, + PortSpecs: []string{"5555"}, + }, + ) + if err != nil { + t.Fatal(err) + } + if err := container.Start(); err != nil { + t.Fatal(err) + } + defer container.Kill() + time.Sleep(300 * time.Millisecond) // Wait for the container to run + conn, err := net.Dial("tcp", + fmt.Sprintf( + "localhost:%s", container.NetworkSettings.PortMapping["5555"], + ), + ) + if err != nil { + t.Fatal(err) + } + defer conn.Close() + output, err := ioutil.ReadAll(conn) + if err != nil { + t.Fatal(err) + } + if string(output) != "well hello there\n" { + t.Fatalf("Received wrong output from network connection: should be '%s', not '%s'", + "well hello there\n", + string(output), + ) + } +} + func TestRestore(t *testing.T) { root, err := ioutil.TempDir("", "docker-test") diff --git a/state.go b/state.go index 2ca713092..f51a06b01 100644 --- a/state.go +++ b/state.go @@ -12,11 +12,15 @@ type State struct { ExitCode int StartedAt time.Time l *sync.Mutex + Ghost bool } // String returns a human-readable description of the state func (s *State) String() string { if s.Running { + if s.Ghost { + return fmt.Sprintf("Ghost") + } return fmt.Sprintf("Up %s", HumanDuration(time.Now().Sub(s.StartedAt))) } return fmt.Sprintf("Exit %d", s.ExitCode) diff --git a/sysinit.go b/sysinit.go index a2c06239e..4b2d6c303 100644 --- a/sysinit.go +++ b/sysinit.go @@ -17,8 +17,7 @@ func setupNetworking(gw string) { if gw == "" { return } - cmd := exec.Command("/sbin/route", "add", "default", "gw", gw) - if err := cmd.Run(); err != nil { + if _, err := ip("route", "add", "default", "via", gw); err != nil { log.Fatalf("Unable to set up networking: %v", err) } } @@ -54,8 +53,7 @@ func changeUser(u string) { } // Clear environment pollution introduced by lxc-start -func cleanupEnv() { - env := os.Environ() +func cleanupEnv(env ListOpts) { os.Clearenv() for _, kv := range env { parts := strings.SplitN(kv, "=", 2) @@ -92,10 +90,13 @@ func SysInit() { var u = flag.String("u", "", "username or uid") var gw = flag.String("g", "", "gateway address") + var flEnv ListOpts + flag.Var(&flEnv, "e", "Set environment variables") + flag.Parse() + cleanupEnv(flEnv) setupNetworking(*gw) - cleanupEnv() changeUser(*u) executeProgram(flag.Arg(0), flag.Args()) } diff --git a/utils.go b/utils.go index 68e12b20b..a039ca6eb 100644 --- a/utils.go +++ b/utils.go @@ -12,9 +12,12 @@ import ( "os" "os/exec" "path/filepath" + "regexp" "runtime" + "strconv" "strings" "sync" + "syscall" "time" ) @@ -69,23 +72,30 @@ type progressReader struct { readTotal int // Expected stream length (bytes) readProgress int // How much has been read so far (bytes) lastUpdate int // How many bytes read at least update + template string // Template to print. Default "%v/%v (%v)" } func (r *progressReader) Read(p []byte) (n int, err error) { read, err := io.ReadCloser(r.reader).Read(p) r.readProgress += read - // Only update progress for every 1% read - updateEvery := int(0.01 * float64(r.readTotal)) - if r.readProgress-r.lastUpdate > updateEvery || r.readProgress == r.readTotal { - fmt.Fprintf(r.output, "%d/%d (%.0f%%)\r", - r.readProgress, - r.readTotal, - float64(r.readProgress)/float64(r.readTotal)*100) + updateEvery := 4096 + if r.readTotal > 0 { + // Only update progress for every 1% read + if increment := int(0.01 * float64(r.readTotal)); increment > updateEvery { + updateEvery = increment + } + } + if r.readProgress-r.lastUpdate > updateEvery || err != nil { + if r.readTotal > 0 { + fmt.Fprintf(r.output, r.template+"\r", r.readProgress, r.readTotal, fmt.Sprintf("%.0f%%", float64(r.readProgress)/float64(r.readTotal)*100)) + } else { + fmt.Fprintf(r.output, r.template+"\r", r.readProgress, "?", "n/a") + } r.lastUpdate = r.readProgress } // Send newline when complete - if err == io.EOF { + if err != nil { fmt.Fprintf(r.output, "\n") } @@ -94,8 +104,11 @@ func (r *progressReader) Read(p []byte) (n int, err error) { func (r *progressReader) Close() error { return io.ReadCloser(r.reader).Close() } -func ProgressReader(r io.ReadCloser, size int, output io.Writer) *progressReader { - return &progressReader{r, output, size, 0, 0} +func ProgressReader(r io.ReadCloser, size int, output io.Writer, template string) *progressReader { + if template == "" { + template = "%v/%v (%v)" + } + return &progressReader{r, output, size, 0, 0, template} } // HumanDuration returns a human-readable approximation of a duration @@ -384,3 +397,104 @@ func CopyEscapable(dst io.Writer, src io.ReadCloser) (written int64, err error) } return written, err } + +type KernelVersionInfo struct { + Kernel int + Major int + Minor int + Flavor string +} + +// FIXME: this doens't build on Darwin +func GetKernelVersion() (*KernelVersionInfo, error) { + var uts syscall.Utsname + + if err := syscall.Uname(&uts); err != nil { + return nil, err + } + + release := make([]byte, len(uts.Release)) + + i := 0 + for _, c := range uts.Release { + release[i] = byte(c) + i++ + } + + tmp := strings.SplitN(string(release), "-", 2) + if len(tmp) != 2 { + return nil, fmt.Errorf("Unrecognized kernel version") + } + tmp2 := strings.SplitN(tmp[0], ".", 3) + if len(tmp2) != 3 { + return nil, fmt.Errorf("Unrecognized kernel version") + } + + kernel, err := strconv.Atoi(tmp2[0]) + if err != nil { + return nil, err + } + + major, err := strconv.Atoi(tmp2[1]) + if err != nil { + return nil, err + } + + minor, err := strconv.Atoi(tmp2[2]) + if err != nil { + return nil, err + } + + flavor := tmp[1] + + return &KernelVersionInfo{ + Kernel: kernel, + Major: major, + Minor: minor, + Flavor: flavor, + }, nil +} + +func (k *KernelVersionInfo) String() string { + return fmt.Sprintf("%d.%d.%d-%s", k.Kernel, k.Major, k.Minor, k.Flavor) +} + +// Compare two KernelVersionInfo struct. +// Returns -1 if a < b, = if a == b, 1 it a > b +func CompareKernelVersion(a, b *KernelVersionInfo) int { + if a.Kernel < b.Kernel { + return -1 + } else if a.Kernel > b.Kernel { + return 1 + } + + if a.Major < b.Major { + return -1 + } else if a.Major > b.Major { + return 1 + } + + if a.Minor < b.Minor { + return -1 + } else if a.Minor > b.Minor { + return 1 + } + + return 0 +} + +func FindCgroupMountpoint(cgroupType string) (string, error) { + output, err := exec.Command("mount").CombinedOutput() + if err != nil { + return "", err + } + + reg := regexp.MustCompile(`^cgroup on (.*) type cgroup \(.*` + cgroupType + `[,\)]`) + for _, line := range strings.Split(string(output), "\n") { + r := reg.FindStringSubmatch(line) + if len(r) == 2 { + return r[1], nil + } + } + return "", fmt.Errorf("cgroup mountpoint not found for %s", cgroupType) +} diff --git a/utils_test.go b/utils_test.go index c15084f61..aa2a1b968 100644 --- a/utils_test.go +++ b/utils_test.go @@ -228,3 +228,36 @@ func assertIndexGet(t *testing.T, index *TruncIndex, input, expectedResult strin t.Fatalf("Getting '%s' returned '%s' instead of '%s'", input, result, expectedResult) } } + +func assertKernelVersion(t *testing.T, a, b *KernelVersionInfo, result int) { + if r := CompareKernelVersion(a, b); r != result { + t.Fatalf("Unepected kernel version comparaison result. Found %d, expected %d", r, result) + } +} + +func TestCompareKernelVersion(t *testing.T) { + assertKernelVersion(t, + &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0}, + &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0}, + 0) + assertKernelVersion(t, + &KernelVersionInfo{Kernel: 2, Major: 6, Minor: 0}, + &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0}, + -1) + assertKernelVersion(t, + &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0}, + &KernelVersionInfo{Kernel: 2, Major: 6, Minor: 0}, + 1) + assertKernelVersion(t, + &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0, Flavor: "0"}, + &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0, Flavor: "16"}, + 0) + assertKernelVersion(t, + &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 5}, + &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0}, + 1) + assertKernelVersion(t, + &KernelVersionInfo{Kernel: 3, Major: 0, Minor: 20, Flavor: "25"}, + &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0, Flavor: "0"}, + -1) +} From 97badbd29e15296b2b4cb55b01246f658c5e9de6 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Mon, 22 Apr 2013 22:04:57 -0700 Subject: [PATCH 077/138] Bumped version to 0.1.8 --- commands.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commands.go b/commands.go index b0440a976..85daeb6b8 100644 --- a/commands.go +++ b/commands.go @@ -18,7 +18,7 @@ import ( "unicode" ) -const VERSION = "0.1.7" +const VERSION = "0.1.8" var ( GIT_COMMIT string From 82b8f7a565ec1fdd82644ef715767ea93d95a0b0 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Mon, 22 Apr 2013 22:29:12 -0700 Subject: [PATCH 078/138] hack/dockerbuilder: a standard build environment for building and uploading official binary builds of docker... inside docker --- hack/dockerbuilder/Dockerfile | 11 +++++++++++ hack/dockerbuilder/dockerbuilder | 29 +++++++++++++++++++++++++++++ hack/dockerbuilder/s3cfg | 3 +++ 3 files changed, 43 insertions(+) create mode 100644 hack/dockerbuilder/Dockerfile create mode 100644 hack/dockerbuilder/dockerbuilder create mode 100644 hack/dockerbuilder/s3cfg diff --git a/hack/dockerbuilder/Dockerfile b/hack/dockerbuilder/Dockerfile new file mode 100644 index 000000000..8ef1e40b9 --- /dev/null +++ b/hack/dockerbuilder/Dockerfile @@ -0,0 +1,11 @@ +# This will build a container capable of producing an official binary build of docker and +# uploading it to S3 +from ubuntu:12.10 +run apt-get update +run RUNLEVEL=1 DEBIAN_FRONTEND=noninteractive apt-get install -y -q s3cmd +run RUNLEVEL=1 DEBIAN_FRONTEND=noninteractive apt-get install -y -q golang +run RUNLEVEL=1 DEBIAN_FRONTEND=noninteractive apt-get install -y -q git +run RUNLEVEL=1 DEBIAN_FRONTEND=noninteractive apt-get install -y -q build-essential +copy dockerbuilder /usr/local/bin/dockerbuilder +copy s3cfg /.s3cfg +# run $img dockerbuilder $REVISION_OR_TAG $S3_ID $S3_KEY diff --git a/hack/dockerbuilder/dockerbuilder b/hack/dockerbuilder/dockerbuilder new file mode 100644 index 000000000..8ad057380 --- /dev/null +++ b/hack/dockerbuilder/dockerbuilder @@ -0,0 +1,29 @@ +#!/bin/sh +set -x +set -e + +PACKAGE=github.com/dotcloud/docker + +if [ $# -lt 3 ]; then + echo "Usage: $0 REVISION AWS_ID AWS_KEY" + exit 1 +fi + +export REVISION=$1 AWS_ID=$2 AWS_KEY=$3 + + +export PATH=/usr/local/bin:$PATH + +mkdir -p /go/src/$PACKAGE +git clone "https://$PACKAGE" /go/src/$PACKAGE +cd /go/src/$PACKAGE +git checkout $REVISION + +# FIXME: checkout to specific revision + +BUILDDIR=/tmp/docker-$REVISION +mkdir -p $BUILDDIR +(cd docker && go get && go build -o $BUILDDIR/docker) + +tar -f /tmp/docker.tgz -C $(dirname $BUILDDIR) -zc $(basename $BUILDDIR) +s3cmd -P put /tmp/docker.tgz s3://get.docker.io/builds/$(uname -s)/$(uname -m)/docker-$REVISION.tgz diff --git a/hack/dockerbuilder/s3cfg b/hack/dockerbuilder/s3cfg new file mode 100644 index 000000000..963af7d36 --- /dev/null +++ b/hack/dockerbuilder/s3cfg @@ -0,0 +1,3 @@ +[default] +access_key = $AWS_ID +secret_key = $AWS_KEY From e413340723871ad2d7a03822ca06993ee3c0a4bc Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Tue, 23 Apr 2013 01:09:29 -0600 Subject: [PATCH 079/138] Update FindCgroupMountpoint to be more forgiving On Gentoo, the memory cgroup is mounted at /sys/fs/cgroup/memory, but the mount line looks like the following: memory on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory) (note that the first word on the line is "memory", not "cgroup", but the other essentials are there, namely the type of cgroup and the memory mount option) --- utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils.go b/utils.go index 5974b7df3..8bcf38367 100644 --- a/utils.go +++ b/utils.go @@ -442,7 +442,7 @@ func FindCgroupMountpoint(cgroupType string) (string, error) { return "", err } - reg := regexp.MustCompile(`^cgroup on (.*) type cgroup \(.*` + cgroupType + `[,\)]`) + reg := regexp.MustCompile(`^.* on (.*) type cgroup \(.*` + cgroupType + `[,\)]`) for _, line := range strings.Split(string(output), "\n") { r := reg.FindStringSubmatch(line) if len(r) == 2 { From 4f6cc5c7330a7389d558dece2e432f13249bd565 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Tue, 23 Apr 2013 00:30:18 -0700 Subject: [PATCH 080/138] Completed Changelog for all past versions --- CHANGELOG.md | 93 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 69 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 66d052e6e..e7c774207 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,32 +1,77 @@ # Changelog -## 0.2.0 (dev) - - Fix Vagrant in windows and OSX - - Fix TTY behavior - - Fix attach/detach/run behavior - - Fix memory/fds leaks - - Fix various race conditions - - Fix `docker diff` for removed files - - Fix `docker stop` for ghost containers - - Fix lxc 0.9 compatibility - - Implement an escape sequence `C-p C-q` in order to detach containers in tty mode - - Implement `-a stdin` in order to write on container's stdin while retrieving its ID - - Implement the possiblity to choose the publicly exposed port - - Implement progress bar for registry push/pull - - Improve documentation - - Improve `docker rmi` in order to remove images by name - - Shortened containers and images IDs - - Add cgroup capabilities detection - - Automatically try to load AUFS module - - Automatically create and configure a bridge `dockbr0` - - Remove the standalone mode +## 0.1.8 (2013-04-22) + - Dynamically detect cgroup capabilities + - Issue stability warning on kernels <3.8 + - 'docker push' buffers on disk instead of memory + - Fix 'docker diff' for removed files + - Fix 'docker stop' for ghost containers + - Fix handling of pidfile + - Various bugfixes and stability improvements -## 0.1.0 (03/23/2013) - - Open-source the project +## 0.1.7 (2013-04-18) + - Container ports are available on localhost + - 'docker ps' shows allocated TCP ports + - Contributors can run 'make hack' to start a continuous integration VM + - Streamline ubuntu packaging & uploading + - Various bugfixes and stability improvements + +## 0.1.6 (2013-04-17) + - Record the author an image with 'docker commit -author' + +## 0.1.5 (2013-04-17) + - Disable standalone mode + - Use a custom DNS resolver with 'docker -d -dns' + - Detect ghost containers + - Improve diagnosis of missing system capabilities + - Allow disabling memory limits at compile time + - Add debian packaging + - Documentation: installing on Arch Linux + - Documentation: running Redis on docker + - Fixed lxc 0.9 compatibility + - Automatically load aufs module + - Various bugfixes and stability improvements + +## 0.1.4 (2013-04-09) + - Full support for TTY emulation + - Detach from a TTY session with the escape sequence `C-p C-q` + - Various bugfixes and stability improvements + - Minor UI improvements + - Automatically create our own bridge interface 'docker0' + +## 0.1.3 (2013-04-04) + - Choose TCP frontend port with '-p :PORT' + - Layer format is versioned + - Major reliability improvements to the process manager + - Various bugfixes and stability improvements + +## 0.1.2 (2013-04-03) + - Set container hostname with 'docker run -h' + - Selective attach at run with 'docker run -a [stdin[,stdout[,stderr]]]' + - Various bugfixes and stability improvements + - UI polish + - Progress bar on push/pull + - Use XZ compression by default + - Make IP allocator lazy + +## 0.1.1 (2013-03-31) + - Display shorthand IDs for convenience + - Stabilize process management + - Layers can include a commit message + - Simplified 'docker attach' + - Fixed support for re-attaching + - Various bugfixes and stability improvements + - Auto-download at run + - Auto-login on push + - Beefed up documentation + +## 0.1.0 (2013-03-23) + - First release - Implement registry in order to push/pull images + - TCP port allocation - Fix termcaps on Linux - - Add the documentation + - Add documentation - Add Vagrant support with Vagrantfile - Add unit tests - - Add repository/tags to ease the image management + - Add repository/tags to ease image management - Improve the layer implementation From cbc4eccd50ac80ff6e21caeb386328f8a5fcfaff Mon Sep 17 00:00:00 2001 From: Alexey Shamrin Date: Tue, 23 Apr 2013 12:52:55 +0400 Subject: [PATCH 081/138] fixed typo in buildingblocks.rst --- docs/sources/concepts/buildingblocks.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/concepts/buildingblocks.rst b/docs/sources/concepts/buildingblocks.rst index 0fc859418..d422e6eef 100644 --- a/docs/sources/concepts/buildingblocks.rst +++ b/docs/sources/concepts/buildingblocks.rst @@ -10,7 +10,7 @@ Building blocks Images ------ -An original container image. These are stored on disk and are comparable with what you normally expect from a stoppped virtual machine image. Images are stored (and retrieved from) repository +An original container image. These are stored on disk and are comparable with what you normally expect from a stopped virtual machine image. Images are stored (and retrieved from) repository Images are stored on your local file system under /var/lib/docker/images From 73da7a12e7d6846a12571e6eac1f16502219c9d4 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Tue, 23 Apr 2013 10:12:46 -0700 Subject: [PATCH 082/138] Increased timeout in TCP port allocation test to pass on slower machines --- runtime_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime_test.go b/runtime_test.go index c43e8641e..396454941 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -273,7 +273,7 @@ func TestAllocatePortLocalhost(t *testing.T) { t.Fatal(err) } defer container.Kill() - time.Sleep(300 * time.Millisecond) // Wait for the container to run + time.Sleep(600 * time.Millisecond) // Wait for the container to run conn, err := net.Dial("tcp", fmt.Sprintf( "localhost:%s", container.NetworkSettings.PortMapping["5555"], From 0512cf9c839be2c5370830cb80bcbec400273eac Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Tue, 23 Apr 2013 10:49:58 -0700 Subject: [PATCH 083/138] dockerbuilder: /usr/local/bin is already set by docker --- hack/dockerbuilder/dockerbuilder | 3 --- 1 file changed, 3 deletions(-) diff --git a/hack/dockerbuilder/dockerbuilder b/hack/dockerbuilder/dockerbuilder index 8ad057380..e5331b603 100644 --- a/hack/dockerbuilder/dockerbuilder +++ b/hack/dockerbuilder/dockerbuilder @@ -11,9 +11,6 @@ fi export REVISION=$1 AWS_ID=$2 AWS_KEY=$3 - -export PATH=/usr/local/bin:$PATH - mkdir -p /go/src/$PACKAGE git clone "https://$PACKAGE" /go/src/$PACKAGE cd /go/src/$PACKAGE From 7577f48dc441d14cb600b799498f29f9b4639ffe Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Tue, 23 Apr 2013 10:53:02 -0700 Subject: [PATCH 084/138] dockerbuilder: build in current directory instead /go and /tmp --- hack/dockerbuilder/dockerbuilder | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/hack/dockerbuilder/dockerbuilder b/hack/dockerbuilder/dockerbuilder index e5331b603..50038b195 100644 --- a/hack/dockerbuilder/dockerbuilder +++ b/hack/dockerbuilder/dockerbuilder @@ -11,16 +11,17 @@ fi export REVISION=$1 AWS_ID=$2 AWS_KEY=$3 -mkdir -p /go/src/$PACKAGE -git clone "https://$PACKAGE" /go/src/$PACKAGE -cd /go/src/$PACKAGE +mkdir -p go/src/$PACKAGE +git clone "https://$PACKAGE" go/src/$PACKAGE +cd go/src/$PACKAGE git checkout $REVISION # FIXME: checkout to specific revision -BUILDDIR=/tmp/docker-$REVISION +BUILDDIR=docker-$REVISION mkdir -p $BUILDDIR (cd docker && go get && go build -o $BUILDDIR/docker) -tar -f /tmp/docker.tgz -C $(dirname $BUILDDIR) -zc $(basename $BUILDDIR) +BUILD_ARCHIVE=docker-$REVISION.tgz +tar -f BUILD_ARCHIVE -C $(dirname $BUILDDIR) -zc $(basename $BUILDDIR) s3cmd -P put /tmp/docker.tgz s3://get.docker.io/builds/$(uname -s)/$(uname -m)/docker-$REVISION.tgz From 5a02c9ba0a8e4dc19001a11d00049ec2c76511f0 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Tue, 23 Apr 2013 10:28:40 -0700 Subject: [PATCH 085/138] Make sure the container is well started prior to perform the test --- runtime_test.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/runtime_test.go b/runtime_test.go index 396454941..1c2634f1d 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -273,7 +273,16 @@ func TestAllocatePortLocalhost(t *testing.T) { t.Fatal(err) } defer container.Kill() - time.Sleep(600 * time.Millisecond) // Wait for the container to run + + setTimeout(t, "Waiting for the container to be started timed out", 2*time.Second, func() { + for { + if container.State.Running { + break + } + time.Sleep(10 * time.Millisecond) + } + }) + conn, err := net.Dial("tcp", fmt.Sprintf( "localhost:%s", container.NetworkSettings.PortMapping["5555"], @@ -293,6 +302,7 @@ func TestAllocatePortLocalhost(t *testing.T) { string(output), ) } + container.Wait() } func TestRestore(t *testing.T) { From a22c78523f20c44e90e7a3adbd01a5560ed4f6ae Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Tue, 23 Apr 2013 11:09:48 -0700 Subject: [PATCH 086/138] Wait for the container to finish in TestAttachDisconnect before destroying it --- commands_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/commands_test.go b/commands_test.go index a64b4f4dc..83b480d52 100644 --- a/commands_test.go +++ b/commands_test.go @@ -394,4 +394,5 @@ func TestAttachDisconnect(t *testing.T) { // Try to avoid the timeoout in destroy. Best effort, don't check error cStdin, _ := container.StdinPipe() cStdin.Close() + container.Wait() } From c45beabcd54090bcb31eaa7a5af5878262b7b0e5 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Tue, 23 Apr 2013 11:22:30 -0700 Subject: [PATCH 087/138] Improve TestMultipleAttachRestart to avoid unnecessary warning --- container_test.go | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/container_test.go b/container_test.go index fef5331e3..81ae883c3 100644 --- a/container_test.go +++ b/container_test.go @@ -116,8 +116,8 @@ func TestMultipleAttachRestart(t *testing.T) { if err := container.Start(); err != nil { t.Fatal(err) } - timeout := make(chan bool) - go func() { + + setTimeout(t, "Timeout reading from the process", 3*time.Second, func() { l1, err = bufio.NewReader(stdout1).ReadString('\n') if err != nil { t.Fatal(err) @@ -139,15 +139,8 @@ func TestMultipleAttachRestart(t *testing.T) { if strings.Trim(l3, " \r\n") != "hello" { t.Fatalf("Unexpected output. Expected [%s], received [%s]", "hello", l3) } - timeout <- false - }() - go func() { - time.Sleep(3 * time.Second) - timeout <- true - }() - if <-timeout { - t.Fatalf("Timeout reading from the process") - } + }) + container.Wait() } func TestDiff(t *testing.T) { From 6ebb2491314afb3be4b0b82d14ddba743ec460de Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Tue, 23 Apr 2013 11:25:16 -0700 Subject: [PATCH 088/138] Remove unecessary memeory limit within tests --- container_test.go | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/container_test.go b/container_test.go index 81ae883c3..b498f4f18 100644 --- a/container_test.go +++ b/container_test.go @@ -22,9 +22,8 @@ func TestIdFormat(t *testing.T) { defer nuke(runtime) container1, err := runtime.Create( &Config{ - Image: GetTestImage(runtime).Id, - Cmd: []string{"/bin/sh", "-c", "echo hello world"}, - Memory: 33554432, + Image: GetTestImage(runtime).Id, + Cmd: []string{"/bin/sh", "-c", "echo hello world"}, }, ) if err != nil { @@ -50,7 +49,6 @@ func TestMultipleAttachRestart(t *testing.T) { Image: GetTestImage(runtime).Id, Cmd: []string{"/bin/sh", "-c", "i=1; while [ $i -le 5 ]; do i=`expr $i + 1`; echo hello; done"}, - Memory: 33554432, }, ) if err != nil { @@ -227,9 +225,8 @@ func TestCommitRun(t *testing.T) { defer nuke(runtime) container1, err := runtime.Create( &Config{ - Image: GetTestImage(runtime).Id, - Cmd: []string{"/bin/sh", "-c", "echo hello > /world"}, - Memory: 33554432, + Image: GetTestImage(runtime).Id, + Cmd: []string{"/bin/sh", "-c", "echo hello > /world"}, }, ) if err != nil { @@ -260,9 +257,8 @@ func TestCommitRun(t *testing.T) { container2, err := runtime.Create( &Config{ - Image: img.Id, - Memory: 33554432, - Cmd: []string{"cat", "/world"}, + Image: img.Id, + Cmd: []string{"cat", "/world"}, }, ) if err != nil { @@ -347,9 +343,8 @@ func TestRun(t *testing.T) { defer nuke(runtime) container, err := runtime.Create( &Config{ - Image: GetTestImage(runtime).Id, - Memory: 33554432, - Cmd: []string{"ls", "-al"}, + Image: GetTestImage(runtime).Id, + Cmd: []string{"ls", "-al"}, }, ) if err != nil { From 2485bb2cd20a453c2c5f0fff3a6481ef539da591 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Tue, 23 Apr 2013 11:45:47 -0700 Subject: [PATCH 089/138] dockerbuilder: use a pristine GOPATH, with the fresh checkout registered at the right path (for internal submodules) --- hack/dockerbuilder/dockerbuilder | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/hack/dockerbuilder/dockerbuilder b/hack/dockerbuilder/dockerbuilder index 50038b195..17381e40e 100644 --- a/hack/dockerbuilder/dockerbuilder +++ b/hack/dockerbuilder/dockerbuilder @@ -11,6 +11,7 @@ fi export REVISION=$1 AWS_ID=$2 AWS_KEY=$3 +START=$(pwd) mkdir -p go/src/$PACKAGE git clone "https://$PACKAGE" go/src/$PACKAGE cd go/src/$PACKAGE @@ -18,10 +19,10 @@ git checkout $REVISION # FIXME: checkout to specific revision -BUILDDIR=docker-$REVISION +BUILDDIR=${START}/docker-$REVISION mkdir -p $BUILDDIR -(cd docker && go get && go build -o $BUILDDIR/docker) +(export GOPATH=${START}/go; cd docker && go get -v && go build -v -o $BUILDDIR/docker) -BUILD_ARCHIVE=docker-$REVISION.tgz -tar -f BUILD_ARCHIVE -C $(dirname $BUILDDIR) -zc $(basename $BUILDDIR) -s3cmd -P put /tmp/docker.tgz s3://get.docker.io/builds/$(uname -s)/$(uname -m)/docker-$REVISION.tgz +BUILD_ARCHIVE=${START}/docker-$REVISION.tgz +tar -f $BUILD_ARCHIVE -C $(dirname $BUILDDIR) -zc $(basename $BUILDDIR) +s3cmd -P put $BUILD_ARCHIVE s3://get.docker.io/builds/$(uname -s)/$(uname -m)/docker-$REVISION.tgz From e03b241fb18fa5dab4ec94c3d86eacb21601ac66 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Tue, 23 Apr 2013 12:07:54 -0700 Subject: [PATCH 090/138] dockerbuilder: build with 'make; cp -R ./bin' --- hack/dockerbuilder/dockerbuilder | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/hack/dockerbuilder/dockerbuilder b/hack/dockerbuilder/dockerbuilder index 17381e40e..de9f94f9f 100644 --- a/hack/dockerbuilder/dockerbuilder +++ b/hack/dockerbuilder/dockerbuilder @@ -12,16 +12,13 @@ fi export REVISION=$1 AWS_ID=$2 AWS_KEY=$3 START=$(pwd) -mkdir -p go/src/$PACKAGE -git clone "https://$PACKAGE" go/src/$PACKAGE -cd go/src/$PACKAGE +git clone "https://$PACKAGE" docker-checkout-$REVISION +cd docker-checkout-$REVISION git checkout $REVISION -# FIXME: checkout to specific revision - +make BUILDDIR=${START}/docker-$REVISION -mkdir -p $BUILDDIR -(export GOPATH=${START}/go; cd docker && go get -v && go build -v -o $BUILDDIR/docker) +cp -R ./bin $BUILDDIR BUILD_ARCHIVE=${START}/docker-$REVISION.tgz tar -f $BUILD_ARCHIVE -C $(dirname $BUILDDIR) -zc $(basename $BUILDDIR) From f744cfd5a75e3b1565740c2f266dbfdf7be8727a Mon Sep 17 00:00:00 2001 From: Daniel Mizyrycki Date: Tue, 23 Apr 2013 13:51:03 -0700 Subject: [PATCH 091/138] packaging-ubuntu: update maintainer documentation for changelog file --- packaging/ubuntu/changelog | 108 +++++++++++++++++++++++++---- packaging/ubuntu/maintainer.ubuntu | 12 ++-- 2 files changed, 102 insertions(+), 18 deletions(-) diff --git a/packaging/ubuntu/changelog b/packaging/ubuntu/changelog index aa5ea6cc8..b0c691366 100644 --- a/packaging/ubuntu/changelog +++ b/packaging/ubuntu/changelog @@ -1,30 +1,110 @@ +lxc-docker (0.1.8-1) precise; urgency=low + + - Dynamically detect cgroup capabilities + - Issue stability warning on kernels <3.8 + - 'docker push' buffers on disk instead of memory + - Fix 'docker diff' for removed files + - Fix 'docker stop' for ghost containers + - Fix handling of pidfile + - Various bugfixes and stability improvements + + -- dotCloud Mon, 22 Apr 2013 00:00:00 -0700 + + +lxc-docker (0.1.7-1) precise; urgency=low + + - Container ports are available on localhost + - 'docker ps' shows allocated TCP ports + - Contributors can run 'make hack' to start a continuous integration VM + - Streamline ubuntu packaging & uploading + - Various bugfixes and stability improvements + + -- dotCloud Thu, 18 Apr 2013 00:00:00 -0700 + + lxc-docker (0.1.6-1) precise; urgency=low - Improvements [+], Updates [*], Bug fixes [-]: - + Multiple improvements, updates and bug fixes + - Record the author an image with 'docker commit -author' - -- dotCloud Wed, 17 Apr 2013 20:43:43 -0700 + -- dotCloud Wed, 17 Apr 2013 00:00:00 -0700 -lxc-docker (0.1.4.1-1) precise; urgency=low +lxc-docker (0.1.5-1) precise; urgency=low - Improvements [+], Updates [*], Bug fixes [-]: - * Test PPA + - Disable standalone mode + - Use a custom DNS resolver with 'docker -d -dns' + - Detect ghost containers + - Improve diagnosis of missing system capabilities + - Allow disabling memory limits at compile time + - Add debian packaging + - Documentation: installing on Arch Linux + - Documentation: running Redis on docker + - Fixed lxc 0.9 compatibility + - Automatically load aufs module + - Various bugfixes and stability improvements - -- dotCloud Mon, 15 Apr 2013 12:14:50 -0700 + -- dotCloud Wed, 17 Apr 2013 00:00:00 -0700 lxc-docker (0.1.4-1) precise; urgency=low - Improvements [+], Updates [*], Bug fixes [-]: - * Changed default bridge interface do 'docker0' - - Fix a race condition when running the port allocator + - Full support for TTY emulation + - Detach from a TTY session with the escape sequence `C-p C-q` + - Various bugfixes and stability improvements + - Minor UI improvements + - Automatically create our own bridge interface 'docker0' - -- dotCloud Fri, 12 Apr 2013 12:20:06 -0700 + -- dotCloud Tue, 9 Apr 2013 00:00:00 -0700 -lxc-docker (0.1.0-1) unstable; urgency=low +lxc-docker (0.1.3-1) precise; urgency=low - * Initial release + - Choose TCP frontend port with '-p :PORT' + - Layer format is versioned + - Major reliability improvements to the process manager + - Various bugfixes and stability improvements - -- dotCloud Mon, 25 Mar 2013 05:51:12 -0700 + -- dotCloud Thu, 4 Apr 2013 00:00:00 -0700 + + +lxc-docker (0.1.2-1) precise; urgency=low + + - Set container hostname with 'docker run -h' + - Selective attach at run with 'docker run -a [stdin[,stdout[,stderr]]]' + - Various bugfixes and stability improvements + - UI polish + - Progress bar on push/pull + - Use XZ compression by default + - Make IP allocator lazy + + -- dotCloud Wed, 3 Apr 2013 00:00:00 -0700 + + +lxc-docker (0.1.1-1) precise; urgency=low + + - Display shorthand IDs for convenience + - Stabilize process management + - Layers can include a commit message + - Simplified 'docker attach' + - Fixed support for re-attaching + - Various bugfixes and stability improvements + - Auto-download at run + - Auto-login on push + - Beefed up documentation + + -- dotCloud Sun, 31 Mar 2013 00:00:00 -0700 + + +lxc-docker (0.1.0-1) precise; urgency=low + + - First release + - Implement registry in order to push/pull images + - TCP port allocation + - Fix termcaps on Linux + - Add documentation + - Add Vagrant support with Vagrantfile + - Add unit tests + - Add repository/tags to ease image management + - Improve the layer implementation + + -- dotCloud Sat, 23 Mar 2013 00:00:00 -0700 diff --git a/packaging/ubuntu/maintainer.ubuntu b/packaging/ubuntu/maintainer.ubuntu index 406498eba..07ab0a1f0 100644 --- a/packaging/ubuntu/maintainer.ubuntu +++ b/packaging/ubuntu/maintainer.ubuntu @@ -15,9 +15,12 @@ accessed adding the following line to /etc/apt/sources.list :: Releasing a new package ~~~~~~~~~~~~~~~~~~~~~~~ -The most relevant information to update is the changelog file: +The most relevant information to update is the packaging/ubuntu/changelog file: Each new release should create a new first paragraph with new release version, -changes, and the maintainer information. +changes, and the maintainer information. The core of this paragraph is +located on CHANGELOG.md. Make sure to transcribe it and translate the formats +(eg: packaging/ubuntu/changelog uses 2 spaces for body change descriptions +instead of 1 space from CHANGELOG.md) Assuming your PPA GPG signing key is on /media/usbdrive/docker.key, load it into the GPG_KEY environment variable with:: @@ -28,8 +31,9 @@ into the GPG_KEY environment variable with:: After this is done and you are ready to upload the package to the PPA, you have a couple of choices: -* Follow README.debian to generate the actual source packages and upload them - to the PPA +* Follow packaging/ubuntu/README.ubuntu to generate the actual source packages + and upload them to the PPA + * Let vagrant do all the work for you:: ( cd docker/packaging/ubuntu; vagrant up ) From a8651a23b2cca4c9a9dcc3a6829e9496d68ddef5 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Tue, 23 Apr 2013 18:32:59 -0700 Subject: [PATCH 092/138] make release: build a binary release of the most recent version tag --- Makefile | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index c3e2f7820..94d3bfc96 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,9 @@ DOCKER_PACKAGE := github.com/dotcloud/docker +RELEASE_VERSION := $(shell git tag | grep -E "v[0-9\.]+$$" | sort -nr | head -n 1) +SRCRELEASE := docker-$(RELEASE_VERSION) +BINRELEASE := docker-$(RELEASE_VERSION).tgz +GIT_ROOT := $(shell git rev-parse --show-toplevel) BUILD_DIR := $(CURDIR)/.gopath GOPATH ?= $(BUILD_DIR) @@ -23,7 +27,7 @@ DOCKER_MAIN := $(DOCKER_DIR)/docker DOCKER_BIN_RELATIVE := bin/docker DOCKER_BIN := $(CURDIR)/$(DOCKER_BIN_RELATIVE) -.PHONY: all clean test hack +.PHONY: all clean test hack release $(BINRELEASE) $(SRCRELEASE) all: $(DOCKER_BIN) @@ -36,6 +40,21 @@ $(DOCKER_DIR): @mkdir -p $(dir $@) @ln -sf $(CURDIR)/ $@ +whichrelease: + echo $(RELEASE_VERSION) + +release: $(BINRELEASE) + +$(SRCRELEASE): + rm -fr $(SRCRELEASE) + git clone $(GIT_ROOT) $(SRCRELEASE) + cd $(SRCRELEASE); git checkout -b $(RELEASE_VERSION) + +# A binary release ready to be uploaded to a mirror +$(BINRELEASE): $(SRCRELEASE) + rm -f $(BINRELEASE) + cd $(SRCRELEASE); make; cp -R bin docker-$(RELEASE_VERSION); tar -f ../$(BINRELEASE) -zv -c docker-$(RELEASE_VERSION) + clean: @rm -rf $(dir $(DOCKER_BIN)) ifeq ($(GOPATH), $(BUILD_DIR)) From 8b8c8bf7cb0ed782d727440a0f2c59d464971353 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Tue, 23 Apr 2013 18:50:53 -0700 Subject: [PATCH 093/138] Fix 'make release RELEASE_VERSION=master' --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 94d3bfc96..d4c126acc 100644 --- a/Makefile +++ b/Makefile @@ -48,7 +48,7 @@ release: $(BINRELEASE) $(SRCRELEASE): rm -fr $(SRCRELEASE) git clone $(GIT_ROOT) $(SRCRELEASE) - cd $(SRCRELEASE); git checkout -b $(RELEASE_VERSION) + cd $(SRCRELEASE); git checkout -q $(RELEASE_VERSION) # A binary release ready to be uploaded to a mirror $(BINRELEASE): $(SRCRELEASE) From b3ab0b561ed6dd06a569fa800109d18070886149 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Tue, 23 Apr 2013 19:41:38 -0700 Subject: [PATCH 094/138] Makefile improvements + Convenience rules: srcrelease, deps - Separate dependency vendoring from building the binary (re-download dependencies with 'make deps') --- Makefile | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index d4c126acc..d6cede4f5 100644 --- a/Makefile +++ b/Makefile @@ -27,24 +27,29 @@ DOCKER_MAIN := $(DOCKER_DIR)/docker DOCKER_BIN_RELATIVE := bin/docker DOCKER_BIN := $(CURDIR)/$(DOCKER_BIN_RELATIVE) -.PHONY: all clean test hack release $(BINRELEASE) $(SRCRELEASE) +.PHONY: all clean test hack release srcrelease $(BINRELEASE) $(SRCRELEASE) $(DOCKER_BIN) $(DOCKER_DIR) all: $(DOCKER_BIN) $(DOCKER_BIN): $(DOCKER_DIR) @mkdir -p $(dir $@) - @(cd $(DOCKER_MAIN); go get $(GO_OPTIONS); go build $(GO_OPTIONS) $(BUILD_OPTIONS) -o $@) + @(cd $(DOCKER_MAIN); go build $(GO_OPTIONS) $(BUILD_OPTIONS) -o $@) @echo $(DOCKER_BIN_RELATIVE) is created. $(DOCKER_DIR): @mkdir -p $(dir $@) + @rm -f $@ @ln -sf $(CURDIR)/ $@ + @(cd $(DOCKER_MAIN); go get $(GO_OPTIONS)) whichrelease: echo $(RELEASE_VERSION) release: $(BINRELEASE) +srcrelease: $(SRCRELEASE) +deps: $(DOCKER_DIR) +# A clean checkout of $RELEASE_VERSION, with vendored dependencies $(SRCRELEASE): rm -fr $(SRCRELEASE) git clone $(GIT_ROOT) $(SRCRELEASE) From a0478f726d8751f37e4e20e3cc5e858bfc111180 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Tue, 23 Apr 2013 22:57:34 -0700 Subject: [PATCH 095/138] dockerbuilder: upload most recent Ubuntu package (note version FOO might not yet be packaged at tag vFOO) --- hack/dockerbuilder/Dockerfile | 14 ++++++--- hack/dockerbuilder/dockerbuilder | 54 ++++++++++++++++++++++++-------- 2 files changed, 51 insertions(+), 17 deletions(-) diff --git a/hack/dockerbuilder/Dockerfile b/hack/dockerbuilder/Dockerfile index 8ef1e40b9..bf5a25de9 100644 --- a/hack/dockerbuilder/Dockerfile +++ b/hack/dockerbuilder/Dockerfile @@ -2,10 +2,16 @@ # uploading it to S3 from ubuntu:12.10 run apt-get update -run RUNLEVEL=1 DEBIAN_FRONTEND=noninteractive apt-get install -y -q s3cmd -run RUNLEVEL=1 DEBIAN_FRONTEND=noninteractive apt-get install -y -q golang -run RUNLEVEL=1 DEBIAN_FRONTEND=noninteractive apt-get install -y -q git -run RUNLEVEL=1 DEBIAN_FRONTEND=noninteractive apt-get install -y -q build-essential +run DEBIAN_FRONTEND=noninteractive apt-get install -y -q s3cmd +# Packages required to checkout and build docker +run DEBIAN_FRONTEND=noninteractive apt-get install -y -q golang +run DEBIAN_FRONTEND=noninteractive apt-get install -y -q git +run DEBIAN_FRONTEND=noninteractive apt-get install -y -q build-essential +# Packages required to build an ubuntu package +run DEBIAN_FRONTEND=noninteractive apt-get install -y -q debhelper +run DEBIAN_FRONTEND=noninteractive apt-get install -y -q autotools-dev +copy fake_initctl /usr/local/bin/initctl +run DEBIAN_FRONTEND=noninteractive apt-get install -y -q devscripts copy dockerbuilder /usr/local/bin/dockerbuilder copy s3cfg /.s3cfg # run $img dockerbuilder $REVISION_OR_TAG $S3_ID $S3_KEY diff --git a/hack/dockerbuilder/dockerbuilder b/hack/dockerbuilder/dockerbuilder index de9f94f9f..faec3be08 100644 --- a/hack/dockerbuilder/dockerbuilder +++ b/hack/dockerbuilder/dockerbuilder @@ -4,22 +4,50 @@ set -e PACKAGE=github.com/dotcloud/docker -if [ $# -lt 3 ]; then - echo "Usage: $0 REVISION AWS_ID AWS_KEY" +if [ $# -gt 1 ]; then + echo "Usage: $0 [REVISION]" exit 1 fi -export REVISION=$1 AWS_ID=$2 AWS_KEY=$3 +export REVISION=$1 -START=$(pwd) -git clone "https://$PACKAGE" docker-checkout-$REVISION -cd docker-checkout-$REVISION -git checkout $REVISION +if [ -z "$AWS_ID" ]; then + echo "Warning: environment variable AWS_ID is not set. Won't upload to S3." + NO_S3=1 +fi -make -BUILDDIR=${START}/docker-$REVISION -cp -R ./bin $BUILDDIR +if [ -z "$AWS_KEY" ]; then + echo "Warning: environment variable AWS_KEY is not set. Won't upload to S3." + NO_S3=1 +fi -BUILD_ARCHIVE=${START}/docker-$REVISION.tgz -tar -f $BUILD_ARCHIVE -C $(dirname $BUILDDIR) -zc $(basename $BUILDDIR) -s3cmd -P put $BUILD_ARCHIVE s3://get.docker.io/builds/$(uname -s)/$(uname -m)/docker-$REVISION.tgz +if [ -z "$GPG_KEY" ]; then + echo "Warning: environment variable GPG_KEY is not set. Ubuntu package upload will not succeed." + NO_UBUNTU=1 +fi + +if [ -z "$REVISION" ]; then + rm -fr docker-master + git clone https://github.com/dotcloud/docker docker-master + cd docker-master +else + rm -fr docker-$REVISION + git init docker-$REVISION + cd docker-$REVISION + git fetch -t https://github.com/dotcloud/docker $REVISION + git reset --hard FETCH_HEAD +fi + +if [ -z "$REVISION" ]; then + make release +else + make release RELEASE_VERSION=$REVISION +fi + +if [ -z "$NO_S3" ]; then + s3cmd -P put docker-$REVISION.tgz s3://get.docker.io/builds/$(uname -s)/$(uname -m)/docker-$REVISION.tgz +fi + +if [ -z "$NO_UBUNTU" ]; then + (cd packaging/ubuntu && make ubuntu) +fi From 874a40ed3a8a6cf5711ac377712c189e0761c706 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Tue, 23 Apr 2013 23:04:54 -0700 Subject: [PATCH 096/138] - Dev: dockerbuilder requires a fake initctl because 'apt-get install devscripts' insists on installing a stupid daemon I never asked for in the first place. --- hack/dockerbuilder/fake_initctl | 3 +++ 1 file changed, 3 insertions(+) create mode 100755 hack/dockerbuilder/fake_initctl diff --git a/hack/dockerbuilder/fake_initctl b/hack/dockerbuilder/fake_initctl new file mode 100755 index 000000000..14c46c8e9 --- /dev/null +++ b/hack/dockerbuilder/fake_initctl @@ -0,0 +1,3 @@ +#!/bin/sh + +echo Whatever you say, man From 90668a8a997c5db27ffdcc25c7921c63526b62f4 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Tue, 23 Apr 2013 23:15:09 -0700 Subject: [PATCH 097/138] Bumped version to 0.2.0 --- CHANGELOG.md | 9 +++++++++ commands.go | 2 +- packaging/ubuntu/changelog | 12 ++++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e7c774207..d74766560 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## 0.2.0 (2012-04-23) + - Runtime: ghost containers can be killed and waited for + * Documentation: update install intructions + - Packaging: fix Vagrantfile + - Development: automate releasing binaries and ubuntu packages + + Add a changelog + - Various bugfixes + + ## 0.1.8 (2013-04-22) - Dynamically detect cgroup capabilities - Issue stability warning on kernels <3.8 diff --git a/commands.go b/commands.go index b2f49a080..f0013d41e 100644 --- a/commands.go +++ b/commands.go @@ -18,7 +18,7 @@ import ( "unicode" ) -const VERSION = "0.1.8" +const VERSION = "0.2.0" var ( GIT_COMMIT string diff --git a/packaging/ubuntu/changelog b/packaging/ubuntu/changelog index b0c691366..6499ae8f6 100644 --- a/packaging/ubuntu/changelog +++ b/packaging/ubuntu/changelog @@ -1,3 +1,15 @@ +lxc-docker (0.2.0-1) precise; urgency=low + + - Runtime: ghost containers can be killed and waited for + - Documentation: update install intructions + - Packaging: fix Vagrantfile + - Development: automate releasing binaries and ubuntu packages + - Add a changelog + - Various bugfixes + + -- dotCloud Mon, 23 Apr 2013 00:00:00 -0700 + + lxc-docker (0.1.8-1) precise; urgency=low - Dynamically detect cgroup capabilities From 2726e3649a204d15dcf79d2709e6457b6ca50c14 Mon Sep 17 00:00:00 2001 From: Daniel Mizyrycki Date: Tue, 23 Apr 2013 09:44:09 -0700 Subject: [PATCH 098/138] vagrant; issue #441: Improve main config including aws ubuntu lts dependency --- Vagrantfile | 67 ++++--------- puppet/manifests/quantal64.pp | 17 ---- puppet/modules/docker/manifests/init.pp | 99 -------------------- puppet/modules/docker/templates/dockerd.conf | 12 --- puppet/modules/docker/templates/profile | 30 ------ 5 files changed, 19 insertions(+), 206 deletions(-) delete mode 100644 puppet/manifests/quantal64.pp delete mode 100644 puppet/modules/docker/manifests/init.pp delete mode 100644 puppet/modules/docker/templates/dockerd.conf delete mode 100644 puppet/modules/docker/templates/profile diff --git a/Vagrantfile b/Vagrantfile index 01cfd1427..0c3b4a021 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -1,55 +1,27 @@ # -*- mode: ruby -*- # vi: set ft=ruby : -def v10(config) - config.vm.box = 'precise64' - config.vm.box_url = 'http://files.vagrantup.com/precise64.box' +BOX_NAME = "ubuntu" +BOX_URI = "http://files.vagrantup.com/precise64.box" +PPA_KEY = "E61D797F63561DC6" - # Install ubuntu packaging dependencies and create ubuntu packages - config.vm.provision :shell, :inline => "echo 'deb http://ppa.launchpad.net/dotcloud/lxc-docker/ubuntu precise main' >>/etc/apt/sources.list" - config.vm.provision :shell, :inline => 'export DEBIAN_FRONTEND=noninteractive; apt-get -qq update; apt-get install -qq -y --force-yes lxc-docker' -end - -Vagrant::VERSION < "1.1.0" and Vagrant::Config.run do |config| - v10(config) -end - -Vagrant::VERSION >= "1.1.0" and Vagrant.configure("1") do |config| - v10(config) +Vagrant::Config.run do |config| + # Setup virtual machine box. This VM configuration code is always executed. + config.vm.box = BOX_NAME + config.vm.box_url = BOX_URI + # Add docker PPA key to the local repository and install docker + pkg_cmd = "apt-key adv --keyserver keyserver.ubuntu.com --recv-keys #{PPA_KEY}; " + pkg_cmd << "echo 'deb http://ppa.launchpad.net/dotcloud/lxc-docker/ubuntu precise main' >>/etc/apt/sources.list; " + pkg_cmd << "apt-get update -qq; apt-get install -q -y lxc-docker" + if ARGV.include?("--provider=aws".downcase) + # Add AUFS dependency to amazon's VM + pkg_cmd << "; apt-get install linux-image-extra-3.2.0-40-virtual" + end + config.vm.provision :shell, :inline => pkg_cmd end +# Providers were added on Vagrant >= 1.1.0 Vagrant::VERSION >= "1.1.0" and Vagrant.configure("2") do |config| - config.vm.provider :aws do |aws| - config.vm.box = "dummy" - config.vm.box_url = "https://github.com/mitchellh/vagrant-aws/raw/master/dummy.box" - aws.access_key_id = ENV["AWS_ACCESS_KEY_ID"] - aws.secret_access_key = ENV["AWS_SECRET_ACCESS_KEY"] - aws.keypair_name = ENV["AWS_KEYPAIR_NAME"] - aws.ssh_private_key_path = ENV["AWS_SSH_PRIVKEY"] - aws.region = "us-east-1" - aws.ami = "ami-d0f89fb9" - aws.ssh_username = "ubuntu" - aws.instance_type = "t1.micro" - end - - config.vm.provider :rackspace do |rs| - config.vm.box = "dummy" - config.vm.box_url = "https://github.com/mitchellh/vagrant-rackspace/raw/master/dummy.box" - config.ssh.private_key_path = ENV["RS_PRIVATE_KEY"] - rs.username = ENV["RS_USERNAME"] - rs.api_key = ENV["RS_API_KEY"] - rs.public_key_path = ENV["RS_PUBLIC_KEY"] - rs.flavor = /512MB/ - rs.image = /Ubuntu/ - end - - config.vm.provider :virtualbox do |vb| - config.vm.box = 'precise64' - config.vm.box_url = 'http://files.vagrantup.com/precise64.box' - end -end - -Vagrant::VERSION >= "1.2.0" and Vagrant.configure("2") do |config| config.vm.provider :aws do |aws, override| config.vm.box = "dummy" config.vm.box_url = "https://github.com/mitchellh/vagrant-aws/raw/master/dummy.box" @@ -75,8 +47,7 @@ Vagrant::VERSION >= "1.2.0" and Vagrant.configure("2") do |config| end config.vm.provider :virtualbox do |vb| - config.vm.box = 'precise64' - config.vm.box_url = 'http://files.vagrantup.com/precise64.box' + config.vm.box = BOX_NAME + config.vm.box_url = BOX_URI end - end diff --git a/puppet/manifests/quantal64.pp b/puppet/manifests/quantal64.pp deleted file mode 100644 index 8ef059165..000000000 --- a/puppet/manifests/quantal64.pp +++ /dev/null @@ -1,17 +0,0 @@ -node default { - exec { - "apt_update" : - command => "/usr/bin/apt-get update" - } - - Package { - require => Exec['apt_update'] - } - - group { "puppet": - ensure => "present" - } - - include "docker" - -} diff --git a/puppet/modules/docker/manifests/init.pp b/puppet/modules/docker/manifests/init.pp deleted file mode 100644 index 702b10e71..000000000 --- a/puppet/modules/docker/manifests/init.pp +++ /dev/null @@ -1,99 +0,0 @@ -class virtualbox { - Package { ensure => "installed" } - - # remove some files from the base vagrant image because they're old - file { "/home/vagrant/docker-master": - ensure => absent, - recurse => true, - force => true, - purge => true, - } - file { "/usr/local/bin/dockerd": - ensure => absent, - } - file { "/usr/local/bin/docker": - ensure => absent, - } - - # Set up VirtualBox guest utils - package { "virtualbox-guest-utils": } - exec { "vbox-add" : - command => "/etc/init.d/vboxadd setup", - require => [ - Package["virtualbox-guest-utils"], - Package["linux-headers-3.5.0-25-generic"], ], - } -} - -class docker { - # update this with latest go binary dist - $go_url = "http://go.googlecode.com/files/go1.0.3.linux-amd64.tar.gz" - - Package { ensure => "installed" } - - package { ["lxc", "debootstrap", "wget", "bsdtar", "git", - "linux-image-3.5.0-25-generic", - "linux-image-extra-3.5.0-25-generic", - "linux-headers-3.5.0-25-generic"]: } - - $ec2_version = file("/etc/ec2_version", "/dev/null") - $rax_version = inline_template("<%= %x{/usr/bin/xenstore-read vm-data/provider_data/provider} %>") - - if ($ec2_version) { - $vagrant_user = "ubuntu" - $vagrant_home = "/home/ubuntu" - } elsif ($rax_version) { - $vagrant_user = "root" - $vagrant_home = "/root" - } else { - # virtualbox is the vagrant default, so it should be safe to assume - $vagrant_user = "vagrant" - $vagrant_home = "/home/vagrant" - include virtualbox - } - - 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", - } - - file { "/etc/init/dockerd.conf": - mode => 600, - owner => "root", - group => "root", - content => template("docker/dockerd.conf"), - } - - file { "/opt/go": - owner => $vagrant_user, - group => $vagrant_user, - recurse => true, - } - - file { "${vagrant_home}/.profile": - mode => 644, - owner => $vagrant_user, - group => $vagrant_user, - content => template("docker/profile"), - } - - exec { "build-docker" : - cwd => "/opt/go/src/github.com/dotcloud/docker", - user => $vagrant_user, - environment => "GOPATH=/opt/go", - command => "/usr/local/go/bin/go get -v ./... && /usr/local/go/bin/go install ./docker", - creates => "/opt/go/bin/docker", - logoutput => "on_failure", - require => [ Exec["fetch-go"], File["/opt/go"] ], - } - - service { "dockerd" : - ensure => "running", - start => "/sbin/initctl start dockerd", - stop => "/sbin/initctl stop dockerd", - require => [ Exec["build-docker"], File["/etc/init/dockerd.conf"] ], - name => "dockerd", - provider => "base" - } -} diff --git a/puppet/modules/docker/templates/dockerd.conf b/puppet/modules/docker/templates/dockerd.conf deleted file mode 100644 index 3abb798c2..000000000 --- a/puppet/modules/docker/templates/dockerd.conf +++ /dev/null @@ -1,12 +0,0 @@ -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 - test -f /etc/default/locale && . /etc/default/locale || true - LANG=$LANG LC_ALL=$LANG /opt/go/bin/docker -d >> /var/log/dockerd 2>&1 -end script diff --git a/puppet/modules/docker/templates/profile b/puppet/modules/docker/templates/profile deleted file mode 100644 index 319c9c5be..000000000 --- a/puppet/modules/docker/templates/profile +++ /dev/null @@ -1,30 +0,0 @@ -# ~/.profile: executed by the command interpreter for login shells. -# This file is not read by bash(1), if ~/.bash_profile or ~/.bash_login -# exists. -# see /usr/share/doc/bash/examples/startup-files for examples. -# the files are located in the bash-doc package. - -# the default umask is set in /etc/profile; for setting the umask -# for ssh logins, install and configure the libpam-umask package. -#umask 022 - -# if running bash -if [ -n "$BASH_VERSION" ]; then - # include .bashrc if it exists - if [ -f "$HOME/.bashrc" ]; then - . "$HOME/.bashrc" - fi -fi - -# set PATH so it includes user's private bin if it exists -if [ -d "$HOME/bin" ] ; then - PATH="$HOME/bin:$PATH" -fi - -export GOPATH=/opt/go -export PATH=$PATH:/usr/local/go/bin:$GOPATH/bin - -docker=/opt/go/src/github.com/dotcloud/docker -if [ -d $docker ]; then - cd $docker -fi From ee298d1420ea58cdafa788d63547dadab47ef76d Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Wed, 24 Apr 2013 17:43:41 -0700 Subject: [PATCH 099/138] Specify a different bridge for tests than for regular runtime --- runtime_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/runtime_test.go b/runtime_test.go index 396454941..50028665e 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -60,6 +60,8 @@ func init() { panic("docker tests needs to be run as root") } + NetworkBridgeIface = "testdockbr0" + // Make it our Store root runtime, err := NewRuntimeFromDirectory(unitTestStoreBase) if err != nil { From 50144aeb42283848db730b936d6b5b6332ec6565 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Wed, 24 Apr 2013 19:01:23 -0700 Subject: [PATCH 100/138] Add -r flag to dockerd in order to restart previously running container. Fixes #26 --- commands.go | 4 +-- docker/docker.go | 7 +++--- runtime.go | 63 ++++++++++++++++++++++++++++++------------------ runtime_test.go | 8 +++--- 4 files changed, 50 insertions(+), 32 deletions(-) diff --git a/commands.go b/commands.go index f0013d41e..918855e6c 100644 --- a/commands.go +++ b/commands.go @@ -993,11 +993,11 @@ func (srv *Server) CmdRun(stdin io.ReadCloser, stdout rcli.DockerConn, args ...s return nil } -func NewServer() (*Server, error) { +func NewServer(autoRestart bool) (*Server, error) { if runtime.GOARCH != "amd64" { log.Fatalf("The docker runtime currently only supports amd64 (not %s). This will change in the future. Aborting.", runtime.GOARCH) } - runtime, err := NewRuntime() + runtime, err := NewRuntime(autoRestart) if err != nil { return nil, err } diff --git a/docker/docker.go b/docker/docker.go index f2194c06c..dfd234609 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -28,6 +28,7 @@ func main() { // FIXME: Switch d and D ? (to be more sshd like) flDaemon := flag.Bool("d", false, "Daemon mode") flDebug := flag.Bool("D", false, "Debug mode") + flAutoRestart := flag.Bool("r", false, "Restart previously running containers") bridgeName := flag.String("b", "", "Attach containers to a pre-existing network bridge") pidfile := flag.String("p", "/var/run/docker.pid", "File containing process PID") flag.Parse() @@ -45,7 +46,7 @@ func main() { flag.Usage() return } - if err := daemon(*pidfile); err != nil { + if err := daemon(*pidfile, *flAutoRestart); err != nil { log.Fatal(err) } } else { @@ -82,7 +83,7 @@ func removePidFile(pidfile string) { } } -func daemon(pidfile string) error { +func daemon(pidfile string, autoRestart bool) error { if err := createPidFile(pidfile); err != nil { log.Fatal(err) } @@ -97,7 +98,7 @@ func daemon(pidfile string) error { os.Exit(0) }() - service, err := docker.NewServer() + service, err := docker.NewServer(autoRestart) if err != nil { return err } diff --git a/runtime.go b/runtime.go index d6eb0f3c9..3bd7f4299 100644 --- a/runtime.go +++ b/runtime.go @@ -31,6 +31,7 @@ type Runtime struct { idIndex *TruncIndex capabilities *Capabilities kernelVersion *KernelVersionInfo + autoRestart bool } var sysInitPath string @@ -167,23 +168,6 @@ func (runtime *Runtime) Register(container *Container) error { // init the wait lock container.waitLock = make(chan struct{}) - // FIXME: if the container is supposed to be running but is not, auto restart it? - // if so, then we need to restart monitor and init a new lock - // If the container is supposed to be running, make sure of it - if container.State.Running { - if output, err := exec.Command("lxc-info", "-n", container.Id).CombinedOutput(); err != nil { - return err - } else { - if !strings.Contains(string(output), "RUNNING") { - Debugf("Container %s was supposed to be running be is not.", container.Id) - container.State.setStopped(-127) - if err := container.ToDisk(); err != nil { - return err - } - } - } - } - // Even if not running, we init the lock (prevents races in start/stop/kill) container.State.initLock() @@ -202,11 +186,43 @@ func (runtime *Runtime) Register(container *Container) error { runtime.containers.PushBack(container) runtime.idIndex.Add(container.Id) + // When we actually restart, Start() do the monitoring. + // However, when we simply 'reattach', we have to restart a monitor + nomonitor := false + + // FIXME: if the container is supposed to be running but is not, auto restart it? + // if so, then we need to restart monitor and init a new lock + // If the container is supposed to be running, make sure of it + if container.State.Running { + if output, err := exec.Command("lxc-info", "-n", container.Id).CombinedOutput(); err != nil { + return err + } else { + if !strings.Contains(string(output), "RUNNING") { + Debugf("Container %s was supposed to be running be is not.", container.Id) + if runtime.autoRestart { + Debugf("Restarting") + container.State.Ghost = false + container.State.setStopped(0) + if err := container.Start(); err != nil { + return err + } + nomonitor = true + } else { + Debugf("Marking as stopped") + container.State.setStopped(-127) + if err := container.ToDisk(); err != nil { + return err + } + } + } + } + } + // If the container is not running or just has been flagged not running // then close the wait lock chan (will be reset upon start) if !container.State.Running { close(container.waitLock) - } else { + } else if !nomonitor { container.allocateNetwork() go container.monitor() } @@ -292,8 +308,8 @@ func (runtime *Runtime) restore() error { } // FIXME: harmonize with NewGraph() -func NewRuntime() (*Runtime, error) { - runtime, err := NewRuntimeFromDirectory("/var/lib/docker") +func NewRuntime(autoRestart bool) (*Runtime, error) { + runtime, err := NewRuntimeFromDirectory("/var/lib/docker", autoRestart) if err != nil { return nil, err } @@ -314,19 +330,19 @@ func NewRuntime() (*Runtime, error) { _, err2 := ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.soft_limit_in_bytes")) runtime.capabilities.MemoryLimit = err1 == nil && err2 == nil if !runtime.capabilities.MemoryLimit { - log.Printf("WARNING: Your kernel does not support cgroup memory limit.") + log.Printf("WARNING: Your kernel does not support cgroup memory limit.") } _, err = ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.memsw.limit_in_bytes")) runtime.capabilities.SwapLimit = err == nil if !runtime.capabilities.SwapLimit { - log.Printf("WARNING: Your kernel does not support cgroup swap limit.") + log.Printf("WARNING: Your kernel does not support cgroup swap limit.") } } return runtime, nil } -func NewRuntimeFromDirectory(root string) (*Runtime, error) { +func NewRuntimeFromDirectory(root string, autoRestart bool) (*Runtime, error) { runtimeRepo := path.Join(root, "containers") if err := os.MkdirAll(runtimeRepo, 0700); err != nil && !os.IsExist(err) { @@ -363,6 +379,7 @@ func NewRuntimeFromDirectory(root string) (*Runtime, error) { authConfig: authConfig, idIndex: NewTruncIndex(), capabilities: &Capabilities{}, + autoRestart: autoRestart, } if err := runtime.restore(); err != nil { diff --git a/runtime_test.go b/runtime_test.go index 50028665e..2622939b6 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -63,7 +63,7 @@ func init() { NetworkBridgeIface = "testdockbr0" // Make it our Store root - runtime, err := NewRuntimeFromDirectory(unitTestStoreBase) + runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, false) if err != nil { panic(err) } @@ -89,7 +89,7 @@ func newTestRuntime() (*Runtime, error) { return nil, err } - runtime, err := NewRuntimeFromDirectory(root) + runtime, err := NewRuntimeFromDirectory(root, false) if err != nil { return nil, err } @@ -310,7 +310,7 @@ func TestRestore(t *testing.T) { t.Fatal(err) } - runtime1, err := NewRuntimeFromDirectory(root) + runtime1, err := NewRuntimeFromDirectory(root, false) if err != nil { t.Fatal(err) } @@ -369,7 +369,7 @@ func TestRestore(t *testing.T) { // Here are are simulating a docker restart - that is, reloading all containers // from scratch - runtime2, err := NewRuntimeFromDirectory(root) + runtime2, err := NewRuntimeFromDirectory(root, false) if err != nil { t.Fatal(err) } From 9d8743a7aed88083b1a7c24a1a684e7a79108c15 Mon Sep 17 00:00:00 2001 From: Brian McCallister Date: Thu, 25 Apr 2013 05:59:31 -0600 Subject: [PATCH 101/138] vmware fusion provider config --- Vagrantfile | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Vagrantfile b/Vagrantfile index 0c3b4a021..41ec6965b 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -50,4 +50,9 @@ Vagrant::VERSION >= "1.1.0" and Vagrant.configure("2") do |config| config.vm.box = BOX_NAME config.vm.box_url = BOX_URI end + + config.vm.provider :vmware_fusion do |vm| + config.vm.box = "precise64" + config.vm.box_url = "http://files.vagrantup.com/precise64_vmware_fusion.box" + end end From 9c7293508dd849bf9086b5095b7412214d56b801 Mon Sep 17 00:00:00 2001 From: Brian McCallister Date: Thu, 25 Apr 2013 06:09:04 -0600 Subject: [PATCH 102/138] get aufs dependencies into vmware image --- Vagrantfile | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Vagrantfile b/Vagrantfile index 41ec6965b..c62980fde 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -54,5 +54,10 @@ Vagrant::VERSION >= "1.1.0" and Vagrant.configure("2") do |config| config.vm.provider :vmware_fusion do |vm| config.vm.box = "precise64" config.vm.box_url = "http://files.vagrantup.com/precise64_vmware_fusion.box" + config.vm.provision :shell, :inline => <<-UPDATE + apt-get update + apt-get dist-upgrade + apt-get install linux-image-extra-3.2.0-40-virtual + UPDATE end end From 4db680fda47306586819a41b80a9a2f6a67f0eb9 Mon Sep 17 00:00:00 2001 From: Brian McCallister Date: Thu, 25 Apr 2013 06:29:13 -0600 Subject: [PATCH 103/138] don't fight the box kernel version, not worth it --- Vagrantfile | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Vagrantfile b/Vagrantfile index c62980fde..2738d0ff7 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -55,9 +55,7 @@ Vagrant::VERSION >= "1.1.0" and Vagrant.configure("2") do |config| config.vm.box = "precise64" config.vm.box_url = "http://files.vagrantup.com/precise64_vmware_fusion.box" config.vm.provision :shell, :inline => <<-UPDATE - apt-get update - apt-get dist-upgrade - apt-get install linux-image-extra-3.2.0-40-virtual + apt-get install -y linux-image-extra-3.2.0-29-virtual UPDATE end end From 51d6228261cdc379aade581ee504b2c59a3e02a9 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 25 Apr 2013 16:48:31 -0700 Subject: [PATCH 104/138] Implement -config and -command in CmdCommit in order to allow autorun --- commands.go | 25 ++++++++++++++++++------ graph.go | 3 ++- image.go | 1 + runtime.go | 56 +++++++++++++++++++++++++++++++++++++++++++++++++---- 4 files changed, 74 insertions(+), 11 deletions(-) diff --git a/commands.go b/commands.go index f0013d41e..8e0a35172 100644 --- a/commands.go +++ b/commands.go @@ -477,7 +477,7 @@ func (srv *Server) CmdImport(stdin io.ReadCloser, stdout rcli.DockerConn, args . } archive = ProgressReader(resp.Body, int(resp.ContentLength), stdout, "Importing %v/%v (%v)") } - img, err := srv.runtime.graph.Create(archive, nil, "Imported from "+src, "") + img, err := srv.runtime.graph.Create(archive, nil, "Imported from "+src, "", nil) if err != nil { return err } @@ -726,6 +726,8 @@ func (srv *Server) CmdCommit(stdin io.ReadCloser, stdout io.Writer, args ...stri "Create a new image from a container's changes") flComment := cmd.String("m", "", "Commit message") flAuthor := cmd.String("author", "", "Author (eg. \"John Hannibal Smith \"") + flConfig := cmd.String("config", "", "Config automatically applied when the image is run. This option must be the last one.") + flCommand := cmd.String("command", "", "Command to run when starting the image") if err := cmd.Parse(args); err != nil { return nil } @@ -734,7 +736,22 @@ func (srv *Server) CmdCommit(stdin io.ReadCloser, stdout io.Writer, args ...stri cmd.Usage() return nil } - img, err := srv.runtime.Commit(containerName, repository, tag, *flComment, *flAuthor) + + var config []string + if *flConfig != "" { + config = strings.Split(*flConfig, " ") + } + if *flCommand != "" { + config = append(config, "", "/bin/sh", "-c", *flCommand) + } else if *flConfig != "" { + config = append(config, "", "") + } + c, err := ParseRun(config, stdout, srv.runtime.capabilities) + if err != nil { + return err + } + + img, err := srv.runtime.Commit(containerName, repository, tag, *flComment, *flAuthor, c) if err != nil { return err } @@ -925,10 +942,6 @@ func (srv *Server) CmdRun(stdin io.ReadCloser, stdout rcli.DockerConn, args ...s fmt.Fprintln(stdout, "Error: Image not specified") return fmt.Errorf("Image not specified") } - if len(config.Cmd) == 0 { - fmt.Fprintln(stdout, "Error: Command not specified") - return fmt.Errorf("Command not specified") - } if config.Tty { stdout.SetOptionRawTerminal() diff --git a/graph.go b/graph.go index c0e500091..bf22bb19f 100644 --- a/graph.go +++ b/graph.go @@ -84,13 +84,14 @@ func (graph *Graph) Get(name string) (*Image, error) { } // Create creates a new image and registers it in the graph. -func (graph *Graph) Create(layerData Archive, container *Container, comment, author string) (*Image, error) { +func (graph *Graph) Create(layerData Archive, container *Container, comment, author string, config *Config) (*Image, error) { img := &Image{ Id: GenerateId(), Comment: comment, Created: time.Now(), DockerVersion: VERSION, Author: author, + Config: config, } if container != nil { img.Parent = container.Image diff --git a/image.go b/image.go index 78a7f02c6..09c0f8dcf 100644 --- a/image.go +++ b/image.go @@ -24,6 +24,7 @@ type Image struct { ContainerConfig Config `json:"container_config,omitempty"` DockerVersion string `json:"docker_version,omitempty"` Author string `json:"author,omitempty"` + Config *Config `json:"config,omitempty"` graph *Graph } diff --git a/runtime.go b/runtime.go index d6eb0f3c9..9d2d889e8 100644 --- a/runtime.go +++ b/runtime.go @@ -77,12 +77,59 @@ func (runtime *Runtime) containerRoot(id string) string { return path.Join(runtime.repository, id) } +func (runtime *Runtime) mergeConfig(userConf, imageConf *Config) { + if userConf.Hostname != "" { + userConf.Hostname = imageConf.Hostname + } + if userConf.User != "" { + userConf.User = imageConf.User + } + if userConf.Memory == 0 { + userConf.Memory = imageConf.Memory + } + if userConf.MemorySwap == 0 { + userConf.MemorySwap = imageConf.MemorySwap + } + if userConf.PortSpecs == nil || len(userConf.PortSpecs) == 0 { + userConf.PortSpecs = imageConf.PortSpecs + } + if !userConf.Tty { + userConf.Tty = userConf.Tty + } + if !userConf.OpenStdin { + userConf.OpenStdin = imageConf.OpenStdin + } + if !userConf.StdinOnce { + userConf.StdinOnce = imageConf.StdinOnce + } + if userConf.Env == nil || len(userConf.Env) == 0 { + userConf.Env = imageConf.Env + } + if userConf.Cmd == nil || len(userConf.Cmd) == 0 { + userConf.Cmd = imageConf.Cmd + } + if userConf.Dns == nil || len(userConf.Dns) == 0 { + userConf.Dns = imageConf.Dns + } +} + func (runtime *Runtime) Create(config *Config) (*Container, error) { + // Lookup image img, err := runtime.repositories.LookupImage(config.Image) if err != nil { return nil, err } + + //runtime.mergeConfig(config, img.Config) + if img.Config != nil { + config = img.Config + } + + if config.Cmd == nil { + return nil, fmt.Errorf("No command specified") + } + // Generate id id := GenerateId() // Generate default hostname @@ -103,6 +150,7 @@ func (runtime *Runtime) Create(config *Config) (*Container, error) { // FIXME: do we need to store this in the container? SysInitPath: sysInitPath, } + container.root = runtime.containerRoot(container.Id) // Step 1: create the container directory. // This doubles as a barrier to avoid race conditions. @@ -249,7 +297,7 @@ func (runtime *Runtime) Destroy(container *Container) error { // Commit creates a new filesystem image from the current state of a container. // The image can optionally be tagged into a repository -func (runtime *Runtime) Commit(id, repository, tag, comment, author string) (*Image, error) { +func (runtime *Runtime) Commit(id, repository, tag, comment, author string, config *Config) (*Image, error) { container := runtime.Get(id) if container == nil { return nil, fmt.Errorf("No such container: %s", id) @@ -261,7 +309,7 @@ func (runtime *Runtime) Commit(id, repository, tag, comment, author string) (*Im return nil, err } // Create a new image from the container's base layers + a new layer from container changes - img, err := runtime.graph.Create(rwTar, container, comment, author) + img, err := runtime.graph.Create(rwTar, container, comment, author, config) if err != nil { return nil, err } @@ -314,13 +362,13 @@ func NewRuntime() (*Runtime, error) { _, err2 := ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.soft_limit_in_bytes")) runtime.capabilities.MemoryLimit = err1 == nil && err2 == nil if !runtime.capabilities.MemoryLimit { - log.Printf("WARNING: Your kernel does not support cgroup memory limit.") + log.Printf("WARNING: Your kernel does not support cgroup memory limit.") } _, err = ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.memsw.limit_in_bytes")) runtime.capabilities.SwapLimit = err == nil if !runtime.capabilities.SwapLimit { - log.Printf("WARNING: Your kernel does not support cgroup swap limit.") + log.Printf("WARNING: Your kernel does not support cgroup swap limit.") } } return runtime, nil From 724e2d6b0aa5b1cbe95f39c5d22e733124cee9be Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 25 Apr 2013 17:02:38 -0700 Subject: [PATCH 105/138] Update unit test in order to comply with new api --- container_test.go | 4 ++-- graph_test.go | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/container_test.go b/container_test.go index fef5331e3..1a23d7315 100644 --- a/container_test.go +++ b/container_test.go @@ -193,7 +193,7 @@ func TestDiff(t *testing.T) { if err != nil { t.Error(err) } - img, err := runtime.graph.Create(rwTar, container1, "unit test commited image - diff", "") + img, err := runtime.graph.Create(rwTar, container1, "unit test commited image - diff", "", nil) if err != nil { t.Error(err) } @@ -258,7 +258,7 @@ func TestCommitRun(t *testing.T) { if err != nil { t.Error(err) } - img, err := runtime.graph.Create(rwTar, container1, "unit test commited image", "") + img, err := runtime.graph.Create(rwTar, container1, "unit test commited image", "", nil) if err != nil { t.Error(err) } diff --git a/graph_test.go b/graph_test.go index 1bd05aaa9..b7ec81698 100644 --- a/graph_test.go +++ b/graph_test.go @@ -62,7 +62,7 @@ func TestGraphCreate(t *testing.T) { if err != nil { t.Fatal(err) } - image, err := graph.Create(archive, nil, "Testing", "") + image, err := graph.Create(archive, nil, "Testing", "", nil) if err != nil { t.Fatal(err) } @@ -122,7 +122,7 @@ func TestMount(t *testing.T) { if err != nil { t.Fatal(err) } - image, err := graph.Create(archive, nil, "Testing", "") + image, err := graph.Create(archive, nil, "Testing", "", nil) if err != nil { t.Fatal(err) } @@ -166,7 +166,7 @@ func createTestImage(graph *Graph, t *testing.T) *Image { if err != nil { t.Fatal(err) } - img, err := graph.Create(archive, nil, "Test image", "") + img, err := graph.Create(archive, nil, "Test image", "", nil) if err != nil { t.Fatal(err) } @@ -181,7 +181,7 @@ func TestDelete(t *testing.T) { t.Fatal(err) } assertNImages(graph, t, 0) - img, err := graph.Create(archive, nil, "Bla bla", "") + img, err := graph.Create(archive, nil, "Bla bla", "", nil) if err != nil { t.Fatal(err) } @@ -192,11 +192,11 @@ func TestDelete(t *testing.T) { assertNImages(graph, t, 0) // Test 2 create (same name) / 1 delete - img1, err := graph.Create(archive, nil, "Testing", "") + img1, err := graph.Create(archive, nil, "Testing", "", nil) if err != nil { t.Fatal(err) } - if _, err = graph.Create(archive, nil, "Testing", ""); err != nil { + if _, err = graph.Create(archive, nil, "Testing", "", nil); err != nil { t.Fatal(err) } assertNImages(graph, t, 2) From 30d327d37ecca58a41f4a370b581ee638ed3ff04 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 25 Apr 2013 17:03:13 -0700 Subject: [PATCH 106/138] Add TestCommitAutoRun --- container_test.go | 78 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/container_test.go b/container_test.go index 1a23d7315..397e5c371 100644 --- a/container_test.go +++ b/container_test.go @@ -226,6 +226,84 @@ func TestDiff(t *testing.T) { } } +func TestCommitAutoRun(t *testing.T) { + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) + container1, err := runtime.Create( + &Config{ + Image: GetTestImage(runtime).Id, + Cmd: []string{"/bin/sh", "-c", "echo hello > /world"}, + }, + ) + if err != nil { + t.Fatal(err) + } + defer runtime.Destroy(container1) + + if container1.State.Running { + t.Errorf("Container shouldn't be running") + } + if err := container1.Run(); err != nil { + t.Fatal(err) + } + if container1.State.Running { + t.Errorf("Container shouldn't be running") + } + + rwTar, err := container1.ExportRw() + if err != nil { + t.Error(err) + } + img, err := runtime.graph.Create(rwTar, container1, "unit test commited image", "", &Config{Cmd: []string{"cat", "/world"}}) + if err != nil { + t.Error(err) + } + + // FIXME: Make a TestCommit that stops here and check docker.root/layers/img.id/world + + container2, err := runtime.Create( + &Config{ + Image: img.Id, + }, + ) + if err != nil { + t.Fatal(err) + } + defer runtime.Destroy(container2) + stdout, err := container2.StdoutPipe() + if err != nil { + t.Fatal(err) + } + stderr, err := container2.StderrPipe() + if err != nil { + t.Fatal(err) + } + if err := container2.Start(); err != nil { + t.Fatal(err) + } + container2.Wait() + output, err := ioutil.ReadAll(stdout) + if err != nil { + t.Fatal(err) + } + output2, err := ioutil.ReadAll(stderr) + if err != nil { + t.Fatal(err) + } + if err := stdout.Close(); err != nil { + t.Fatal(err) + } + if err := stderr.Close(); err != nil { + t.Fatal(err) + } + if string(output) != "hello\n" { + t.Fatalf("Unexpected output. Expected %s, received: %s (err: %s)", "hello\n", output, output2) + } +} + func TestCommitRun(t *testing.T) { runtime, err := newTestRuntime() if err != nil { From 86ad98e72aa764a236984b3cc4ab8d59071b876a Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Fri, 26 Apr 2013 08:54:29 -0600 Subject: [PATCH 107/138] Add contrib/mkimage-debian.sh used to create the tianon/debian images --- contrib/mkimage-debian.sh | 45 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100755 contrib/mkimage-debian.sh diff --git a/contrib/mkimage-debian.sh b/contrib/mkimage-debian.sh new file mode 100755 index 000000000..64cd13922 --- /dev/null +++ b/contrib/mkimage-debian.sh @@ -0,0 +1,45 @@ +#!/bin/bash +set -e + +latestSuite='wheezy' + +repo="$1" +suite="${2:-$latestSuite}" +mirror="${3:-http://ftp.us.debian.org/debian}" + +if [ ! "$repo" ]; then + echo >&2 "usage: $0 repo [suite [mirror]]" + echo >&2 " ie: $0 tianon/debian squeeze" + exit 1 +fi + +target="/tmp/docker-rootfs-$$-$RANDOM-debian-$suite" + +cd "$(dirname "$(readlink -f "$BASH_SOURCE")")" +returnTo="$(pwd -P)" + +set -x + +# bootstrap +mkdir -p "$target" +sudo debootstrap --verbose --variant=minbase --include=iproute,iputils-ping "$suite" "$target" "$mirror" + +cd "$target" + +# create the image +img=$(sudo tar -c . | docker import -) + +# tag suite +docker tag $img $repo $suite + +if [ "$suite" = "$latestSuite" ]; then + # tag latest + docker tag $img $repo latest +fi + +# test the image +docker run -i -t $repo:$suite echo success + +# cleanup +cd "$returnTo" +sudo rm -rf "$target" From ae97477284fade20520b5991709d6b65d5fd4442 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Fri, 26 Apr 2013 10:48:33 -0700 Subject: [PATCH 108/138] Remove -command in CmdCommit and make -config use Json --- commands.go | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/commands.go b/commands.go index 8e0a35172..3349a1aa2 100644 --- a/commands.go +++ b/commands.go @@ -726,8 +726,7 @@ func (srv *Server) CmdCommit(stdin io.ReadCloser, stdout io.Writer, args ...stri "Create a new image from a container's changes") flComment := cmd.String("m", "", "Commit message") flAuthor := cmd.String("author", "", "Author (eg. \"John Hannibal Smith \"") - flConfig := cmd.String("config", "", "Config automatically applied when the image is run. This option must be the last one.") - flCommand := cmd.String("command", "", "Command to run when starting the image") + flConfig := cmd.String("config", "", "Config automatically applied when the image is run. "+`(ex: -config '{"Cmd": ["cat", "/world"], "PortSpecs": ["22"]}')`) if err := cmd.Parse(args); err != nil { return nil } @@ -737,21 +736,14 @@ func (srv *Server) CmdCommit(stdin io.ReadCloser, stdout io.Writer, args ...stri return nil } - var config []string + config := &Config{} if *flConfig != "" { - config = strings.Split(*flConfig, " ") - } - if *flCommand != "" { - config = append(config, "", "/bin/sh", "-c", *flCommand) - } else if *flConfig != "" { - config = append(config, "", "") - } - c, err := ParseRun(config, stdout, srv.runtime.capabilities) - if err != nil { - return err + if err := json.Unmarshal([]byte(*flConfig), config); err != nil { + return err + } } - img, err := srv.runtime.Commit(containerName, repository, tag, *flComment, *flAuthor, c) + img, err := srv.runtime.Commit(containerName, repository, tag, *flComment, *flAuthor, config) if err != nil { return err } From 9042535f5af607f2362fa3f995427e1c5aab664e Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Fri, 26 Apr 2013 14:32:55 -0700 Subject: [PATCH 109/138] Move the capabilities detection into a runtime method --- runtime.go | 39 ++++++++++++++++++++++----------------- runtime_test.go | 2 +- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/runtime.go b/runtime.go index 3bd7f4299..4ea11b85b 100644 --- a/runtime.go +++ b/runtime.go @@ -307,6 +307,27 @@ func (runtime *Runtime) restore() error { return nil } +func (runtime *Runtime) UpdateCapabilities(quiet bool) { + if cgroupMemoryMountpoint, err := FindCgroupMountpoint("memory"); err != nil { + if !quiet { + log.Printf("WARNING: %s\n", err) + } + } else { + _, err1 := ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.limit_in_bytes")) + _, err2 := ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.soft_limit_in_bytes")) + runtime.capabilities.MemoryLimit = err1 == nil && err2 == nil + if !runtime.capabilities.MemoryLimit && !quiet { + log.Printf("WARNING: Your kernel does not support cgroup memory limit.") + } + + _, err = ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.memsw.limit_in_bytes")) + runtime.capabilities.SwapLimit = err == nil + if !runtime.capabilities.SwapLimit && !quiet { + log.Printf("WARNING: Your kernel does not support cgroup swap limit.") + } + } +} + // FIXME: harmonize with NewGraph() func NewRuntime(autoRestart bool) (*Runtime, error) { runtime, err := NewRuntimeFromDirectory("/var/lib/docker", autoRestart) @@ -322,23 +343,7 @@ func NewRuntime(autoRestart bool) (*Runtime, error) { log.Printf("WARNING: You are running linux kernel version %s, which might be unstable running docker. Please upgrade your kernel to 3.8.0.", k.String()) } } - - if cgroupMemoryMountpoint, err := FindCgroupMountpoint("memory"); err != nil { - log.Printf("WARNING: %s\n", err) - } else { - _, err1 := ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.limit_in_bytes")) - _, err2 := ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.soft_limit_in_bytes")) - runtime.capabilities.MemoryLimit = err1 == nil && err2 == nil - if !runtime.capabilities.MemoryLimit { - log.Printf("WARNING: Your kernel does not support cgroup memory limit.") - } - - _, err = ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.memsw.limit_in_bytes")) - runtime.capabilities.SwapLimit = err == nil - if !runtime.capabilities.SwapLimit { - log.Printf("WARNING: Your kernel does not support cgroup swap limit.") - } - } + runtime.UpdateCapabilities(false) return runtime, nil } diff --git a/runtime_test.go b/runtime_test.go index d069afd00..e9be838c0 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -93,7 +93,7 @@ func newTestRuntime() (*Runtime, error) { if err != nil { return nil, err } - + runtime.UpdateCapabilities(true) return runtime, nil } From 4b3354af3fa4cea59271ace7cc4d1d072312b688 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Sun, 28 Apr 2013 12:31:28 -0600 Subject: [PATCH 110/138] Improve mkimage-debian script to also tag using the release version number of the final image (6.0.7, 7.0, etc.) This is as discussed on #447. --- contrib/mkimage-debian.sh | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/contrib/mkimage-debian.sh b/contrib/mkimage-debian.sh index 64cd13922..dcc8e0848 100755 --- a/contrib/mkimage-debian.sh +++ b/contrib/mkimage-debian.sh @@ -1,7 +1,16 @@ #!/bin/bash set -e -latestSuite='wheezy' +# these should match the names found at http://www.debian.org/releases/ +stableSuite='squeeze' +testingSuite='wheezy' +unstableSuite='sid' + +# if suite is equal to this, it gets the "latest" tag +latestSuite="$testingSuite" + +variant='minbase' +include='iproute,iputils-ping' repo="$1" suite="${2:-$latestSuite}" @@ -22,7 +31,7 @@ set -x # bootstrap mkdir -p "$target" -sudo debootstrap --verbose --variant=minbase --include=iproute,iputils-ping "$suite" "$target" "$mirror" +sudo debootstrap --verbose --variant="$variant" --include="$include" "$suite" "$target" "$mirror" cd "$target" @@ -40,6 +49,13 @@ fi # test the image docker run -i -t $repo:$suite echo success +# unstable's version numbers match testing (since it's mostly just a sandbox for testing), so it doesn't get a version number tag +if [ "$suite" != "$unstableSuite" -a "$suite" != 'unstable' ]; then + # tag the specific version + ver=$(docker run $repo:$suite cat /etc/debian_version) + docker tag $img $repo $ver +fi + # cleanup cd "$returnTo" sudo rm -rf "$target" From ab34115b42cbcb42cb91593601d6daff2c7f32ad Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Sun, 28 Apr 2013 13:37:52 -0600 Subject: [PATCH 111/138] Use default mirror from debootstrap when not explicitly provided, and add better target directory naming --- contrib/mkimage-debian.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/mkimage-debian.sh b/contrib/mkimage-debian.sh index dcc8e0848..ace555ada 100755 --- a/contrib/mkimage-debian.sh +++ b/contrib/mkimage-debian.sh @@ -14,7 +14,7 @@ include='iproute,iputils-ping' repo="$1" suite="${2:-$latestSuite}" -mirror="${3:-http://ftp.us.debian.org/debian}" +mirror="${3:-}" # stick to the default debootstrap mirror if one is not provided if [ ! "$repo" ]; then echo >&2 "usage: $0 repo [suite [mirror]]" @@ -22,7 +22,7 @@ if [ ! "$repo" ]; then exit 1 fi -target="/tmp/docker-rootfs-$$-$RANDOM-debian-$suite" +target="/tmp/docker-rootfs-debian-$suite-$$-$RANDOM" cd "$(dirname "$(readlink -f "$BASH_SOURCE")")" returnTo="$(pwd -P)" From ebe157ebb567965e05cca45a1221cd36ec48a052 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Sun, 28 Apr 2013 01:27:56 -0700 Subject: [PATCH 112/138] Update the crashTest to have the dockerpath in env --- contrib/crashTest.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contrib/crashTest.go b/contrib/crashTest.go index fa9cda605..a15d0e47e 100644 --- a/contrib/crashTest.go +++ b/contrib/crashTest.go @@ -5,10 +5,11 @@ import ( "log" "os" "os/exec" + "path" "time" ) -const DOCKER_PATH = "/home/creack/dotcloud/docker/docker/docker" +var DOCKER_PATH string = path.Join(os.Getenv("DOCKERPATH"), "docker") func runDaemon() (*exec.Cmd, error) { os.Remove("/var/run/docker.pid") From 20c2a4f80f4171ffb59ea61bdcf57d8edc970fa8 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Sun, 28 Apr 2013 03:54:22 -0700 Subject: [PATCH 113/138] add network endpoint for crashTest --- contrib/crashTest.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/contrib/crashTest.go b/contrib/crashTest.go index a15d0e47e..3c4ea894f 100644 --- a/contrib/crashTest.go +++ b/contrib/crashTest.go @@ -1,8 +1,10 @@ package main import ( + "fmt" "io" "log" + "net" "os" "os/exec" "path" @@ -39,17 +41,35 @@ func crashTest() error { return err } + var endpoint string + if ep := os.Getenv("TEST_ENDPOINT"); ep == "" { + endpoint = "192.168.56.1:7979" + } else { + endpoint = ep + } + conn, _ := net.Dial("tcp", endpoint) + + restartCount := 0 + totalTestCount := 1 for { daemon, err := runDaemon() if err != nil { return err } + if conn != nil { + fmt.Fprintf(conn, "RESTART: %d\n", restartCount) + } + restartCount++ // time.Sleep(5000 * time.Millisecond) var stop bool go func() error { stop = false for i := 0; i < 100 && !stop; i++ { func() error { + if conn != nil { + fmt.Fprintf(conn, "TEST: %d\n", totalTestCount) + } + totalTestCount++ cmd := exec.Command(DOCKER_PATH, "run", "base", "echo", "hello", "world") log.Printf("%d", i) outPipe, err := cmd.StdoutPipe() From 76a1a7cf5ba2d2db2c7e5873df529f3f956a2156 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Sun, 28 Apr 2013 06:23:02 -0700 Subject: [PATCH 114/138] Simplify the crashTest --- contrib/crashTest.go | 40 ++++++++++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/contrib/crashTest.go b/contrib/crashTest.go index 3c4ea894f..cc280ae53 100644 --- a/contrib/crashTest.go +++ b/contrib/crashTest.go @@ -1,6 +1,7 @@ package main import ( + "bufio" "fmt" "io" "log" @@ -13,8 +14,10 @@ import ( var DOCKER_PATH string = path.Join(os.Getenv("DOCKERPATH"), "docker") +// WARNING: this crashTest will 1) crash your host, 2) remove all containers func runDaemon() (*exec.Cmd, error) { os.Remove("/var/run/docker.pid") + exec.Command("rm", "-rf", "/var/lib/docker/containers") cmd := exec.Command(DOCKER_PATH, "-d") outPipe, err := cmd.StdoutPipe() if err != nil { @@ -47,7 +50,19 @@ func crashTest() error { } else { endpoint = ep } - conn, _ := net.Dial("tcp", endpoint) + + c := make(chan bool) + var conn io.Writer + + go func() { + conn, _ = net.Dial("tcp", endpoint) + c <- false + }() + go func() { + time.Sleep(2 * time.Second) + c <- true + }() + <-c restartCount := 0 totalTestCount := 1 @@ -56,22 +71,17 @@ func crashTest() error { if err != nil { return err } - if conn != nil { - fmt.Fprintf(conn, "RESTART: %d\n", restartCount) - } restartCount++ // time.Sleep(5000 * time.Millisecond) var stop bool go func() error { stop = false - for i := 0; i < 100 && !stop; i++ { + for i := 0; i < 100 && !stop; { func() error { if conn != nil { - fmt.Fprintf(conn, "TEST: %d\n", totalTestCount) + fmt.Fprintf(conn, "%d\n", totalTestCount) } - totalTestCount++ cmd := exec.Command(DOCKER_PATH, "run", "base", "echo", "hello", "world") - log.Printf("%d", i) outPipe, err := cmd.StdoutPipe() if err != nil { return err @@ -92,6 +102,20 @@ func crashTest() error { go inPipe.Write([]byte("hello world!!!!!\n")) inPipe.Close() + go func() error { + r := bufio.NewReader(outPipe) + if out, err := r.ReadString('\n'); err != nil { + return err + } else if out == "hello world\n" { + log.Printf("%d", i) + if conn != nil { + fmt.Fprintf(conn, "%d\n", totalTestCount) + } + i++ + totalTestCount++ + } + return nil + }() if err := cmd.Wait(); err != nil { return err } From cdc2657ee9bdb1464a17d9daecc0659297d0ee83 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Sun, 28 Apr 2013 07:10:58 -0700 Subject: [PATCH 115/138] Improve crashTest --- contrib/crashTest.go | 54 ++++++++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/contrib/crashTest.go b/contrib/crashTest.go index cc280ae53..58e4cd782 100644 --- a/contrib/crashTest.go +++ b/contrib/crashTest.go @@ -1,7 +1,6 @@ package main import ( - "bufio" "fmt" "io" "log" @@ -17,7 +16,7 @@ var DOCKER_PATH string = path.Join(os.Getenv("DOCKERPATH"), "docker") // WARNING: this crashTest will 1) crash your host, 2) remove all containers func runDaemon() (*exec.Cmd, error) { os.Remove("/var/run/docker.pid") - exec.Command("rm", "-rf", "/var/lib/docker/containers") + exec.Command("rm", "-rf", "/var/lib/docker/containers").Run() cmd := exec.Command(DOCKER_PATH, "-d") outPipe, err := cmd.StdoutPipe() if err != nil { @@ -86,10 +85,10 @@ func crashTest() error { if err != nil { return err } - inPipe, err := cmd.StdinPipe() - if err != nil { - return err - } + // inPipe, err := cmd.StdinPipe() + // if err != nil { + // return err + // } if err := cmd.Start(); err != nil { return err } @@ -97,27 +96,34 @@ func crashTest() error { io.Copy(os.Stdout, outPipe) }() // Expecting error, do not check - inPipe.Write([]byte("hello world!!!!!\n")) - go inPipe.Write([]byte("hello world!!!!!\n")) - go inPipe.Write([]byte("hello world!!!!!\n")) - inPipe.Close() + // inPipe.Write([]byte("hello world!!!!!\n")) + // go inPipe.Write([]byte("hello world!!!!!\n")) + // go inPipe.Write([]byte("hello world!!!!!\n")) + // inPipe.Close() - go func() error { - r := bufio.NewReader(outPipe) - if out, err := r.ReadString('\n'); err != nil { - return err - } else if out == "hello world\n" { - log.Printf("%d", i) - if conn != nil { - fmt.Fprintf(conn, "%d\n", totalTestCount) - } - i++ - totalTestCount++ - } - return nil - }() + // go func() error { + // r := bufio.NewReader(outPipe) + // if out, err := r.ReadString('\n'); err != nil { + // return err + // } else if out == "hello world\n" { + // log.Printf("%d", i) + // if conn != nil { + // fmt.Fprintf(conn, "%d\n", totalTestCount) + // } + // i++ + // totalTestCount++ + // } + // return nil + // }() if err := cmd.Wait(); err != nil { return err + } else { + log.Printf("%d", i) + if conn != nil { + fmt.Fprintf(conn, "%d\n", totalTestCount) + } + i++ + totalTestCount++ } outPipe.Close() return nil From 5051c20833cd3310d27564fe20f856f7e77b92f3 Mon Sep 17 00:00:00 2001 From: Bruno Bigras Date: Mon, 29 Apr 2013 15:53:50 -0300 Subject: [PATCH 116/138] Use the 80 port with keyserver.ubuntu.com Use the 80 port with keyserver.ubuntu.com so it works with corporate firewalls --- Vagrantfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Vagrantfile b/Vagrantfile index 2738d0ff7..4cde1f049 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -10,7 +10,7 @@ Vagrant::Config.run do |config| config.vm.box = BOX_NAME config.vm.box_url = BOX_URI # Add docker PPA key to the local repository and install docker - pkg_cmd = "apt-key adv --keyserver keyserver.ubuntu.com --recv-keys #{PPA_KEY}; " + pkg_cmd = "apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys #{PPA_KEY}; " pkg_cmd << "echo 'deb http://ppa.launchpad.net/dotcloud/lxc-docker/ubuntu precise main' >>/etc/apt/sources.list; " pkg_cmd << "apt-get update -qq; apt-get install -q -y lxc-docker" if ARGV.include?("--provider=aws".downcase) From c6119da33925fccf6af3f232f7aa7c75de41493b Mon Sep 17 00:00:00 2001 From: Al Tobey Date: Tue, 30 Apr 2013 17:37:43 +0000 Subject: [PATCH 117/138] Use /proc/mounts instead of mount(8) Specifically, Ubuntu Precise's cgroup-lite script uses mount -n to mount the cgroup filesystems so they don't appear in mtab, so detection always fails unless the admin updates mtab with /proc/mounts. /proc/mounts is valid on just about every Linux machine in existence and as a bonus is much easier to parse. I also removed the regex in favor of a more accurate parser that should also support monolitic cgroup mounts (e.g. mount -t cgroup none /cgroup). --- utils.go | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/utils.go b/utils.go index 8bcf38367..297b798af 100644 --- a/utils.go +++ b/utils.go @@ -12,7 +12,6 @@ import ( "os" "os/exec" "path/filepath" - "regexp" "runtime" "strings" "sync" @@ -437,17 +436,23 @@ func CompareKernelVersion(a, b *KernelVersionInfo) int { } func FindCgroupMountpoint(cgroupType string) (string, error) { - output, err := exec.Command("mount").CombinedOutput() + output, err := ioutil.ReadFile("/proc/mounts") if err != nil { return "", err } - reg := regexp.MustCompile(`^.* on (.*) type cgroup \(.*` + cgroupType + `[,\)]`) + // /proc/mounts has 6 fields per line, one mount per line, e.g. + // cgroup /sys/fs/cgroup/devices cgroup rw,relatime,devices 0 0 for _, line := range strings.Split(string(output), "\n") { - r := reg.FindStringSubmatch(line) - if len(r) == 2 { - return r[1], nil + parts := strings.Split(line, " ") + if parts[2] == "cgroup" { + for _, opt := range strings.Split(parts[3], ",") { + if opt == cgroupType { + return parts[1], nil + } + } } } + return "", fmt.Errorf("cgroup mountpoint not found for %s", cgroupType) } From d97661aa715321a017190bd24870473ac6df34a3 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Tue, 30 Apr 2013 11:16:26 -0700 Subject: [PATCH 118/138] Improve crashTest --- contrib/crashTest.go | 51 +++++++++++++------------------------------- 1 file changed, 15 insertions(+), 36 deletions(-) diff --git a/contrib/crashTest.go b/contrib/crashTest.go index 58e4cd782..d4a889e8d 100644 --- a/contrib/crashTest.go +++ b/contrib/crashTest.go @@ -77,53 +77,32 @@ func crashTest() error { stop = false for i := 0; i < 100 && !stop; { func() error { - if conn != nil { - fmt.Fprintf(conn, "%d\n", totalTestCount) - } - cmd := exec.Command(DOCKER_PATH, "run", "base", "echo", "hello", "world") + cmd := exec.Command(DOCKER_PATH, "run", "base", "echo", fmt.Sprintf("%d", totalTestCount)) + i++ + totalTestCount++ outPipe, err := cmd.StdoutPipe() if err != nil { return err } - // inPipe, err := cmd.StdinPipe() - // if err != nil { - // return err - // } + inPipe, err := cmd.StdinPipe() + if err != nil { + return err + } if err := cmd.Start(); err != nil { return err } - go func() { - io.Copy(os.Stdout, outPipe) - }() + if conn != nil { + go io.Copy(conn, outPipe) + } + // Expecting error, do not check - // inPipe.Write([]byte("hello world!!!!!\n")) - // go inPipe.Write([]byte("hello world!!!!!\n")) - // go inPipe.Write([]byte("hello world!!!!!\n")) - // inPipe.Close() + inPipe.Write([]byte("hello world!!!!!\n")) + go inPipe.Write([]byte("hello world!!!!!\n")) + go inPipe.Write([]byte("hello world!!!!!\n")) + inPipe.Close() - // go func() error { - // r := bufio.NewReader(outPipe) - // if out, err := r.ReadString('\n'); err != nil { - // return err - // } else if out == "hello world\n" { - // log.Printf("%d", i) - // if conn != nil { - // fmt.Fprintf(conn, "%d\n", totalTestCount) - // } - // i++ - // totalTestCount++ - // } - // return nil - // }() if err := cmd.Wait(); err != nil { return err - } else { - log.Printf("%d", i) - if conn != nil { - fmt.Fprintf(conn, "%d\n", totalTestCount) - } - i++ - totalTestCount++ } outPipe.Close() return nil From 038ca5ee392b974b1ace08d0a6359b02006e6aac Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Wed, 1 May 2013 00:14:52 -0700 Subject: [PATCH 119/138] docker-build: added support for 'maintainer' keyword --- contrib/docker-build/docker-build | 18 ++++++++++-------- contrib/docker-build/example.changefile | 1 + 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/contrib/docker-build/docker-build b/contrib/docker-build/docker-build index f2fc34068..f0313c423 100755 --- a/contrib/docker-build/docker-build +++ b/contrib/docker-build/docker-build @@ -49,26 +49,27 @@ def docker(args, stdin=None): def image_exists(img): return docker(["inspect", img]).read().strip() != "" -def run_and_commit(img_in, cmd, stdin=None): +def run_and_commit(img_in, cmd, stdin=None, author=None): run_id = docker(["run"] + (["-i", "-a", "stdin"] if stdin else ["-d"]) + [img_in, "/bin/sh", "-c", cmd], stdin=stdin).read().rstrip() print "---> Waiting for " + run_id result=int(docker(["wait", run_id]).read().rstrip()) if result != 0: print "!!! '{}' return non-zero exit code '{}'. Aborting.".format(cmd, result) sys.exit(1) - return docker(["commit", run_id]).read().rstrip() + return docker(["commit"] + (["-author", author] if author else []) + [run_id]).read().rstrip() -def insert(base, src, dst): +def insert(base, src, dst, author=None): print "COPY {} to {} in {}".format(src, dst, base) if dst == "": raise Exception("Missing destination path") stdin = file(src) stdin.seek(0) - return run_and_commit(base, "cat > {0}; chmod +x {0}".format(dst), stdin=stdin) + return run_and_commit(base, "cat > {0}; chmod +x {0}".format(dst), stdin=stdin, author=author) def main(): base="" + maintainer="" steps = [] try: for line in sys.stdin.readlines(): @@ -77,19 +78,20 @@ def main(): if line == "" or line[0] == "#": continue op, param = line.split(" ", 1) + print op.upper() + " " + param if op == "from": - print "FROM " + param base = param steps.append(base) + elif op == "maintainer": + maintainer = param elif op == "run": - print "RUN " + param - result = run_and_commit(base, param) + result = run_and_commit(base, param, author=maintainer) steps.append(result) base = result print "===> " + base elif op == "copy": src, dst = param.split(" ", 1) - result = insert(base, src, dst) + result = insert(base, src, dst, author=maintainer) steps.append(result) base = result print "===> " + base diff --git a/contrib/docker-build/example.changefile b/contrib/docker-build/example.changefile index 19261de82..7cd482095 100644 --- a/contrib/docker-build/example.changefile +++ b/contrib/docker-build/example.changefile @@ -1,4 +1,5 @@ # Start build from a know base image +maintainer Solomon Hykes from base:ubuntu-12.10 # Update ubuntu sources run echo 'deb http://archive.ubuntu.com/ubuntu quantal main universe multiverse' > /etc/apt/sources.list From 40ccf1d30039a7ee975ddb2fc8aa989b1fa680fc Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Wed, 1 May 2013 00:42:11 -0700 Subject: [PATCH 120/138] new Dockerfile keyword: 'push' --- contrib/docker-build/docker-build | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/contrib/docker-build/docker-build b/contrib/docker-build/docker-build index f0313c423..85bebaefd 100755 --- a/contrib/docker-build/docker-build +++ b/contrib/docker-build/docker-build @@ -65,7 +65,13 @@ def insert(base, src, dst, author=None): stdin = file(src) stdin.seek(0) return run_and_commit(base, "cat > {0}; chmod +x {0}".format(dst), stdin=stdin, author=author) - + +def push(base, dst, author=None): + print "PUSH to {} in {}".format(dst, base) + if dst == "": + raise Exception("Missing argument to push") + tar = subprocess.Popen(["tar", "-c", "."], stdout=subprocess.PIPE).stdout + return run_and_commit(base, "mkdir -p '{0}' && tar -C '{0}' -x".format(dst), stdin=tar, author=author) def main(): base="" @@ -95,6 +101,11 @@ def main(): steps.append(result) base = result print "===> " + base + elif op == "push": + result = push(base, param.strip(), author=maintainer) + steps.append(result) + base=result + print "===> " + base else: print "Skipping uknown op " + op except: From 03b83b3210865a1981cec43dbc9fe5d24995c060 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Wed, 1 May 2013 00:44:36 -0700 Subject: [PATCH 121/138] Fix example dockerfile --- contrib/docker-build/example.changefile | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/contrib/docker-build/example.changefile b/contrib/docker-build/example.changefile index 7cd482095..d76bbb438 100644 --- a/contrib/docker-build/example.changefile +++ b/contrib/docker-build/example.changefile @@ -6,7 +6,8 @@ run echo 'deb http://archive.ubuntu.com/ubuntu quantal main universe multiverse' run apt-get update # Install system packages run DEBIAN_FRONTEND=noninteractive apt-get install -y -q git -run DEBIAN_FRONTEND=noninteractive apt-get install -y -q curl -run DEBIAN_FRONTEND=noninteractive apt-get install -y -q golang +run DEBIAN_FRONTEND=noninteractive apt-get install -y -q curl +run DEBIAN_FRONTEND=noninteractive apt-get install -y -q golang # Insert files from the host (./myscript must be present in the current directory) -copy myscript /usr/local/bin/myscript +copy myscript /usr/local/bin/myscript +push /src From a3ce90b78bf5edcacdfd4312e8ec9e25b59d6abb Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Wed, 1 May 2013 00:49:28 -0700 Subject: [PATCH 122/138] Added dummy script for docker-build example --- contrib/docker-build/myscript | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 contrib/docker-build/myscript diff --git a/contrib/docker-build/myscript b/contrib/docker-build/myscript new file mode 100644 index 000000000..a6ffda5f1 --- /dev/null +++ b/contrib/docker-build/myscript @@ -0,0 +1,3 @@ +#!/bin/sh + +echo hello, world! From 904c2a0fc353ba08e93decf169baa4ea35fe35a8 Mon Sep 17 00:00:00 2001 From: Ken Cochrane Date: Wed, 1 May 2013 12:31:46 -0400 Subject: [PATCH 123/138] added the registry API to the docker docs --- docs/sources/index.rst | 1 + docs/sources/registry/api.rst | 464 ++++++++++++++++++ docs/sources/registry/index.rst | 15 + .../static_files/docker_pull_chart.png | Bin 0 -> 24445 bytes .../static_files/docker_push_chart.png | Bin 0 -> 30219 bytes 5 files changed, 480 insertions(+) create mode 100644 docs/sources/registry/api.rst create mode 100644 docs/sources/registry/index.rst create mode 100644 docs/sources/static_files/docker_pull_chart.png create mode 100644 docs/sources/static_files/docker_push_chart.png diff --git a/docs/sources/index.rst b/docs/sources/index.rst index cc21a69bf..4e724a0cd 100644 --- a/docs/sources/index.rst +++ b/docs/sources/index.rst @@ -15,6 +15,7 @@ This documentation has the following resources: examples/index contributing/index commandline/index + registry/index faq diff --git a/docs/sources/registry/api.rst b/docs/sources/registry/api.rst new file mode 100644 index 000000000..1cca9fb24 --- /dev/null +++ b/docs/sources/registry/api.rst @@ -0,0 +1,464 @@ +=================== +Docker Registry API +=================== + +.. contents:: Table of Contents + +1. The 3 roles +=============== + +1.1 Index +--------- + +The Index is responsible for centralizing information about: +- User accounts +- Checksums of the images +- Public namespaces + +The Index has different components: +- Web UI +- Meta-data store (comments, stars, list public repositories) +- Authentication service +- Tokenization + +The index is authoritative for those information. + +We expect that there will be only one instance of the index, run and managed by dotCloud. + +1.2 Registry +------------ +- It stores the images and the graph for a set of repositories +- It does not have user accounts data +- It has no notion of user accounts or authorization +- It delegates authentication and authorization to the Index Auth service using tokens +- It supports different storage backends (S3, cloud files, local FS) +- It doesn’t have a local database +- It will be open-sourced at some point + +We expect that there will be multiple registries out there. To help to grasp the context, here are some examples of registries: + +- **sponsor registry**: such a registry is provided by a third-party hosting infrastructure as a convenience for their customers and the docker community as a whole. Its costs are supported by the third party, but the management and operation of the registry are supported by dotCloud. It features read/write access, and delegates authentication and authorization to the Index. +- **mirror registry**: such a registry is provided by a third-party hosting infrastructure but is targeted at their customers only. Some mechanism (unspecified to date) ensures that public images are pulled from a sponsor registry to the mirror registry, to make sure that the customers of the third-party provider can “docker pull” those images locally. +- **vendor registry**: such a registry is provided by a software vendor, who wants to distribute docker images. It would be operated and managed by the vendor. Only users authorized by the vendor would be able to get write access. Some images would be public (accessible for anyone), others private (accessible only for authorized users). Authentication and authorization would be delegated to the Index. The goal of vendor registries is to let someone do “docker pull basho/riak1.3” and automatically push from the vendor registry (instead of a sponsor registry); i.e. get all the convenience of a sponsor registry, while retaining control on the asset distribution. +- **private registry**: such a registry is located behind a firewall, or protected by an additional security layer (HTTP authorization, SSL client-side certificates, IP address authorization...). The registry is operated by a private entity, outside of dotCloud’s control. It can optionally delegate additional authorization to the Index, but it is not mandatory. + +.. note:: + + Mirror registries and private registries which do not use the Index don’t even need to run the registry code. They can be implemented by any kind of transport implementing HTTP GET and PUT. Read-only registries can be powered by a simple static HTTP server. + +.. note:: + + The latter implies that while HTTP is the protocol of choice for a registry, multiple schemes are possible (and in some cases, trivial): + - HTTP with GET (and PUT for read-write registries); + - local mount point; + - remote docker addressed through SSH. + +The latter would only require two new commands in docker, e.g. “registryget” and “registryput”, wrapping access to the local filesystem (and optionally doing consistency checks). Authentication and authorization are then delegated to SSH (e.g. with public keys). + +1.3 Docker +---------- + +On top of being a runtime for LXC, Docker is the Registry client. It supports: +- Push / Pull on the registry +- Client authentication on the Index + +2. Workflow +=========== + +2.1 Pull +-------- + +.. image:: /static_files/docker_pull_chart.png + +1. Contact the Index to know where I should download “samalba/busybox” +2. Index replies: + a. “samalba/busybox” is on Registry A + b. here are the checksums for “samalba/busybox” (for all layers) + c. token +3. Contact Registry A to receive the layers for “samalba/busybox” (all of them to the base image). Registry A is authoritative for “samalba/busybox” but keeps a copy of all inherited layers and serve them all from the same location. +4. registry contacts index to verify if token/user is allowed to download images +5. Index returns true/false lettings registry know if it should proceed or error out +6. Get the payload for all layers + +It’s possible to run docker pull https:///repositories/samalba/busybox. In this case, docker bypasses the Index. However the security is not guaranteed (in case Registry A is corrupted) because there won’t be any checksum checks. + +Currently registry redirects to s3 urls for downloads, going forward all downloads need to be streamed through the registry. The Registry will then abstract the calls to S3 by a top-level class which implements sub-classes for S3 and local storage. + +Token is only returned when it is a private repo, public repos do not require tokens to be returned. The Registry will still contact the Index to make sure the pull is authorized (“is it ok to download this repos without a Token?”). + +API (pulling repository foo/bar): +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +1. (Docker -> Index) GET /v1/repositories/foo/bar/images + **Headers**: + Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== + X-Docker-Token: true + **Action**: + (looking up the foo/bar in db and gets images and checksums for that repo (all if no tag is specified, if tag, only checksums for those tags) see part 4.4.1) + +2. (Index -> Docker) HTTP 200 OK + + **Headers**: + - Authorization: Token signature=123abc,repository=”foo/bar”,access=write + - X-Docker-Endpoints: registry.docker.io [, registry2.docker.io] + **Body**: + Jsonified checksums (see part 4.4.1) + +3. (Docker -> Registry) GET /v1/repositories/foo/bar/tags/latest + **Headers**: + Authorization: Token signature=123abc,repository=”foo/bar”,access=write + +4. (Registry -> Index) GET /v1/repositories/foo/bar/images + + **Headers**: + Authorization: Token signature=123abc,repository=”foo/bar”,access=read + + **Body**: + + + **Action**: + ( Lookup token see if they have access to pull.) + + If good: + HTTP 200 OK + Index will invalidate the token + If bad: + HTTP 401 Unauthorized + +5. (Docker -> Registry) GET /v1/images/928374982374/ancestry + **Action**: + (for each image id returned in the registry, fetch /json + /layer) + +.. note:: + + If someone makes a second request, then we will always give a new token, never reuse tokens. + +2.2 Push +-------- + +.. image:: /static_files/docker_push_chart.png + +1. Contact the index to allocate the repository name “samalba/busybox” (authentication required with user credentials) +2. If authentication works and namespace available, “samalba/busybox” is allocated and a temporary token is returned (namespace is marked as initialized in index) +3. Push the image on the registry (along with the token) +4. Registry A contacts the Index to verify the token (token must corresponds to the repository name) +5. Index validates the token. Registry A starts reading the stream pushed by docker and store the repository (with its images) +6. docker contacts the index to give checksums for upload images + +.. note:: + + **It’s possible not to use the Index at all!** In this case, a deployed version of the Registry is deployed to store and serve images. Those images are not authentified and the security is not guaranteed. + +.. note:: + + **Index can be replaced!** For a private Registry deployed, a custom Index can be used to serve and validate token according to different policies. + +Docker computes the checksums and submit them to the Index at the end of the push. When a repository name does not have checksums on the Index, it means that the push is in progress (since checksums are submitted at the end). + +API (pushing repos foo/bar): +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +1. (Docker -> Index) PUT /v1/repositories/foo/bar/ + **Headers**: + Authorization: Basic sdkjfskdjfhsdkjfh== + X-Docker-Token: true + + **Action**:: + - in index, we allocated a new repository, and set to initialized + + **Body**:: + (The body contains the list of images that are going to be pushed, with empty checksums. The checksums will be set at the end of the push):: + + [{“id”: “9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”}] + +2. (Index -> Docker) 200 Created + **Headers**: + - WWW-Authenticate: Token signature=123abc,repository=”foo/bar”,access=write + - X-Docker-Endpoints: registry.docker.io [, registry2.docker.io] + +3. (Docker -> Registry) PUT /v1/images/98765432_parent/json + **Headers**: + Authorization: Token signature=123abc,repository=”foo/bar”,access=write + +4. (Registry->Index) GET /v1/repositories/foo/bar/images + **Headers**: + Authorization: Token signature=123abc,repository=”foo/bar”,access=write + **Action**:: + - Index: + will invalidate the token. + - Registry: + grants a session (if token is approved) and fetches the images id + +5. (Docker -> Registry) PUT /v1/images/98765432_parent/json + **Headers**:: + - Authorization: Token signature=123abc,repository=”foo/bar”,access=write + - Cookie: (Cookie provided by the Registry) + +6. (Docker -> Registry) PUT /v1/images/98765432/json + **Headers**: + Cookie: (Cookie provided by the Registry) + +7. (Docker -> Registry) PUT /v1/images/98765432_parent/layer + **Headers**: + Cookie: (Cookie provided by the Registry) + +8. (Docker -> Registry) PUT /v1/images/98765432/layer + **Headers**: + X-Docker-Checksum: sha256:436745873465fdjkhdfjkgh + +9. (Docker -> Registry) PUT /v1/repositories/foo/bar/tags/latest + **Headers**: + Cookie: (Cookie provided by the Registry) + **Body**: + “98765432” + +10. (Docker -> Index) PUT /v1/repositories/foo/bar/images + + **Headers**: + Authorization: Basic 123oislifjsldfj== + X-Docker-Endpoints: registry1.docker.io (no validation on this right now) + + **Body**: + (The image, id’s, tags and checksums) + + [{“id”: “9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”, + “checksum”: “b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087”}] + + **Return** HTTP 204 + +.. note:: + + If push fails and they need to start again, what happens in the index, there will already be a record for the namespace/name, but it will be initialized. Should we allow it, or mark as name already used? One edge case could be if someone pushes the same thing at the same time with two different shells. + + If it's a retry on the Registry, Docker has a cookie (provided by the registry after token validation). So the Index won’t have to provide a new token. + +3. How to use the Registry in standalone mode +============================================= + +The Index has two main purposes (along with its fancy social features): + +- Resolve short names (to avoid passing absolute URLs all the time) + - username/projectname -> https://registry.docker.io/users//repositories// + - team/projectname -> https://registry.docker.io/team//repositories// +- Authenticate a user as a repos owner (for a central referenced repository) + +3.1 Without an Index +-------------------- +Using the Registry without the Index can be useful to store the images on a private network without having to rely on an external entity controlled by dotCloud. + +In this case, the registry will be launched in a special mode (--standalone? --no-index?). In this mode, the only thing which changes is that Registry will never contact the Index to verify a token. It will be the Registry owner responsibility to authenticate the user who pushes (or even pulls) an image using any mechanism (HTTP auth, IP based, etc...). + +In this scenario, the Registry is responsible for the security in case of data corruption since the checksums are not delivered by a trusted entity. + +As hinted previously, a standalone registry can also be implemented by any HTTP server handling GET/PUT requests (or even only GET requests if no write access is necessary). + +3.2 With an Index +----------------- + +The Index data needed by the Registry are simple: +- Serve the checksums +- Provide and authorize a Token + +In the scenario of a Registry running on a private network with the need of centralizing and authorizing, it’s easy to use a custom Index. + +The only challenge will be to tell Docker to contact (and trust) this custom Index. Docker will be configurable at some point to use a specific Index, it’ll be the private entity responsibility (basically the organization who uses Docker in a private environment) to maintain the Index and the Docker’s configuration among its consumers. + +4. The API +========== + +The first version of the api is available here: https://github.com/jpetazzo/docker/blob/acd51ecea8f5d3c02b00a08176171c59442df8b3/docs/images-repositories-push-pull.md + +4.1 Images +---------- + +The format returned in the images is not defined here (for layer and json), basically because Registry stores exactly the same kind of information as Docker uses to manage them. + +The format of ancestry is a line-separated list of image ids, in age order. I.e. the image’s parent is on the last line, the parent of the parent on the next-to-last line, etc.; if the image has no parent, the file is empty. + +GET /v1/images//layer +PUT /v1/images//layer +GET /v1/images//json +PUT /v1/images//json +GET /v1/images//ancestry +PUT /v1/images//ancestry + +4.2 Users +--------- + +4.2.1 Create a user (Index) +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +POST /v1/users + +**Body**: + {"email": "sam@dotcloud.com", "password": "toto42", "username": "foobar"'} + +**Validation**: + - **username** : min 4 character, max 30 characters, all lowercase no special characters. + - **password**: min 5 characters + +**Valid**: return HTTP 200 + +Errors: HTTP 400 (we should create error codes for possible errors) +- invalid json +- missing field +- wrong format (username, password, email, etc) +- forbidden name +- name already exists + +.. note:: + + A user account will be valid only if the email has been validated (a validation link is sent to the email address). + +4.2.2 Update a user (Index) +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +PUT /v1/users/ + +**Body**: + {"password": "toto"} + +.. note:: + + We can also update email address, if they do, they will need to reverify their new email address. + +4.2.3 Login (Index) +^^^^^^^^^^^^^^^^^^^ +Does nothing else but asking for a user authentication. Can be used to validate credentials. HTTP Basic Auth for now, maybe change in future. + +GET /v1/users + +**Return**: + - Valid: HTTP 200 + - Invalid login: HTTP 401 + - Account inactive: HTTP 403 Account is not Active + +4.3 Tags (Registry) +------------------- + +The Registry does not know anything about users. Even though repositories are under usernames, it’s just a namespace for the registry. Allowing us to implement organizations or different namespaces per user later, without modifying the Registry’s API. + +4.3.1 Get all tags +^^^^^^^^^^^^^^^^^^ + +GET /v1/repositories///tags + +**Return**: HTTP 200 + { + "latest": "9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f", + “0.1.1”: “b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087” + } + +4.3.2 Read the content of a tag (resolve the image id) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +GET /v1/repositories///tags/ + +**Return**: + "9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f" + +4.3.3 Delete a tag (registry) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +DELETE /v1/repositories///tags/ + +4.4 Images (Index) +------------------ + +For the Index to “resolve” the repository name to a Registry location, it uses the X-Docker-Endpoints header. In other terms, this requests always add a “X-Docker-Endpoints” to indicate the location of the registry which hosts this repository. + +4.4.1 Get the images +^^^^^^^^^^^^^^^^^^^^^ + +GET /v1/repositories///images + +**Return**: HTTP 200 + [{“id”: “9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”, “checksum”: “md5:b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087”}] + + +4.4.2 Add/update the images +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +You always add images, you never remove them. + +PUT /v1/repositories///images + +**Body**: + [ {“id”: “9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”, “checksum”: “sha256:b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087”} ] + +**Return** 204 + +5. Chaining Registries +====================== + +It’s possible to chain Registries server for several reasons: +- Load balancing +- Delegate the next request to another server + +When a Registry is a reference for a repository, it should host the entire images chain in order to avoid breaking the chain during the download. + +The Index and Registry use this mechanism to redirect on one or the other. + +Example with an image download: +On every request, a special header can be returned: + +X-Docker-Endpoints: server1,server2 + +On the next request, the client will always pick a server from this list. + +6. Authentication & Authorization +================================= + +6.1 On the Index +----------------- + +The Index supports both “Basic” and “Token” challenges. Usually when there is a “401 Unauthorized”, the Index replies this:: + + 401 Unauthorized + WWW-Authenticate: Basic realm="auth required",Token + +You have 3 options: + +1. Provide user credentials and ask for a token + + **Header**: + - Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== + - X-Docker-Token: true + + In this case, along with the 200 response, you’ll get a new token (if user auth is ok): + + **Response**: + - 200 OK + - X-Docker-Token: Token signature=123abc,repository=”foo/bar”,access=read + +2. Provide user credentials only + + **Header**: + Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== + +3. Provide Token + + **Header**: + Authorization: Token signature=123abc,repository=”foo/bar”,access=read + +6.2 On the Registry +------------------- + +The Registry only supports the Token challenge:: + + 401 Unauthorized + WWW-Authenticate: Token + +The only way is to provide a token on “401 Unauthorized” responses:: + + Authorization: Token signature=123abc,repository=”foo/bar”,access=read + +Usually, the Registry provides a Cookie when a Token verification succeeded. Every time the Registry passes a Cookie, you have to pass it back the same cookie.:: + + 200 OK + Set-Cookie: session="wD/J7LqL5ctqw8haL10vgfhrb2Q=?foo=UydiYXInCnAxCi4=×tamp=RjEzNjYzMTQ5NDcuNDc0NjQzCi4="; Path=/; HttpOnly + +Next request:: + + GET /(...) + Cookie: session="wD/J7LqL5ctqw8haL10vgfhrb2Q=?foo=UydiYXInCnAxCi4=×tamp=RjEzNjYzMTQ5NDcuNDc0NjQzCi4=" diff --git a/docs/sources/registry/index.rst b/docs/sources/registry/index.rst new file mode 100644 index 000000000..d3788f53c --- /dev/null +++ b/docs/sources/registry/index.rst @@ -0,0 +1,15 @@ +:title: docker Registry documentation +:description: Documentation for docker Registry and Registry API +:keywords: docker, registry, api, index + + + +Registry +======== + +Contents: + +.. toctree:: + :maxdepth: 2 + + api diff --git a/docs/sources/static_files/docker_pull_chart.png b/docs/sources/static_files/docker_pull_chart.png new file mode 100644 index 0000000000000000000000000000000000000000..73a145239c46c7eced1c7919258cddcc7ebdd443 GIT binary patch literal 24445 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 literal 0 HcmV?d00001 diff --git a/docs/sources/static_files/docker_push_chart.png b/docs/sources/static_files/docker_push_chart.png new file mode 100644 index 0000000000000000000000000000000000000000..6486355e914b842c3ce1a04d1924b081a301244b GIT binary patch literal 30219 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 rkf8rt>W}uZK&FE>|sM8TNkwiM(eX literal 0 HcmV?d00001 From 7ff65d40d557e0a128534c096f3cab0c10a79f7b Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Wed, 1 May 2013 11:22:06 -0700 Subject: [PATCH 124/138] Actually use the mergeConfig function --- runtime.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/runtime.go b/runtime.go index 9d2d889e8..c0502363e 100644 --- a/runtime.go +++ b/runtime.go @@ -121,9 +121,8 @@ func (runtime *Runtime) Create(config *Config) (*Container, error) { return nil, err } - //runtime.mergeConfig(config, img.Config) if img.Config != nil { - config = img.Config + runtime.mergeConfig(config, img.Config) } if config.Cmd == nil { From 5c30faf6f7aba58c9b8be580acb043d56b64db1b Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Wed, 1 May 2013 12:45:45 -0700 Subject: [PATCH 125/138] Set a layer's default runtime options with 'docker commit -run' instead of 'docker commit -config' --- commands.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commands.go b/commands.go index 7d4837612..1e624e682 100644 --- a/commands.go +++ b/commands.go @@ -726,7 +726,7 @@ func (srv *Server) CmdCommit(stdin io.ReadCloser, stdout io.Writer, args ...stri "Create a new image from a container's changes") flComment := cmd.String("m", "", "Commit message") flAuthor := cmd.String("author", "", "Author (eg. \"John Hannibal Smith \"") - flConfig := cmd.String("config", "", "Config automatically applied when the image is run. "+`(ex: -config '{"Cmd": ["cat", "/world"], "PortSpecs": ["22"]}')`) + flConfig := cmd.String("run", "", "Config automatically applied when the image is run. "+`(ex: {"Cmd": ["cat", "/world"], "PortSpecs": ["22"]}')`) if err := cmd.Parse(args); err != nil { return nil } From 08812096f580c820d8cd06e3cb2128871551f60f Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Wed, 1 May 2013 14:16:56 -0700 Subject: [PATCH 126/138] New Dockerfile operation 'expose' exposes default tcp ports --- contrib/docker-build/docker-build | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/contrib/docker-build/docker-build b/contrib/docker-build/docker-build index 85bebaefd..d342afd35 100755 --- a/contrib/docker-build/docker-build +++ b/contrib/docker-build/docker-build @@ -49,14 +49,17 @@ def docker(args, stdin=None): def image_exists(img): return docker(["inspect", img]).read().strip() != "" -def run_and_commit(img_in, cmd, stdin=None, author=None): +def image_config(img): + return json.loads(docker(["inspect", img]).read()).get("Config", {}) + +def run_and_commit(img_in, cmd, stdin=None, author=None, run=None): run_id = docker(["run"] + (["-i", "-a", "stdin"] if stdin else ["-d"]) + [img_in, "/bin/sh", "-c", cmd], stdin=stdin).read().rstrip() print "---> Waiting for " + run_id result=int(docker(["wait", run_id]).read().rstrip()) if result != 0: print "!!! '{}' return non-zero exit code '{}'. Aborting.".format(cmd, result) sys.exit(1) - return docker(["commit"] + (["-author", author] if author else []) + [run_id]).read().rstrip() + return docker(["commit"] + (["-author", author] if author else []) + (["-run", json.dumps(run)] if run is not None else []) + [run_id]).read().rstrip() def insert(base, src, dst, author=None): print "COPY {} to {} in {}".format(src, dst, base) @@ -106,6 +109,14 @@ def main(): steps.append(result) base=result print "===> " + base + elif op == "expose": + config = image_config(base) + portspec = param.strip() + config.setdefault("PortSpecs", []).append(portspec) + result = run_and_commit(base, "# (nop) expose port {}".format(portspec), author=maintainer, run=config) + steps.append(result) + base=result + print "===> " + base else: print "Skipping uknown op " + op except: From a75a1b3859e6c30f0a24b262bdfc524dcdc07c3b Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Wed, 1 May 2013 15:19:55 -0700 Subject: [PATCH 127/138] When no -config is set while committing, use the config of the base image --- commands.go | 3 ++- graph.go | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/commands.go b/commands.go index 1e624e682..f4e2a5622 100644 --- a/commands.go +++ b/commands.go @@ -736,8 +736,9 @@ func (srv *Server) CmdCommit(stdin io.ReadCloser, stdout io.Writer, args ...stri return nil } - config := &Config{} + var config *Config if *flConfig != "" { + config = &Config{} if err := json.Unmarshal([]byte(*flConfig), config); err != nil { return err } diff --git a/graph.go b/graph.go index bf22bb19f..3823868c8 100644 --- a/graph.go +++ b/graph.go @@ -97,6 +97,11 @@ func (graph *Graph) Create(layerData Archive, container *Container, comment, aut img.Parent = container.Image img.Container = container.Id img.ContainerConfig = *container.Config + if config == nil { + if parentImage, err := graph.Get(container.Image); err == nil && parentImage != nil { + img.Config = parentImage.Config + } + } } if err := graph.Register(layerData, img); err != nil { return nil, err From bb61678b570fabeb5d21c86fa3f7b5456111f7a2 Mon Sep 17 00:00:00 2001 From: Daniel Mizyrycki Date: Wed, 1 May 2013 11:20:10 -0700 Subject: [PATCH 128/138] development; issue #374: Refactor 'make hack' making Vagrantfile and VM more useful --- Makefile | 8 ++-- hack/{README.md => README.rst} | 0 hack/Vagrantfile | 36 +++++++++++++++ hack/environment/README.rst | 20 +++++++++ hack/environment/bash_profile | 19 ++++++++ .../environment}/buildbot.conf | 0 .../environment}/master.cfg | 9 ++-- .../environment}/post-commit | 0 hack/environment/requirements.txt | 6 +++ hack/environment/setup.sh | 45 +++++++++++++++++++ 10 files changed, 134 insertions(+), 9 deletions(-) rename hack/{README.md => README.rst} (100%) create mode 100644 hack/Vagrantfile create mode 100644 hack/environment/README.rst create mode 100644 hack/environment/bash_profile rename {buildbot/buildbot-cfg => hack/environment}/buildbot.conf (100%) rename {buildbot/buildbot-cfg => hack/environment}/master.cfg (86%) rename {buildbot/buildbot-cfg => hack/environment}/post-commit (100%) create mode 100644 hack/environment/requirements.txt create mode 100755 hack/environment/setup.sh diff --git a/Makefile b/Makefile index d6cede4f5..2d9ba2c60 100644 --- a/Makefile +++ b/Makefile @@ -38,8 +38,7 @@ $(DOCKER_BIN): $(DOCKER_DIR) $(DOCKER_DIR): @mkdir -p $(dir $@) - @rm -f $@ - @ln -sf $(CURDIR)/ $@ + @if [ -h $@ ]; then rm -f $@; ln -sf $(CURDIR)/ $@; fi @(cd $(DOCKER_MAIN); go get $(GO_OPTIONS)) whichrelease: @@ -75,4 +74,7 @@ fmt: @gofmt -s -l -w . hack: - cd $(CURDIR)/buildbot && vagrant up + cd $(CURDIR)/hack && vagrant up + +ssh-dev: + cd $(CURDIR)/hack && vagrant ssh diff --git a/hack/README.md b/hack/README.rst similarity index 100% rename from hack/README.md rename to hack/README.rst diff --git a/hack/Vagrantfile b/hack/Vagrantfile new file mode 100644 index 000000000..6e614892e --- /dev/null +++ b/hack/Vagrantfile @@ -0,0 +1,36 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +BOX_NAME = "ubuntu" +BOX_URI = "http://files.vagrantup.com/precise64.box" +PPA_KEY = "E61D797F63561DC6" +VM_IP = "192.168.33.21" +USER = "vagrant" +GOPATH = "/data/docker" +DOCKER_PATH = "#{GOPATH}/src/github.com/dotcloud/docker" +CFG_PATH = "#{DOCKER_PATH}/hack/environment" +BUILDBOT_PATH = "/data/buildbot" + +Vagrant::Config.run do |config| + # Setup virtual machine box + config.vm.box = BOX_NAME + config.vm.box_url = BOX_URI + config.vm.share_folder "v-data", DOCKER_PATH, "#{File.dirname(__FILE__)}/.." + config.vm.network :hostonly, VM_IP + # Stop if deployment has been done + config.vm.provision :shell, :inline => "[ ! -f /usr/bin/git ]" + # Touch for makefile + pkg_cmd = "touch #{DOCKER_PATH}; " + # Install docker dependencies + pkg_cmd << "export DEBIAN_FRONTEND=noninteractive; apt-get -qq update; " \ + "apt-get install -q -y lxc bsdtar git golang make; " \ + "chown -R #{USER}.#{USER} #{GOPATH}; " \ + "install -m 0664 #{CFG_PATH}/bash_profile /home/#{USER}/.bash_profile" + config.vm.provision :shell, :inline => pkg_cmd + # Deploy buildbot CI + pkg_cmd = "apt-get install -q -y python-dev python-pip supervisor; " \ + "pip install -r #{CFG_PATH}/requirements.txt; " \ + "chown #{USER}.#{USER} /data; cd /data; " \ + "#{CFG_PATH}/setup.sh #{USER} #{GOPATH} #{DOCKER_PATH} #{CFG_PATH} #{BUILDBOT_PATH}" + config.vm.provision :shell, :inline => pkg_cmd +end diff --git a/hack/environment/README.rst b/hack/environment/README.rst new file mode 100644 index 000000000..a52b9769e --- /dev/null +++ b/hack/environment/README.rst @@ -0,0 +1,20 @@ +Buildbot +======== + +Buildbot is a continuous integration system designed to automate the +build/test cycle. By automatically rebuilding and testing the tree each time +something has changed, build problems are pinpointed quickly, before other +developers are inconvenienced by the failure. + +When running 'make hack' at the docker root directory, it spawns a virtual +machine in the background running a buildbot instance and adds a git +post-commit hook that automatically run docker tests for you. + +You can check your buildbot instance at http://192.168.33.21:8010/waterfall + + +Buildbot dependencies +--------------------- + +vagrant, virtualbox packages and python package requests + diff --git a/hack/environment/bash_profile b/hack/environment/bash_profile new file mode 100644 index 000000000..77eed7911 --- /dev/null +++ b/hack/environment/bash_profile @@ -0,0 +1,19 @@ +# ~/.bash_profile : executed by the command interpreter for login shells. + +# if running bash +if [ -n "$BASH_VERSION" ]; then + # include .bashrc if it exists + if [ -f "$HOME/.bashrc" ]; then + . "$HOME/.bashrc" + fi +fi + +# set PATH so it includes user's private bin if it exists +[ -d "$HOME/bin" ] && PATH="$HOME/bin:$PATH" + +docker=/data/docker/src/github.com/dotcloud/docker +[ -d $docker ] && cd $docker + +export GOPATH=/data/docker +export PATH=$PATH:$GOPATH/bin + diff --git a/buildbot/buildbot-cfg/buildbot.conf b/hack/environment/buildbot.conf similarity index 100% rename from buildbot/buildbot-cfg/buildbot.conf rename to hack/environment/buildbot.conf diff --git a/buildbot/buildbot-cfg/master.cfg b/hack/environment/master.cfg similarity index 86% rename from buildbot/buildbot-cfg/master.cfg rename to hack/environment/master.cfg index c786e418e..fad023b60 100644 --- a/buildbot/buildbot-cfg/master.cfg +++ b/hack/environment/master.cfg @@ -13,8 +13,8 @@ TEST_USER = 'buildbot' # Credential to authenticate build triggers TEST_PWD = 'docker' # Credential to authenticate build triggers BUILDER_NAME = 'docker' BUILDPASSWORD = 'pass-docker' # Credential to authenticate buildworkers -DOCKER_PATH = '/data/docker' - +GOPATH = '/data/docker' +DOCKER_PATH = '{0}/src/github.com/dotcloud/docker'.format(GOPATH) c = BuildmasterConfig = {} @@ -28,10 +28,7 @@ c['slavePortnum'] = PORT_MASTER c['schedulers'] = [ForceScheduler(name='trigger',builderNames=[BUILDER_NAME])] # Docker test command -test_cmd = """( - cd {0}/..; rm -rf docker-tmp; git clone docker docker-tmp; - cd docker-tmp; make test; exit_status=$?; - cd ..; rm -rf docker-tmp; exit $exit_status)""".format(DOCKER_PATH) +test_cmd = "GOPATH={0} make -C {1} test".format(GOPATH,DOCKER_PATH) # Builder factory = BuildFactory() diff --git a/buildbot/buildbot-cfg/post-commit b/hack/environment/post-commit similarity index 100% rename from buildbot/buildbot-cfg/post-commit rename to hack/environment/post-commit diff --git a/hack/environment/requirements.txt b/hack/environment/requirements.txt new file mode 100644 index 000000000..0e451b017 --- /dev/null +++ b/hack/environment/requirements.txt @@ -0,0 +1,6 @@ +sqlalchemy<=0.7.9 +sqlalchemy-migrate>=0.7.2 +buildbot==0.8.7p1 +buildbot_slave==0.8.7p1 +nose==1.2.1 +requests==1.1.0 diff --git a/hack/environment/setup.sh b/hack/environment/setup.sh new file mode 100755 index 000000000..7aa06a518 --- /dev/null +++ b/hack/environment/setup.sh @@ -0,0 +1,45 @@ +#!/bin/bash + +# Setup of buildbot configuration. Package installation is being done by +# Vagrantfile +# Dependencies: buildbot, buildbot-slave, supervisor + +USER=$1 +GOPATH=$2 +DOCKER_PATH=$3 +CFG_PATH=$4 +BUILDBOT_PATH=$5 +SLAVE_NAME="buildworker" +SLAVE_SOCKET="localhost:9989" +BUILDBOT_PWD="pass-docker" +IP=$(sed -nE 's/VM_IP = "(.+)"/\1/p' ${DOCKER_PATH}/hack/Vagrantfile) +export PATH="/bin:sbin:/usr/bin:/usr/sbin:/usr/local/bin" + +function run { su $USER -c "$1"; } + +# Exit if buildbot has already been installed +[ -d "$BUILDBOT_PATH" ] && exit 0 + +# Setup buildbot +run "mkdir -p $BUILDBOT_PATH" +cd $BUILDBOT_PATH +run "buildbot create-master master" +run "cp $CFG_PATH/master.cfg master" +run "sed -i 's/localhost/$IP/' master/master.cfg" +run "sed -i -E 's#(GOPATH = ).+#\1\"$GOPATH\"#' master/master.cfg" +run "sed -i -E 's#(DOCKER_PATH = ).+#\1\"$DOCKER_PATH\"#' master/master.cfg" +run "buildslave create-slave slave $SLAVE_SOCKET $SLAVE_NAME $BUILDBOT_PWD" + +# Allow buildbot subprocesses (docker tests) to properly run in containers, +# in particular with docker -u +run "sed -i 's/^umask = None/umask = 000/' slave/buildbot.tac" + +# Setup supervisor +cp $CFG_PATH/buildbot.conf /etc/supervisor/conf.d/buildbot.conf +sed -i -E "s/^chmod=0700.+/chmod=0770\nchown=root:$USER/" /etc/supervisor/supervisord.conf +kill -HUP $(pgrep -f "/usr/bin/python /usr/bin/supervisord") + +# Add git hook +cp $CFG_PATH/post-commit $DOCKER_PATH/.git/hooks +sed -i "s/localhost/$IP/" $DOCKER_PATH/.git/hooks/post-commit + From eeb03164cf1ea1b6a2abae2536cb1360c9ac8251 Mon Sep 17 00:00:00 2001 From: Daniel Mizyrycki Date: Wed, 1 May 2013 15:25:58 -0700 Subject: [PATCH 129/138] development; issue #374: Upgrade development VM box to Ubuntu-13.04 with kernel 3.8 --- hack/Vagrantfile | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/hack/Vagrantfile b/hack/Vagrantfile index 6e614892e..250731ef4 100644 --- a/hack/Vagrantfile +++ b/hack/Vagrantfile @@ -1,9 +1,8 @@ # -*- mode: ruby -*- # vi: set ft=ruby : -BOX_NAME = "ubuntu" -BOX_URI = "http://files.vagrantup.com/precise64.box" -PPA_KEY = "E61D797F63561DC6" +BOX_NAME = "ubuntu-dev" +BOX_URI = "http://cloud-images.ubuntu.com/raring/current/raring-server-cloudimg-vagrant-amd64-disk1.box" VM_IP = "192.168.33.21" USER = "vagrant" GOPATH = "/data/docker" @@ -23,7 +22,7 @@ Vagrant::Config.run do |config| pkg_cmd = "touch #{DOCKER_PATH}; " # Install docker dependencies pkg_cmd << "export DEBIAN_FRONTEND=noninteractive; apt-get -qq update; " \ - "apt-get install -q -y lxc bsdtar git golang make; " \ + "apt-get install -q -y lxc bsdtar git golang make linux-image-extra-3.8.0-19-generic; " \ "chown -R #{USER}.#{USER} #{GOPATH}; " \ "install -m 0664 #{CFG_PATH}/bash_profile /home/#{USER}/.bash_profile" config.vm.provision :shell, :inline => pkg_cmd From c20e46587d3fb09257e6ce7d085467da403210ee Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Wed, 1 May 2013 15:43:02 -0700 Subject: [PATCH 130/138] Update commandline Commit doc --- docs/sources/commandline/command/commit.rst | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/sources/commandline/command/commit.rst b/docs/sources/commandline/command/commit.rst index 2af05ff09..c73f8d189 100644 --- a/docs/sources/commandline/command/commit.rst +++ b/docs/sources/commandline/command/commit.rst @@ -9,3 +9,19 @@ Create a new image from a container's changes -m="": Commit message + -author="": Author (eg. "John Hannibal Smith " + -run="": Config automatically applied when the image is run. "+`(ex: {"Cmd": ["cat", "/world"], "PortSpecs": ["22"]}') + +Full -run example:: + + {"Hostname": "", + "User": "", + "Memory": 0, + "MemorySwap": 0, + "PortSpecs": ["22", "80", "443"], + "Tty": true, + "OpenStdin": true, + "StdinOnce": true, + "Env": ["FOO=BAR", "FOO2=BAR2"], + "Cmd": ["cat", "-e", "/etc/resolv.conf"], + "Dns": ["8.8.8.8", "8.8.4.4"]} From d172da58ceddd93c4a93132eae652e7996e1a2d2 Mon Sep 17 00:00:00 2001 From: Daniel Mizyrycki Date: Wed, 1 May 2013 15:59:54 -0700 Subject: [PATCH 131/138] development; issue #374: Update VM documentation --- hack/README.rst | 26 ++++++++++++++++++++++++++ hack/environment/README.rst | 21 +-------------------- 2 files changed, 27 insertions(+), 20 deletions(-) diff --git a/hack/README.rst b/hack/README.rst index 06cdd5085..4607b6a4a 100644 --- a/hack/README.rst +++ b/hack/README.rst @@ -1 +1,27 @@ This directory contains material helpful for hacking on docker. + +make hack +========= + +Set up an Ubuntu 13.04 virtual machine for developers including kernel 3.8 +and buildbot. The environment is setup in a way that can be used through +the usual go workflow and/or the root Makefile. You can either edit on +your host, or inside the VM (using make ssh-dev) and run and test docker +inside the VM. + +dependencies: vagrant, virtualbox packages and python package requests + + +Buildbot +~~~~~~~~ + +Buildbot is a continuous integration system designed to automate the +build/test cycle. By automatically rebuilding and testing the tree each time +something has changed, build problems are pinpointed quickly, before other +developers are inconvenienced by the failure. + +When running 'make hack' at the docker root directory, it spawns a virtual +machine in the background running a buildbot instance and adds a git +post-commit hook that automatically run docker tests for you. + +You can check your buildbot instance at http://192.168.33.21:8010/waterfall diff --git a/hack/environment/README.rst b/hack/environment/README.rst index a52b9769e..da5c885e6 100644 --- a/hack/environment/README.rst +++ b/hack/environment/README.rst @@ -1,20 +1 @@ -Buildbot -======== - -Buildbot is a continuous integration system designed to automate the -build/test cycle. By automatically rebuilding and testing the tree each time -something has changed, build problems are pinpointed quickly, before other -developers are inconvenienced by the failure. - -When running 'make hack' at the docker root directory, it spawns a virtual -machine in the background running a buildbot instance and adds a git -post-commit hook that automatically run docker tests for you. - -You can check your buildbot instance at http://192.168.33.21:8010/waterfall - - -Buildbot dependencies ---------------------- - -vagrant, virtualbox packages and python package requests - +Files used to setup the developer virtual machine From e7fb7f13d51b4026571e47981f0d432cc4b59f80 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Wed, 1 May 2013 16:43:37 -0700 Subject: [PATCH 132/138] new Dockerfile keyword: cmd to set a default runtime command --- contrib/docker-build/docker-build | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/contrib/docker-build/docker-build b/contrib/docker-build/docker-build index d342afd35..18d3153e8 100755 --- a/contrib/docker-build/docker-build +++ b/contrib/docker-build/docker-build @@ -50,7 +50,7 @@ def image_exists(img): return docker(["inspect", img]).read().strip() != "" def image_config(img): - return json.loads(docker(["inspect", img]).read()).get("Config", {}) + return json.loads(docker(["inspect", img]).read()).get("config", {}) def run_and_commit(img_in, cmd, stdin=None, author=None, run=None): run_id = docker(["run"] + (["-i", "-a", "stdin"] if stdin else ["-d"]) + [img_in, "/bin/sh", "-c", cmd], stdin=stdin).read().rstrip() @@ -111,12 +111,22 @@ def main(): print "===> " + base elif op == "expose": config = image_config(base) + if config.get("PortSpecs") is None: + config["PortSpecs"] = [] portspec = param.strip() - config.setdefault("PortSpecs", []).append(portspec) + config["PortSpecs"].append(portspec) result = run_and_commit(base, "# (nop) expose port {}".format(portspec), author=maintainer, run=config) steps.append(result) base=result print "===> " + base + elif op == "cmd": + config = image_config(base) + cmd = list(json.loads(param)) + config["Cmd"] = cmd + result = run_and_commit(base, "# (nop) set default command to '{}'".format(" ".join(cmd)), author=maintainer, run=config) + steps.append(result) + base=result + print "===> " + base else: print "Skipping uknown op " + op except: From d42639e5c5623109acd302ddead237ce4d9c2617 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Wed, 1 May 2013 17:17:13 -0700 Subject: [PATCH 133/138] Bumped version to 0.2.1 --- CHANGELOG.md | 10 ++++++++++ commands.go | 2 +- packaging/ubuntu/changelog | 15 +++++++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d74766560..72bf381fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## 0.2.1 (2012-05-01) + + 'docker commit -run' bundles a layer with default runtime options: command, ports etc. + * Improve install process on Vagrant + + New Dockerfile operation: "maintainer" + + New Dockerfile operation: "expose" + + New Dockerfile operation: "cmd" + + Contrib script to build a Debian base layer + + 'docker -d -r': restart crashed containers at daemon startup + * Runtime: improve test coverage + ## 0.2.0 (2012-04-23) - Runtime: ghost containers can be killed and waited for * Documentation: update install intructions diff --git a/commands.go b/commands.go index f4e2a5622..4be282bce 100644 --- a/commands.go +++ b/commands.go @@ -18,7 +18,7 @@ import ( "unicode" ) -const VERSION = "0.2.0" +const VERSION = "0.2.1" var ( GIT_COMMIT string diff --git a/packaging/ubuntu/changelog b/packaging/ubuntu/changelog index 6499ae8f6..88f6c5021 100644 --- a/packaging/ubuntu/changelog +++ b/packaging/ubuntu/changelog @@ -1,3 +1,18 @@ + +lxc-docker (0.2.1-1) precise; urgency=low + + - 'docker commit -run' bundles a layer with default runtime options: command, ports etc. + - Improve install process on Vagrant + - New Dockerfile operation: "maintainer" + - New Dockerfile operation: "expose" + - New Dockerfile operation: "cmd" + - Contrib script to build a Debian base layer + - 'docker -d -r': restart crashed containers at daemon startup + - Runtime: improve test coverage + + -- dotCloud Wed, 1 May 2013 00:00:00 -0700 + + lxc-docker (0.2.0-1) precise; urgency=low - Runtime: ghost containers can be killed and waited for From 58b95878f13c8de443529fbd47a67988b4735e1a Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Thu, 2 May 2013 01:16:23 +0000 Subject: [PATCH 134/138] - Hack: fix dockerbuilder to build feature branches --- hack/dockerbuilder/dockerbuilder | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hack/dockerbuilder/dockerbuilder b/hack/dockerbuilder/dockerbuilder index faec3be08..5e803aa0b 100644 --- a/hack/dockerbuilder/dockerbuilder +++ b/hack/dockerbuilder/dockerbuilder @@ -34,7 +34,7 @@ else rm -fr docker-$REVISION git init docker-$REVISION cd docker-$REVISION - git fetch -t https://github.com/dotcloud/docker $REVISION + git fetch -t https://github.com/dotcloud/docker $REVISION:$REVISION git reset --hard FETCH_HEAD fi From 71199f595de081a4f2e93142d9f5b98df16e9089 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Wed, 1 May 2013 18:32:38 -0700 Subject: [PATCH 135/138] New Dockerfile operation: 'add' --- contrib/docker-build/docker-build | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/contrib/docker-build/docker-build b/contrib/docker-build/docker-build index 18d3153e8..c82377e10 100755 --- a/contrib/docker-build/docker-build +++ b/contrib/docker-build/docker-build @@ -69,11 +69,14 @@ def insert(base, src, dst, author=None): stdin.seek(0) return run_and_commit(base, "cat > {0}; chmod +x {0}".format(dst), stdin=stdin, author=author) -def push(base, dst, author=None): +def add(base, src, dst, author=None): print "PUSH to {} in {}".format(dst, base) + if src == ".": + tar = subprocess.Popen(["tar", "-c", "."], stdout=subprocess.PIPE).stdout + else: + tar = subprocess.Popen(["curl", src], stdout=subprocess.PIPE).stdout if dst == "": raise Exception("Missing argument to push") - tar = subprocess.Popen(["tar", "-c", "."], stdout=subprocess.PIPE).stdout return run_and_commit(base, "mkdir -p '{0}' && tar -C '{0}' -x".format(dst), stdin=tar, author=author) def main(): @@ -104,8 +107,9 @@ def main(): steps.append(result) base = result print "===> " + base - elif op == "push": - result = push(base, param.strip(), author=maintainer) + elif op == "add": + src, dst = param.split(" ", 1) + result = add(base, src, dst, author=maintainer) steps.append(result) base=result print "===> " + base From 0d9475346f3548c525ecfbbd44c95a47a0013f0e Mon Sep 17 00:00:00 2001 From: Daniel Mizyrycki Date: Wed, 1 May 2013 18:49:31 -0700 Subject: [PATCH 136/138] Fix main Vagrantfile --- Vagrantfile | 8 -------- 1 file changed, 8 deletions(-) diff --git a/Vagrantfile b/Vagrantfile index 4cde1f049..319fbc753 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -50,12 +50,4 @@ Vagrant::VERSION >= "1.1.0" and Vagrant.configure("2") do |config| config.vm.box = BOX_NAME config.vm.box_url = BOX_URI end - - config.vm.provider :vmware_fusion do |vm| - config.vm.box = "precise64" - config.vm.box_url = "http://files.vagrantup.com/precise64_vmware_fusion.box" - config.vm.provision :shell, :inline => <<-UPDATE - apt-get install -y linux-image-extra-3.2.0-29-virtual - UPDATE - end end From 6ca3b151b1fc0b838b55586adbc6c0990cfb2586 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Wed, 1 May 2013 22:05:36 -0700 Subject: [PATCH 137/138] * Hack: improve the way dockerbuilder is built --- hack/dockerbuilder/Dockerfile | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/hack/dockerbuilder/Dockerfile b/hack/dockerbuilder/Dockerfile index bf5a25de9..5a7a3160a 100644 --- a/hack/dockerbuilder/Dockerfile +++ b/hack/dockerbuilder/Dockerfile @@ -1,5 +1,6 @@ # This will build a container capable of producing an official binary build of docker and # uploading it to S3 +maintainer Solomon Hykes from ubuntu:12.10 run apt-get update run DEBIAN_FRONTEND=noninteractive apt-get install -y -q s3cmd @@ -10,8 +11,9 @@ run DEBIAN_FRONTEND=noninteractive apt-get install -y -q build-essential # Packages required to build an ubuntu package run DEBIAN_FRONTEND=noninteractive apt-get install -y -q debhelper run DEBIAN_FRONTEND=noninteractive apt-get install -y -q autotools-dev -copy fake_initctl /usr/local/bin/initctl +add . /src +run cp /src/dockerbuilder /usr/local/bin/ +run cp /src/fake_initctl /usr/local/bin/initctl +run cp /src/s3cfg /.s3cfg run DEBIAN_FRONTEND=noninteractive apt-get install -y -q devscripts -copy dockerbuilder /usr/local/bin/dockerbuilder -copy s3cfg /.s3cfg -# run $img dockerbuilder $REVISION_OR_TAG $S3_ID $S3_KEY +cmd dockerbuilder From e7271cdaae50db382d5b5728904063c07d5549e4 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Thu, 2 May 2013 05:56:51 +0000 Subject: [PATCH 138/138] dockerbuilder: fix permissions --- hack/dockerbuilder/Dockerfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hack/dockerbuilder/Dockerfile b/hack/dockerbuilder/Dockerfile index 5a7a3160a..55540984f 100644 --- a/hack/dockerbuilder/Dockerfile +++ b/hack/dockerbuilder/Dockerfile @@ -12,8 +12,8 @@ run DEBIAN_FRONTEND=noninteractive apt-get install -y -q build-essential run DEBIAN_FRONTEND=noninteractive apt-get install -y -q debhelper run DEBIAN_FRONTEND=noninteractive apt-get install -y -q autotools-dev add . /src -run cp /src/dockerbuilder /usr/local/bin/ -run cp /src/fake_initctl /usr/local/bin/initctl +run cp /src/dockerbuilder /usr/local/bin/ && chmod +x /usr/local/bin/dockerbuilder +run cp /src/fake_initctl /usr/local/bin/initctl && chmod +x /usr/local/bin/initctl run cp /src/s3cfg /.s3cfg run DEBIAN_FRONTEND=noninteractive apt-get install -y -q devscripts -cmd dockerbuilder +cmd ["dockerbuilder"]