diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000000..72bf381fa8 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,96 @@ +# 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 + - 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 + - '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.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 documentation + - Add Vagrant support with Vagrantfile + - Add unit tests + - Add repository/tags to ease image management + - Improve the layer implementation diff --git a/Makefile b/Makefile index c89f0f33b0..67b805b830 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) @@ -13,10 +17,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 @@ -26,7 +27,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 release srcrelease $(BINRELEASE) $(SRCRELEASE) $(DOCKER_BIN) $(DOCKER_DIR) all: $(DOCKER_BIN) @@ -39,6 +40,24 @@ $(DOCKER_DIR): @mkdir -p $(dir $@) @ln -sf $(CURDIR)/ $@ +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) + cd $(SRCRELEASE); git checkout -q $(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)) @@ -52,3 +71,9 @@ test: all fmt: @gofmt -s -l -w . + +hack: + cd $(CURDIR)/hack && vagrant up + +ssh-dev: + cd $(CURDIR)/hack && vagrant ssh diff --git a/README.md b/README.md index c186d9a063..13ec817e2b 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/Vagrantfile b/Vagrantfile index 48b3ef567a..319fbc7530 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -1,41 +1,37 @@ # -*- mode: ruby -*- # 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" +BOX_NAME = "ubuntu" +BOX_URI = "http://files.vagrantup.com/precise64.box" +PPA_KEY = "E61D797F63561DC6" - config.vm.share_folder "v-data", "/opt/go/src/github.com/dotcloud/docker", File.dirname(__FILE__) - - # 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" +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 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) + # 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 -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 - +# 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.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"] - aws.ssh_private_key_path = ENV["AWS_SSH_PRIVKEY"] + override.ssh.private_key_path = ENV["AWS_SSH_PRIVKEY"] + override.ssh.username = "ubuntu" aws.region = "us-east-1" - aws.ami = "ami-ae9806c7" - aws.ssh_username = "ubuntu" + aws.ami = "ami-d0f89fb9" aws.instance_type = "t1.micro" end @@ -51,7 +47,7 @@ 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 = BOX_NAME + config.vm.box_url = BOX_URI end end diff --git a/api.go b/api.go index b574bab4d9..df442ab39a 100644 --- a/api.go +++ b/api.go @@ -22,7 +22,7 @@ func ListenAndServe(addr string, rtime *Runtime) error { r.Path("/version").Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { log.Println(r.Method, r.RequestURI) - m := ApiVersion{VERSION, GIT_COMMIT, NO_MEMORY_LIMIT} + m := ApiVersion{VERSION, GIT_COMMIT, rtime.capabilities.MemoryLimit, rtime.capabilities.SwapLimit} b, err := json.Marshal(m) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) @@ -291,6 +291,7 @@ func ListenAndServe(addr string, rtime *Runtime) error { out.Command = command out.Created = container.Created.Unix() out.Status = container.State.String() + out.Ports = container.NetworkSettings.PortMappingHuman() } outs = append(outs, out) } @@ -305,6 +306,12 @@ func ListenAndServe(addr string, rtime *Runtime) error { r.Path("/containers/{name:.*}/commit").Methods("POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { log.Println(r.Method, r.RequestURI) + var config Config + if err := json.NewDecoder(r.Body).Decode(&config); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + if err := r.ParseForm(); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return @@ -313,9 +320,10 @@ func ListenAndServe(addr string, rtime *Runtime) error { name := vars["name"] repo := r.Form.Get("repo") tag := r.Form.Get("tag") + author := r.Form.Get("author") comment := r.Form.Get("comment") - img, err := rtime.Commit(name, repo, tag, comment) + img, err := rtime.Commit(name, repo, tag, comment, author, &config) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return @@ -433,9 +441,9 @@ func ListenAndServe(addr string, rtime *Runtime) error { http.Error(w, err.Error(), http.StatusInternalServerError) return } - archive = ProgressReader(resp.Body, int(resp.ContentLength), file) + archive = ProgressReader(resp.Body, int(resp.ContentLength), file, "Importing %v/%v (%v)") } - img, err := rtime.graph.Create(archive, nil, "Imported from "+src) + img, err := rtime.graph.Create(archive, nil, "Imported from "+src, "", nil) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return @@ -458,7 +466,19 @@ func ListenAndServe(addr string, rtime *Runtime) error { http.Error(w, err.Error(), http.StatusInternalServerError) return } + var memoryW, swapW bool + if config.Memory > 0 && !rtime.capabilities.MemoryLimit { + memoryW = true + log.Println("WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.") + config.Memory = 0 + } + + if config.Memory > 0 && !rtime.capabilities.SwapLimit { + swapW = true + log.Println("WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.") + config.MemorySwap = -1 + } container, err := rtime.Create(&config) if err != nil { if rtime.graph.IsNotExist(err) { @@ -468,8 +488,16 @@ func ListenAndServe(addr string, rtime *Runtime) error { } return } + var out ApiRun + out.Id = container.ShortId() + if memoryW { + out.Warnings = append(out.Warnings, "Your kernel does not support memory limit capabilities. Limitation discarded.") + } + if swapW { + out.Warnings = append(out.Warnings, "Your kernel does not support memory swap capabilities. Limitation discarded.") + } - b, err := json.Marshal(ApiId{container.ShortId()}) + b, err := json.Marshal(out) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } else { @@ -479,10 +507,17 @@ func ListenAndServe(addr string, rtime *Runtime) error { r.Path("/containers/{name:.*}/restart").Methods("POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { log.Println(r.Method, r.RequestURI) + if err := r.ParseForm(); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + t, err := strconv.Atoi(r.Form.Get("t")) + if err != nil || t < 0 { + t = 10 + } vars := mux.Vars(r) name := vars["name"] if container := rtime.Get(name); container != nil { - if err := container.Restart(); err != nil { + if err := container.Restart(t); err != nil { http.Error(w, "Error restarting container "+name+": "+err.Error(), http.StatusInternalServerError) return } @@ -545,10 +580,17 @@ func ListenAndServe(addr string, rtime *Runtime) error { r.Path("/containers/{name:.*}/stop").Methods("POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { log.Println(r.Method, r.RequestURI) + if err := r.ParseForm(); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + t, err := strconv.Atoi(r.Form.Get("t")) + if err != nil || t < 0 { + t = 10 + } vars := mux.Vars(r) name := vars["name"] if container := rtime.Get(name); container != nil { - if err := container.Stop(); err != nil { + if err := container.Stop(t); err != nil { http.Error(w, "Error stopping container "+name+": "+err.Error(), http.StatusInternalServerError) return } @@ -603,17 +645,7 @@ func ListenAndServe(addr string, rtime *Runtime) error { return } defer file.Close() - if container.Config.Tty { - oldState, err := SetRawTerminal() - if err != nil { - if os.Getenv("DEBUG") != "" { - log.Printf("Can't set the terminal in raw mode: %s", err) - } - } else { - defer RestoreTerminal(oldState) - } - } // Flush the options to make sure the client sets the raw mode conn.Write([]byte{}) @@ -640,6 +672,23 @@ func ListenAndServe(addr string, rtime *Runtime) error { //stream if stream == "1" { + + if container.State.Ghost { + fmt.Fprintf(file, "error: Impossible to attach to a ghost container") + return + } + + if container.Config.Tty { + oldState, err := SetRawTerminal() + if err != nil { + if os.Getenv("DEBUG") != "" { + log.Printf("Can't set the terminal in raw mode: %s", err) + } + } else { + defer RestoreTerminal(oldState) + } + + } var ( cStdin io.ReadCloser cStdout, cStderr io.Writer diff --git a/api_params.go b/api_params.go index 0d3a6c08cb..0b46f8c727 100644 --- a/api_params.go +++ b/api_params.go @@ -28,20 +28,27 @@ type ApiContainers struct { Command string `json:",omitempty"` Created int64 `json:",omitempty"` Status string `json:",omitempty"` + Ports string `json:",omitempty"` } type ApiId struct { Id string } +type ApiRun struct { + Id string + Warnings []string +} + type ApiPort struct { Port string } type ApiVersion struct { - Version string - GitCommit string - MemoryLimitDisabled bool + Version string + GitCommit string + MemoryLimit bool + SwapLimit bool } type ApiWait struct { diff --git a/archive.go b/archive.go index d09d3d6b97..8a011eb6e1 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 0000000000..a52b9769ef --- /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 0000000000..ea027f0666 --- /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 0000000000..5e4e7432fd --- /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.pp b/buildbot/buildbot.pp new file mode 100644 index 0000000000..8109cdc2a0 --- /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 0000000000..0e451b017d --- /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 4fcf3dcd39..d847ed365f 100644 --- a/commands.go +++ b/commands.go @@ -18,11 +18,10 @@ import ( "time" ) -const VERSION = "0.1.4" +const VERSION = "0.2.1" var ( - GIT_COMMIT string - NO_MEMORY_LIMIT bool + GIT_COMMIT string ) func ParseCommands(args []string) error { @@ -248,8 +247,11 @@ func CmdVersion(args []string) error { } fmt.Println("Version:", out.Version) fmt.Println("Git Commit:", out.GitCommit) - if out.MemoryLimitDisabled { - fmt.Println("Memory limit disabled") + if !out.MemoryLimit { + fmt.Println("WARNING: No memory limit support") + } + if !out.SwapLimit { + fmt.Println("WARNING: No swap limit support") } return nil @@ -285,7 +287,8 @@ func CmdInfo(args []string) error { } func CmdStop(args []string) error { - cmd := Subcmd("stop", "CONTAINER [CONTAINER...]", "Stop a running container") + cmd := Subcmd("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 } @@ -294,8 +297,11 @@ func CmdStop(args []string) error { return nil } - for _, name := range args { - _, _, err := call("POST", "/containers/"+name+"/stop", nil) + v := url.Values{} + v.Set("t", strconv.Itoa(*nSeconds)) + + for _, name := range cmd.Args() { + _, _, err := call("POST", "/containers/"+name+"/stop?"+v.Encode(), nil) if err != nil { fmt.Printf("%s", err) } else { @@ -306,7 +312,8 @@ func CmdStop(args []string) error { } func CmdRestart(args []string) error { - cmd := Subcmd("restart", "CONTAINER [CONTAINER...]", "Restart a running container") + cmd := Subcmd("restart", "[OPTIONS] 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 } @@ -315,8 +322,11 @@ func CmdRestart(args []string) error { return nil } - for _, name := range args { - _, _, err := call("POST", "/containers/"+name+"/restart", nil) + v := url.Values{} + v.Set("t", strconv.Itoa(*nSeconds)) + + for _, name := range cmd.Args() { + _, _, err := call("POST", "/containers/"+name+"/restart?"+v.Encode(), nil) if err != nil { fmt.Printf("%s", err) } else { @@ -682,12 +692,12 @@ func CmdPs(args []string) error { } w := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0) if !*quiet { - fmt.Fprintln(w, "ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS") + fmt.Fprintln(w, "ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS") } for _, out := range outs { if !*quiet { - fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s ago\n", out.Id, out.Image, out.Command, out.Status, HumanDuration(time.Now().Sub(time.Unix(out.Created, 0)))) + fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s ago\t%s\n", out.Id, out.Image, out.Command, out.Status, HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Ports) } else { fmt.Fprintln(w, out.Id) } @@ -702,6 +712,8 @@ func CmdPs(args []string) error { func CmdCommit(args []string) error { cmd := Subcmd("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 \"") + 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 } @@ -710,12 +722,21 @@ func CmdCommit(args []string) error { cmd.Usage() return nil } + v := url.Values{} v.Set("repo", repository) v.Set("tag", tag) v.Set("comment", *flComment) + v.Set("author", *flAuthor) + var config *Config + if *flConfig != "" { + config = &Config{} + if err := json.Unmarshal([]byte(*flConfig), config); err != nil { + return err + } + } - body, _, err := call("POST", "/containers/"+name+"/commit?"+v.Encode(), nil) + body, _, err := call("POST", "/containers/"+name+"/commit?"+v.Encode(), config) if err != nil { return err } @@ -941,12 +962,16 @@ func CmdRun(args []string) error { return err } - var out ApiId + var out ApiRun err = json.Unmarshal(body, &out) if err != nil { return err } + for _, warning := range out.Warnings { + fmt.Fprintln(os.Stderr, "WARNING: ", warning) + } + v := url.Values{} v.Set("logs", "1") v.Set("stream", "1") @@ -972,17 +997,13 @@ func CmdRun(args []string) error { if err != nil { return err } - - if err := hijack("POST", "/containers/"+out.Id+"/attach?"+v.Encode(), config.Tty); err != nil { - return err - } - - /* - if err := <-attach; err != nil { + if config.AttachStdin || config.AttachStdout || config.AttachStderr { + if err := hijack("POST", "/containers/"+out.Id+"/attach?"+v.Encode(), config.Tty); err != nil { return err } - */ - + } else { + fmt.Println(out.Id) + } return nil } diff --git a/commands_test.go b/commands_test.go index 1be4a2abe0..83b480d52a 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) @@ -239,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 { @@ -384,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() } diff --git a/container.go b/container.go index 0f40daadf3..aa65737f16 100644 --- a/container.go +++ b/container.go @@ -11,7 +11,9 @@ import ( "os" "os/exec" "path" + "sort" "strconv" + "strings" "syscall" "time" ) @@ -81,11 +83,6 @@ func ParseRun(args []string) (*Config, *flag.FlagSet, 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.Println("WARNING: This version of docker has been compiled without memory limit support. Discarding -m.") - *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)") @@ -135,6 +132,7 @@ func ParseRun(args []string) (*Config, *flag.FlagSet, error) { Dns: flDns, Image: image, } + // When allocating stdin in attached mode, close stdin at client disconnect if config.OpenStdin && config.AttachStdin { config.StdinOnce = true @@ -150,6 +148,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 } @@ -367,10 +375,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 @@ -390,21 +403,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 +433,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() @@ -506,16 +520,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() @@ -564,7 +604,7 @@ func (container *Container) monitor() { } func (container *Container) kill() error { - if !container.State.Running || container.cmd == nil { + if !container.State.Running { return nil } @@ -576,6 +616,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 @@ -593,21 +636,15 @@ 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() } -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 ghot container") - } // 1. Send a SIGTERM if output, err := exec.Command("lxc-kill", "-n", container.Id, "15").CombinedOutput(); err != nil { @@ -619,8 +656,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 +665,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 d5f3694c59..c3a891cf4c 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 { @@ -97,7 +95,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) } @@ -116,8 +114,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,18 +137,87 @@ 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) { + 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", "", nil) + 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) { +func TestCommitAutoRun(t *testing.T) { runtime, err := newTestRuntime() if err != nil { t.Fatal(err) @@ -158,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 { @@ -182,7 +248,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", "", &Config{Cmd: []string{"cat", "/world"}}) if err != nil { t.Error(err) } @@ -191,9 +257,86 @@ func TestCommitRun(t *testing.T) { container2, err := runtime.Create( &Config{ - Image: img.Id, - Memory: 33554432, - Cmd: []string{"cat", "/world"}, + 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 { + 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", "", nil) + 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, + Cmd: []string{"cat", "/world"}, }, ) if err != nil { @@ -278,9 +421,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 { diff --git a/contrib/crashTest.go b/contrib/crashTest.go index 34749b52d3..d4a889e8d3 100644 --- a/contrib/crashTest.go +++ b/contrib/crashTest.go @@ -1,16 +1,22 @@ package main import ( + "fmt" "io" "log" + "net" "os" "os/exec" + "path" "time" ) -const DOCKER_PATH = "/home/creack/dotcloud/docker/docker/docker" +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").Run() cmd := exec.Command(DOCKER_PATH, "-d") outPipe, err := cmd.StdoutPipe() if err != nil { @@ -37,17 +43,43 @@ func crashTest() error { return err } + var endpoint string + if ep := os.Getenv("TEST_ENDPOINT"); ep == "" { + endpoint = "192.168.56.1:7979" + } else { + endpoint = ep + } + + 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 for { daemon, err := runDaemon() if err != nil { return err } - time.Sleep(5000 * time.Millisecond) + restartCount++ + // time.Sleep(5000 * time.Millisecond) + var stop bool go func() error { - for i := 0; i < 100; i++ { - go func() error { - cmd := exec.Command(DOCKER_PATH, "run", "base", "echo", "hello", "world") - log.Printf("%d", i) + stop = false + for i := 0; i < 100 && !stop; { + func() error { + cmd := exec.Command(DOCKER_PATH, "run", "base", "echo", fmt.Sprintf("%d", totalTestCount)) + i++ + totalTestCount++ outPipe, err := cmd.StdoutPipe() if err != nil { return err @@ -59,9 +91,10 @@ func crashTest() error { 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")) @@ -74,12 +107,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 } diff --git a/contrib/docker-build/docker-build b/contrib/docker-build/docker-build index 934e47ec8f..c82377e107 100755 --- a/contrib/docker-build/docker-build +++ b/contrib/docker-build/docker-build @@ -49,26 +49,39 @@ 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 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", 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): +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 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") + return run_and_commit(base, "mkdir -p '{0}' && tar -C '{0}' -x".format(dst), stdin=tar, author=author) def main(): base="" + maintainer="" steps = [] try: for line in sys.stdin.readlines(): @@ -77,26 +90,51 @@ 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 + elif op == "add": + src, dst = param.split(" ", 1) + result = add(base, src, dst, author=maintainer) + steps.append(result) + base=result + print "===> " + base + elif op == "expose": + config = image_config(base) + if config.get("PortSpecs") is None: + config["PortSpecs"] = [] + portspec = param.strip() + 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: - docker(["rmi"] + steps) + docker(["rmi"] + steps[1:]) raise print base diff --git a/contrib/docker-build/example.changefile b/contrib/docker-build/example.changefile index 19261de82b..d76bbb4389 100644 --- a/contrib/docker-build/example.changefile +++ b/contrib/docker-build/example.changefile @@ -1,11 +1,13 @@ # 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 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 diff --git a/contrib/docker-build/myscript b/contrib/docker-build/myscript new file mode 100644 index 0000000000..a6ffda5f11 --- /dev/null +++ b/contrib/docker-build/myscript @@ -0,0 +1,3 @@ +#!/bin/sh + +echo hello, world! diff --git a/contrib/install.sh b/contrib/install.sh index b0a998332e..d7c6e66466 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/mkimage-debian.sh b/contrib/mkimage-debian.sh new file mode 100755 index 0000000000..ace555ada6 --- /dev/null +++ b/contrib/mkimage-debian.sh @@ -0,0 +1,61 @@ +#!/bin/bash +set -e + +# 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}" +mirror="${3:-}" # stick to the default debootstrap mirror if one is not provided + +if [ ! "$repo" ]; then + echo >&2 "usage: $0 repo [suite [mirror]]" + echo >&2 " ie: $0 tianon/debian squeeze" + exit 1 +fi + +target="/tmp/docker-rootfs-debian-$suite-$$-$RANDOM" + +cd "$(dirname "$(readlink -f "$BASH_SOURCE")")" +returnTo="$(pwd -P)" + +set -x + +# bootstrap +mkdir -p "$target" +sudo debootstrap --verbose --variant="$variant" --include="$include" "$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 + +# 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" diff --git a/contrib/vagrant-docker/README.md b/contrib/vagrant-docker/README.md new file mode 100644 index 0000000000..5852ea1927 --- /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/docker/docker.go b/docker/docker.go index 4639921d52..80d424be3c 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -7,16 +7,17 @@ import ( "github.com/dotcloud/docker/rcli" "github.com/dotcloud/docker/term" "io" + "io/ioutil" "log" "os" "os/signal" "runtime" + "strconv" "syscall" ) var ( - GIT_COMMIT string - NO_MEMORY_LIMIT string + GIT_COMMIT string ) func main() { @@ -28,6 +29,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() @@ -40,16 +42,12 @@ 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 { + if err := daemon(*pidfile, *flAutoRestart); err != nil { log.Fatal(err) } } else { @@ -60,8 +58,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) @@ -81,7 +84,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) } @@ -99,7 +102,7 @@ func daemon(pidfile string) 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 := docker.NewRuntime() + runtime, err := docker.NewRuntime(autoRestart) if err != nil { return err } diff --git a/docs/Makefile b/docs/Makefile index f74bf5705e..77f14ee92f 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/commandline/command/commit.rst b/docs/sources/commandline/command/commit.rst index 2af05ff093..c73f8d1898 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"]} diff --git a/docs/sources/concepts/buildingblocks.rst b/docs/sources/concepts/buildingblocks.rst index 0fc859418f..d422e6eef3 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 diff --git a/docs/sources/examples/python_web_app.rst b/docs/sources/examples/python_web_app.rst index 7b7edc05e3..33caa52c1e 100644 --- a/docs/sources/examples/python_web_app.rst +++ b/docs/sources/examples/python_web_app.rst @@ -49,7 +49,7 @@ Save the changed we just made in the container to a new image called "_/builds/g WEB_WORKER=$(docker run -d -p 5000 $BUILD_IMG /usr/local/bin/runapp) - **"docker run -d "** run a command in a new container. We pass "-d" so it runs as a daemon. - **"-p 5000"* the web app is going to listen on this port, so it must be mapped from the container to the host system. +- **"-p 5000"** the web app is going to listen on this port, so it must be mapped from the container to the host system. - **"$BUILD_IMG"** is the image we want to run the command inside of. - **/usr/local/bin/runapp** is the command which starts the web app. diff --git a/docs/sources/examples/running_examples.rst b/docs/sources/examples/running_examples.rst index 4042add487..3d2593c710 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/gettingstarted/index.html b/docs/sources/gettingstarted/index.html index b86e9bbdd4..96175d6dec 100644 --- a/docs/sources/gettingstarted/index.html +++ b/docs/sources/gettingstarted/index.html @@ -71,34 +71,40 @@

Installing on Ubuntu

+ +

Requirements

+
    +
  • Ubuntu 12.04 (LTS) (64-bit)
  • +
  • or Ubuntu 12.10 (quantal) (64-bit)
  • +
  1. -

    Install dependencies:

    +

    Install dependencies

    + The linux-image-extra package is only needed on standard Ubuntu EC2 AMIs in order to install the aufs kernel module. +
    sudo apt-get install linux-image-extra-`uname -r`
    -
    -
    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:

    +

    Install Docker

    +

    Add the Ubuntu PPA (Personal Package Archive) sources to your apt sources list, update and install.

    +

    You may see some warnings that the GPG keys cannot be verified.

    +
    +
    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
    +
    + + +
  3. + +
  4. +

    Run!

    -
    wget http://get.docker.io/builds/$(uname -s)/$(uname -m)/docker-master.tgz
    -
    tar -xf docker-master.tgz
    +
    docker run -i -t ubuntu /bin/bash
  5. -
  6. -

    Run your first container!

    - -
    cd docker-master
    -
    sudo ./docker run -i -t base /bin/bash
    -
    -

    Done!

    -

    Consider adding docker to your PATH for simplicity.

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

diff --git a/docs/sources/index.rst b/docs/sources/index.rst index cc21a69bf5..4e724a0cd2 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/installation/amazon.rst b/docs/sources/installation/amazon.rst index 5260b992bd..012c78f401 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 0000000000..db013c6cb9 --- /dev/null +++ b/docs/sources/installation/archlinux.rst @@ -0,0 +1,65 @@ +.. _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/binaries.rst b/docs/sources/installation/binaries.rst new file mode 100644 index 0000000000..3428f9f279 --- /dev/null +++ b/docs/sources/installation/binaries.rst @@ -0,0 +1,53 @@ +.. _binaries: + +Binaries +======== + + **Please note this project is currently under heavy development. It should not be used in production.** + + +Right now, the officially supported distributions are: + +- Ubuntu 12.04 (precise LTS) (64-bit) +- Ubuntu 12.10 (quantal) (64-bit) + + +Install dependencies: +--------------------- + +:: + + 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 docker binary: + +:: + + 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! +------------------------- + +:: + + docker run -i -t ubuntu /bin/bash + + + +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 b02e9c83ac..0726d9b715 100644 --- a/docs/sources/installation/index.rst +++ b/docs/sources/installation/index.rst @@ -13,7 +13,9 @@ Contents: :maxdepth: 1 ubuntulinux - macos + binaries + archlinux + vagrant windows amazon upgrading diff --git a/docs/sources/installation/macos.rst b/docs/sources/installation/macos.rst deleted file mode 100644 index 09eca22b92..0000000000 --- a/docs/sources/installation/macos.rst +++ /dev/null @@ -1,66 +0,0 @@ - -Mac OS X and other 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 be out of date because it depends on some binaries to be updated and published - - -Requirements ------------- - -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). - -Installation ------------- - -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/ubuntulinux.rst b/docs/sources/installation/ubuntulinux.rst index bf83a5bc88..ebf9876e22 100644 --- a/docs/sources/installation/ubuntulinux.rst +++ b/docs/sources/installation/ubuntulinux.rst @@ -6,51 +6,56 @@ 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. +- Ubuntu 12.04 (precise LTS) (64-bit) +- Ubuntu 12.10 (quantal) (64-bit) -Install dependencies: ---------------------- +Dependencies +------------ -:: +The linux-image-extra package is only needed on standard Ubuntu EC2 AMIs in order to install the aufs kernel module. - sudo apt-get install lxc wget bsdtar curl - sudo apt-get install linux-image-extra-`uname -r` +.. code-block:: bash -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 + sudo apt-get install linux-image-extra-`uname -r` -To run docker as a daemon, in the background, and allow non-root users to run ``docker`` start -docker -d +Installation +------------ -:: - - sudo ./docker -d & +Docker is available as a Ubuntu PPA (Personal Package Archive), +`hosted on launchpad `_ +which makes installing Docker on Ubuntu very easy. -Consider adding docker to your PATH for simplicity. -Continue with the :ref:`hello_world` example. \ No newline at end of file +Add the custom package sources to your apt sources list. Copy and paste 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. Confirm install. + +.. code-block:: bash + + sudo apt-get install lxc-docker + + +Verify it worked + +.. code-block:: bash + + docker + + +**Done!**, now continue with the :ref:`hello_world` example. diff --git a/docs/sources/installation/upgrading.rst b/docs/sources/installation/upgrading.rst index 4a1de88a7c..66825ac643 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 new file mode 100644 index 0000000000..465a6c3388 --- /dev/null +++ b/docs/sources/installation/vagrant.rst @@ -0,0 +1,70 @@ + +.. _install_using_vagrant: + +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:** +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 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 + + +Spin it up +---------- + +1. Fetch the docker sources (this includes the Vagrantfile for machine setup). + + .. 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 'official' Precise64 base ubuntu virtual machine image from vagrantup.com + * Boot this image in virtualbox + * 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. + +Connect +------- + +To access the VM and use Docker, Run ``vagrant ssh`` from the same directory as where you ran +``vagrant up``. Vagrant will connect you to the correct VM. + +.. code-block:: bash + + vagrant ssh + +Run +----- + +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 6091d6bac1..a89d3a9014 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 new file mode 100644 index 0000000000..97ffd2c0e5 --- /dev/null +++ b/docs/sources/nginx.conf @@ -0,0 +1,6 @@ + +# 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; diff --git a/docs/sources/registry/api.rst b/docs/sources/registry/api.rst new file mode 100644 index 0000000000..1cca9fb244 --- /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 0000000000..d3788f53cc --- /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 0000000000..73a145239c Binary files /dev/null and b/docs/sources/static_files/docker_pull_chart.png differ 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 0000000000..6486355e91 Binary files /dev/null and b/docs/sources/static_files/docker_push_chart.png differ diff --git a/getKernelVersion_darwin.go b/getKernelVersion_darwin.go new file mode 100644 index 0000000000..be3b733b68 --- /dev/null +++ b/getKernelVersion_darwin.go @@ -0,0 +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 new file mode 100644 index 0000000000..04bb1edcb8 --- /dev/null +++ b/getKernelVersion_linux.go @@ -0,0 +1,69 @@ +package docker + +import ( + "bytes" + "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++ + } + + // 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) + + 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/graph.go b/graph.go index e7044c25a0..3823868c83 100644 --- a/graph.go +++ b/graph.go @@ -2,6 +2,7 @@ package docker import ( "fmt" + "io" "io/ioutil" "os" "path" @@ -83,17 +84,24 @@ 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, 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 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 @@ -111,7 +119,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 +136,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 +171,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 7c40330aa4..b7ec81698f 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", "", nil) 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", "", nil) 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", "", nil) 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", "", nil) 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", "", 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) diff --git a/hack/README.md b/hack/README.md deleted file mode 100644 index 06cdd50854..0000000000 --- a/hack/README.md +++ /dev/null @@ -1 +0,0 @@ -This directory contains material helpful for hacking on docker. diff --git a/hack/README.rst b/hack/README.rst new file mode 100644 index 0000000000..4607b6a4a9 --- /dev/null +++ b/hack/README.rst @@ -0,0 +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/Vagrantfile b/hack/Vagrantfile new file mode 100644 index 0000000000..250731ef47 --- /dev/null +++ b/hack/Vagrantfile @@ -0,0 +1,35 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +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" +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 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 + # 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/dockerbuilder/Dockerfile b/hack/dockerbuilder/Dockerfile new file mode 100644 index 0000000000..55540984fd --- /dev/null +++ b/hack/dockerbuilder/Dockerfile @@ -0,0 +1,19 @@ +# 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 +# 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 +add . /src +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"] diff --git a/hack/dockerbuilder/dockerbuilder b/hack/dockerbuilder/dockerbuilder new file mode 100644 index 0000000000..5e803aa0b2 --- /dev/null +++ b/hack/dockerbuilder/dockerbuilder @@ -0,0 +1,53 @@ +#!/bin/sh +set -x +set -e + +PACKAGE=github.com/dotcloud/docker + +if [ $# -gt 1 ]; then + echo "Usage: $0 [REVISION]" + exit 1 +fi + +export REVISION=$1 + +if [ -z "$AWS_ID" ]; then + echo "Warning: environment variable AWS_ID is not set. Won't upload to S3." + NO_S3=1 +fi + +if [ -z "$AWS_KEY" ]; then + echo "Warning: environment variable AWS_KEY is not set. Won't upload to S3." + NO_S3=1 +fi + +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:$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 diff --git a/hack/dockerbuilder/fake_initctl b/hack/dockerbuilder/fake_initctl new file mode 100755 index 0000000000..14c46c8e94 --- /dev/null +++ b/hack/dockerbuilder/fake_initctl @@ -0,0 +1,3 @@ +#!/bin/sh + +echo Whatever you say, man diff --git a/hack/dockerbuilder/s3cfg b/hack/dockerbuilder/s3cfg new file mode 100644 index 0000000000..963af7d365 --- /dev/null +++ b/hack/dockerbuilder/s3cfg @@ -0,0 +1,3 @@ +[default] +access_key = $AWS_ID +secret_key = $AWS_KEY diff --git a/hack/environment/README.rst b/hack/environment/README.rst new file mode 100644 index 0000000000..da5c885e66 --- /dev/null +++ b/hack/environment/README.rst @@ -0,0 +1 @@ +Files used to setup the developer virtual machine diff --git a/hack/environment/bash_profile b/hack/environment/bash_profile new file mode 100644 index 0000000000..77eed79116 --- /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/hack/environment/buildbot.conf b/hack/environment/buildbot.conf new file mode 100644 index 0000000000..b162f4e7c7 --- /dev/null +++ b/hack/environment/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/hack/environment/master.cfg b/hack/environment/master.cfg new file mode 100644 index 0000000000..fad023b602 --- /dev/null +++ b/hack/environment/master.cfg @@ -0,0 +1,43 @@ +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 +GOPATH = '/data/docker' +DOCKER_PATH = '{0}/src/github.com/dotcloud/docker'.format(GOPATH) + +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 = "GOPATH={0} make -C {1} test".format(GOPATH,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/hack/environment/post-commit b/hack/environment/post-commit new file mode 100755 index 0000000000..0173fe504f --- /dev/null +++ b/hack/environment/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/hack/environment/requirements.txt b/hack/environment/requirements.txt new file mode 100644 index 0000000000..0e451b017d --- /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 0000000000..7aa06a5184 --- /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 + diff --git a/image.go b/image.go index 83bf9481ae..09c0f8dcf6 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,8 @@ 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"` + Config *Config `json:"config,omitempty"` graph *Graph } @@ -89,10 +93,31 @@ 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) - 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 { @@ -112,34 +137,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 } diff --git a/network.go b/network.go index 706c31fa4e..373625d59c 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 } diff --git a/packaging/archlinux/README.archlinux b/packaging/archlinux/README.archlinux new file mode 100644 index 0000000000..f20d2d25bc --- /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/ubuntu/Makefile b/packaging/ubuntu/Makefile index beec903fc9..dbdf1af7a9 100644 --- a/packaging/ubuntu/Makefile +++ b/packaging/ubuntu/Makefile @@ -1,73 +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 -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 +VERSION_TAG=v$(shell head -1 changelog | sed 's/^.\+(\(.\+\)-[0-9]\+).\+$$/\1/') -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 + 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 -$(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} + 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 0000000000..286a6f8d52 --- /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 0000000000..0689eea1c2 --- /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 0000000000..88f6c5021e --- /dev/null +++ b/packaging/ubuntu/changelog @@ -0,0 +1,137 @@ + +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 + - 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 + - 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 + + - Record the author an image with 'docker commit -author' + + -- dotCloud Wed, 17 Apr 2013 00:00:00 -0700 + + +lxc-docker (0.1.5-1) precise; urgency=low + + - 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 Wed, 17 Apr 2013 00:00:00 -0700 + + +lxc-docker (0.1.4-1) precise; urgency=low + + - 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 Tue, 9 Apr 2013 00:00:00 -0700 + + +lxc-docker (0.1.3-1) precise; urgency=low + + - Choose TCP frontend port with '-p :PORT' + - Layer format is versioned + - Major reliability improvements to the process manager + - Various bugfixes and stability improvements + + -- 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/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/control b/packaging/ubuntu/control new file mode 100644 index 0000000000..c52303a88b --- /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 0000000000..668c8635e4 --- /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/packaging/ubuntu/debian/changelog b/packaging/ubuntu/debian/changelog deleted file mode 100644 index d8932885e6..0000000000 --- a/packaging/ubuntu/debian/changelog +++ /dev/null @@ -1,5 +0,0 @@ -lxc-docker (1) precise; urgency=low - - * Initial release - - -- dotCloud Mon, 25 Mar 2013 05:51:12 -0700 diff --git a/packaging/ubuntu/debian/control b/packaging/ubuntu/debian/control deleted file mode 100644 index 1ad913854d..0000000000 --- a/packaging/ubuntu/debian/control +++ /dev/null @@ -1,19 +0,0 @@ -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 -Standards-Version: 3.9.3 - -Package: lxc-docker -Architecture: amd64 -Depends: ${shlibs:Depends}, ${misc:Depends}, lxc, wget, bsdtar, curl, sqlite3 -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/packaging/ubuntu/debian/copyright b/packaging/ubuntu/debian/copyright deleted file mode 100644 index c6c97190a9..0000000000 --- a/packaging/ubuntu/debian/copyright +++ /dev/null @@ -1,209 +0,0 @@ -Format: http://dep.debian.net/deps/dep5 -Upstream-Name: 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/packaging/ubuntu/etc/docker.upstart b/packaging/ubuntu/docker.upstart similarity index 50% rename from packaging/ubuntu/etc/docker.upstart rename to packaging/ubuntu/docker.upstart index 6cfe9d2616..07e7e8a890 100644 --- a/packaging/ubuntu/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/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/lxc-docker.postinst b/packaging/ubuntu/lxc-docker.postinst new file mode 100644 index 0000000000..5d04c5b55d --- /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 0000000000..824f15cff0 --- /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 0000000000..07ab0a1f0e --- /dev/null +++ b/packaging/ubuntu/maintainer.ubuntu @@ -0,0 +1,39 @@ +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 packaging/ubuntu/changelog file: +Each new release should create a new first paragraph with new release version, +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:: + + 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 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 ) 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 diff --git a/puppet/manifests/quantal64.pp b/puppet/manifests/quantal64.pp deleted file mode 100644 index 8ef0591650..0000000000 --- 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 702b10e71e..0000000000 --- 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 3abb798c2b..0000000000 --- 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 319c9c5be8..0000000000 --- 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 diff --git a/registry.go b/registry.go index 428db1b968..74b166906f 100644 --- a/registry.go +++ b/registry.go @@ -7,6 +7,7 @@ import ( "io" "io/ioutil" "net/http" + "os" "path" "strings" ) @@ -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 { @@ -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) diff --git a/runtime.go b/runtime.go index 3fe07c7ea6..6e03226b36 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,9 @@ type Runtime struct { repositories *TagStore authConfig *auth.AuthConfig idIndex *TruncIndex + capabilities *Capabilities + kernelVersion *KernelVersionInfo + autoRestart bool } var sysInitPath string @@ -69,12 +78,58 @@ 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 } + + if img.Config != nil { + runtime.mergeConfig(config, img.Config) + } + + if config.Cmd == nil { + return nil, fmt.Errorf("No command specified") + } + // Generate id id := GenerateId() // Generate default hostname @@ -95,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. @@ -159,29 +215,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 - } - } - } - } - - // 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() @@ -199,6 +232,47 @@ func (runtime *Runtime) Register(container *Container) error { // done 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 if !nomonitor { + container.allocateNetwork() + go container.monitor() + } return nil } @@ -217,7 +291,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 { @@ -238,7 +312,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, config *Config) (*Image, error) { container := runtime.Get(id) if container == nil { return nil, fmt.Errorf("No such container: %s", id) @@ -250,7 +324,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, config) if err != nil { return nil, err } @@ -280,12 +354,47 @@ func (runtime *Runtime) restore() error { return nil } -// FIXME: harmonize with NewGraph() -func NewRuntime() (*Runtime, error) { - return NewRuntimeFromDirectory("/var/lib/docker") +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.") + } + } } -func NewRuntimeFromDirectory(root string) (*Runtime, error) { +// FIXME: harmonize with NewGraph() +func NewRuntime(autoRestart bool) (*Runtime, error) { + runtime, err := NewRuntimeFromDirectory("/var/lib/docker", autoRestart) + if err != nil { + return nil, err + } + + 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()) + } + } + runtime.UpdateCapabilities(false) + return runtime, nil +} + +func NewRuntimeFromDirectory(root string, autoRestart bool) (*Runtime, error) { runtimeRepo := path.Join(root, "containers") if err := os.MkdirAll(runtimeRepo, 0700); err != nil && !os.IsExist(err) { @@ -321,6 +430,8 @@ func NewRuntimeFromDirectory(root string) (*Runtime, error) { repositories: repositories, 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 b990deaf38..e9be838c0e 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,11 +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 +const unitTestStoreBase string = "/var/lib/docker/unit-tests" func nuke(runtime *Runtime) error { var wg sync.WaitGroup @@ -48,8 +48,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() @@ -62,15 +60,10 @@ 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 + NetworkBridgeIface = "testdockbr0" // Make it our Store root - runtime, err := NewRuntimeFromDirectory(root) + runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, false) if err != nil { panic(err) } @@ -96,11 +89,11 @@ func newTestRuntime() (*Runtime, error) { return nil, err } - runtime, err := NewRuntimeFromDirectory(root) + runtime, err := NewRuntimeFromDirectory(root, false) if err != nil { return nil, err } - + runtime.UpdateCapabilities(true) return runtime, nil } @@ -263,6 +256,57 @@ 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() + + 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"], + ), + ) + 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), + ) + } + container.Wait() +} + func TestRestore(t *testing.T) { root, err := ioutil.TempDir("", "docker-test") @@ -276,7 +320,7 @@ func TestRestore(t *testing.T) { t.Fatal(err) } - runtime1, err := NewRuntimeFromDirectory(root) + runtime1, err := NewRuntimeFromDirectory(root, false) if err != nil { t.Fatal(err) } @@ -335,7 +379,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) } diff --git a/sysinit.go b/sysinit.go index 2c1106db10..4b2d6c3032 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()) } diff --git a/utils.go b/utils.go index 5ff1ad7a03..364bd73251 100644 --- a/utils.go +++ b/utils.go @@ -71,23 +71,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") } @@ -96,8 +103,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 @@ -405,3 +415,65 @@ func SetRawTerminal() (*term.State, error) { func RestoreTerminal(state *term.State) { term.Restore(int(os.Stdin.Fd()), state) } + +type KernelVersionInfo struct { + Kernel int + Major int + Minor int + Flavor string +} + +// FIXME: this doens't build on Darwin +func GetKernelVersion() (*KernelVersionInfo, error) { + return getKernelVersion() +} + +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 := ioutil.ReadFile("/proc/mounts") + if err != nil { + return "", err + } + + // /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") { + 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) +} diff --git a/utils_test.go b/utils_test.go index c15084f61e..aa2a1b9682 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) +}