diff --git a/.mailmap b/.mailmap index 83c18fa29..1f38e55e2 100644 --- a/.mailmap +++ b/.mailmap @@ -2,7 +2,7 @@ -Guillaume J. Charmes creack +Guillaume J. Charmes @@ -16,4 +16,6 @@ Tim Terhorst Andy Smith + +Thatcher Peskens diff --git a/AUTHORS b/AUTHORS index e8979aac6..e7c6834cf 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,24 +1,34 @@ +Al Tobey +Alexey Shamrin Andrea Luzzardi Andy Rothfusz Andy Smith Antony Messerli +Barry Allard +Brandon Liu Brian McCallister +Bruno Bigras Caleb Spare Charles Hooper Daniel Mizyrycki Daniel Robinson +Daniel Von Fange Dominik Honnef Don Spaulding +Dr Nic Williams +Evan Wies ezbercih Flavio Castelli Francisco Souza Frederick F. Kautz IV Guillaume J. Charmes +Harley Laue Hunter Blanks Jeff Lindsay Jeremy Grosser Joffrey F John Costa +Jonas Pfenniger Jonathan Rudenberg Julien Barbier Jérôme Petazzoni @@ -27,8 +37,11 @@ Kevin J. Lynagh Louis Opter Maxim Treskin Mikhail Sobolev +Nate Jones Nelson Chen Niall O'Higgins +odk- +Paul Bowsher Paul Hammond Piotr Bogdan Robert Obryk @@ -38,6 +51,8 @@ Silas Sewell Solomon Hykes Sridhar Ratnakumar Thatcher Peskens +Thomas Bikeev +Tianon Gravi Tim Terhorst Troy Howard unclejack diff --git a/CHANGELOG.md b/CHANGELOG.md index 5036d87c1..a9e2dab79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,37 @@ # Changelog +## 0.3.2 (2013-05-09) + * Runtime: Store the actual archive on commit + * Registry: Improve the checksum process + * Registry: Use the size to have a good progress bar while pushing + * Registry: Use the actual archive if it exists in order to speed up the push + - Registry: Fix error 400 on push + +## 0.3.1 (2013-05-08) + + Builder: Implement the autorun capability within docker builder + + Builder: Add caching to docker builder + + Builder: Add support for docker builder with native API as top level command + + Runtime: Add go version to debug infos + + Builder: Implement ENV within docker builder + + Registry: Add docker search top level command in order to search a repository + + Images: output graph of images to dot (graphviz) + + Documentation: new introduction and high-level overview + + Documentation: Add the documentation for docker builder + + Website: new high-level overview + - Makefile: Swap "go get" for "go get -d", especially to compile on go1.1rc + - Images: fix ByParent function + - Builder: Check the command existance prior create and add Unit tests for the case + - Registry: Fix pull for official images with specific tag + - Registry: Fix issue when login in with a different user and trying to push + - Documentation: CSS fix for docker documentation to make REST API docs look better. + - Documentation: Fixed CouchDB example page header mistake + - Documentation: fixed README formatting + * Registry: Improve checksum - async calculation + * Runtime: kernel version - don't show the dash if flavor is empty + * Documentation: updated www.docker.io website. + * Builder: use any whitespaces instead of tabs + * Packaging: packaging ubuntu; issue #510: Use goland-stable PPA package to build docker + ## 0.3.0 (2013-05-06) + Registry: Implement the new registry + Documentation: new example: sharing data between 2 couchdb databases diff --git a/Makefile b/Makefile index bae7d6490..9527d3f75 100644 --- a/Makefile +++ b/Makefile @@ -39,7 +39,7 @@ $(DOCKER_BIN): $(DOCKER_DIR) $(DOCKER_DIR): @mkdir -p $(dir $@) @if [ -h $@ ]; then rm -f $@; fi; ln -sf $(CURDIR)/ $@ - @(cd $(DOCKER_MAIN); go get $(GO_OPTIONS)) + @(cd $(DOCKER_MAIN); go get -d $(GO_OPTIONS)) whichrelease: echo $(RELEASE_VERSION) diff --git a/README.md b/README.md index 5b80fe253..918fdaade 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Docker is an open-source implementation of the deployment engine which powers [d It benefits directly from the experience accumulated over several years of large-scale operation and support of hundreds of thousands of applications and databases. -![Docker L](docs/sources/static_files/lego_docker.jpg "Docker") +![Docker L](docs/sources/concepts/images/lego_docker.jpg "Docker") ## Better than VMs @@ -21,12 +21,12 @@ are VMWare's vmdk, Oracle Virtualbox's vdi, and Amazon EC2's ami. In theory thes automatically package their application into a "machine" for easy distribution and deployment. In practice, that almost never happens, for a few reasons: - * *Size*: VMs are very large which makes them impractical to store and transfer. - * *Performance*: running VMs consumes significant CPU and memory, which makes them impractical in many scenarios, for example local development of multi-tier applications, and - large-scale deployment of cpu and memory-intensive applications on large numbers of machines. - * *Portability*: competing VM environments don't play well with each other. Although conversion tools do exist, they are limited and add even more overhead. - * *Hardware-centric*: VMs were designed with machine operators in mind, not software developers. As a result, they offer very limited tooling for what developers need most: - building, testing and running their software. For example, VMs offer no facilities for application versioning, monitoring, configuration, logging or service discovery. + * *Size*: VMs are very large which makes them impractical to store and transfer. + * *Performance*: running VMs consumes significant CPU and memory, which makes them impractical in many scenarios, for example local development of multi-tier applications, and + large-scale deployment of cpu and memory-intensive applications on large numbers of machines. + * *Portability*: competing VM environments don't play well with each other. Although conversion tools do exist, they are limited and add even more overhead. + * *Hardware-centric*: VMs were designed with machine operators in mind, not software developers. As a result, they offer very limited tooling for what developers need most: + building, testing and running their software. For example, VMs offer no facilities for application versioning, monitoring, configuration, logging or service discovery. By contrast, Docker relies on a different sandboxing method known as *containerization*. Unlike traditional virtualization, containerization takes place at the kernel level. Most modern operating system kernels now support the primitives necessary @@ -35,7 +35,7 @@ for containerization, including Linux with [openvz](http://openvz.org), [vserver Docker builds on top of these low-level primitives to offer developers a portable format and runtime environment that solves all 4 problems. Docker containers are small (and their transfer can be optimized with layers), they have basically zero memory and cpu overhead, -the are completely portable and are designed from the ground up with an application-centric design. +they are completely portable and are designed from the ground up with an application-centric design. The best part: because docker operates at the OS level, it can still be run inside a VM! @@ -46,7 +46,7 @@ Docker does not require that you buy into a particular programming language, fra Is your application a unix process? Does it use files, tcp connections, environment variables, standard unix streams and command-line arguments as inputs and outputs? Then docker can run it. -Can your application's build be expressed a sequence of such commands? Then docker can build it. +Can your application's build be expressed as a sequence of such commands? Then docker can build it. ## Escape dependency hell @@ -70,21 +70,21 @@ Docker solves dependency hell by giving the developer a simple way to express *a and streamline the process of assembling them. If this makes you think of [XKCD 927](http://xkcd.com/927/), don't worry. Docker doesn't *replace* your favorite packaging systems. It simply orchestrates their use in a simple and repeatable way. How does it do that? With layers. -Docker defines a build as running a sequence unix commands, one after the other, in the same container. Build commands modify the contents of the container +Docker defines a build as running a sequence of unix commands, one after the other, in the same container. Build commands modify the contents of the container (usually by installing new files on the filesystem), the next command modifies it some more, etc. Since each build command inherits the result of the previous commands, the *order* in which the commands are executed expresses *dependencies*. Here's a typical docker build process: ```bash -from ubuntu:12.10 -run apt-get update -run apt-get install python -run apt-get install python-pip -run pip install django -run apt-get install curl -run curl http://github.com/shykes/helloflask/helloflask/master.tar.gz | tar -zxv -run cd master && pip install -r requirements.txt +from ubuntu:12.10 +run apt-get update +run DEBIAN_FRONTEND=noninteractive apt-get install -q -y python +run DEBIAN_FRONTEND=noninteractive apt-get install -q -y python-pip +run pip install django +run DEBIAN_FRONTEND=noninteractive apt-get install -q -y curl +run curl -L https://github.com/shykes/helloflask/archive/master.tar.gz | tar -xzv +run cd helloflask-master && pip install -r requirements.txt ``` Note that Docker doesn't care *how* dependencies are built - as long as they can be built by running a unix command in a container. @@ -293,7 +293,7 @@ a format that is self-describing and portable, so that any compliant runtime can The spec for Standard Containers is currently a work in progress, but it is very straightforward. It mostly defines 1) an image format, 2) a set of standard operations, and 3) an execution environment. -A great analogy for this is the shipping container. Just like Standard Containers are a fundamental unit of software delivery, shipping containers (http://bricks.argz.com/ins/7823-1/12) are a fundamental unit of physical delivery. +A great analogy for this is the shipping container. Just like how Standard Containers are a fundamental unit of software delivery, shipping containers (http://bricks.argz.com/ins/7823-1/12) are a fundamental unit of physical delivery. ### 1. STANDARD OPERATIONS @@ -321,7 +321,7 @@ Similarly, before Standard Containers, by the time a software component ran in p ### 5. INDUSTRIAL-GRADE DELIVERY -There are 17 million shipping containers in existence, packed with every physical good imaginable. Every single one of them can be loaded on the same boats, by the same cranes, in the same facilities, and sent anywhere in the World with incredible efficiency. It is embarrassing to think that a 30 ton shipment of coffee can safely travel half-way across the World in *less time* than it takes a software team to deliver its code from one datacenter to another sitting 10 miles away. +There are 17 million shipping containers in existence, packed with every physical good imaginable. Every single one of them can be loaded onto the same boats, by the same cranes, in the same facilities, and sent anywhere in the World with incredible efficiency. It is embarrassing to think that a 30 ton shipment of coffee can safely travel half-way across the World in *less time* than it takes a software team to deliver its code from one datacenter to another sitting 10 miles away. With Standard Containers we can put an end to that embarrassment, by making INDUSTRIAL-GRADE DELIVERY of software a reality. diff --git a/Vagrantfile b/Vagrantfile index 06e8b47a4..3d568266a 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -3,41 +3,60 @@ BOX_NAME = ENV['BOX_NAME'] || "ubuntu" BOX_URI = ENV['BOX_URI'] || "http://files.vagrantup.com/precise64.box" -PPA_KEY = "E61D797F63561DC6" +AWS_REGION = ENV['AWS_REGION'] || "us-east-1" +AWS_AMI = ENV['AWS_AMI'] || "ami-d0f89fb9" 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.d/lxc-docker.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" + + # Provision docker and new kernel if deployment was not done + if Dir.glob("#{File.dirname(__FILE__)}/.vagrant/machines/default/*/id").empty? + # Add lxc-docker package + pkg_cmd = "apt-get update -qq; apt-get install -q -y python-software-properties; " \ + "add-apt-repository -y ppa:dotcloud/lxc-docker; apt-get update -qq; " \ + "apt-get install -q -y lxc-docker; " + # Add X.org Ubuntu backported 3.8 kernel + pkg_cmd << "add-apt-repository -y ppa:ubuntu-x-swat/r-lts-backport; " \ + "apt-get update -qq; apt-get install -q -y linux-image-3.8.0-19-generic; " + # Add guest additions if local vbox VM + is_vbox = true + ARGV.each do |arg| is_vbox &&= !arg.downcase.start_with?("--provider") end + if is_vbox + pkg_cmd << "apt-get install -q -y linux-headers-3.8.0-19-generic dkms; " \ + "echo 'Downloading VBox Guest Additions...'; " \ + "wget -q http://dlc.sun.com.edgesuite.net/virtualbox/4.2.12/VBoxGuestAdditions_4.2.12.iso; " + # Prepare the VM to add guest additions after reboot + pkg_cmd << "echo -e 'mount -o loop,ro /home/vagrant/VBoxGuestAdditions_4.2.12.iso /mnt\n" \ + "echo yes | /mnt/VBoxLinuxAdditions.run\numount /mnt\n" \ + "rm /root/guest_additions.sh; ' > /root/guest_additions.sh; " \ + "chmod 700 /root/guest_additions.sh; " \ + "sed -i -E 's#^exit 0#[ -x /root/guest_additions.sh ] \\&\\& /root/guest_additions.sh#' /etc/rc.local; " \ + "echo 'Installation of VBox Guest Additions is proceeding in the background.'; " \ + "echo '\"vagrant reload\" can be used in about 2 minutes to activate the new guest additions.'; " + end + # Activate new kernel + pkg_cmd << "shutdown -r +1; " + config.vm.provision :shell, :inline => pkg_cmd end - config.vm.provision :shell, :inline => pkg_cmd end + # Providers were added on Vagrant >= 1.1.0 Vagrant::VERSION >= "1.1.0" and Vagrant.configure("2") do |config| config.vm.provider :aws do |aws, override| - config.vm.box = "dummy" - config.vm.box_url = "https://github.com/mitchellh/vagrant-aws/raw/master/dummy.box" aws.access_key_id = ENV["AWS_ACCESS_KEY_ID"] aws.secret_access_key = ENV["AWS_SECRET_ACCESS_KEY"] aws.keypair_name = ENV["AWS_KEYPAIR_NAME"] override.ssh.private_key_path = ENV["AWS_SSH_PRIVKEY"] override.ssh.username = "ubuntu" - aws.region = "us-east-1" - aws.ami = "ami-d0f89fb9" + aws.region = AWS_REGION + aws.ami = AWS_AMI aws.instance_type = "t1.micro" end config.vm.provider :rackspace do |rs| - config.vm.box = "dummy" - config.vm.box_url = "https://github.com/mitchellh/vagrant-rackspace/raw/master/dummy.box" config.ssh.private_key_path = ENV["RS_PRIVATE_KEY"] rs.username = ENV["RS_USERNAME"] rs.api_key = ENV["RS_API_KEY"] diff --git a/api.go b/api.go new file mode 100644 index 000000000..29103fac1 --- /dev/null +++ b/api.go @@ -0,0 +1,655 @@ +package docker + +import ( + "encoding/json" + "fmt" + "github.com/dotcloud/docker/auth" + "github.com/dotcloud/docker/utils" + "github.com/gorilla/mux" + "io" + "log" + "net/http" + "strconv" + "strings" +) + +func hijackServer(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) { + conn, _, err := w.(http.Hijacker).Hijack() + if err != nil { + return nil, nil, err + } + // Flush the options to make sure the client sets the raw mode + conn.Write([]byte{}) + return conn, conn, nil +} + +//If we don't do this, POST method without Content-type (even with empty body) will fail +func parseForm(r *http.Request) error { + if err := r.ParseForm(); err != nil && !strings.HasPrefix(err.Error(), "mime:") { + return err + } + return nil +} + +func httpError(w http.ResponseWriter, err error) { + if strings.HasPrefix(err.Error(), "No such") { + http.Error(w, err.Error(), http.StatusNotFound) + } else if strings.HasPrefix(err.Error(), "Bad parameter") { + http.Error(w, err.Error(), http.StatusBadRequest) + } else { + http.Error(w, err.Error(), http.StatusInternalServerError) + } +} + +func writeJson(w http.ResponseWriter, b []byte) { + w.Header().Set("Content-Type", "application/json") + w.Write(b) +} + +func getBoolParam(value string) (bool, error) { + if value == "1" || strings.ToLower(value) == "true" { + return true, nil + } + if value == "" || value == "0" || strings.ToLower(value) == "false" { + return false, nil + } + return false, fmt.Errorf("Bad parameter") +} + +func getAuth(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + b, err := json.Marshal(srv.registry.GetAuthConfig()) + if err != nil { + return err + } + writeJson(w, b) + return nil +} + +func postAuth(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + config := &auth.AuthConfig{} + if err := json.NewDecoder(r.Body).Decode(config); err != nil { + return err + } + + if config.Username == srv.registry.GetAuthConfig().Username { + config.Password = srv.registry.GetAuthConfig().Password + } + + newAuthConfig := auth.NewAuthConfig(config.Username, config.Password, config.Email, srv.runtime.root) + status, err := auth.Login(newAuthConfig) + if err != nil { + return err + } + srv.registry.ResetClient(newAuthConfig) + + if status != "" { + b, err := json.Marshal(&ApiAuth{Status: status}) + if err != nil { + return err + } + writeJson(w, b) + return nil + } + w.WriteHeader(http.StatusNoContent) + return nil +} + +func getVersion(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + m := srv.DockerVersion() + b, err := json.Marshal(m) + if err != nil { + return err + } + writeJson(w, b) + return nil +} + +func postContainersKill(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if vars == nil { + return fmt.Errorf("Missing parameter") + } + name := vars["name"] + if err := srv.ContainerKill(name); err != nil { + return err + } + w.WriteHeader(http.StatusNoContent) + return nil +} + +func getContainersExport(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if vars == nil { + return fmt.Errorf("Missing parameter") + } + name := vars["name"] + + if err := srv.ContainerExport(name, w); err != nil { + utils.Debugf("%s", err.Error()) + return err + } + return nil +} + +func getImagesJson(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := parseForm(r); err != nil { + return err + } + + all, err := getBoolParam(r.Form.Get("all")) + if err != nil { + return err + } + filter := r.Form.Get("filter") + + outs, err := srv.Images(all, filter) + if err != nil { + return err + } + b, err := json.Marshal(outs) + if err != nil { + return err + } + writeJson(w, b) + return nil +} + +func getImagesViz(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := srv.ImagesViz(w); err != nil { + return err + } + return nil +} + +func getInfo(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + out := srv.DockerInfo() + b, err := json.Marshal(out) + if err != nil { + return err + } + writeJson(w, b) + return nil +} + +func getImagesHistory(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if vars == nil { + return fmt.Errorf("Missing parameter") + } + name := vars["name"] + outs, err := srv.ImageHistory(name) + if err != nil { + return err + } + b, err := json.Marshal(outs) + if err != nil { + return err + } + writeJson(w, b) + return nil +} + +func getContainersChanges(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if vars == nil { + return fmt.Errorf("Missing parameter") + } + name := vars["name"] + changesStr, err := srv.ContainerChanges(name) + if err != nil { + return err + } + b, err := json.Marshal(changesStr) + if err != nil { + return err + } + writeJson(w, b) + return nil +} + +func getContainersPs(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := parseForm(r); err != nil { + return err + } + all, err := getBoolParam(r.Form.Get("all")) + if err != nil { + return err + } + since := r.Form.Get("since") + before := r.Form.Get("before") + n, err := strconv.Atoi(r.Form.Get("limit")) + if err != nil { + n = -1 + } + + outs := srv.Containers(all, n, since, before) + b, err := json.Marshal(outs) + if err != nil { + return err + } + writeJson(w, b) + return nil +} + +func postImagesTag(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := parseForm(r); err != nil { + return err + } + repo := r.Form.Get("repo") + tag := r.Form.Get("tag") + if vars == nil { + return fmt.Errorf("Missing parameter") + } + name := vars["name"] + force, err := getBoolParam(r.Form.Get("force")) + if err != nil { + return err + } + + if err := srv.ContainerTag(name, repo, tag, force); err != nil { + return err + } + w.WriteHeader(http.StatusCreated) + return nil +} + +func postCommit(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := parseForm(r); err != nil { + return err + } + config := &Config{} + if err := json.NewDecoder(r.Body).Decode(config); err != nil { + utils.Debugf("%s", err.Error()) + } + repo := r.Form.Get("repo") + tag := r.Form.Get("tag") + container := r.Form.Get("container") + author := r.Form.Get("author") + comment := r.Form.Get("comment") + id, err := srv.ContainerCommit(container, repo, tag, author, comment, config) + if err != nil { + return err + } + b, err := json.Marshal(&ApiId{id}) + if err != nil { + return err + } + w.WriteHeader(http.StatusCreated) + writeJson(w, b) + return nil +} + +// Creates an image from Pull or from Import +func postImagesCreate(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := parseForm(r); err != nil { + return err + } + + src := r.Form.Get("fromSrc") + image := r.Form.Get("fromImage") + tag := r.Form.Get("tag") + repo := r.Form.Get("repo") + + if image != "" { //pull + registry := r.Form.Get("registry") + if err := srv.ImagePull(image, tag, registry, w); err != nil { + return err + } + } else { //import + if err := srv.ImageImport(src, repo, tag, r.Body, w); err != nil { + return err + } + } + return nil +} + +func getImagesSearch(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := parseForm(r); err != nil { + return err + } + + term := r.Form.Get("term") + outs, err := srv.ImagesSearch(term) + if err != nil { + return err + } + b, err := json.Marshal(outs) + if err != nil { + return err + } + writeJson(w, b) + return nil +} + +func postImagesInsert(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := parseForm(r); err != nil { + return err + } + + url := r.Form.Get("url") + path := r.Form.Get("path") + if vars == nil { + return fmt.Errorf("Missing parameter") + } + name := vars["name"] + + if err := srv.ImageInsert(name, url, path, w); err != nil { + return err + } + return nil +} + +func postImagesPush(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := parseForm(r); err != nil { + return err + } + registry := r.Form.Get("registry") + + if vars == nil { + return fmt.Errorf("Missing parameter") + } + name := vars["name"] + + if err := srv.ImagePush(name, registry, w); err != nil { + return err + } + return nil +} + +func postContainersCreate(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + config := &Config{} + if err := json.NewDecoder(r.Body).Decode(config); err != nil { + return err + } + id, err := srv.ContainerCreate(config) + if err != nil { + return err + } + + out := &ApiRun{ + Id: id, + } + if config.Memory > 0 && !srv.runtime.capabilities.MemoryLimit { + log.Println("WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.") + out.Warnings = append(out.Warnings, "Your kernel does not support memory limit capabilities. Limitation discarded.") + } + if config.Memory > 0 && !srv.runtime.capabilities.SwapLimit { + log.Println("WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.") + out.Warnings = append(out.Warnings, "Your kernel does not support memory swap capabilities. Limitation discarded.") + } + b, err := json.Marshal(out) + if err != nil { + return err + } + w.WriteHeader(http.StatusCreated) + writeJson(w, b) + return nil +} + +func postContainersRestart(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := parseForm(r); err != nil { + return err + } + t, err := strconv.Atoi(r.Form.Get("t")) + if err != nil || t < 0 { + t = 10 + } + if vars == nil { + return fmt.Errorf("Missing parameter") + } + name := vars["name"] + if err := srv.ContainerRestart(name, t); err != nil { + return err + } + w.WriteHeader(http.StatusNoContent) + return nil +} + +func deleteContainers(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := parseForm(r); err != nil { + return err + } + if vars == nil { + return fmt.Errorf("Missing parameter") + } + name := vars["name"] + removeVolume, err := getBoolParam(r.Form.Get("v")) + if err != nil { + return err + } + + if err := srv.ContainerDestroy(name, removeVolume); err != nil { + return err + } + w.WriteHeader(http.StatusNoContent) + return nil +} + +func deleteImages(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if vars == nil { + return fmt.Errorf("Missing parameter") + } + name := vars["name"] + if err := srv.ImageDelete(name); err != nil { + return err + } + w.WriteHeader(http.StatusNoContent) + return nil +} + +func postContainersStart(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if vars == nil { + return fmt.Errorf("Missing parameter") + } + name := vars["name"] + if err := srv.ContainerStart(name); err != nil { + return err + } + w.WriteHeader(http.StatusNoContent) + return nil +} + +func postContainersStop(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := parseForm(r); err != nil { + return err + } + t, err := strconv.Atoi(r.Form.Get("t")) + if err != nil || t < 0 { + t = 10 + } + + if vars == nil { + return fmt.Errorf("Missing parameter") + } + name := vars["name"] + + if err := srv.ContainerStop(name, t); err != nil { + return err + } + w.WriteHeader(http.StatusNoContent) + return nil +} + +func postContainersWait(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if vars == nil { + return fmt.Errorf("Missing parameter") + } + name := vars["name"] + status, err := srv.ContainerWait(name) + if err != nil { + return err + } + b, err := json.Marshal(&ApiWait{StatusCode: status}) + if err != nil { + return err + } + writeJson(w, b) + return nil +} + +func postContainersAttach(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := parseForm(r); err != nil { + return err + } + logs, err := getBoolParam(r.Form.Get("logs")) + if err != nil { + return err + } + stream, err := getBoolParam(r.Form.Get("stream")) + if err != nil { + return err + } + stdin, err := getBoolParam(r.Form.Get("stdin")) + if err != nil { + return err + } + stdout, err := getBoolParam(r.Form.Get("stdout")) + if err != nil { + return err + } + stderr, err := getBoolParam(r.Form.Get("stderr")) + if err != nil { + return err + } + + if vars == nil { + return fmt.Errorf("Missing parameter") + } + name := vars["name"] + + in, out, err := hijackServer(w) + if err != nil { + return err + } + defer in.Close() + + fmt.Fprintf(out, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n") + if err := srv.ContainerAttach(name, logs, stream, stdin, stdout, stderr, in, out); err != nil { + fmt.Fprintf(out, "Error: %s\n", err) + } + return nil +} + +func getContainersByName(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if vars == nil { + return fmt.Errorf("Missing parameter") + } + name := vars["name"] + + container, err := srv.ContainerInspect(name) + if err != nil { + return err + } + b, err := json.Marshal(container) + if err != nil { + return err + } + writeJson(w, b) + return nil +} + +func getImagesByName(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if vars == nil { + return fmt.Errorf("Missing parameter") + } + name := vars["name"] + + image, err := srv.ImageInspect(name) + if err != nil { + return err + } + b, err := json.Marshal(image) + if err != nil { + return err + } + writeJson(w, b) + return nil +} + +func postImagesGetCache(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + apiConfig := &ApiImageConfig{} + if err := json.NewDecoder(r.Body).Decode(apiConfig); err != nil { + return err + } + + image, err := srv.ImageGetCached(apiConfig.Id, apiConfig.Config) + if err != nil { + return err + } + if image == nil { + w.WriteHeader(http.StatusNotFound) + return nil + } + apiId := &ApiId{Id: image.Id} + b, err := json.Marshal(apiId) + if err != nil { + return err + } + writeJson(w, b) + return nil +} + +func ListenAndServe(addr string, srv *Server, logging bool) error { + r := mux.NewRouter() + log.Printf("Listening for HTTP on %s\n", addr) + + m := map[string]map[string]func(*Server, http.ResponseWriter, *http.Request, map[string]string) error{ + "GET": { + "/auth": getAuth, + "/version": getVersion, + "/info": getInfo, + "/images/json": getImagesJson, + "/images/viz": getImagesViz, + "/images/search": getImagesSearch, + "/images/{name:.*}/history": getImagesHistory, + "/images/{name:.*}/json": getImagesByName, + "/containers/ps": getContainersPs, + "/containers/{name:.*}/export": getContainersExport, + "/containers/{name:.*}/changes": getContainersChanges, + "/containers/{name:.*}/json": getContainersByName, + }, + "POST": { + "/auth": postAuth, + "/commit": postCommit, + "/images/create": postImagesCreate, + "/images/{name:.*}/insert": postImagesInsert, + "/images/{name:.*}/push": postImagesPush, + "/images/{name:.*}/tag": postImagesTag, + "/images/getCache": postImagesGetCache, + "/containers/create": postContainersCreate, + "/containers/{name:.*}/kill": postContainersKill, + "/containers/{name:.*}/restart": postContainersRestart, + "/containers/{name:.*}/start": postContainersStart, + "/containers/{name:.*}/stop": postContainersStop, + "/containers/{name:.*}/wait": postContainersWait, + "/containers/{name:.*}/attach": postContainersAttach, + }, + "DELETE": { + "/containers/{name:.*}": deleteContainers, + "/images/{name:.*}": deleteImages, + }, + } + + for method, routes := range m { + for route, fct := range routes { + utils.Debugf("Registering %s, %s", method, route) + // NOTE: scope issue, make sure the variables are local and won't be changed + localRoute := route + localMethod := method + localFct := fct + r.Path(localRoute).Methods(localMethod).HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + utils.Debugf("Calling %s %s", localMethod, localRoute) + if logging { + log.Println(r.Method, r.RequestURI) + } + if strings.Contains(r.Header.Get("User-Agent"), "Docker-Client/") { + userAgent := strings.Split(r.Header.Get("User-Agent"), "/") + if len(userAgent) == 2 && userAgent[1] != VERSION { + utils.Debugf("Warning: client and server don't have the same version (client: %s, server: %s)", userAgent[1], VERSION) + } + } + if err := localFct(srv, w, r, mux.Vars(r)); err != nil { + httpError(w, err) + } + }) + } + } + + return http.ListenAndServe(addr, r) +} diff --git a/api_params.go b/api_params.go new file mode 100644 index 000000000..1a24ab287 --- /dev/null +++ b/api_params.go @@ -0,0 +1,71 @@ +package docker + +type ApiHistory struct { + Id string + Created int64 + CreatedBy string +} + +type ApiImages struct { + Repository string `json:",omitempty"` + Tag string `json:",omitempty"` + Id string + Created int64 +} + +type ApiInfo struct { + Containers int + Version string + Images int + Debug bool + GoVersion string + NFd int `json:",omitempty"` + NGoroutines int `json:",omitempty"` +} + +type ApiContainers struct { + Id string + Image string + Command string + Created int64 + Status string + Ports string +} + +type ApiSearch struct { + Name string + Description string +} + +type ApiId struct { + Id string +} + +type ApiRun struct { + Id string + Warnings []string +} + +type ApiPort struct { + Port string +} + +type ApiVersion struct { + Version string + GitCommit string + MemoryLimit bool + SwapLimit bool +} + +type ApiWait struct { + StatusCode int +} + +type ApiAuth struct { + Status string +} + +type ApiImageConfig struct { + Id string + *Config +} diff --git a/api_test.go b/api_test.go new file mode 100644 index 000000000..dd685ffec --- /dev/null +++ b/api_test.go @@ -0,0 +1,1273 @@ +package docker + +import ( + "archive/tar" + "bufio" + "bytes" + "encoding/json" + "github.com/dotcloud/docker/auth" + "github.com/dotcloud/docker/registry" + "github.com/dotcloud/docker/utils" + "io" + "net" + "net/http" + "net/http/httptest" + "os" + "path" + "testing" + "time" +) + +func TestGetAuth(t *testing.T) { + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) + + srv := &Server{ + runtime: runtime, + registry: registry.NewRegistry(runtime.root), + } + + r := httptest.NewRecorder() + + authConfig := &auth.AuthConfig{ + Username: "utest", + Password: "utest", + Email: "utest@yopmail.com", + } + + authConfigJson, err := json.Marshal(authConfig) + if err != nil { + t.Fatal(err) + } + + req, err := http.NewRequest("POST", "/auth", bytes.NewReader(authConfigJson)) + if err != nil { + t.Fatal(err) + } + + if err := postAuth(srv, r, req, nil); err != nil { + t.Fatal(err) + } + + if r.Code != http.StatusOK && r.Code != 0 { + t.Fatalf("%d OK or 0 expected, received %d\n", http.StatusOK, r.Code) + } + + newAuthConfig := srv.registry.GetAuthConfig() + if newAuthConfig.Username != authConfig.Username || + newAuthConfig.Email != authConfig.Email { + t.Fatalf("The auth configuration hasn't been set correctly") + } +} + +func TestGetVersion(t *testing.T) { + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) + + srv := &Server{runtime: runtime} + + r := httptest.NewRecorder() + + if err := getVersion(srv, r, nil, nil); err != nil { + t.Fatal(err) + } + + v := &ApiVersion{} + if err = json.Unmarshal(r.Body.Bytes(), v); err != nil { + t.Fatal(err) + } + if v.Version != VERSION { + t.Errorf("Excepted version %s, %s found", VERSION, v.Version) + } +} + +func TestGetInfo(t *testing.T) { + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) + + srv := &Server{runtime: runtime} + + r := httptest.NewRecorder() + + if err := getInfo(srv, r, nil, nil); err != nil { + t.Fatal(err) + } + + infos := &ApiInfo{} + err = json.Unmarshal(r.Body.Bytes(), infos) + if err != nil { + t.Fatal(err) + } + if infos.Version != VERSION { + t.Errorf("Excepted version %s, %s found", VERSION, infos.Version) + } +} + +func TestGetImagesJson(t *testing.T) { + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) + + srv := &Server{runtime: runtime} + + // all=0 + req, err := http.NewRequest("GET", "/images/json?all=0", nil) + if err != nil { + t.Fatal(err) + } + + r := httptest.NewRecorder() + + if err := getImagesJson(srv, r, req, nil); err != nil { + t.Fatal(err) + } + + images := []ApiImages{} + if err := json.Unmarshal(r.Body.Bytes(), &images); err != nil { + t.Fatal(err) + } + + if len(images) != 1 { + t.Errorf("Excepted 1 image, %d found", len(images)) + } + + if images[0].Repository != unitTestImageName { + t.Errorf("Excepted image %s, %s found", unitTestImageName, images[0].Repository) + } + + r2 := httptest.NewRecorder() + + // all=1 + req2, err := http.NewRequest("GET", "/images/json?all=true", nil) + if err != nil { + t.Fatal(err) + } + + if err := getImagesJson(srv, r2, req2, nil); err != nil { + t.Fatal(err) + } + + images2 := []ApiImages{} + if err := json.Unmarshal(r2.Body.Bytes(), &images2); err != nil { + t.Fatal(err) + } + + if len(images2) != 1 { + t.Errorf("Excepted 1 image, %d found", len(images2)) + } + + if images2[0].Id != GetTestImage(runtime).Id { + t.Errorf("Retrieved image Id differs, expected %s, received %s", GetTestImage(runtime).Id, images2[0].Id) + } + + r3 := httptest.NewRecorder() + + // filter=a + req3, err := http.NewRequest("GET", "/images/json?filter=a", nil) + if err != nil { + t.Fatal(err) + } + + if err := getImagesJson(srv, r3, req3, nil); err != nil { + t.Fatal(err) + } + + images3 := []ApiImages{} + if err := json.Unmarshal(r3.Body.Bytes(), &images3); err != nil { + t.Fatal(err) + } + + if len(images3) != 0 { + t.Errorf("Excepted 1 image, %d found", len(images3)) + } + + r4 := httptest.NewRecorder() + + // all=foobar + req4, err := http.NewRequest("GET", "/images/json?all=foobar", nil) + if err != nil { + t.Fatal(err) + } + + err = getImagesJson(srv, r4, req4, nil) + if err == nil { + t.Fatalf("Error expected, received none") + } + + httpError(r4, err) + if r4.Code != http.StatusBadRequest { + t.Fatalf("%d Bad Request expected, received %d\n", http.StatusBadRequest, r4.Code) + } +} + +func TestGetImagesViz(t *testing.T) { + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) + + srv := &Server{runtime: runtime} + + r := httptest.NewRecorder() + if err := getImagesViz(srv, r, nil, nil); err != nil { + t.Fatal(err) + } + + if r.Code != http.StatusOK { + t.Fatalf("%d OK expected, received %d\n", http.StatusOK, r.Code) + } + + reader := bufio.NewReader(r.Body) + line, err := reader.ReadString('\n') + if err != nil { + t.Fatal(err) + } + if line != "digraph docker {\n" { + t.Errorf("Excepted digraph docker {\n, %s found", line) + } +} + +func TestGetImagesSearch(t *testing.T) { + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) + + srv := &Server{ + runtime: runtime, + registry: registry.NewRegistry(runtime.root), + } + + r := httptest.NewRecorder() + + req, err := http.NewRequest("GET", "/images/search?term=redis", nil) + if err != nil { + t.Fatal(err) + } + + if err := getImagesSearch(srv, r, req, nil); err != nil { + t.Fatal(err) + } + + results := []ApiSearch{} + if err := json.Unmarshal(r.Body.Bytes(), &results); err != nil { + t.Fatal(err) + } + if len(results) < 2 { + t.Errorf("Excepted at least 2 lines, %d found", len(results)) + } +} + +func TestGetImagesHistory(t *testing.T) { + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) + + srv := &Server{runtime: runtime} + + r := httptest.NewRecorder() + + if err := getImagesHistory(srv, r, nil, map[string]string{"name": unitTestImageName}); err != nil { + t.Fatal(err) + } + + history := []ApiHistory{} + if err := json.Unmarshal(r.Body.Bytes(), &history); err != nil { + t.Fatal(err) + } + if len(history) != 1 { + t.Errorf("Excepted 1 line, %d found", len(history)) + } +} + +func TestGetImagesByName(t *testing.T) { + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) + + srv := &Server{runtime: runtime} + + r := httptest.NewRecorder() + if err := getImagesByName(srv, r, nil, map[string]string{"name": unitTestImageName}); err != nil { + t.Fatal(err) + } + + img := &Image{} + if err := json.Unmarshal(r.Body.Bytes(), img); err != nil { + t.Fatal(err) + } + if img.Id != GetTestImage(runtime).Id || img.Comment != "Imported from http://get.docker.io/images/busybox" { + t.Errorf("Error inspecting image") + } +} + +func TestGetContainersPs(t *testing.T) { + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) + + srv := &Server{runtime: runtime} + + container, err := NewBuilder(runtime).Create(&Config{ + Image: GetTestImage(runtime).Id, + Cmd: []string{"echo", "test"}, + }) + if err != nil { + t.Fatal(err) + } + defer runtime.Destroy(container) + + req, err := http.NewRequest("GET", "/containers?quiet=1&all=1", nil) + if err != nil { + t.Fatal(err) + } + + r := httptest.NewRecorder() + if err := getContainersPs(srv, r, req, nil); err != nil { + t.Fatal(err) + } + containers := []ApiContainers{} + if err := json.Unmarshal(r.Body.Bytes(), &containers); err != nil { + t.Fatal(err) + } + if len(containers) != 1 { + t.Fatalf("Excepted %d container, %d found", 1, len(containers)) + } + if containers[0].Id != container.Id { + t.Fatalf("Container ID mismatch. Expected: %s, received: %s\n", container.Id, containers[0].Id) + } +} + +func TestGetContainersExport(t *testing.T) { + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) + + srv := &Server{runtime: runtime} + + builder := NewBuilder(runtime) + + // Create a container and remove a file + container, err := builder.Create( + &Config{ + Image: GetTestImage(runtime).Id, + Cmd: []string{"touch", "/test"}, + }, + ) + if err != nil { + t.Fatal(err) + } + defer runtime.Destroy(container) + + if err := container.Run(); err != nil { + t.Fatal(err) + } + + r := httptest.NewRecorder() + if err = getContainersExport(srv, r, nil, map[string]string{"name": container.Id}); err != nil { + t.Fatal(err) + } + + if r.Code != http.StatusOK { + t.Fatalf("%d OK expected, received %d\n", http.StatusOK, r.Code) + } + + found := false + for tarReader := tar.NewReader(r.Body); ; { + h, err := tarReader.Next() + if err != nil { + if err == io.EOF { + break + } + t.Fatal(err) + } + if h.Name == "./test" { + found = true + break + } + } + if !found { + t.Fatalf("The created test file has not been found in the exported image") + } +} + +func TestGetContainersChanges(t *testing.T) { + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) + + srv := &Server{runtime: runtime} + + builder := NewBuilder(runtime) + + // Create a container and remove a file + container, err := builder.Create( + &Config{ + Image: GetTestImage(runtime).Id, + Cmd: []string{"/bin/rm", "/etc/passwd"}, + }, + ) + if err != nil { + t.Fatal(err) + } + defer runtime.Destroy(container) + + if err := container.Run(); err != nil { + t.Fatal(err) + } + + r := httptest.NewRecorder() + if err := getContainersChanges(srv, r, nil, map[string]string{"name": container.Id}); err != nil { + t.Fatal(err) + } + changes := []Change{} + if err := json.Unmarshal(r.Body.Bytes(), &changes); err != nil { + t.Fatal(err) + } + + // Check the changelog + success := false + for _, elem := range changes { + 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") + } +} + +func TestGetContainersByName(t *testing.T) { + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) + + srv := &Server{runtime: runtime} + + builder := NewBuilder(runtime) + + // Create a container and remove a file + container, err := builder.Create( + &Config{ + Image: GetTestImage(runtime).Id, + Cmd: []string{"echo", "test"}, + }, + ) + if err != nil { + t.Fatal(err) + } + defer runtime.Destroy(container) + + r := httptest.NewRecorder() + if err := getContainersByName(srv, r, nil, map[string]string{"name": container.Id}); err != nil { + t.Fatal(err) + } + outContainer := &Container{} + if err := json.Unmarshal(r.Body.Bytes(), outContainer); err != nil { + t.Fatal(err) + } + if outContainer.Id != container.Id { + t.Fatalf("Wrong containers retrieved. Expected %s, recieved %s", container.Id, outContainer.Id) + } +} + +func TestPostAuth(t *testing.T) { + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) + + srv := &Server{ + runtime: runtime, + registry: registry.NewRegistry(runtime.root), + } + + authConfigOrig := &auth.AuthConfig{ + Username: "utest", + Email: "utest@yopmail.com", + } + srv.registry.ResetClient(authConfigOrig) + + r := httptest.NewRecorder() + if err := getAuth(srv, r, nil, nil); err != nil { + t.Fatal(err) + } + + authConfig := &auth.AuthConfig{} + if err := json.Unmarshal(r.Body.Bytes(), authConfig); err != nil { + t.Fatal(err) + } + + if authConfig.Username != authConfigOrig.Username || authConfig.Email != authConfigOrig.Email { + t.Errorf("The retrieve auth mismatch with the one set.") + } +} + +func TestPostCommit(t *testing.T) { + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) + + srv := &Server{runtime: runtime} + + builder := NewBuilder(runtime) + + // Create a container and remove a file + container, err := builder.Create( + &Config{ + Image: GetTestImage(runtime).Id, + Cmd: []string{"touch", "/test"}, + }, + ) + if err != nil { + t.Fatal(err) + } + defer runtime.Destroy(container) + + if err := container.Run(); err != nil { + t.Fatal(err) + } + + req, err := http.NewRequest("POST", "/commit?repo=testrepo&testtag=tag&container="+container.Id, bytes.NewReader([]byte{})) + if err != nil { + t.Fatal(err) + } + + r := httptest.NewRecorder() + if err := postCommit(srv, r, req, nil); err != nil { + t.Fatal(err) + } + if r.Code != http.StatusCreated { + t.Fatalf("%d Created expected, received %d\n", http.StatusCreated, r.Code) + } + + apiId := &ApiId{} + if err := json.Unmarshal(r.Body.Bytes(), apiId); err != nil { + t.Fatal(err) + } + if _, err := runtime.graph.Get(apiId.Id); err != nil { + t.Fatalf("The image has not been commited") + } +} + +func TestPostImagesCreate(t *testing.T) { + // FIXME: Use the staging in order to perform tests + + // runtime, err := newTestRuntime() + // if err != nil { + // t.Fatal(err) + // } + // defer nuke(runtime) + + // srv := &Server{runtime: runtime} + + // stdin, stdinPipe := io.Pipe() + // stdout, stdoutPipe := io.Pipe() + + // c1 := make(chan struct{}) + // go func() { + // defer close(c1) + + // r := &hijackTester{ + // ResponseRecorder: httptest.NewRecorder(), + // in: stdin, + // out: stdoutPipe, + // } + + // req, err := http.NewRequest("POST", "/images/create?fromImage="+unitTestImageName, bytes.NewReader([]byte{})) + // if err != nil { + // t.Fatal(err) + // } + + // body, err := postImagesCreate(srv, r, req, nil) + // if err != nil { + // t.Fatal(err) + // } + // if body != nil { + // t.Fatalf("No body expected, received: %s\n", body) + // } + // }() + + // // Acknowledge hijack + // setTimeout(t, "hijack acknowledge timed out", 2*time.Second, func() { + // stdout.Read([]byte{}) + // stdout.Read(make([]byte, 4096)) + // }) + + // setTimeout(t, "Waiting for imagesCreate output", 5*time.Second, func() { + // reader := bufio.NewReader(stdout) + // line, err := reader.ReadString('\n') + // if err != nil { + // t.Fatal(err) + // } + // if !strings.HasPrefix(line, "Pulling repository d from") { + // t.Fatalf("Expected Pulling repository docker-ut from..., found %s", line) + // } + // }) + + // // Close pipes (client disconnects) + // if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil { + // t.Fatal(err) + // } + + // // Wait for imagesCreate to finish, the client disconnected, therefore, Create finished his job + // setTimeout(t, "Waiting for imagesCreate timed out", 10*time.Second, func() { + // <-c1 + // }) +} + +func TestPostImagesInsert(t *testing.T) { + // runtime, err := newTestRuntime() + // if err != nil { + // t.Fatal(err) + // } + // defer nuke(runtime) + + // srv := &Server{runtime: runtime} + + // stdin, stdinPipe := io.Pipe() + // stdout, stdoutPipe := io.Pipe() + + // // Attach to it + // c1 := make(chan struct{}) + // go func() { + // defer close(c1) + // r := &hijackTester{ + // ResponseRecorder: httptest.NewRecorder(), + // in: stdin, + // out: stdoutPipe, + // } + + // req, err := http.NewRequest("POST", "/images/"+unitTestImageName+"/insert?path=%2Ftest&url=https%3A%2F%2Fraw.github.com%2Fdotcloud%2Fdocker%2Fmaster%2FREADME.md", bytes.NewReader([]byte{})) + // if err != nil { + // t.Fatal(err) + // } + // if err := postContainersCreate(srv, r, req, nil); err != nil { + // t.Fatal(err) + // } + // }() + + // // Acknowledge hijack + // setTimeout(t, "hijack acknowledge timed out", 5*time.Second, func() { + // stdout.Read([]byte{}) + // stdout.Read(make([]byte, 4096)) + // }) + + // id := "" + // setTimeout(t, "Waiting for imagesInsert output", 10*time.Second, func() { + // for { + // reader := bufio.NewReader(stdout) + // id, err = reader.ReadString('\n') + // if err != nil { + // t.Fatal(err) + // } + // } + // }) + + // // Close pipes (client disconnects) + // if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil { + // t.Fatal(err) + // } + + // // Wait for attach to finish, the client disconnected, therefore, Attach finished his job + // setTimeout(t, "Waiting for CmdAttach timed out", 2*time.Second, func() { + // <-c1 + // }) + + // img, err := srv.runtime.repositories.LookupImage(id) + // if err != nil { + // t.Fatalf("New image %s expected but not found", id) + // } + + // layer, err := img.layer() + // if err != nil { + // t.Fatal(err) + // } + + // if _, err := os.Stat(path.Join(layer, "test")); err != nil { + // t.Fatalf("The test file has not been found") + // } + + // if err := srv.runtime.graph.Delete(img.Id); err != nil { + // t.Fatal(err) + // } +} + +func TestPostImagesPush(t *testing.T) { + //FIXME: Use staging in order to perform tests + // runtime, err := newTestRuntime() + // if err != nil { + // t.Fatal(err) + // } + // defer nuke(runtime) + + // srv := &Server{runtime: runtime} + + // stdin, stdinPipe := io.Pipe() + // stdout, stdoutPipe := io.Pipe() + + // c1 := make(chan struct{}) + // go func() { + // r := &hijackTester{ + // ResponseRecorder: httptest.NewRecorder(), + // in: stdin, + // out: stdoutPipe, + // } + + // req, err := http.NewRequest("POST", "/images/docker-ut/push", bytes.NewReader([]byte{})) + // if err != nil { + // t.Fatal(err) + // } + + // body, err := postImagesPush(srv, r, req, map[string]string{"name": "docker-ut"}) + // close(c1) + // if err != nil { + // t.Fatal(err) + // } + // if body != nil { + // t.Fatalf("No body expected, received: %s\n", body) + // } + // }() + + // // Acknowledge hijack + // setTimeout(t, "hijack acknowledge timed out", 2*time.Second, func() { + // stdout.Read([]byte{}) + // stdout.Read(make([]byte, 4096)) + // }) + + // setTimeout(t, "Waiting for imagesCreate output", 5*time.Second, func() { + // reader := bufio.NewReader(stdout) + // line, err := reader.ReadString('\n') + // if err != nil { + // t.Fatal(err) + // } + // if !strings.HasPrefix(line, "Processing checksum") { + // t.Fatalf("Processing checksum..., found %s", line) + // } + // }) + + // // Close pipes (client disconnects) + // if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil { + // t.Fatal(err) + // } + + // // Wait for imagesPush to finish, the client disconnected, therefore, Push finished his job + // setTimeout(t, "Waiting for imagesPush timed out", 10*time.Second, func() { + // <-c1 + // }) +} + +func TestPostImagesTag(t *testing.T) { + // FIXME: Use staging in order to perform tests + + // runtime, err := newTestRuntime() + // if err != nil { + // t.Fatal(err) + // } + // defer nuke(runtime) + + // srv := &Server{runtime: runtime} + + // r := httptest.NewRecorder() + + // req, err := http.NewRequest("POST", "/images/docker-ut/tag?repo=testrepo&tag=testtag", bytes.NewReader([]byte{})) + // if err != nil { + // t.Fatal(err) + // } + + // body, err := postImagesTag(srv, r, req, map[string]string{"name": "docker-ut"}) + // if err != nil { + // t.Fatal(err) + // } + + // if body != nil { + // t.Fatalf("No body expected, received: %s\n", body) + // } + // if r.Code != http.StatusCreated { + // t.Fatalf("%d Created expected, received %d\n", http.StatusCreated, r.Code) + // } +} + +func TestPostContainersCreate(t *testing.T) { + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) + + srv := &Server{runtime: runtime} + + configJson, err := json.Marshal(&Config{ + Image: GetTestImage(runtime).Id, + Memory: 33554432, + Cmd: []string{"touch", "/test"}, + }) + if err != nil { + t.Fatal(err) + } + + req, err := http.NewRequest("POST", "/containers/create", bytes.NewReader(configJson)) + if err != nil { + t.Fatal(err) + } + + r := httptest.NewRecorder() + if err := postContainersCreate(srv, r, req, nil); err != nil { + t.Fatal(err) + } + if r.Code != http.StatusCreated { + t.Fatalf("%d Created expected, received %d\n", http.StatusCreated, r.Code) + } + + apiRun := &ApiRun{} + if err := json.Unmarshal(r.Body.Bytes(), apiRun); err != nil { + t.Fatal(err) + } + + container := srv.runtime.Get(apiRun.Id) + if container == nil { + t.Fatalf("Container not created") + } + + if err := container.Run(); err != nil { + t.Fatal(err) + } + + if _, err := os.Stat(path.Join(container.rwPath(), "test")); err != nil { + if os.IsNotExist(err) { + utils.Debugf("Err: %s", err) + t.Fatalf("The test file has not been created") + } + t.Fatal(err) + } +} + +func TestPostContainersKill(t *testing.T) { + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) + + srv := &Server{runtime: runtime} + + container, err := NewBuilder(runtime).Create( + &Config{ + Image: GetTestImage(runtime).Id, + Cmd: []string{"/bin/cat"}, + OpenStdin: true, + }, + ) + if err != nil { + t.Fatal(err) + } + defer runtime.Destroy(container) + + if err := container.Start(); err != nil { + t.Fatal(err) + } + + // Give some time to the process to start + container.WaitTimeout(500 * time.Millisecond) + + if !container.State.Running { + t.Errorf("Container should be running") + } + + r := httptest.NewRecorder() + if err := postContainersKill(srv, r, nil, map[string]string{"name": container.Id}); err != nil { + t.Fatal(err) + } + if r.Code != http.StatusNoContent { + t.Fatalf("%d NO CONTENT expected, received %d\n", http.StatusNoContent, r.Code) + } + if container.State.Running { + t.Fatalf("The container hasn't been killed") + } +} + +func TestPostContainersRestart(t *testing.T) { + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) + + srv := &Server{runtime: runtime} + + container, err := NewBuilder(runtime).Create( + &Config{ + Image: GetTestImage(runtime).Id, + Cmd: []string{"/bin/cat"}, + OpenStdin: true, + }, + ) + if err != nil { + t.Fatal(err) + } + defer runtime.Destroy(container) + + if err := container.Start(); err != nil { + t.Fatal(err) + } + + // Give some time to the process to start + container.WaitTimeout(500 * time.Millisecond) + + if !container.State.Running { + t.Errorf("Container should be running") + } + + req, err := http.NewRequest("POST", "/containers/"+container.Id+"/restart?t=1", bytes.NewReader([]byte{})) + if err != nil { + t.Fatal(err) + } + r := httptest.NewRecorder() + if err := postContainersRestart(srv, r, req, map[string]string{"name": container.Id}); err != nil { + t.Fatal(err) + } + if r.Code != http.StatusNoContent { + t.Fatalf("%d NO CONTENT expected, received %d\n", http.StatusNoContent, r.Code) + } + + // Give some time to the process to restart + container.WaitTimeout(500 * time.Millisecond) + + if !container.State.Running { + t.Fatalf("Container should be running") + } + + if err := container.Kill(); err != nil { + t.Fatal(err) + } +} + +func TestPostContainersStart(t *testing.T) { + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) + + srv := &Server{runtime: runtime} + + container, err := NewBuilder(runtime).Create( + &Config{ + Image: GetTestImage(runtime).Id, + Cmd: []string{"/bin/cat"}, + OpenStdin: true, + }, + ) + if err != nil { + t.Fatal(err) + } + defer runtime.Destroy(container) + + r := httptest.NewRecorder() + if err := postContainersStart(srv, r, nil, map[string]string{"name": container.Id}); err != nil { + t.Fatal(err) + } + if r.Code != http.StatusNoContent { + t.Fatalf("%d NO CONTENT expected, received %d\n", http.StatusNoContent, r.Code) + } + + // Give some time to the process to start + container.WaitTimeout(500 * time.Millisecond) + + if !container.State.Running { + t.Errorf("Container should be running") + } + + r = httptest.NewRecorder() + if err = postContainersStart(srv, r, nil, map[string]string{"name": container.Id}); err == nil { + t.Fatalf("A running containter should be able to be started") + } + + if err := container.Kill(); err != nil { + t.Fatal(err) + } +} + +func TestPostContainersStop(t *testing.T) { + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) + + srv := &Server{runtime: runtime} + + container, err := NewBuilder(runtime).Create( + &Config{ + Image: GetTestImage(runtime).Id, + Cmd: []string{"/bin/cat"}, + OpenStdin: true, + }, + ) + if err != nil { + t.Fatal(err) + } + defer runtime.Destroy(container) + + if err := container.Start(); err != nil { + t.Fatal(err) + } + + // Give some time to the process to start + container.WaitTimeout(500 * time.Millisecond) + + if !container.State.Running { + t.Errorf("Container should be running") + } + + // Note: as it is a POST request, it requires a body. + req, err := http.NewRequest("POST", "/containers/"+container.Id+"/stop?t=1", bytes.NewReader([]byte{})) + if err != nil { + t.Fatal(err) + } + r := httptest.NewRecorder() + if err := postContainersStop(srv, r, req, map[string]string{"name": container.Id}); err != nil { + t.Fatal(err) + } + if r.Code != http.StatusNoContent { + t.Fatalf("%d NO CONTENT expected, received %d\n", http.StatusNoContent, r.Code) + } + if container.State.Running { + t.Fatalf("The container hasn't been stopped") + } +} + +func TestPostContainersWait(t *testing.T) { + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) + + srv := &Server{runtime: runtime} + + container, err := NewBuilder(runtime).Create( + &Config{ + Image: GetTestImage(runtime).Id, + Cmd: []string{"/bin/sleep", "1"}, + OpenStdin: true, + }, + ) + if err != nil { + t.Fatal(err) + } + defer runtime.Destroy(container) + + if err := container.Start(); err != nil { + t.Fatal(err) + } + + setTimeout(t, "Wait timed out", 3*time.Second, func() { + r := httptest.NewRecorder() + if err := postContainersWait(srv, r, nil, map[string]string{"name": container.Id}); err != nil { + t.Fatal(err) + } + apiWait := &ApiWait{} + if err := json.Unmarshal(r.Body.Bytes(), apiWait); err != nil { + t.Fatal(err) + } + if apiWait.StatusCode != 0 { + t.Fatalf("Non zero exit code for sleep: %d\n", apiWait.StatusCode) + } + }) + + if container.State.Running { + t.Fatalf("The container should be stopped after wait") + } +} + +func TestPostContainersAttach(t *testing.T) { + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) + + srv := &Server{runtime: runtime} + + container, err := NewBuilder(runtime).Create( + &Config{ + Image: GetTestImage(runtime).Id, + Cmd: []string{"/bin/cat"}, + OpenStdin: true, + }, + ) + if err != nil { + t.Fatal(err) + } + defer runtime.Destroy(container) + + // Start the process + if err := container.Start(); err != nil { + t.Fatal(err) + } + + stdin, stdinPipe := io.Pipe() + stdout, stdoutPipe := io.Pipe() + + // Attach to it + c1 := make(chan struct{}) + go func() { + defer close(c1) + + r := &hijackTester{ + ResponseRecorder: httptest.NewRecorder(), + in: stdin, + out: stdoutPipe, + } + + req, err := http.NewRequest("POST", "/containers/"+container.Id+"/attach?stream=1&stdin=1&stdout=1&stderr=1", bytes.NewReader([]byte{})) + if err != nil { + t.Fatal(err) + } + + if err := postContainersAttach(srv, r, req, map[string]string{"name": container.Id}); err != nil { + t.Fatal(err) + } + }() + + // Acknowledge hijack + setTimeout(t, "hijack acknowledge timed out", 2*time.Second, func() { + stdout.Read([]byte{}) + stdout.Read(make([]byte, 4096)) + }) + + 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) + } + }) + + // Close pipes (client disconnects) + if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil { + t.Fatal(err) + } + + // Wait for attach to finish, the client disconnected, therefore, Attach finished his job + setTimeout(t, "Waiting for CmdAttach timed out", 2*time.Second, func() { + <-c1 + }) + + // We closed stdin, expect /bin/cat to still be running + // Wait a little bit to make sure container.monitor() did his thing + err = container.WaitTimeout(500 * time.Millisecond) + if err == nil || !container.State.Running { + t.Fatalf("/bin/cat is not running after closing stdin") + } + + // Try to avoid the timeoout in destroy. Best effort, don't check error + cStdin, _ := container.StdinPipe() + cStdin.Close() + container.Wait() +} + +// FIXME: Test deleting running container +// FIXME: Test deleting container with volume +// FIXME: Test deleting volume in use by other container +func TestDeleteContainers(t *testing.T) { + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) + + srv := &Server{runtime: runtime} + + container, err := NewBuilder(runtime).Create(&Config{ + Image: GetTestImage(runtime).Id, + Cmd: []string{"touch", "/test"}, + }) + if err != nil { + t.Fatal(err) + } + defer runtime.Destroy(container) + + if err := container.Run(); err != nil { + t.Fatal(err) + } + + req, err := http.NewRequest("DELETE", "/containers/"+container.Id, nil) + if err != nil { + t.Fatal(err) + } + r := httptest.NewRecorder() + if err := deleteContainers(srv, r, req, map[string]string{"name": container.Id}); err != nil { + t.Fatal(err) + } + if r.Code != http.StatusNoContent { + t.Fatalf("%d NO CONTENT expected, received %d\n", http.StatusNoContent, r.Code) + } + + if c := runtime.Get(container.Id); c != nil { + t.Fatalf("The container as not been deleted") + } + + if _, err := os.Stat(path.Join(container.rwPath(), "test")); err == nil { + t.Fatalf("The test file has not been deleted") + } +} + +func TestDeleteImages(t *testing.T) { + //FIXME: Implement this test + t.Log("Test not implemented") +} + +// Mocked types for tests +type NopConn struct { + io.ReadCloser + io.Writer +} + +func (c *NopConn) LocalAddr() net.Addr { return nil } +func (c *NopConn) RemoteAddr() net.Addr { return nil } +func (c *NopConn) SetDeadline(t time.Time) error { return nil } +func (c *NopConn) SetReadDeadline(t time.Time) error { return nil } +func (c *NopConn) SetWriteDeadline(t time.Time) error { return nil } + +type hijackTester struct { + *httptest.ResponseRecorder + in io.ReadCloser + out io.Writer +} + +func (t *hijackTester) Hijack() (net.Conn, *bufio.ReadWriter, error) { + bufrw := bufio.NewReadWriter(bufio.NewReader(t.in), bufio.NewWriter(t.out)) + conn := &NopConn{ + ReadCloser: t.in, + Writer: t.out, + } + return conn, bufrw, nil +} diff --git a/auth/auth.go b/auth/auth.go index 5a5987ace..2b99c9503 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -15,13 +15,13 @@ import ( const CONFIGFILE = ".dockercfg" // the registry server we want to login against -const INDEX_SERVER = "https://index.docker.io" +const INDEX_SERVER = "https://index.docker.io/v1" type AuthConfig struct { Username string `json:"username"` Password string `json:"password"` Email string `json:"email"` - rootPath string `json:-` + rootPath string } func NewAuthConfig(username, password, email, rootPath string) *AuthConfig { @@ -33,6 +33,13 @@ func NewAuthConfig(username, password, email, rootPath string) *AuthConfig { } } +func IndexServerAddress() string { + if os.Getenv("DOCKER_INDEX_URL") != "" { + return os.Getenv("DOCKER_INDEX_URL") + "/v1" + } + return INDEX_SERVER +} + // create a base64 encoded auth string to store in config func EncodeAuth(authConfig *AuthConfig) string { authStr := authConfig.Username + ":" + authConfig.Password @@ -119,7 +126,7 @@ func Login(authConfig *AuthConfig) (string, error) { // using `bytes.NewReader(jsonBody)` here causes the server to respond with a 411 status. b := strings.NewReader(string(jsonBody)) - req1, err := http.Post(INDEX_SERVER+"/v1/users/", "application/json; charset=utf-8", b) + req1, err := http.Post(IndexServerAddress()+"/users/", "application/json; charset=utf-8", b) if err != nil { return "", fmt.Errorf("Server Error: %s", err) } @@ -139,7 +146,7 @@ func Login(authConfig *AuthConfig) (string, error) { "Please check your e-mail for a confirmation link.") } else if reqStatusCode == 400 { if string(reqBody) == "\"Username or email already exists\"" { - req, err := http.NewRequest("GET", INDEX_SERVER+"/v1/users/", nil) + req, err := http.NewRequest("GET", IndexServerAddress()+"/users/", nil) req.SetBasicAuth(authConfig.Username, authConfig.Password) resp, err := client.Do(req) if err != nil { diff --git a/auth/auth_test.go b/auth/auth_test.go index ca584f931..6c8d032cf 100644 --- a/auth/auth_test.go +++ b/auth/auth_test.go @@ -1,6 +1,10 @@ package auth import ( + "crypto/rand" + "encoding/hex" + "os" + "strings" "testing" ) @@ -21,3 +25,49 @@ func TestEncodeAuth(t *testing.T) { t.Fatal("AuthString encoding isn't correct.") } } + +func TestLogin(t *testing.T) { + os.Setenv("DOCKER_INDEX_URL", "https://indexstaging-docker.dotcloud.com") + defer os.Setenv("DOCKER_INDEX_URL", "") + authConfig := NewAuthConfig("unittester", "surlautrerivejetattendrai", "noise+unittester@dotcloud.com", "/tmp") + status, err := Login(authConfig) + if err != nil { + t.Fatal(err) + } + if status != "Login Succeeded\n" { + t.Fatalf("Expected status \"Login Succeeded\", found \"%s\" instead", status) + } +} + +func TestCreateAccount(t *testing.T) { + os.Setenv("DOCKER_INDEX_URL", "https://indexstaging-docker.dotcloud.com") + defer os.Setenv("DOCKER_INDEX_URL", "") + tokenBuffer := make([]byte, 16) + _, err := rand.Read(tokenBuffer) + if err != nil { + t.Fatal(err) + } + token := hex.EncodeToString(tokenBuffer)[:12] + username := "ut" + token + authConfig := NewAuthConfig(username, "test42", "docker-ut+"+token+"@example.com", "/tmp") + status, err := Login(authConfig) + if err != nil { + t.Fatal(err) + } + expectedStatus := "Account created. Please use the confirmation link we sent" + + " to your e-mail to activate it.\n" + if status != expectedStatus { + t.Fatalf("Expected status: \"%s\", found \"%s\" instead.", expectedStatus, status) + } + + status, err = Login(authConfig) + if err == nil { + t.Fatalf("Expected error but found nil instead") + } + + expectedError := "Login: Account is not Active" + + if !strings.Contains(err.Error(), expectedError) { + t.Fatalf("Expected message \"%s\" but found \"%s\" instead", expectedError, err.Error()) + } +} diff --git a/buildbot/README.rst b/buildbot/README.rst deleted file mode 100644 index a52b9769e..000000000 --- a/buildbot/README.rst +++ /dev/null @@ -1,20 +0,0 @@ -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 deleted file mode 100644 index ea027f066..000000000 --- a/buildbot/Vagrantfile +++ /dev/null @@ -1,28 +0,0 @@ -# -*- 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 deleted file mode 100755 index 5e4e7432f..000000000 --- a/buildbot/buildbot-cfg/buildbot-cfg.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/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 deleted file mode 100644 index 8109cdc2a..000000000 --- a/buildbot/buildbot.pp +++ /dev/null @@ -1,32 +0,0 @@ -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/builder.go b/builder.go new file mode 100644 index 000000000..5f56f65d0 --- /dev/null +++ b/builder.go @@ -0,0 +1,118 @@ +package docker + +import ( + "fmt" + "os" + "path" + "time" +) + +type Builder struct { + runtime *Runtime + repositories *TagStore + graph *Graph + + config *Config + image *Image +} + +func NewBuilder(runtime *Runtime) *Builder { + return &Builder{ + runtime: runtime, + graph: runtime.graph, + repositories: runtime.repositories, + } +} + +func (builder *Builder) Create(config *Config) (*Container, error) { + // Lookup image + img, err := builder.repositories.LookupImage(config.Image) + if err != nil { + return nil, err + } + + if img.Config != nil { + MergeConfig(config, img.Config) + } + + if config.Cmd == nil || len(config.Cmd) == 0 { + return nil, fmt.Errorf("No command specified") + } + + // Generate id + id := GenerateId() + // Generate default hostname + // FIXME: the lxc template no longer needs to set a default hostname + if config.Hostname == "" { + config.Hostname = id[:12] + } + + container := &Container{ + // FIXME: we should generate the ID here instead of receiving it as an argument + Id: id, + Created: time.Now(), + Path: config.Cmd[0], + Args: config.Cmd[1:], //FIXME: de-duplicate from config + Config: config, + Image: img.Id, // Always use the resolved image id + NetworkSettings: &NetworkSettings{}, + // FIXME: do we need to store this in the container? + SysInitPath: sysInitPath, + } + container.root = builder.runtime.containerRoot(container.Id) + // Step 1: create the container directory. + // This doubles as a barrier to avoid race conditions. + if err := os.Mkdir(container.root, 0700); err != nil { + return nil, err + } + + // If custom dns exists, then create a resolv.conf for the container + if len(config.Dns) > 0 { + container.ResolvConfPath = path.Join(container.root, "resolv.conf") + f, err := os.Create(container.ResolvConfPath) + if err != nil { + return nil, err + } + defer f.Close() + for _, dns := range config.Dns { + if _, err := f.Write([]byte("nameserver " + dns + "\n")); err != nil { + return nil, err + } + } + } else { + container.ResolvConfPath = "/etc/resolv.conf" + } + + // Step 2: save the container json + if err := container.ToDisk(); err != nil { + return nil, err + } + // Step 3: register the container + if err := builder.runtime.Register(container); err != nil { + return nil, err + } + return container, nil +} + +// Commit creates a new filesystem image from the current state of a container. +// The image can optionally be tagged into a repository +func (builder *Builder) Commit(container *Container, repository, tag, comment, author string, config *Config) (*Image, error) { + // FIXME: freeze the container before copying it to avoid data corruption? + // FIXME: this shouldn't be in commands. + rwTar, err := container.ExportRw() + if err != nil { + return nil, err + } + // Create a new image from the container's base layers + a new layer from container changes + img, err := builder.graph.Create(rwTar, container, comment, author, config) + if err != nil { + return nil, err + } + // Register the image if needed + if repository != "" { + if err := builder.repositories.Set(repository, tag, img.Id, true); err != nil { + return img, err + } + } + return img, nil +} diff --git a/builder_client.go b/builder_client.go new file mode 100644 index 000000000..ceeab002c --- /dev/null +++ b/builder_client.go @@ -0,0 +1,311 @@ +package docker + +import ( + "bufio" + "encoding/json" + "fmt" + "github.com/dotcloud/docker/utils" + "io" + "net/url" + "os" + "reflect" + "strings" +) + +type BuilderClient interface { + Build(io.Reader) (string, error) + CmdFrom(string) error + CmdRun(string) error +} + +type builderClient struct { + cli *DockerCli + + image string + maintainer string + config *Config + + tmpContainers map[string]struct{} + tmpImages map[string]struct{} + + needCommit bool +} + +func (b *builderClient) clearTmp(containers, images map[string]struct{}) { + for c := range containers { + if _, _, err := b.cli.call("DELETE", "/containers/"+c, nil); err != nil { + utils.Debugf("%s", err) + } + utils.Debugf("Removing container %s", c) + } + for i := range images { + if _, _, err := b.cli.call("DELETE", "/images/"+i, nil); err != nil { + utils.Debugf("%s", err) + } + utils.Debugf("Removing image %s", i) + } +} + +func (b *builderClient) CmdFrom(name string) error { + obj, statusCode, err := b.cli.call("GET", "/images/"+name+"/json", nil) + if statusCode == 404 { + + remote := name + var tag string + if strings.Contains(remote, ":") { + remoteParts := strings.Split(remote, ":") + tag = remoteParts[1] + remote = remoteParts[0] + } + var out io.Writer + if os.Getenv("DEBUG") != "" { + out = os.Stdout + } else { + out = &utils.NopWriter{} + } + if err := b.cli.stream("POST", "/images/create?fromImage="+remote+"&tag="+tag, nil, out); err != nil { + return err + } + obj, _, err = b.cli.call("GET", "/images/"+name+"/json", nil) + if err != nil { + return err + } + } + if err != nil { + return err + } + + img := &ApiId{} + if err := json.Unmarshal(obj, img); err != nil { + return err + } + b.image = img.Id + utils.Debugf("Using image %s", b.image) + return nil +} + +func (b *builderClient) CmdMaintainer(name string) error { + b.needCommit = true + b.maintainer = name + return nil +} + +func (b *builderClient) CmdRun(args string) error { + if b.image == "" { + return fmt.Errorf("Please provide a source image with `from` prior to run") + } + config, _, err := ParseRun([]string{b.image, "/bin/sh", "-c", args}, nil) + if err != nil { + return err + } + + cmd, env := b.config.Cmd, b.config.Env + b.config.Cmd = nil + MergeConfig(b.config, config) + + body, statusCode, err := b.cli.call("POST", "/images/getCache", &ApiImageConfig{Id: b.image, Config: b.config}) + if err != nil { + if statusCode != 404 { + return err + } + } + if statusCode != 404 { + apiId := &ApiId{} + if err := json.Unmarshal(body, apiId); err != nil { + return err + } + utils.Debugf("Use cached version") + b.image = apiId.Id + return nil + } + cid, err := b.run() + if err != nil { + return err + } + b.config.Cmd, b.config.Env = cmd, env + return b.commit(cid) +} + +func (b *builderClient) CmdEnv(args string) error { + b.needCommit = true + tmp := strings.SplitN(args, " ", 2) + if len(tmp) != 2 { + return fmt.Errorf("Invalid ENV format") + } + key := strings.Trim(tmp[0], " ") + value := strings.Trim(tmp[1], " ") + + for i, elem := range b.config.Env { + if strings.HasPrefix(elem, key+"=") { + b.config.Env[i] = key + "=" + value + return nil + } + } + b.config.Env = append(b.config.Env, key+"="+value) + return nil +} + +func (b *builderClient) CmdCmd(args string) error { + b.needCommit = true + var cmd []string + if err := json.Unmarshal([]byte(args), &cmd); err != nil { + utils.Debugf("Error unmarshalling: %s, using /bin/sh -c", err) + b.config.Cmd = []string{"/bin/sh", "-c", args} + } else { + b.config.Cmd = cmd + } + return nil +} + +func (b *builderClient) CmdExpose(args string) error { + ports := strings.Split(args, " ") + b.config.PortSpecs = append(ports, b.config.PortSpecs...) + return nil +} + +func (b *builderClient) CmdInsert(args string) error { + // FIXME: Reimplement this once the remove_hijack branch gets merged. + // We need to retrieve the resulting Id + return fmt.Errorf("INSERT not implemented") +} + +func (b *builderClient) run() (string, error) { + if b.image == "" { + return "", fmt.Errorf("Please provide a source image with `from` prior to run") + } + b.config.Image = b.image + body, _, err := b.cli.call("POST", "/containers/create", b.config) + if err != nil { + return "", err + } + + apiRun := &ApiRun{} + if err := json.Unmarshal(body, apiRun); err != nil { + return "", err + } + for _, warning := range apiRun.Warnings { + fmt.Fprintln(os.Stderr, "WARNING: ", warning) + } + + //start the container + _, _, err = b.cli.call("POST", "/containers/"+apiRun.Id+"/start", nil) + if err != nil { + return "", err + } + b.tmpContainers[apiRun.Id] = struct{}{} + + // Wait for it to finish + body, _, err = b.cli.call("POST", "/containers/"+apiRun.Id+"/wait", nil) + if err != nil { + return "", err + } + apiWait := &ApiWait{} + if err := json.Unmarshal(body, apiWait); err != nil { + return "", err + } + if apiWait.StatusCode != 0 { + return "", fmt.Errorf("The command %v returned a non-zero code: %d", b.config.Cmd, apiWait.StatusCode) + } + + return apiRun.Id, nil +} + +func (b *builderClient) commit(id string) error { + if b.image == "" { + return fmt.Errorf("Please provide a source image with `from` prior to run") + } + b.config.Image = b.image + + if id == "" { + cmd := b.config.Cmd + b.config.Cmd = []string{"true"} + if cid, err := b.run(); err != nil { + return err + } else { + id = cid + } + b.config.Cmd = cmd + } + + // Commit the container + v := url.Values{} + v.Set("container", id) + v.Set("author", b.maintainer) + + body, _, err := b.cli.call("POST", "/commit?"+v.Encode(), b.config) + if err != nil { + return err + } + apiId := &ApiId{} + if err := json.Unmarshal(body, apiId); err != nil { + return err + } + b.tmpImages[apiId.Id] = struct{}{} + b.image = apiId.Id + b.needCommit = false + return nil +} + +func (b *builderClient) Build(dockerfile io.Reader) (string, error) { + defer b.clearTmp(b.tmpContainers, b.tmpImages) + file := bufio.NewReader(dockerfile) + for { + line, err := file.ReadString('\n') + if err != nil { + if err == io.EOF { + break + } + return "", err + } + line = strings.Replace(strings.TrimSpace(line), " ", " ", 1) + // Skip comments and empty line + if len(line) == 0 || line[0] == '#' { + continue + } + tmp := strings.SplitN(line, " ", 2) + if len(tmp) != 2 { + return "", fmt.Errorf("Invalid Dockerfile format") + } + instruction := strings.ToLower(strings.Trim(tmp[0], " ")) + arguments := strings.Trim(tmp[1], " ") + + fmt.Printf("%s %s (%s)\n", strings.ToUpper(instruction), arguments, b.image) + + method, exists := reflect.TypeOf(b).MethodByName("Cmd" + strings.ToUpper(instruction[:1]) + strings.ToLower(instruction[1:])) + if !exists { + fmt.Printf("Skipping unknown instruction %s\n", strings.ToUpper(instruction)) + } + ret := method.Func.Call([]reflect.Value{reflect.ValueOf(b), reflect.ValueOf(arguments)})[0].Interface() + if ret != nil { + return "", ret.(error) + } + + fmt.Printf("===> %v\n", b.image) + } + if b.needCommit { + if err := b.commit(""); err != nil { + return "", err + } + } + if b.image != "" { + // The build is successful, keep the temporary containers and images + for i := range b.tmpImages { + delete(b.tmpImages, i) + } + for i := range b.tmpContainers { + delete(b.tmpContainers, i) + } + fmt.Printf("Build finished. image id: %s\n", b.image) + return b.image, nil + } + return "", fmt.Errorf("An error occured during the build\n") +} + +func NewBuilderClient(addr string, port int) BuilderClient { + return &builderClient{ + cli: NewDockerCli(addr, port), + config: &Config{}, + tmpContainers: make(map[string]struct{}), + tmpImages: make(map[string]struct{}), + } +} diff --git a/commands.go b/commands.go index cdc948e78..5e459a1d9 100644 --- a/commands.go +++ b/commands.go @@ -3,15 +3,20 @@ package docker import ( "bytes" "encoding/json" + "flag" "fmt" "github.com/dotcloud/docker/auth" - "github.com/dotcloud/docker/rcli" + "github.com/dotcloud/docker/term" + "github.com/dotcloud/docker/utils" "io" - "log" + "io/ioutil" + "net" "net/http" + "net/http/httputil" "net/url" + "os" "path/filepath" - "runtime" + "reflect" "strconv" "strings" "text/tabwriter" @@ -19,61 +24,124 @@ import ( "unicode" ) -const VERSION = "0.3.0" +const VERSION = "0.3.2" var ( GIT_COMMIT string ) -func (srv *Server) Name() string { - return "docker" +func ParseCommands(args ...string) error { + cli := NewDockerCli("0.0.0.0", 4243) + + if len(args) > 0 { + methodName := "Cmd" + strings.ToUpper(args[0][:1]) + strings.ToLower(args[0][1:]) + method, exists := reflect.TypeOf(cli).MethodByName(methodName) + if !exists { + fmt.Println("Error: Command not found:", args[0]) + return cli.CmdHelp(args...) + } + ret := method.Func.CallSlice([]reflect.Value{ + reflect.ValueOf(cli), + reflect.ValueOf(args[1:]), + })[0].Interface() + if ret == nil { + return nil + } + return ret.(error) + } + return cli.CmdHelp(args...) } -// FIXME: Stop violating DRY by repeating usage here and in Subcmd declarations -func (srv *Server) Help() string { +func (cli *DockerCli) CmdHelp(args ...string) error { help := "Usage: docker COMMAND [arg...]\n\nA self-sufficient runtime for linux containers.\n\nCommands:\n" - for _, cmd := range [][]string{ - {"attach", "Attach to a running container"}, - {"commit", "Create a new image from a container's changes"}, - {"diff", "Inspect changes on a container's filesystem"}, - {"export", "Stream the contents of a container as a tar archive"}, - {"history", "Show the history of an image"}, - {"images", "List images"}, - {"import", "Create a new filesystem image from the contents of a tarball"}, - {"info", "Display system-wide information"}, - {"inspect", "Return low-level information on a container"}, - {"kill", "Kill a running container"}, - {"login", "Register or Login to the docker registry server"}, - {"logs", "Fetch the logs of a container"}, - {"port", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT"}, - {"ps", "List containers"}, - {"pull", "Pull an image or a repository from the docker registry server"}, - {"push", "Push an image or a repository to the docker registry server"}, - {"restart", "Restart a running container"}, - {"rm", "Remove a container"}, - {"rmi", "Remove an image"}, - {"run", "Run a command in a new container"}, - {"start", "Start a stopped container"}, - {"stop", "Stop a running container"}, - {"tag", "Tag an image into a repository"}, - {"version", "Show the docker version information"}, - {"wait", "Block until a container stops, then print its exit code"}, + for cmd, description := range map[string]string{ + "attach": "Attach to a running container", + "build": "Build a container from Dockerfile or via stdin", + "commit": "Create a new image from a container's changes", + "diff": "Inspect changes on a container's filesystem", + "export": "Stream the contents of a container as a tar archive", + "history": "Show the history of an image", + "images": "List images", + "import": "Create a new filesystem image from the contents of a tarball", + "info": "Display system-wide information", + "insert": "Insert a file in an image", + "inspect": "Return low-level information on a container", + "kill": "Kill a running container", + "login": "Register or Login to the docker registry server", + "logs": "Fetch the logs of a container", + "port": "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT", + "ps": "List containers", + "pull": "Pull an image or a repository from the docker registry server", + "push": "Push an image or a repository to the docker registry server", + "restart": "Restart a running container", + "rm": "Remove a container", + "rmi": "Remove an image", + "run": "Run a command in a new container", + "search": "Search for an image in the docker index", + "start": "Start a stopped container", + "stop": "Stop a running container", + "tag": "Tag an image into a repository", + "version": "Show the docker version information", + "wait": "Block until a container stops, then print its exit code", } { - help += fmt.Sprintf(" %-10.10s%s\n", cmd[0], cmd[1]) + help += fmt.Sprintf(" %-10.10s%s\n", cmd, description) } - return help + fmt.Println(help) + return nil +} + +func (cli *DockerCli) CmdInsert(args ...string) error { + cmd := Subcmd("insert", "IMAGE URL PATH", "Insert a file from URL in the IMAGE at PATH") + if err := cmd.Parse(args); err != nil { + return nil + } + if cmd.NArg() != 3 { + cmd.Usage() + return nil + } + + v := url.Values{} + v.Set("url", cmd.Arg(1)) + v.Set("path", cmd.Arg(2)) + + err := cli.stream("POST", "/images/"+cmd.Arg(0)+"/insert?"+v.Encode(), nil, os.Stdout) + if err != nil { + return err + } + return nil +} + +func (cli *DockerCli) CmdBuild(args ...string) error { + cmd := Subcmd("build", "-|Dockerfile", "Build an image from Dockerfile or via stdin") + if err := cmd.Parse(args); err != nil { + return nil + } + var ( + file io.ReadCloser + err error + ) + + if cmd.NArg() == 0 { + file, err = os.Open("Dockerfile") + if err != nil { + return err + } + } else if cmd.Arg(0) == "-" { + file = os.Stdin + } else { + file, err = os.Open(cmd.Arg(0)) + if err != nil { + return err + } + } + if _, err := NewBuilderClient("0.0.0.0", 4243).Build(file); err != nil { + return err + } + return nil } // 'docker login': login / register a user to registry service. -func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error { - // Read a line on raw terminal with support for simple backspace - // sequences and echo. - // - // This function is necessary because the login command must be done in a - // raw terminal for two reasons: - // - we have to read a password (without echoing it); - // - the rcli "protocol" only supports cannonical and raw modes and you - // can't tune it once the command as been started. +func (cli *DockerCli) CmdLogin(args ...string) error { var readStringOnRawTerminal = func(stdin io.Reader, stdout io.Writer, echo bool) string { char := make([]byte, 1) buffer := make([]byte, 64) @@ -116,55 +184,79 @@ func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout rcli.DockerConn, args .. return readStringOnRawTerminal(stdin, stdout, false) } - stdout.SetOptionRawTerminal() + oldState, err := term.SetRawTerminal() + if err != nil { + return err + } else { + defer term.RestoreTerminal(oldState) + } - cmd := rcli.Subcmd(stdout, "login", "", "Register or Login to the docker registry server") + cmd := Subcmd("login", "", "Register or Login to the docker registry server") if err := cmd.Parse(args); err != nil { return nil } + body, _, err := cli.call("GET", "/auth", nil) + if err != nil { + return err + } + + var out auth.AuthConfig + err = json.Unmarshal(body, &out) + if err != nil { + return err + } + var username string var password string var email string - fmt.Fprint(stdout, "Username (", srv.runtime.authConfig.Username, "): ") - username = readAndEchoString(stdin, stdout) + fmt.Print("Username (", out.Username, "): ") + username = readAndEchoString(os.Stdin, os.Stdout) if username == "" { - username = srv.runtime.authConfig.Username + username = out.Username } - if username != srv.runtime.authConfig.Username { - fmt.Fprint(stdout, "Password: ") - password = readString(stdin, stdout) + if username != out.Username { + fmt.Print("Password: ") + password = readString(os.Stdin, os.Stdout) if password == "" { return fmt.Errorf("Error : Password Required") } - fmt.Fprint(stdout, "Email (", srv.runtime.authConfig.Email, "): ") - email = readAndEchoString(stdin, stdout) + fmt.Print("Email (", out.Email, "): ") + email = readAndEchoString(os.Stdin, os.Stdout) if email == "" { - email = srv.runtime.authConfig.Email + email = out.Email } } else { - password = srv.runtime.authConfig.Password - email = srv.runtime.authConfig.Email + email = out.Email } - newAuthConfig := auth.NewAuthConfig(username, password, email, srv.runtime.root) - status, err := auth.Login(newAuthConfig) + + out.Username = username + out.Password = password + out.Email = email + + body, _, err = cli.call("POST", "/auth", out) if err != nil { - fmt.Fprintf(stdout, "Error: %s\r\n", err) - } else { - srv.runtime.authConfig = newAuthConfig + return err } - if status != "" { - fmt.Fprint(stdout, status) + + var out2 ApiAuth + err = json.Unmarshal(body, &out2) + if err != nil { + return err + } + if out2.Status != "" { + term.RestoreTerminal(oldState) + fmt.Print(out2.Status) } return nil } // 'docker wait': block until a container stops -func (srv *Server) CmdWait(stdin io.ReadCloser, stdout io.Writer, args ...string) error { - cmd := rcli.Subcmd(stdout, "wait", "CONTAINER [CONTAINER...]", "Block until a container stops, then print its exit code.") +func (cli *DockerCli) CmdWait(args ...string) error { + cmd := Subcmd("wait", "CONTAINER [CONTAINER...]", "Block until a container stops, then print its exit code.") if err := cmd.Parse(args); err != nil { return nil } @@ -173,39 +265,61 @@ func (srv *Server) CmdWait(stdin io.ReadCloser, stdout io.Writer, args ...string return nil } for _, name := range cmd.Args() { - if container := srv.runtime.Get(name); container != nil { - fmt.Fprintln(stdout, container.Wait()) + body, _, err := cli.call("POST", "/containers/"+name+"/wait", nil) + if err != nil { + fmt.Printf("%s", err) } else { - return fmt.Errorf("No such container: %s", name) + var out ApiWait + err = json.Unmarshal(body, &out) + if err != nil { + return err + } + fmt.Println(out.StatusCode) } } return nil } // 'docker version': show version information -func (srv *Server) CmdVersion(stdin io.ReadCloser, stdout io.Writer, args ...string) error { - fmt.Fprintf(stdout, "Version: %s\n", VERSION) - fmt.Fprintf(stdout, "Git Commit: %s\n", GIT_COMMIT) - fmt.Fprintf(stdout, "Kernel: %s\n", srv.runtime.kernelVersion) - if !srv.runtime.capabilities.MemoryLimit { - fmt.Fprintf(stdout, "WARNING: No memory limit support\n") +func (cli *DockerCli) CmdVersion(args ...string) error { + cmd := Subcmd("version", "", "Show the docker version information.") + fmt.Println(len(args)) + if err := cmd.Parse(args); err != nil { + return nil } - if !srv.runtime.capabilities.SwapLimit { - fmt.Fprintf(stdout, "WARNING: No swap limit support\n") + + fmt.Println(cmd.NArg()) + if cmd.NArg() > 0 { + cmd.Usage() + return nil } + + body, _, err := cli.call("GET", "/version", nil) + if err != nil { + return err + } + + var out ApiVersion + err = json.Unmarshal(body, &out) + if err != nil { + utils.Debugf("Error unmarshal: body: %s, err: %s\n", body, err) + return err + } + fmt.Println("Version:", out.Version) + fmt.Println("Git Commit:", out.GitCommit) + if !out.MemoryLimit { + fmt.Println("WARNING: No memory limit support") + } + if !out.SwapLimit { + fmt.Println("WARNING: No swap limit support") + } + return nil } // 'docker info': display system-wide information. -func (srv *Server) CmdInfo(stdin io.ReadCloser, stdout io.Writer, args ...string) error { - images, _ := srv.runtime.graph.All() - var imgcount int - if images == nil { - imgcount = 0 - } else { - imgcount = len(images) - } - cmd := rcli.Subcmd(stdout, "info", "", "Display system-wide information.") +func (cli *DockerCli) CmdInfo(args ...string) error { + cmd := Subcmd("info", "", "Display system-wide information") if err := cmd.Parse(args); err != nil { return nil } @@ -213,148 +327,28 @@ func (srv *Server) CmdInfo(stdin io.ReadCloser, stdout io.Writer, args ...string cmd.Usage() return nil } - fmt.Fprintf(stdout, "containers: %d\nversion: %s\nimages: %d\n", - len(srv.runtime.List()), - VERSION, - imgcount) - if !rcli.DEBUG_FLAG { - return nil - } - fmt.Fprintln(stdout, "debug mode enabled") - fmt.Fprintf(stdout, "fds: %d\ngoroutines: %d\n", getTotalUsedFds(), runtime.NumGoroutine()) - return nil -} - -func (srv *Server) CmdStop(stdin io.ReadCloser, stdout io.Writer, args ...string) error { - cmd := rcli.Subcmd(stdout, "stop", "[OPTIONS] CONTAINER [CONTAINER...]", "Stop a running container") - nSeconds := cmd.Int("t", 10, "wait t seconds before killing the container") - if err := cmd.Parse(args); err != nil { - return nil - } - if cmd.NArg() < 1 { - cmd.Usage() - return nil - } - for _, name := range cmd.Args() { - if container := srv.runtime.Get(name); container != nil { - if err := container.Stop(*nSeconds); err != nil { - return err - } - fmt.Fprintln(stdout, container.ShortId()) - } else { - return fmt.Errorf("No such container: %s", name) - } - } - return nil -} - -func (srv *Server) CmdRestart(stdin io.ReadCloser, stdout io.Writer, args ...string) error { - cmd := rcli.Subcmd(stdout, "restart", "CONTAINER [CONTAINER...]", "Restart a running container") - nSeconds := cmd.Int("t", 10, "wait t seconds before killing the container") - if err := cmd.Parse(args); err != nil { - return nil - } - if cmd.NArg() < 1 { - cmd.Usage() - return nil - } - for _, name := range cmd.Args() { - if container := srv.runtime.Get(name); container != nil { - if err := container.Restart(*nSeconds); err != nil { - return err - } - fmt.Fprintln(stdout, container.ShortId()) - } else { - return fmt.Errorf("No such container: %s", name) - } - } - return nil -} - -func (srv *Server) CmdStart(stdin io.ReadCloser, stdout io.Writer, args ...string) error { - cmd := rcli.Subcmd(stdout, "start", "CONTAINER [CONTAINER...]", "Start a stopped container") - if err := cmd.Parse(args); err != nil { - return nil - } - if cmd.NArg() < 1 { - cmd.Usage() - return nil - } - for _, name := range cmd.Args() { - if container := srv.runtime.Get(name); container != nil { - if err := container.Start(); err != nil { - return err - } - fmt.Fprintln(stdout, container.ShortId()) - } else { - return fmt.Errorf("No such container: %s", name) - } - } - return nil -} - -func (srv *Server) CmdInspect(stdin io.ReadCloser, stdout io.Writer, args ...string) error { - cmd := rcli.Subcmd(stdout, "inspect", "CONTAINER", "Return low-level information on a container") - if err := cmd.Parse(args); err != nil { - return nil - } - if cmd.NArg() < 1 { - cmd.Usage() - return nil - } - name := cmd.Arg(0) - var obj interface{} - if container := srv.runtime.Get(name); container != nil { - obj = container - } else if image, err := srv.runtime.repositories.LookupImage(name); err == nil && image != nil { - obj = image - } else { - // No output means the object does not exist - // (easier to script since stdout and stderr are not differentiated atm) - return nil - } - data, err := json.Marshal(obj) + body, _, err := cli.call("GET", "/info", nil) if err != nil { return err } - indented := new(bytes.Buffer) - if err = json.Indent(indented, data, "", " "); err != nil { - return err - } - if _, err := io.Copy(stdout, indented); err != nil { - return err - } - stdout.Write([]byte{'\n'}) - return nil -} -func (srv *Server) CmdPort(stdin io.ReadCloser, stdout io.Writer, args ...string) error { - cmd := rcli.Subcmd(stdout, "port", "CONTAINER PRIVATE_PORT", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT") - if err := cmd.Parse(args); err != nil { - return nil + var out ApiInfo + err = json.Unmarshal(body, &out) + if err != nil { + return err } - if cmd.NArg() != 2 { - cmd.Usage() - return nil - } - name := cmd.Arg(0) - privatePort := cmd.Arg(1) - if container := srv.runtime.Get(name); container == nil { - return fmt.Errorf("No such container: %s", name) - } else { - if frontend, exists := container.NetworkSettings.PortMapping[privatePort]; !exists { - return fmt.Errorf("No private port '%s' allocated on %s", privatePort, name) - } else { - fmt.Fprintln(stdout, frontend) - } + fmt.Printf("containers: %d\nversion: %s\nimages: %d\nGo version: %s\n", out.Containers, out.Version, out.Images, out.GoVersion) + if out.Debug { + fmt.Println("debug mode enabled") + fmt.Printf("fds: %d\ngoroutines: %d\n", out.NFd, out.NGoroutines) } return nil } -// 'docker rmi IMAGE' removes all images with the name IMAGE -func (srv *Server) CmdRmi(stdin io.ReadCloser, stdout io.Writer, args ...string) (err error) { - cmd := rcli.Subcmd(stdout, "rmimage", "IMAGE [IMAGE...]", "Remove an image") +func (cli *DockerCli) CmdStop(args ...string) error { + 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 } @@ -362,20 +356,69 @@ func (srv *Server) CmdRmi(stdin io.ReadCloser, stdout io.Writer, args ...string) cmd.Usage() return nil } + + v := url.Values{} + v.Set("t", strconv.Itoa(*nSeconds)) + for _, name := range cmd.Args() { - img, err := srv.runtime.repositories.LookupImage(name) + _, _, err := cli.call("POST", "/containers/"+name+"/stop?"+v.Encode(), nil) if err != nil { - return err - } - if err := srv.runtime.graph.Delete(img.Id); err != nil { - return err + fmt.Printf("%s", err) + } else { + fmt.Println(name) } } return nil } -func (srv *Server) CmdHistory(stdin io.ReadCloser, stdout io.Writer, args ...string) error { - cmd := rcli.Subcmd(stdout, "history", "IMAGE", "Show the history of an image") +func (cli *DockerCli) CmdRestart(args ...string) error { + 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 + } + if cmd.NArg() < 1 { + cmd.Usage() + return nil + } + + v := url.Values{} + v.Set("t", strconv.Itoa(*nSeconds)) + + for _, name := range cmd.Args() { + _, _, err := cli.call("POST", "/containers/"+name+"/restart?"+v.Encode(), nil) + if err != nil { + fmt.Printf("%s", err) + } else { + fmt.Println(name) + } + } + return nil +} + +func (cli *DockerCli) CmdStart(args ...string) error { + cmd := Subcmd("start", "CONTAINER [CONTAINER...]", "Restart a stopped container") + if err := cmd.Parse(args); err != nil { + return nil + } + if cmd.NArg() < 1 { + cmd.Usage() + return nil + } + + for _, name := range args { + _, _, err := cli.call("POST", "/containers/"+name+"/start", nil) + if err != nil { + fmt.Printf("%s", err) + } else { + fmt.Println(name) + } + } + return nil +} + +func (cli *DockerCli) CmdInspect(args ...string) error { + cmd := Subcmd("inspect", "CONTAINER|IMAGE", "Return low-level information on a container/image") if err := cmd.Parse(args); err != nil { return nil } @@ -383,25 +426,106 @@ func (srv *Server) CmdHistory(stdin io.ReadCloser, stdout io.Writer, args ...str cmd.Usage() return nil } - image, err := srv.runtime.repositories.LookupImage(cmd.Arg(0)) + obj, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil) + if err != nil { + obj, _, err = cli.call("GET", "/images/"+cmd.Arg(0)+"/json", nil) + if err != nil { + return err + } + } + + indented := new(bytes.Buffer) + if err = json.Indent(indented, obj, "", " "); err != nil { + return err + } + if _, err := io.Copy(os.Stdout, indented); err != nil { + return err + } + return nil +} + +func (cli *DockerCli) CmdPort(args ...string) error { + cmd := Subcmd("port", "CONTAINER PRIVATE_PORT", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT") + if err := cmd.Parse(args); err != nil { + return nil + } + if cmd.NArg() != 2 { + cmd.Usage() + return nil + } + + body, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil) if err != nil { return err } - w := tabwriter.NewWriter(stdout, 20, 1, 3, ' ', 0) - defer w.Flush() - fmt.Fprintln(w, "ID\tCREATED\tCREATED BY") - return image.WalkHistory(func(img *Image) error { - fmt.Fprintf(w, "%s\t%s\t%s\n", - srv.runtime.repositories.ImageName(img.ShortId()), - HumanDuration(time.Now().Sub(img.Created))+" ago", - strings.Join(img.ContainerConfig.Cmd, " "), - ) - return nil - }) + var out Container + err = json.Unmarshal(body, &out) + if err != nil { + return err + } + + if frontend, exists := out.NetworkSettings.PortMapping[cmd.Arg(1)]; exists { + fmt.Println(frontend) + } else { + return fmt.Errorf("error: No private port '%s' allocated on %s", cmd.Arg(1), cmd.Arg(0)) + } + return nil } -func (srv *Server) CmdRm(stdin io.ReadCloser, stdout io.Writer, args ...string) error { - cmd := rcli.Subcmd(stdout, "rm", "[OPTIONS] CONTAINER [CONTAINER...]", "Remove a container") +// 'docker rmi IMAGE' removes all images with the name IMAGE +func (cli *DockerCli) CmdRmi(args ...string) error { + cmd := Subcmd("rmi", "IMAGE [IMAGE...]", "Remove an image") + if err := cmd.Parse(args); err != nil { + return nil + } + if cmd.NArg() < 1 { + cmd.Usage() + return nil + } + + for _, name := range cmd.Args() { + _, _, err := cli.call("DELETE", "/images/"+name, nil) + if err != nil { + fmt.Printf("%s", err) + } else { + fmt.Println(name) + } + } + return nil +} + +func (cli *DockerCli) CmdHistory(args ...string) error { + cmd := Subcmd("history", "IMAGE", "Show the history of an image") + if err := cmd.Parse(args); err != nil { + return nil + } + if cmd.NArg() != 1 { + cmd.Usage() + return nil + } + + body, _, err := cli.call("GET", "/images/"+cmd.Arg(0)+"/history", nil) + if err != nil { + return err + } + + var outs []ApiHistory + err = json.Unmarshal(body, &outs) + if err != nil { + return err + } + w := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0) + fmt.Fprintln(w, "ID\tCREATED\tCREATED BY") + + for _, out := range outs { + fmt.Fprintf(w, "%s\t%s ago\t%s\n", out.Id, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.CreatedBy) + } + w.Flush() + return nil +} + +func (cli *DockerCli) CmdRm(args ...string) error { + cmd := Subcmd("rm", "[OPTIONS] CONTAINER [CONTAINER...]", "Remove a container") v := cmd.Bool("v", false, "Remove the volumes associated to the container") if err := cmd.Parse(args); err != nil { return nil @@ -410,46 +534,24 @@ func (srv *Server) CmdRm(stdin io.ReadCloser, stdout io.Writer, args ...string) cmd.Usage() return nil } - volumes := make(map[string]struct{}) - for _, name := range cmd.Args() { - container := srv.runtime.Get(name) - if container == nil { - return fmt.Errorf("No such container: %s", name) - } - // Store all the deleted containers volumes - for _, volumeId := range container.Volumes { - volumes[volumeId] = struct{}{} - } - if err := srv.runtime.Destroy(container); err != nil { - fmt.Fprintln(stdout, "Error destroying container "+name+": "+err.Error()) - } - } + val := url.Values{} if *v { - // Retrieve all volumes from all remaining containers - usedVolumes := make(map[string]*Container) - for _, container := range srv.runtime.List() { - for _, containerVolumeId := range container.Volumes { - usedVolumes[containerVolumeId] = container - } - } - - for volumeId := range volumes { - // If the requested volu - if c, exists := usedVolumes[volumeId]; exists { - fmt.Fprintf(stdout, "The volume %s is used by the container %s. Impossible to remove it. Skipping.\n", volumeId, c.Id) - continue - } - if err := srv.runtime.volumes.Delete(volumeId); err != nil { - return err - } + val.Set("v", "1") + } + for _, name := range cmd.Args() { + _, _, err := cli.call("DELETE", "/containers/"+name+"?"+val.Encode(), nil) + if err != nil { + fmt.Printf("%s", err) + } else { + fmt.Println(name) } } return nil } // 'docker kill NAME' kills a running container -func (srv *Server) CmdKill(stdin io.ReadCloser, stdout io.Writer, args ...string) error { - cmd := rcli.Subcmd(stdout, "kill", "CONTAINER [CONTAINER...]", "Kill a running container") +func (cli *DockerCli) CmdKill(args ...string) error { + cmd := Subcmd("kill", "CONTAINER [CONTAINER...]", "Kill a running container") if err := cmd.Parse(args); err != nil { return nil } @@ -457,23 +559,20 @@ func (srv *Server) CmdKill(stdin io.ReadCloser, stdout io.Writer, args ...string cmd.Usage() return nil } - for _, name := range cmd.Args() { - container := srv.runtime.Get(name) - if container == nil { - return fmt.Errorf("No such container: %s", name) - } - if err := container.Kill(); err != nil { - fmt.Fprintln(stdout, "Error killing container "+name+": "+err.Error()) + + for _, name := range args { + _, _, err := cli.call("POST", "/containers/"+name+"/kill", nil) + if err != nil { + fmt.Printf("%s", err) + } else { + fmt.Println(name) } } return nil } -func (srv *Server) CmdImport(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error { - stdout.Flush() - cmd := rcli.Subcmd(stdout, "import", "URL|- [REPOSITORY [TAG]]", "Create a new filesystem image from the contents of a tarball") - var archive io.Reader - var resp *http.Response +func (cli *DockerCli) CmdImport(args ...string) error { + cmd := Subcmd("import", "URL|- [REPOSITORY [TAG]]", "Create a new filesystem image from the contents of a tarball") if err := cmd.Parse(args); err != nil { return nil @@ -482,137 +581,114 @@ func (srv *Server) CmdImport(stdin io.ReadCloser, stdout rcli.DockerConn, args . cmd.Usage() return nil } - src := cmd.Arg(0) - if src == "-" { - archive = stdin - } else { - u, err := url.Parse(src) - if err != nil { - return err - } - if u.Scheme == "" { - u.Scheme = "http" - u.Host = src - u.Path = "" - } - fmt.Fprintln(stdout, "Downloading from", u) - // Download with curl (pretty progress bar) - // If curl is not available, fallback to http.Get() - resp, err = Download(u.String(), stdout) - if err != nil { - return err - } - archive = ProgressReader(resp.Body, int(resp.ContentLength), stdout, "Importing %v/%v (%v)") - } - img, err := srv.runtime.graph.Create(archive, nil, "Imported from "+src, "", nil) + src, repository, tag := cmd.Arg(0), cmd.Arg(1), cmd.Arg(2) + v := url.Values{} + v.Set("repo", repository) + v.Set("tag", tag) + v.Set("fromSrc", src) + + err := cli.stream("POST", "/images/create?"+v.Encode(), os.Stdin, os.Stdout) if err != nil { return err } - // Optionally register the image at REPO/TAG - if repository := cmd.Arg(1); repository != "" { - tag := cmd.Arg(2) // Repository will handle an empty tag properly - if err := srv.runtime.repositories.Set(repository, tag, img.Id, true); err != nil { - return err - } - } - fmt.Fprintln(stdout, img.ShortId()) return nil } -func (srv *Server) CmdPush(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error { - cmd := rcli.Subcmd(stdout, "push", "NAME", "Push an image or a repository to the registry") +func (cli *DockerCli) CmdPush(args ...string) error { + cmd := Subcmd("push", "[OPTION] NAME", "Push an image or a repository to the registry") registry := cmd.String("registry", "", "Registry host to push the image to") if err := cmd.Parse(args); err != nil { return nil } - local := cmd.Arg(0) + name := cmd.Arg(0) - if local == "" { + if name == "" { cmd.Usage() return nil } + body, _, err := cli.call("GET", "/auth", nil) + if err != nil { + return err + } + + var out auth.AuthConfig + err = json.Unmarshal(body, &out) + if err != nil { + return err + } + // If the login failed AND we're using the index, abort - if *registry == "" && (srv.runtime.authConfig == nil || srv.runtime.authConfig.Username == "") { - if err := srv.CmdLogin(stdin, stdout, args...); err != nil { + if *registry == "" && out.Username == "" { + if err := cli.CmdLogin(args...); err != nil { return err } - if srv.runtime.authConfig == nil || srv.runtime.authConfig.Username == "" { + + body, _, err = cli.call("GET", "/auth", nil) + if err != nil { + return err + } + err = json.Unmarshal(body, &out) + if err != nil { + return err + } + + if out.Username == "" { return fmt.Errorf("Please login prior to push. ('docker login')") } } - var remote string - - tmp := strings.SplitN(local, "/", 2) - if len(tmp) == 1 { - return fmt.Errorf( - "Impossible to push a \"root\" repository. Please rename your repository in / (ex: %s/%s)", - srv.runtime.authConfig.Username, local) - } else { - remote = local + if len(strings.SplitN(name, "/", 2)) == 1 { + return fmt.Errorf("Impossible to push a \"root\" repository. Please rename your repository in / (ex: %s/%s)", out.Username, name) } - Debugf("Pushing [%s] to [%s]\n", local, remote) - - // Try to get the image - img, err := srv.runtime.graph.Get(local) - if err != nil { - Debugf("The push refers to a repository [%s] (len: %d)\n", local, len(srv.runtime.repositories.Repositories[local])) - // If it fails, try to get the repository - if localRepo, exists := srv.runtime.repositories.Repositories[local]; exists { - if err := srv.runtime.graph.PushRepository(stdout, remote, localRepo, srv.runtime.authConfig); err != nil { - return err - } - return nil - } - - return err - } - err = srv.runtime.graph.PushImage(stdout, img, *registry, nil) - if err != nil { + v := url.Values{} + v.Set("registry", *registry) + if err := cli.stream("POST", "/images/"+name+"/push?"+v.Encode(), nil, os.Stdout); err != nil { return err } return nil } -func (srv *Server) CmdPull(stdin io.ReadCloser, stdout io.Writer, args ...string) error { - cmd := rcli.Subcmd(stdout, "pull", "NAME", "Pull an image or a repository from the registry") +func (cli *DockerCli) CmdPull(args ...string) error { + cmd := Subcmd("pull", "NAME", "Pull an image or a repository from the registry") tag := cmd.String("t", "", "Download tagged image in repository") registry := cmd.String("registry", "", "Registry to download from. Necessary if image is pulled by ID") if err := cmd.Parse(args); err != nil { return nil } - remote := cmd.Arg(0) - if remote == "" { + + if cmd.NArg() != 1 { cmd.Usage() return nil } + remote := cmd.Arg(0) if strings.Contains(remote, ":") { remoteParts := strings.Split(remote, ":") tag = &remoteParts[1] remote = remoteParts[0] } - // FIXME: CmdPull should be a wrapper around Runtime.Pull() - if *registry != "" { - if err := srv.runtime.graph.PullImage(stdout, remote, *registry, nil); err != nil { - return err - } - return nil - } - if err := srv.runtime.graph.PullRepository(stdout, remote, *tag, srv.runtime.repositories, srv.runtime.authConfig); err != nil { + v := url.Values{} + v.Set("fromImage", remote) + v.Set("tag", *tag) + v.Set("registry", *registry) + + if err := cli.stream("POST", "/images/create?"+v.Encode(), nil, os.Stdout); err != nil { return err } + return nil } -func (srv *Server) CmdImages(stdin io.ReadCloser, stdout io.Writer, args ...string) error { - cmd := rcli.Subcmd(stdout, "images", "[OPTIONS] [NAME]", "List images") - //limit := cmd.Int("l", 0, "Only show the N most recent versions of each image") +func (cli *DockerCli) CmdImages(args ...string) error { + cmd := Subcmd("images", "[OPTIONS] [NAME]", "List images") quiet := cmd.Bool("q", false, "only show numeric IDs") - flAll := cmd.Bool("a", false, "show all images") + all := cmd.Bool("a", false, "show all images") + noTrunc := cmd.Bool("notrunc", false, "Don't truncate output") + flViz := cmd.Bool("viz", false, "output graph in graphviz format") + if err := cmd.Parse(args); err != nil { return nil } @@ -620,154 +696,157 @@ func (srv *Server) CmdImages(stdin io.ReadCloser, stdout io.Writer, args ...stri cmd.Usage() return nil } - var nameFilter string - if cmd.NArg() == 1 { - nameFilter = cmd.Arg(0) - } - w := tabwriter.NewWriter(stdout, 20, 1, 3, ' ', 0) - if !*quiet { - fmt.Fprintln(w, "REPOSITORY\tTAG\tID\tCREATED") - } - var allImages map[string]*Image - var err error - if *flAll { - allImages, err = srv.runtime.graph.Map() + + if *flViz { + body, _, err := cli.call("GET", "/images/viz", false) + if err != nil { + return err + } + fmt.Printf("%s", body) } else { - allImages, err = srv.runtime.graph.Heads() - } - if err != nil { - return err - } - for name, repository := range srv.runtime.repositories.Repositories { - if nameFilter != "" && name != nameFilter { - continue + v := url.Values{} + if cmd.NArg() == 1 { + v.Set("filter", cmd.Arg(0)) } - for tag, id := range repository { - image, err := srv.runtime.graph.Get(id) - if err != nil { - log.Printf("Warning: couldn't load %s from %s/%s: %s", id, name, tag, err) - continue + if *all { + v.Set("all", "1") + } + + body, _, err := cli.call("GET", "/images/json?"+v.Encode(), nil) + if err != nil { + return err + } + + var outs []ApiImages + err = json.Unmarshal(body, &outs) + if err != nil { + return err + } + + w := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0) + if !*quiet { + fmt.Fprintln(w, "REPOSITORY\tTAG\tID\tCREATED") + } + + for _, out := range outs { + if out.Repository == "" { + out.Repository = "" } - delete(allImages, id) + if out.Tag == "" { + out.Tag = "" + } + if !*quiet { - for idx, field := range []string{ - /* REPOSITORY */ name, - /* TAG */ tag, - /* ID */ TruncateId(id), - /* CREATED */ HumanDuration(time.Now().Sub(image.Created)) + " ago", - } { - if idx == 0 { - w.Write([]byte(field)) - } else { - w.Write([]byte("\t" + field)) - } + fmt.Fprintf(w, "%s\t%s\t", out.Repository, out.Tag) + if *noTrunc { + fmt.Fprintf(w, "%s\t", out.Id) + } else { + fmt.Fprintf(w, "%s\t", utils.TruncateId(out.Id)) } - w.Write([]byte{'\n'}) + fmt.Fprintf(w, "%s ago\n", utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0)))) } else { - stdout.Write([]byte(image.ShortId() + "\n")) + if *noTrunc { + fmt.Fprintln(w, out.Id) + } else { + fmt.Fprintln(w, utils.TruncateId(out.Id)) + } } } - } - // Display images which aren't part of a - if nameFilter == "" { - for id, image := range allImages { - if !*quiet { - for idx, field := range []string{ - /* REPOSITORY */ "", - /* TAG */ "", - /* ID */ TruncateId(id), - /* CREATED */ HumanDuration(time.Now().Sub(image.Created)) + " ago", - } { - if idx == 0 { - w.Write([]byte(field)) - } else { - w.Write([]byte("\t" + field)) - } - } - w.Write([]byte{'\n'}) - } else { - stdout.Write([]byte(image.ShortId() + "\n")) - } + + if !*quiet { + w.Flush() } } - if !*quiet { - w.Flush() - } return nil } -func (srv *Server) CmdPs(stdin io.ReadCloser, stdout io.Writer, args ...string) error { - cmd := rcli.Subcmd(stdout, - "ps", "[OPTIONS]", "List containers") +func (cli *DockerCli) CmdPs(args ...string) error { + cmd := Subcmd("ps", "[OPTIONS]", "List containers") quiet := cmd.Bool("q", false, "Only display numeric IDs") - flAll := cmd.Bool("a", false, "Show all containers. Only running containers are shown by default.") - flFull := cmd.Bool("notrunc", false, "Don't truncate output") - latest := cmd.Bool("l", false, "Show only the latest created container, include non-running ones.") - nLast := cmd.Int("n", -1, "Show n last created containers, include non-running ones.") + all := cmd.Bool("a", false, "Show all containers. Only running containers are shown by default.") + noTrunc := cmd.Bool("notrunc", false, "Don't truncate output") + nLatest := cmd.Bool("l", false, "Show only the latest created container, include non-running ones.") + since := cmd.String("sinceId", "", "Show only containers created since Id, include non-running ones.") + before := cmd.String("beforeId", "", "Show only container created before Id, include non-running ones.") + last := cmd.Int("n", -1, "Show n last created containers, include non-running ones.") + if err := cmd.Parse(args); err != nil { return nil } - if *nLast == -1 && *latest { - *nLast = 1 + v := url.Values{} + if *last == -1 && *nLatest { + *last = 1 } - w := tabwriter.NewWriter(stdout, 12, 1, 3, ' ', 0) + if *all { + v.Set("all", "1") + } + if *last != -1 { + v.Set("limit", strconv.Itoa(*last)) + } + if *since != "" { + v.Set("since", *since) + } + if *before != "" { + v.Set("before", *before) + } + + body, _, err := cli.call("GET", "/containers/ps?"+v.Encode(), nil) + if err != nil { + return err + } + + var outs []ApiContainers + err = json.Unmarshal(body, &outs) + if err != nil { + return err + } + w := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0) if !*quiet { - fmt.Fprintln(w, "ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tCOMMENT\tPORTS") + fmt.Fprintln(w, "ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS") } - for i, container := range srv.runtime.List() { - if !container.State.Running && !*flAll && *nLast == -1 { - continue - } - if i == *nLast { - break - } + + for _, out := range outs { if !*quiet { - command := fmt.Sprintf("%s %s", container.Path, strings.Join(container.Args, " ")) - if !*flFull { - command = Trunc(command, 20) + if *noTrunc { + fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s ago\t%s\n", out.Id, out.Image, out.Command, out.Status, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Ports) + } else { + fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s ago\t%s\n", utils.TruncateId(out.Id), out.Image, utils.Trunc(out.Command, 20), out.Status, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Ports) } - for idx, field := range []string{ - /* ID */ container.ShortId(), - /* IMAGE */ srv.runtime.repositories.ImageName(container.Image), - /* COMMAND */ command, - /* CREATED */ HumanDuration(time.Now().Sub(container.Created)) + " ago", - /* STATUS */ container.State.String(), - /* COMMENT */ "", - /* PORTS */ container.NetworkSettings.PortMappingHuman(), - } { - if idx == 0 { - w.Write([]byte(field)) - } else { - w.Write([]byte("\t" + field)) - } - } - w.Write([]byte{'\n'}) } else { - stdout.Write([]byte(container.ShortId() + "\n")) + if *noTrunc { + fmt.Fprintln(w, out.Id) + } else { + fmt.Fprintln(w, utils.TruncateId(out.Id)) + } } } + if !*quiet { w.Flush() } return nil } -func (srv *Server) CmdCommit(stdin io.ReadCloser, stdout io.Writer, args ...string) error { - cmd := rcli.Subcmd(stdout, - "commit", "[OPTIONS] CONTAINER [REPOSITORY [TAG]]", - "Create a new image from a container's changes") +func (cli *DockerCli) 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 } - containerName, repository, tag := cmd.Arg(0), cmd.Arg(1), cmd.Arg(2) - if containerName == "" { + name, repository, tag := cmd.Arg(0), cmd.Arg(1), cmd.Arg(2) + if name == "" { cmd.Usage() return nil } + v := url.Values{} + v.Set("container", name) + v.Set("repo", repository) + v.Set("tag", tag) + v.Set("comment", *flComment) + v.Set("author", *flAuthor) var config *Config if *flConfig != "" { config = &Config{} @@ -775,64 +854,40 @@ func (srv *Server) CmdCommit(stdin io.ReadCloser, stdout io.Writer, args ...stri return err } } - - img, err := srv.runtime.Commit(containerName, repository, tag, *flComment, *flAuthor, config) + body, _, err := cli.call("POST", "/commit?"+v.Encode(), config) if err != nil { return err } - fmt.Fprintln(stdout, img.ShortId()) + + apiId := &ApiId{} + err = json.Unmarshal(body, apiId) + if err != nil { + return err + } + + fmt.Println(apiId.Id) return nil } -func (srv *Server) CmdExport(stdin io.ReadCloser, stdout io.Writer, args ...string) error { - cmd := rcli.Subcmd(stdout, - "export", "CONTAINER", - "Export the contents of a filesystem as a tar archive") +func (cli *DockerCli) CmdExport(args ...string) error { + cmd := Subcmd("export", "CONTAINER", "Export the contents of a filesystem as a tar archive") if err := cmd.Parse(args); err != nil { return nil } - name := cmd.Arg(0) - if container := srv.runtime.Get(name); container != nil { - data, err := container.Export() - if err != nil { - return err - } - // Stream the entire contents of the container (basically a volatile snapshot) - if _, err := io.Copy(stdout, data); err != nil { - return err - } - return nil - } - return fmt.Errorf("No such container: %s", name) -} -func (srv *Server) CmdDiff(stdin io.ReadCloser, stdout io.Writer, args ...string) error { - cmd := rcli.Subcmd(stdout, - "diff", "CONTAINER", - "Inspect changes on a container's filesystem") - if err := cmd.Parse(args); err != nil { - return nil - } - if cmd.NArg() < 1 { + if cmd.NArg() != 1 { cmd.Usage() return nil } - if container := srv.runtime.Get(cmd.Arg(0)); container == nil { - return fmt.Errorf("No such container") - } else { - changes, err := container.Changes() - if err != nil { - return err - } - for _, change := range changes { - fmt.Fprintln(stdout, change.String()) - } + + if err := cli.stream("GET", "/containers/"+cmd.Arg(0)+"/export", nil, os.Stdout); err != nil { + return err } return nil } -func (srv *Server) CmdLogs(stdin io.ReadCloser, stdout io.Writer, args ...string) error { - cmd := rcli.Subcmd(stdout, "logs", "CONTAINER", "Fetch the logs of a container") +func (cli *DockerCli) CmdDiff(args ...string) error { + cmd := Subcmd("diff", "CONTAINER", "Inspect changes on a container's filesystem") if err := cmd.Parse(args); err != nil { return nil } @@ -840,31 +895,25 @@ func (srv *Server) CmdLogs(stdin io.ReadCloser, stdout io.Writer, args ...string cmd.Usage() return nil } - name := cmd.Arg(0) - if container := srv.runtime.Get(name); container != nil { - logStdout, err := container.ReadLog("stdout") - if err != nil { - return err - } - logStderr, err := container.ReadLog("stderr") - if err != nil { - return err - } - // FIXME: Interpolate stdout and stderr instead of concatenating them - // FIXME: Differentiate stdout and stderr in the remote protocol - if _, err := io.Copy(stdout, logStdout); err != nil { - return err - } - if _, err := io.Copy(stdout, logStderr); err != nil { - return err - } - return nil + + body, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/changes", nil) + if err != nil { + return err } - return fmt.Errorf("No such container: %s", cmd.Arg(0)) + + changes := []Change{} + err = json.Unmarshal(body, &changes) + if err != nil { + return err + } + for _, change := range changes { + fmt.Println(change.String()) + } + return nil } -func (srv *Server) CmdAttach(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error { - cmd := rcli.Subcmd(stdout, "attach", "CONTAINER", "Attach to a running container") +func (cli *DockerCli) CmdLogs(args ...string) error { + cmd := Subcmd("logs", "CONTAINER", "Fetch the logs of a container") if err := cmd.Parse(args); err != nil { return nil } @@ -872,40 +921,86 @@ func (srv *Server) CmdAttach(stdin io.ReadCloser, stdout rcli.DockerConn, args . cmd.Usage() return nil } - name := cmd.Arg(0) - container := srv.runtime.Get(name) - if container == nil { - return fmt.Errorf("No such container: %s", name) + + v := url.Values{} + v.Set("logs", "1") + v.Set("stdout", "1") + v.Set("stderr", "1") + + if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), false); err != nil { + return err + } + return nil +} + +func (cli *DockerCli) CmdAttach(args ...string) error { + cmd := Subcmd("attach", "CONTAINER", "Attach to a running container") + if err := cmd.Parse(args); err != nil { + return nil + } + if cmd.NArg() != 1 { + cmd.Usage() + return nil } - if container.State.Ghost { - return fmt.Errorf("Impossible to attach to a ghost container") + body, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil) + if err != nil { + return err } - if container.Config.Tty { - stdout.SetOptionRawTerminal() + container := &Container{} + err = json.Unmarshal(body, container) + if err != nil { + return err } - // Flush the options to make sure the client sets the raw mode - stdout.Flush() - return <-container.Attach(stdin, nil, stdout, stdout) + + v := url.Values{} + v.Set("stream", "1") + v.Set("stdout", "1") + v.Set("stderr", "1") + v.Set("stdin", "1") + + if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), container.Config.Tty); err != nil { + return err + } + return nil +} + +func (cli *DockerCli) CmdSearch(args ...string) error { + cmd := Subcmd("search", "NAME", "Search the docker index for images") + if err := cmd.Parse(args); err != nil { + return nil + } + if cmd.NArg() != 1 { + cmd.Usage() + return nil + } + + v := url.Values{} + v.Set("term", cmd.Arg(0)) + body, _, err := cli.call("GET", "/images/search?"+v.Encode(), nil) + if err != nil { + return err + } + + outs := []ApiSearch{} + err = json.Unmarshal(body, &outs) + if err != nil { + return err + } + fmt.Printf("Found %d results matching your query (\"%s\")\n", len(outs), cmd.Arg(0)) + w := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0) + fmt.Fprintf(w, "NAME\tDESCRIPTION\n") + for _, out := range outs { + fmt.Fprintf(w, "%s\t%s\n", out.Name, out.Description) + } + w.Flush() + return nil } // Ports type - Used to parse multiple -p flags type ports []int -func (p *ports) String() string { - return fmt.Sprint(*p) -} - -func (p *ports) Set(value string) error { - port, err := strconv.Atoi(value) - if err != nil { - return fmt.Errorf("Invalid port: %v", value) - } - *p = append(*p, port) - return nil -} - // ListOpts type type ListOpts []string @@ -964,106 +1059,241 @@ func (opts PathOpts) Set(val string) error { return nil } -func (srv *Server) CmdTag(stdin io.ReadCloser, stdout io.Writer, args ...string) error { - cmd := rcli.Subcmd(stdout, "tag", "[OPTIONS] IMAGE REPOSITORY [TAG]", "Tag an image into a repository") +func (cli *DockerCli) CmdTag(args ...string) error { + cmd := Subcmd("tag", "[OPTIONS] IMAGE REPOSITORY [TAG]", "Tag an image into a repository") force := cmd.Bool("f", false, "Force") if err := cmd.Parse(args); err != nil { return nil } - if cmd.NArg() < 2 { + if cmd.NArg() != 2 && cmd.NArg() != 3 { cmd.Usage() return nil } - return srv.runtime.repositories.Set(cmd.Arg(1), cmd.Arg(2), cmd.Arg(0), *force) -} -func (srv *Server) CmdRun(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error { - config, err := ParseRun(args, stdout, srv.runtime.capabilities) - if err != nil { + v := url.Values{} + v.Set("repo", cmd.Arg(1)) + if cmd.NArg() == 3 { + v.Set("tag", cmd.Arg(2)) + } + + if *force { + v.Set("force", "1") + } + + if _, _, err := cli.call("POST", "/images/"+cmd.Arg(0)+"/tag?"+v.Encode(), nil); err != nil { return err } - if config.Image == "" { - fmt.Fprintln(stdout, "Error: Image not specified") - return fmt.Errorf("Image not specified") - } - - if config.Tty { - stdout.SetOptionRawTerminal() - } - // Flush the options to make sure the client sets the raw mode - // or tell the client there is no options - stdout.Flush() - - // Create new container - container, err := srv.runtime.Create(config) - if err != nil { - // If container not found, try to pull it - if srv.runtime.graph.IsNotExist(err) { - fmt.Fprintf(stdout, "Image %s not found, trying to pull it from registry.\r\n", config.Image) - if err = srv.CmdPull(stdin, stdout, config.Image); err != nil { - return err - } - if container, err = srv.runtime.Create(config); err != nil { - return err - } - } else { - return err - } - } - var ( - cStdin io.ReadCloser - cStdout, cStderr io.Writer - ) - if config.AttachStdin { - r, w := io.Pipe() - go func() { - defer w.Close() - defer Debugf("Closing buffered stdin pipe") - io.Copy(w, stdin) - }() - cStdin = r - } - if config.AttachStdout { - cStdout = stdout - } - if config.AttachStderr { - cStderr = stdout // FIXME: rcli can't differentiate stdout from stderr - } - - attachErr := container.Attach(cStdin, stdin, cStdout, cStderr) - Debugf("Starting\n") - if err := container.Start(); err != nil { - return err - } - if cStdout == nil && cStderr == nil { - fmt.Fprintln(stdout, container.ShortId()) - } - Debugf("Waiting for attach to return\n") - <-attachErr - // Expecting I/O pipe error, discarding - - // If we are in stdinonce mode, wait for the process to end - // otherwise, simply return - if config.StdinOnce && !config.Tty { - container.Wait() - } return nil } -func NewServer(autoRestart bool) (*Server, error) { - if runtime.GOARCH != "amd64" { - log.Fatalf("The docker runtime currently only supports amd64 (not %s). This will change in the future. Aborting.", runtime.GOARCH) - } - runtime, err := NewRuntime(autoRestart) +func (cli *DockerCli) CmdRun(args ...string) error { + config, cmd, err := ParseRun(args, nil) if err != nil { - return nil, err + return err } - srv := &Server{ - runtime: runtime, + if config.Image == "" { + cmd.Usage() + return nil } - return srv, nil + + //create the container + body, statusCode, err := cli.call("POST", "/containers/create", config) + //if image not found try to pull it + if statusCode == 404 { + v := url.Values{} + v.Set("fromImage", config.Image) + err = cli.stream("POST", "/images/create?"+v.Encode(), nil, os.Stderr) + if err != nil { + return err + } + body, _, err = cli.call("POST", "/containers/create", config) + if err != nil { + return err + } + } + if err != nil { + return err + } + + 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") + + if config.AttachStdin { + v.Set("stdin", "1") + } + if config.AttachStdout { + v.Set("stdout", "1") + } + if config.AttachStderr { + v.Set("stderr", "1") + + } + + //start the container + _, _, err = cli.call("POST", "/containers/"+out.Id+"/start", nil) + if err != nil { + return err + } + + if config.AttachStdin || config.AttachStdout || config.AttachStderr { + if err := cli.hijack("POST", "/containers/"+out.Id+"/attach?"+v.Encode(), config.Tty); err != nil { + return err + } + } + if !config.AttachStdout && !config.AttachStderr { + fmt.Println(out.Id) + } + return nil } -type Server struct { - runtime *Runtime +func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int, error) { + var params io.Reader + if data != nil { + buf, err := json.Marshal(data) + if err != nil { + return nil, -1, err + } + params = bytes.NewBuffer(buf) + } + + req, err := http.NewRequest(method, fmt.Sprintf("http://%s:%d", cli.host, cli.port)+path, params) + if err != nil { + return nil, -1, err + } + req.Header.Set("User-Agent", "Docker-Client/"+VERSION) + if data != nil { + req.Header.Set("Content-Type", "application/json") + } else if method == "POST" { + req.Header.Set("Content-Type", "plain/text") + } + resp, err := http.DefaultClient.Do(req) + if err != nil { + if strings.Contains(err.Error(), "connection refused") { + return nil, -1, fmt.Errorf("Can't connect to docker daemon. Is 'docker -d' running on this host?") + } + return nil, -1, err + } + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, -1, err + } + if resp.StatusCode < 200 || resp.StatusCode >= 400 { + return nil, resp.StatusCode, fmt.Errorf("error: %s", body) + } + return body, resp.StatusCode, nil +} + +func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) error { + if (method == "POST" || method == "PUT") && in == nil { + in = bytes.NewReader([]byte{}) + } + req, err := http.NewRequest(method, fmt.Sprintf("http://%s:%d%s", cli.host, cli.port, path), in) + if err != nil { + return err + } + req.Header.Set("User-Agent", "Docker-Client/"+VERSION) + if method == "POST" { + req.Header.Set("Content-Type", "plain/text") + } + resp, err := http.DefaultClient.Do(req) + if err != nil { + if strings.Contains(err.Error(), "connection refused") { + return fmt.Errorf("Can't connect to docker daemon. Is 'docker -d' running on this host?") + } + return err + } + defer resp.Body.Close() + if resp.StatusCode < 200 || resp.StatusCode >= 400 { + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + return fmt.Errorf("error: %s", body) + } + + if _, err := io.Copy(out, resp.Body); err != nil { + return err + } + return nil +} + +func (cli *DockerCli) hijack(method, path string, setRawTerminal bool) error { + req, err := http.NewRequest(method, path, nil) + if err != nil { + return err + } + req.Header.Set("Content-Type", "plain/text") + dial, err := net.Dial("tcp", fmt.Sprintf("%s:%d", cli.host, cli.port)) + if err != nil { + return err + } + clientconn := httputil.NewClientConn(dial, nil) + clientconn.Do(req) + defer clientconn.Close() + + rwc, br := clientconn.Hijack() + defer rwc.Close() + + receiveStdout := utils.Go(func() error { + _, err := io.Copy(os.Stdout, br) + return err + }) + + if setRawTerminal && term.IsTerminal(int(os.Stdin.Fd())) && os.Getenv("NORAW") == "" { + if oldState, err := term.SetRawTerminal(); err != nil { + return err + } else { + defer term.RestoreTerminal(oldState) + } + } + + sendStdin := utils.Go(func() error { + _, err := io.Copy(rwc, os.Stdin) + if err := rwc.(*net.TCPConn).CloseWrite(); err != nil { + fmt.Fprintf(os.Stderr, "Couldn't send EOF: %s\n", err) + } + return err + }) + + if err := <-receiveStdout; err != nil { + return err + } + + if !term.IsTerminal(int(os.Stdin.Fd())) { + if err := <-sendStdin; err != nil { + return err + } + } + return nil + +} + +func Subcmd(name, signature, description string) *flag.FlagSet { + flags := flag.NewFlagSet(name, flag.ContinueOnError) + flags.Usage = func() { + fmt.Printf("\nUsage: docker %s %s\n\n%s\n\n", name, signature, description) + flags.PrintDefaults() + } + return flags +} + +func NewDockerCli(host string, port int) *DockerCli { + return &DockerCli{host, port} +} + +type DockerCli struct { + host string + port int } diff --git a/commands_test.go b/commands_test.go index 83b480d52..05ece80da 100644 --- a/commands_test.go +++ b/commands_test.go @@ -3,9 +3,8 @@ package docker import ( "bufio" "fmt" - "github.com/dotcloud/docker/rcli" "io" - "io/ioutil" + _ "io/ioutil" "strings" "testing" "time" @@ -59,6 +58,7 @@ func assertPipe(input, output string, r io.Reader, w io.Writer, count int) error return nil } +/*TODO func cmdWait(srv *Server, container *Container) error { stdout, stdoutPipe := io.Pipe() @@ -73,6 +73,77 @@ func cmdWait(srv *Server, container *Container) error { return closeWrap(stdout, stdoutPipe) } +func cmdImages(srv *Server, args ...string) (string, error) { + stdout, stdoutPipe := io.Pipe() + + go func() { + if err := srv.CmdImages(nil, stdoutPipe, args...); err != nil { + return + } + + // force the pipe closed, so that the code below gets an EOF + stdoutPipe.Close() + }() + + output, err := ioutil.ReadAll(stdout) + if err != nil { + return "", err + } + + // Cleanup pipes + return string(output), closeWrap(stdout, stdoutPipe) +} + +// TestImages checks that 'docker images' displays information correctly +func TestImages(t *testing.T) { + + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) + + srv := &Server{runtime: runtime} + + output, err := cmdImages(srv) + + if !strings.Contains(output, "REPOSITORY") { + t.Fatal("'images' should have a header") + } + if !strings.Contains(output, "docker-ut") { + t.Fatal("'images' should show the docker-ut image") + } + if !strings.Contains(output, "e9aa60c60128") { + t.Fatal("'images' should show the docker-ut image id") + } + + output, err = cmdImages(srv, "-q") + + if strings.Contains(output, "REPOSITORY") { + t.Fatal("'images -q' should not have a header") + } + if strings.Contains(output, "docker-ut") { + t.Fatal("'images' should not show the docker-ut image name") + } + if !strings.Contains(output, "e9aa60c60128") { + t.Fatal("'images' should show the docker-ut image id") + } + + output, err = cmdImages(srv, "-viz") + + if !strings.HasPrefix(output, "digraph docker {") { + t.Fatal("'images -v' should start with the dot header") + } + if !strings.HasSuffix(output, "}\n") { + t.Fatal("'images -v' should end with a '}'") + } + if !strings.Contains(output, "base -> \"e9aa60c60128\" [style=invis]") { + t.Fatal("'images -v' should have the docker-ut image id node") + } + + // todo: add checks for -a +} + // TestRunHostname checks that 'docker run -h' correctly sets a custom hostname func TestRunHostname(t *testing.T) { runtime, err := newTestRuntime() @@ -339,9 +410,10 @@ func TestAttachDisconnect(t *testing.T) { srv := &Server{runtime: runtime} - container, err := runtime.Create( + container, err := NewBuilder(runtime).Create( &Config{ Image: GetTestImage(runtime).Id, + CpuShares: 1000, Memory: 33554432, Cmd: []string{"/bin/cat"}, OpenStdin: true, @@ -396,3 +468,4 @@ func TestAttachDisconnect(t *testing.T) { cStdin.Close() container.Wait() } +*/ diff --git a/container.go b/container.go index 311e475ac..a82ce0291 100644 --- a/container.go +++ b/container.go @@ -2,8 +2,9 @@ package docker import ( "encoding/json" + "flag" "fmt" - "github.com/dotcloud/docker/rcli" + "github.com/dotcloud/docker/utils" "github.com/kr/pty" "io" "io/ioutil" @@ -39,8 +40,8 @@ type Container struct { ResolvConfPath string cmd *exec.Cmd - stdout *writeBroadcaster - stderr *writeBroadcaster + stdout *utils.WriteBroadcaster + stderr *utils.WriteBroadcaster stdin io.ReadCloser stdinPipe io.WriteCloser ptyMaster io.Closer @@ -56,6 +57,7 @@ type Config struct { User string Memory int64 // Memory limit (in bytes) MemorySwap int64 // Total memory usage (memory + swap); set `-1' to disable swap + CpuShares int64 // CPU shares (relative weight vs. other containers) AttachStdin bool AttachStdout bool AttachStderr bool @@ -71,8 +73,8 @@ type Config struct { VolumesFrom string } -func ParseRun(args []string, stdout io.Writer, capabilities *Capabilities) (*Config, error) { - cmd := rcli.Subcmd(stdout, "run", "[OPTIONS] IMAGE COMMAND [ARG...]", "Run a command in a new container") +func ParseRun(args []string, capabilities *Capabilities) (*Config, *flag.FlagSet, error) { + cmd := Subcmd("run", "[OPTIONS] IMAGE COMMAND [ARG...]", "Run a command in a new container") if len(args) > 0 && args[0] != "--help" { cmd.SetOutput(ioutil.Discard) } @@ -86,11 +88,13 @@ func ParseRun(args []string, stdout io.Writer, capabilities *Capabilities) (*Con flTty := cmd.Bool("t", false, "Allocate a pseudo-tty") flMemory := cmd.Int64("m", 0, "Memory limit (in bytes)") - if *flMemory > 0 && !capabilities.MemoryLimit { - fmt.Fprintf(stdout, "WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n") + if capabilities != nil && *flMemory > 0 && !capabilities.MemoryLimit { + //fmt.Fprintf(stdout, "WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n") *flMemory = 0 } + flCpuShares := cmd.Int64("c", 0, "CPU shares (relative weight)") + var flPorts ListOpts cmd.Var(&flPorts, "p", "Expose a container's port to the host (use 'docker port' to see the actual mapping)") @@ -106,10 +110,10 @@ func ParseRun(args []string, stdout io.Writer, capabilities *Capabilities) (*Con flVolumesFrom := cmd.String("volumes-from", "", "Mount volumes from the specified container") if err := cmd.Parse(args); err != nil { - return nil, err + return nil, cmd, err } if *flDetach && len(flAttach) > 0 { - return nil, fmt.Errorf("Conflicting options: -a and -d") + return nil, cmd, fmt.Errorf("Conflicting options: -a and -d") } // If neither -d or -a are set, attach to everything by default if len(flAttach) == 0 && !*flDetach { @@ -137,6 +141,7 @@ func ParseRun(args []string, stdout io.Writer, capabilities *Capabilities) (*Con Tty: *flTty, OpenStdin: *flStdin, Memory: *flMemory, + CpuShares: *flCpuShares, AttachStdin: flAttach.Get("stdin"), AttachStdout: flAttach.Get("stdout"), AttachStderr: flAttach.Get("stderr"), @@ -148,8 +153,8 @@ func ParseRun(args []string, stdout io.Writer, capabilities *Capabilities) (*Con VolumesFrom: *flVolumesFrom, } - if *flMemory > 0 && !capabilities.SwapLimit { - fmt.Fprintf(stdout, "WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.\n") + if capabilities != nil && *flMemory > 0 && !capabilities.SwapLimit { + //fmt.Fprintf(stdout, "WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.\n") config.MemorySwap = -1 } @@ -157,7 +162,7 @@ func ParseRun(args []string, stdout io.Writer, capabilities *Capabilities) (*Con if config.OpenStdin && config.AttachStdin { config.StdinOnce = true } - return config, nil + return config, cmd, nil } type NetworkSettings struct { @@ -178,6 +183,23 @@ func (settings *NetworkSettings) PortMappingHuman() string { return strings.Join(mapping, ", ") } +// Inject the io.Reader at the given path. Note: do not close the reader +func (container *Container) Inject(file io.Reader, pth string) error { + // Make sure the directory exists + if err := os.MkdirAll(path.Join(container.rwPath(), path.Dir(pth)), 0755); err != nil { + return err + } + // FIXME: Handle permissions/already existing dest + dest, err := os.Create(path.Join(container.rwPath(), pth)) + if err != nil { + return err + } + if _, err := io.Copy(dest, file); err != nil { + return err + } + return nil +} + func (container *Container) Cmd() *exec.Cmd { return container.cmd } @@ -230,9 +252,9 @@ func (container *Container) startPty() error { // Copy the PTYs to our broadcasters go func() { defer container.stdout.CloseWriters() - Debugf("[startPty] Begin of stdout pipe") + utils.Debugf("[startPty] Begin of stdout pipe") io.Copy(container.stdout, ptyMaster) - Debugf("[startPty] End of stdout pipe") + utils.Debugf("[startPty] End of stdout pipe") }() // stdin @@ -241,9 +263,9 @@ func (container *Container) startPty() error { container.cmd.SysProcAttr = &syscall.SysProcAttr{Setctty: true, Setsid: true} go func() { defer container.stdin.Close() - Debugf("[startPty] Begin of stdin pipe") + utils.Debugf("[startPty] Begin of stdin pipe") io.Copy(ptyMaster, container.stdin) - Debugf("[startPty] End of stdin pipe") + utils.Debugf("[startPty] End of stdin pipe") }() } if err := container.cmd.Start(); err != nil { @@ -263,9 +285,9 @@ func (container *Container) start() error { } go func() { defer stdin.Close() - Debugf("Begin of stdin pipe [start]") + utils.Debugf("Begin of stdin pipe [start]") io.Copy(stdin, container.stdin) - Debugf("End of stdin pipe [start]") + utils.Debugf("End of stdin pipe [start]") }() } return container.cmd.Start() @@ -282,8 +304,8 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s errors <- err } else { go func() { - Debugf("[start] attach stdin\n") - defer Debugf("[end] attach stdin\n") + utils.Debugf("[start] attach stdin\n") + defer utils.Debugf("[end] attach stdin\n") // No matter what, when stdin is closed (io.Copy unblock), close stdout and stderr if cStdout != nil { defer cStdout.Close() @@ -295,12 +317,12 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s defer cStdin.Close() } if container.Config.Tty { - _, err = CopyEscapable(cStdin, stdin) + _, err = utils.CopyEscapable(cStdin, stdin) } else { _, err = io.Copy(cStdin, stdin) } if err != nil { - Debugf("[error] attach stdin: %s\n", err) + utils.Debugf("[error] attach stdin: %s\n", err) } // Discard error, expecting pipe error errors <- nil @@ -314,8 +336,8 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s } else { cStdout = p go func() { - Debugf("[start] attach stdout\n") - defer Debugf("[end] attach stdout\n") + utils.Debugf("[start] attach stdout\n") + defer utils.Debugf("[end] attach stdout\n") // If we are in StdinOnce mode, then close stdin if container.Config.StdinOnce { if stdin != nil { @@ -327,7 +349,7 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s } _, err := io.Copy(stdout, cStdout) if err != nil { - Debugf("[error] attach stdout: %s\n", err) + utils.Debugf("[error] attach stdout: %s\n", err) } errors <- err }() @@ -340,8 +362,8 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s } else { cStderr = p go func() { - Debugf("[start] attach stderr\n") - defer Debugf("[end] attach stderr\n") + utils.Debugf("[start] attach stderr\n") + defer utils.Debugf("[end] attach stderr\n") // If we are in StdinOnce mode, then close stdin if container.Config.StdinOnce { if stdin != nil { @@ -353,13 +375,13 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s } _, err := io.Copy(stderr, cStderr) if err != nil { - Debugf("[error] attach stderr: %s\n", err) + utils.Debugf("[error] attach stderr: %s\n", err) } errors <- err }() } } - return Go(func() error { + return utils.Go(func() error { if cStdout != nil { defer cStdout.Close() } @@ -369,14 +391,14 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s // FIXME: how do clean up the stdin goroutine without the unwanted side effect // of closing the passed stdin? Add an intermediary io.Pipe? for i := 0; i < nJobs; i += 1 { - Debugf("Waiting for job %d/%d\n", i+1, nJobs) + utils.Debugf("Waiting for job %d/%d\n", i+1, nJobs) if err := <-errors; err != nil { - Debugf("Job %d returned error %s. Aborting all jobs\n", i+1, err) + utils.Debugf("Job %d returned error %s. Aborting all jobs\n", i+1, err) return err } - Debugf("Job %d completed successfully\n", i+1) + utils.Debugf("Job %d completed successfully\n", i+1) } - Debugf("All jobs completed successfully\n") + utils.Debugf("All jobs completed successfully\n") return nil }) } @@ -534,13 +556,13 @@ func (container *Container) StdinPipe() (io.WriteCloser, error) { func (container *Container) StdoutPipe() (io.ReadCloser, error) { reader, writer := io.Pipe() container.stdout.AddWriter(writer) - return newBufReader(reader), nil + return utils.NewBufReader(reader), nil } func (container *Container) StderrPipe() (io.ReadCloser, error) { reader, writer := io.Pipe() container.stderr.AddWriter(writer) - return newBufReader(reader), nil + return utils.NewBufReader(reader), nil } func (container *Container) allocateNetwork() error { @@ -588,20 +610,20 @@ func (container *Container) waitLxc() error { func (container *Container) monitor() { // Wait for the program to exit - Debugf("Waiting for process") + utils.Debugf("Waiting for process") // 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) + utils.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) + utils.Debugf("%s: Process: %s", container.Id, err) } } - Debugf("Process finished") + utils.Debugf("Process finished") var exitCode int = -1 if container.cmd != nil { @@ -612,19 +634,19 @@ func (container *Container) monitor() { container.releaseNetwork() if container.Config.OpenStdin { if err := container.stdin.Close(); err != nil { - Debugf("%s: Error close stdin: %s", container.Id, err) + utils.Debugf("%s: Error close stdin: %s", container.Id, err) } } if err := container.stdout.CloseWriters(); err != nil { - Debugf("%s: Error close stdout: %s", container.Id, err) + utils.Debugf("%s: Error close stdout: %s", container.Id, err) } if err := container.stderr.CloseWriters(); err != nil { - Debugf("%s: Error close stderr: %s", container.Id, err) + utils.Debugf("%s: Error close stderr: %s", container.Id, err) } if container.ptyMaster != nil { if err := container.ptyMaster.Close(); err != nil { - Debugf("%s: Error closing Pty master: %s", container.Id, err) + utils.Debugf("%s: Error closing Pty master: %s", container.Id, err) } } @@ -741,7 +763,7 @@ func (container *Container) RwChecksum() (string, error) { if err != nil { return "", err } - return HashData(rwData) + return utils.HashData(rwData) } func (container *Container) Export() (Archive, error) { @@ -812,7 +834,7 @@ func (container *Container) Unmount() error { // In case of a collision a lookup with Runtime.Get() will fail, and the caller // will need to use a langer prefix, or the full-length container Id. func (container *Container) ShortId() string { - return TruncateId(container.Id) + return utils.TruncateId(container.Id) } func (container *Container) logPath(name string) string { diff --git a/container_test.go b/container_test.go index c3a891cf4..3ed1763a3 100644 --- a/container_test.go +++ b/container_test.go @@ -20,7 +20,7 @@ func TestIdFormat(t *testing.T) { t.Fatal(err) } defer nuke(runtime) - container1, err := runtime.Create( + container1, err := NewBuilder(runtime).Create( &Config{ Image: GetTestImage(runtime).Id, Cmd: []string{"/bin/sh", "-c", "echo hello world"}, @@ -44,7 +44,7 @@ func TestMultipleAttachRestart(t *testing.T) { t.Fatal(err) } defer nuke(runtime) - container, err := runtime.Create( + container, err := NewBuilder(runtime).Create( &Config{ Image: GetTestImage(runtime).Id, Cmd: []string{"/bin/sh", "-c", @@ -148,8 +148,10 @@ func TestDiff(t *testing.T) { } defer nuke(runtime) + builder := NewBuilder(runtime) + // Create a container and remove a file - container1, err := runtime.Create( + container1, err := builder.Create( &Config{ Image: GetTestImage(runtime).Id, Cmd: []string{"/bin/rm", "/etc/passwd"}, @@ -190,7 +192,7 @@ func TestDiff(t *testing.T) { } // Create a new container from the commited image - container2, err := runtime.Create( + container2, err := builder.Create( &Config{ Image: img.Id, Cmd: []string{"cat", "/etc/passwd"}, @@ -223,7 +225,9 @@ func TestCommitAutoRun(t *testing.T) { t.Fatal(err) } defer nuke(runtime) - container1, err := runtime.Create( + + builder := NewBuilder(runtime) + container1, err := builder.Create( &Config{ Image: GetTestImage(runtime).Id, Cmd: []string{"/bin/sh", "-c", "echo hello > /world"}, @@ -254,8 +258,7 @@ func TestCommitAutoRun(t *testing.T) { } // FIXME: Make a TestCommit that stops here and check docker.root/layers/img.id/world - - container2, err := runtime.Create( + container2, err := builder.Create( &Config{ Image: img.Id, }, @@ -301,7 +304,10 @@ func TestCommitRun(t *testing.T) { t.Fatal(err) } defer nuke(runtime) - container1, err := runtime.Create( + + builder := NewBuilder(runtime) + + container1, err := builder.Create( &Config{ Image: GetTestImage(runtime).Id, Cmd: []string{"/bin/sh", "-c", "echo hello > /world"}, @@ -333,7 +339,7 @@ func TestCommitRun(t *testing.T) { // FIXME: Make a TestCommit that stops here and check docker.root/layers/img.id/world - container2, err := runtime.Create( + container2, err := builder.Create( &Config{ Image: img.Id, Cmd: []string{"cat", "/world"}, @@ -380,10 +386,11 @@ func TestStart(t *testing.T) { t.Fatal(err) } defer nuke(runtime) - container, err := runtime.Create( + container, err := NewBuilder(runtime).Create( &Config{ Image: GetTestImage(runtime).Id, Memory: 33554432, + CpuShares: 1000, Cmd: []string{"/bin/cat"}, OpenStdin: true, }, @@ -393,6 +400,11 @@ func TestStart(t *testing.T) { } defer runtime.Destroy(container) + cStdin, err := container.StdinPipe() + if err != nil { + t.Fatal(err) + } + if err := container.Start(); err != nil { t.Fatal(err) } @@ -408,7 +420,6 @@ func TestStart(t *testing.T) { } // Try to avoid the timeoout in destroy. Best effort, don't check error - cStdin, _ := container.StdinPipe() cStdin.Close() container.WaitTimeout(2 * time.Second) } @@ -419,7 +430,7 @@ func TestRun(t *testing.T) { t.Fatal(err) } defer nuke(runtime) - container, err := runtime.Create( + container, err := NewBuilder(runtime).Create( &Config{ Image: GetTestImage(runtime).Id, Cmd: []string{"ls", "-al"}, @@ -447,7 +458,7 @@ func TestOutput(t *testing.T) { t.Fatal(err) } defer nuke(runtime) - container, err := runtime.Create( + container, err := NewBuilder(runtime).Create( &Config{ Image: GetTestImage(runtime).Id, Cmd: []string{"echo", "-n", "foobar"}, @@ -472,7 +483,7 @@ func TestKillDifferentUser(t *testing.T) { t.Fatal(err) } defer nuke(runtime) - container, err := runtime.Create(&Config{ + container, err := NewBuilder(runtime).Create(&Config{ Image: GetTestImage(runtime).Id, Cmd: []string{"tail", "-f", "/etc/resolv.conf"}, User: "daemon", @@ -520,7 +531,7 @@ func TestKill(t *testing.T) { t.Fatal(err) } defer nuke(runtime) - container, err := runtime.Create(&Config{ + container, err := NewBuilder(runtime).Create(&Config{ Image: GetTestImage(runtime).Id, Cmd: []string{"cat", "/dev/zero"}, }, @@ -566,7 +577,9 @@ func TestExitCode(t *testing.T) { } defer nuke(runtime) - trueContainer, err := runtime.Create(&Config{ + builder := NewBuilder(runtime) + + trueContainer, err := builder.Create(&Config{ Image: GetTestImage(runtime).Id, Cmd: []string{"/bin/true", ""}, }) @@ -581,7 +594,7 @@ func TestExitCode(t *testing.T) { t.Errorf("Unexpected exit code %d (expected 0)", trueContainer.State.ExitCode) } - falseContainer, err := runtime.Create(&Config{ + falseContainer, err := builder.Create(&Config{ Image: GetTestImage(runtime).Id, Cmd: []string{"/bin/false", ""}, }) @@ -603,7 +616,7 @@ func TestRestart(t *testing.T) { t.Fatal(err) } defer nuke(runtime) - container, err := runtime.Create(&Config{ + container, err := NewBuilder(runtime).Create(&Config{ Image: GetTestImage(runtime).Id, Cmd: []string{"echo", "-n", "foobar"}, }, @@ -636,7 +649,7 @@ func TestRestartStdin(t *testing.T) { t.Fatal(err) } defer nuke(runtime) - container, err := runtime.Create(&Config{ + container, err := NewBuilder(runtime).Create(&Config{ Image: GetTestImage(runtime).Id, Cmd: []string{"cat"}, @@ -715,8 +728,10 @@ func TestUser(t *testing.T) { } defer nuke(runtime) + builder := NewBuilder(runtime) + // Default user must be root - container, err := runtime.Create(&Config{ + container, err := builder.Create(&Config{ Image: GetTestImage(runtime).Id, Cmd: []string{"id"}, }, @@ -734,7 +749,7 @@ func TestUser(t *testing.T) { } // Set a username - container, err = runtime.Create(&Config{ + container, err = builder.Create(&Config{ Image: GetTestImage(runtime).Id, Cmd: []string{"id"}, @@ -754,7 +769,7 @@ func TestUser(t *testing.T) { } // Set a UID - container, err = runtime.Create(&Config{ + container, err = builder.Create(&Config{ Image: GetTestImage(runtime).Id, Cmd: []string{"id"}, @@ -774,7 +789,7 @@ func TestUser(t *testing.T) { } // Set a different user by uid - container, err = runtime.Create(&Config{ + container, err = builder.Create(&Config{ Image: GetTestImage(runtime).Id, Cmd: []string{"id"}, @@ -796,7 +811,7 @@ func TestUser(t *testing.T) { } // Set a different user by username - container, err = runtime.Create(&Config{ + container, err = builder.Create(&Config{ Image: GetTestImage(runtime).Id, Cmd: []string{"id"}, @@ -823,7 +838,9 @@ func TestMultipleContainers(t *testing.T) { } defer nuke(runtime) - container1, err := runtime.Create(&Config{ + builder := NewBuilder(runtime) + + container1, err := builder.Create(&Config{ Image: GetTestImage(runtime).Id, Cmd: []string{"cat", "/dev/zero"}, }, @@ -833,7 +850,7 @@ func TestMultipleContainers(t *testing.T) { } defer runtime.Destroy(container1) - container2, err := runtime.Create(&Config{ + container2, err := builder.Create(&Config{ Image: GetTestImage(runtime).Id, Cmd: []string{"cat", "/dev/zero"}, }, @@ -879,7 +896,7 @@ func TestStdin(t *testing.T) { t.Fatal(err) } defer nuke(runtime) - container, err := runtime.Create(&Config{ + container, err := NewBuilder(runtime).Create(&Config{ Image: GetTestImage(runtime).Id, Cmd: []string{"cat"}, @@ -926,7 +943,7 @@ func TestTty(t *testing.T) { t.Fatal(err) } defer nuke(runtime) - container, err := runtime.Create(&Config{ + container, err := NewBuilder(runtime).Create(&Config{ Image: GetTestImage(runtime).Id, Cmd: []string{"cat"}, @@ -973,7 +990,7 @@ func TestEnv(t *testing.T) { t.Fatal(err) } defer nuke(runtime) - container, err := runtime.Create(&Config{ + container, err := NewBuilder(runtime).Create(&Config{ Image: GetTestImage(runtime).Id, Cmd: []string{"/usr/bin/env"}, }, @@ -1047,12 +1064,17 @@ func TestLXCConfig(t *testing.T) { memMin := 33554432 memMax := 536870912 mem := memMin + rand.Intn(memMax-memMin) - container, err := runtime.Create(&Config{ + // CPU shares as well + cpuMin := 100 + cpuMax := 10000 + cpu := cpuMin + rand.Intn(cpuMax-cpuMin) + container, err := NewBuilder(runtime).Create(&Config{ Image: GetTestImage(runtime).Id, Cmd: []string{"/bin/true"}, - Hostname: "foobar", - Memory: int64(mem), + Hostname: "foobar", + Memory: int64(mem), + CpuShares: int64(cpu), }, ) if err != nil { @@ -1074,7 +1096,7 @@ func BenchmarkRunSequencial(b *testing.B) { } defer nuke(runtime) for i := 0; i < b.N; i++ { - container, err := runtime.Create(&Config{ + container, err := NewBuilder(runtime).Create(&Config{ Image: GetTestImage(runtime).Id, Cmd: []string{"echo", "-n", "foo"}, }, @@ -1109,7 +1131,7 @@ func BenchmarkRunParallel(b *testing.B) { complete := make(chan error) tasks = append(tasks, complete) go func(i int, complete chan error) { - container, err := runtime.Create(&Config{ + container, err := NewBuilder(runtime).Create(&Config{ Image: GetTestImage(runtime).Id, Cmd: []string{"echo", "-n", "foo"}, }, diff --git a/contrib/docker-build/docker-build b/contrib/docker-build/docker-build index c82377e10..654cc8979 100755 --- a/contrib/docker-build/docker-build +++ b/contrib/docker-build/docker-build @@ -89,7 +89,7 @@ def main(): # Skip comments and empty lines if line == "" or line[0] == "#": continue - op, param = line.split(" ", 1) + op, param = line.split(None, 1) print op.upper() + " " + param if op == "from": base = param diff --git a/docker/docker.go b/docker/docker.go index dfd234609..c8c1a6560 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -4,9 +4,7 @@ import ( "flag" "fmt" "github.com/dotcloud/docker" - "github.com/dotcloud/docker/rcli" - "github.com/dotcloud/docker/term" - "io" + "github.com/dotcloud/docker/utils" "io/ioutil" "log" "os" @@ -20,7 +18,7 @@ var ( ) func main() { - if docker.SelfPath() == "/sbin/init" { + if utils.SelfPath() == "/sbin/init" { // Running in init mode docker.SysInit() return @@ -48,10 +46,12 @@ func main() { } if err := daemon(*pidfile, *flAutoRestart); err != nil { log.Fatal(err) + os.Exit(-1) } } else { - if err := runCommand(flag.Args()); err != nil { + if err := docker.ParseCommands(flag.Args()...); err != nil { log.Fatal(err) + os.Exit(-1) } } } @@ -98,50 +98,10 @@ func daemon(pidfile string, autoRestart bool) error { os.Exit(0) }() - service, err := docker.NewServer(autoRestart) + server, err := docker.NewServer(autoRestart) if err != nil { return err } - return rcli.ListenAndServe("tcp", "127.0.0.1:4242", service) -} -func runCommand(args []string) error { - // FIXME: we want to use unix sockets here, but net.UnixConn doesn't expose - // CloseWrite(), which we need to cleanly signal that stdin is closed without - // closing the connection. - // See http://code.google.com/p/go/issues/detail?id=3345 - if conn, err := rcli.Call("tcp", "127.0.0.1:4242", args...); err == nil { - options := conn.GetOptions() - if options.RawTerminal && - term.IsTerminal(int(os.Stdin.Fd())) && - os.Getenv("NORAW") == "" { - if oldState, err := rcli.SetRawTerminal(); err != nil { - return err - } else { - defer rcli.RestoreTerminal(oldState) - } - } - receiveStdout := docker.Go(func() error { - _, err := io.Copy(os.Stdout, conn) - return err - }) - sendStdin := docker.Go(func() error { - _, err := io.Copy(conn, os.Stdin) - if err := conn.CloseWrite(); err != nil { - log.Printf("Couldn't send EOF: " + err.Error()) - } - return err - }) - if err := <-receiveStdout; err != nil { - return err - } - if !term.IsTerminal(int(os.Stdin.Fd())) { - if err := <-sendStdin; err != nil { - return err - } - } - } else { - return fmt.Errorf("Can't connect to docker daemon. Is 'docker -d' running on this host?") - } - return nil + return docker.ListenAndServe("0.0.0.0:4243", server, true) } diff --git a/docs/Makefile b/docs/Makefile index 77f14ee92..26168b6f3 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -44,32 +44,28 @@ clean: -rm -rf $(BUILDDIR)/* docs: - -rm -rf $(BUILDDIR)/* + #-rm -rf $(BUILDDIR)/* $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/html - cp sources/index.html $(BUILDDIR)/html/ - cp -r sources/gettingstarted $(BUILDDIR)/html/ - 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." + @echo "Build finished. The documentation pages are now in $(BUILDDIR)/html." + + +site: + cp -r website $(BUILDDIR)/ + cp -r theme/docker/static/ $(BUILDDIR)/website/ + @echo + @echo "The Website pages are in $(BUILDDIR)/site." connect: - @echo pushing changes to staging site - @cd _build/html/ ; \ - @dotcloud list ; \ - dotcloud connect dockerwebsite + @echo connecting dotcloud to www.docker.io website, make sure to use user 1 + @cd _build/website/ ; \ + dotcloud connect dockerwebsite ; + dotcloud list push: - @cd _build/html/ ; \ + @cd _build/website/ ; \ dotcloud push -github-deploy: docs - rm -fr github-deploy - git clone ssh://git@github.com/dotcloud/docker github-deploy - cd github-deploy && git checkout -f gh-pages && git rm -r * && rsync -avH ../_build/html/ ./ && touch .nojekyll && echo "docker.io" > CNAME && git add * && git commit -m "Updating docs" - $(VERSIONS): @echo "Hello world" diff --git a/docs/README.md b/docs/README.md index fce7f238f..e1ca45b08 100644 --- a/docs/README.md +++ b/docs/README.md @@ -15,6 +15,7 @@ Installation * Work in your own fork of the code, we accept pull requests. * Install sphinx: ``pip install sphinx`` +* Install sphinx httpdomain contrib package ``sphinxcontrib-httpdomain`` * If pip is not available you can probably install it using your favorite package manager as **python-pip** Usage diff --git a/docs/sources/.nojekyll b/docs/sources/.nojekyll deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs/sources/CNAME b/docs/sources/CNAME deleted file mode 100644 index 243e48226..000000000 --- a/docs/sources/CNAME +++ /dev/null @@ -1 +0,0 @@ -docker.io diff --git a/docs/sources/api/docker_remote_api.rst b/docs/sources/api/docker_remote_api.rst new file mode 100644 index 000000000..4c8ebe847 --- /dev/null +++ b/docs/sources/api/docker_remote_api.rst @@ -0,0 +1,1012 @@ +:title: Remote API +:description: API Documentation for Docker +:keywords: API, Docker, rcli, REST, documentation + +================= +Docker Remote API +================= + +.. contents:: Table of Contents + +1. Brief introduction +===================== + +- The Remote API is replacing rcli +- Default port in the docker deamon is 4243 +- The API tends to be REST, but for some complex commands, like attach or pull, the HTTP connection is hijacked to transport stdout stdin and stderr + +2. Endpoints +============ + +2.1 Containers +-------------- + +List containers +*************** + +.. http:get:: /containers/ps + + List containers + + **Example request**: + + .. sourcecode:: http + + GET /containers/ps?all=1&before=8dfafdbc3a40 HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + [ + { + "Id": "8dfafdbc3a40", + "Image": "base:latest", + "Command": "echo 1", + "Created": 1367854155, + "Status": "Exit 0" + }, + { + "Id": "9cd87474be90", + "Image": "base:latest", + "Command": "echo 222222", + "Created": 1367854155, + "Status": "Exit 0" + }, + { + "Id": "3176a2479c92", + "Image": "base:latest", + "Command": "echo 3333333333333333", + "Created": 1367854154, + "Status": "Exit 0" + }, + { + "Id": "4cb07b47f9fb", + "Image": "base:latest", + "Command": "echo 444444444444444444444444444444444", + "Created": 1367854152, + "Status": "Exit 0" + } + ] + + :query all: 1/True/true or 0/False/false, Show all containers. Only running containers are shown by default + :query limit: Show ``limit`` last created containers, include non-running ones. + :query since: Show only containers created since Id, include non-running ones. + :query before: Show only containers created before Id, include non-running ones. + :statuscode 200: no error + :statuscode 400: bad parameter + :statuscode 500: server error + + +Create a container +****************** + +.. http:post:: /containers/create + + Create a container + + **Example request**: + + .. sourcecode:: http + + POST /containers/create HTTP/1.1 + Content-Type: application/json + + { + "Hostname":"", + "User":"", + "Memory":0, + "MemorySwap":0, + "AttachStdin":false, + "AttachStdout":true, + "AttachStderr":true, + "PortSpecs":null, + "Tty":false, + "OpenStdin":false, + "StdinOnce":false, + "Env":null, + "Cmd":[ + "date" + ], + "Dns":null, + "Image":"base", + "Volumes":{}, + "VolumesFrom":"" + } + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 201 OK + Content-Type: application/json + + { + "Id":"e90e34656806" + "Warnings":[] + } + + :jsonparam config: the container's configuration + :statuscode 201: no error + :statuscode 404: no such container + :statuscode 500: server error + + +Inspect a container +******************* + +.. http:get:: /containers/(id)/json + + Return low-level information on the container ``id`` + + **Example request**: + + .. sourcecode:: http + + GET /containers/4fa6e0f0c678/json HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + { + "Id": "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2", + "Created": "2013-05-07T14:51:42.041847+02:00", + "Path": "date", + "Args": [], + "Config": { + "Hostname": "4fa6e0f0c678", + "User": "", + "Memory": 0, + "MemorySwap": 0, + "AttachStdin": false, + "AttachStdout": true, + "AttachStderr": true, + "PortSpecs": null, + "Tty": false, + "OpenStdin": false, + "StdinOnce": false, + "Env": null, + "Cmd": [ + "date" + ], + "Dns": null, + "Image": "base", + "Volumes": {}, + "VolumesFrom": "" + }, + "State": { + "Running": false, + "Pid": 0, + "ExitCode": 0, + "StartedAt": "2013-05-07T14:51:42.087658+02:01360", + "Ghost": false + }, + "Image": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", + "NetworkSettings": { + "IpAddress": "", + "IpPrefixLen": 0, + "Gateway": "", + "Bridge": "", + "PortMapping": null + }, + "SysInitPath": "/home/kitty/go/src/github.com/dotcloud/docker/bin/docker", + "ResolvConfPath": "/etc/resolv.conf", + "Volumes": {} + } + + :statuscode 200: no error + :statuscode 404: no such container + :statuscode 500: server error + + +Inspect changes on a container's filesystem +******************************************* + +.. http:get:: /containers/(id)/changes + + Inspect changes on container ``id`` 's filesystem + + **Example request**: + + .. sourcecode:: http + + GET /containers/4fa6e0f0c678/changes HTTP/1.1 + + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + [ + { + "Path":"/dev", + "Kind":0 + }, + { + "Path":"/dev/kmsg", + "Kind":1 + }, + { + "Path":"/test", + "Kind":1 + } + ] + + :statuscode 200: no error + :statuscode 404: no such container + :statuscode 500: server error + + +Export a container +****************** + +.. http:get:: /containers/(id)/export + + Export the contents of container ``id`` + + **Example request**: + + .. sourcecode:: http + + GET /containers/4fa6e0f0c678/export HTTP/1.1 + + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/octet-stream + + {{ STREAM }} + + :statuscode 200: no error + :statuscode 404: no such container + :statuscode 500: server error + + +Start a container +***************** + +.. http:post:: /containers/(id)/start + + Start the container ``id`` + + **Example request**: + + .. sourcecode:: http + + POST /containers/e90e34656806/start HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + + :statuscode 200: no error + :statuscode 404: no such container + :statuscode 500: server error + + +Stop a contaier +*************** + +.. http:post:: /containers/(id)/stop + + Stop the container ``id`` + + **Example request**: + + .. sourcecode:: http + + POST /containers/e90e34656806/stop?t=5 HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 204 OK + + :query t: number of seconds to wait before killing the container + :statuscode 204: no error + :statuscode 404: no such container + :statuscode 500: server error + + +Restart a container +******************* + +.. http:post:: /containers/(id)/restart + + Restart the container ``id`` + + **Example request**: + + .. sourcecode:: http + + POST /containers/e90e34656806/restart?t=5 HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 204 OK + + :query t: number of seconds to wait before killing the container + :statuscode 204: no error + :statuscode 404: no such container + :statuscode 500: server error + + +Kill a container +**************** + +.. http:post:: /containers/(id)/kill + + Kill the container ``id`` + + **Example request**: + + .. sourcecode:: http + + POST /containers/e90e34656806/kill HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 204 OK + + :statuscode 204: no error + :statuscode 404: no such container + :statuscode 500: server error + + +Attach to a container +********************* + +.. http:post:: /containers/(id)/attach + + Stop the container ``id`` + + **Example request**: + + .. sourcecode:: http + + POST /containers/16253994b7c4/attach?logs=1&stream=0&stdout=1 HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/vnd.docker.raw-stream + + {{ STREAM }} + + :query logs: 1/True/true or 0/False/false, return logs. Default false + :query stream: 1/True/true or 0/False/false, return stream. Default false + :query stdin: 1/True/true or 0/False/false, if stream=true, attach to stdin. Default false + :query stdout: 1/True/true or 0/False/false, if logs=true, return stdout log, if stream=true, attach to stdout. Default false + :query stderr: 1/True/true or 0/False/false, if logs=true, return stderr log, if stream=true, attach to stderr. Default false + :statuscode 200: no error + :statuscode 400: bad parameter + :statuscode 404: no such container + :statuscode 500: server error + + +Wait a container +**************** + +.. http:post:: /containers/(id)/wait + + Block until container ``id`` stops, then returns the exit code + + **Example request**: + + .. sourcecode:: http + + POST /containers/16253994b7c4/wait HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + {"StatusCode":0} + + :statuscode 200: no error + :statuscode 404: no such container + :statuscode 500: server error + + +Remove a container +******************* + +.. http:delete:: /containers/(id) + + Remove the container ``id`` from the filesystem + + **Example request**: + + .. sourcecode:: http + + DELETE /containers/16253994b7c4?v=1 HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 204 OK + + :query v: 1/True/true or 0/False/false, Remove the volumes associated to the container. Default false + :statuscode 204: no error + :statuscode 400: bad parameter + :statuscode 404: no such container + :statuscode 500: server error + + +2.2 Images +---------- + +List Images +*********** + +.. http:get:: /images/(format) + + List images ``format`` could be json or viz (json default) + + **Example request**: + + .. sourcecode:: http + + GET /images/json?all=0 HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + [ + { + "Repository":"base", + "Tag":"ubuntu-12.10", + "Id":"b750fe79269d", + "Created":1364102658 + }, + { + "Repository":"base", + "Tag":"ubuntu-quantal", + "Id":"b750fe79269d", + "Created":1364102658 + } + ] + + + **Example request**: + + .. sourcecode:: http + + GET /images/viz HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: text/plain + + digraph docker { + "d82cbacda43a" -> "074be284591f" + "1496068ca813" -> "08306dc45919" + "08306dc45919" -> "0e7893146ac2" + "b750fe79269d" -> "1496068ca813" + base -> "27cf78414709" [style=invis] + "f71189fff3de" -> "9a33b36209ed" + "27cf78414709" -> "b750fe79269d" + "0e7893146ac2" -> "d6434d954665" + "d6434d954665" -> "d82cbacda43a" + base -> "e9aa60c60128" [style=invis] + "074be284591f" -> "f71189fff3de" + "b750fe79269d" [label="b750fe79269d\nbase",shape=box,fillcolor="paleturquoise",style="filled,rounded"]; + "e9aa60c60128" [label="e9aa60c60128\nbase2",shape=box,fillcolor="paleturquoise",style="filled,rounded"]; + "9a33b36209ed" [label="9a33b36209ed\ntest",shape=box,fillcolor="paleturquoise",style="filled,rounded"]; + base [style=invisible] + } + + :query all: 1/True/true or 0/False/false, Show all containers. Only running containers are shown by default + :statuscode 200: no error + :statuscode 400: bad parameter + :statuscode 500: server error + + +Create an image +*************** + +.. http:post:: /images/create + + Create an image, either by pull it from the registry or by importing it + + **Example request**: + + .. sourcecode:: http + + POST /images/create?fromImage=base HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/vnd.docker.raw-stream + + {{ STREAM }} + + :query fromImage: name of the image to pull + :query fromSrc: source to import, - means stdin + :query repo: repository + :query tag: tag + :query registry: the registry to pull from + :statuscode 200: no error + :statuscode 500: server error + + +Insert a file in a image +************************ + +.. http:post:: /images/(name)/insert + + Insert a file from ``url`` in the image ``name`` at ``path`` + + **Example request**: + + .. sourcecode:: http + + POST /images/test/insert?path=/usr&url=myurl HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + + {{ STREAM }} + + :statuscode 200: no error + :statuscode 500: server error + + +Inspect an image +**************** + +.. http:get:: /images/(name)/json + + Return low-level information on the image ``name`` + + **Example request**: + + .. sourcecode:: http + + GET /images/base/json HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + { + "id":"b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", + "parent":"27cf784147099545", + "created":"2013-03-23T22:24:18.818426-07:00", + "container":"3d67245a8d72ecf13f33dffac9f79dcdf70f75acb84d308770391510e0c23ad0", + "container_config": + { + "Hostname":"", + "User":"", + "Memory":0, + "MemorySwap":0, + "AttachStdin":false, + "AttachStdout":false, + "AttachStderr":false, + "PortSpecs":null, + "Tty":true, + "OpenStdin":true, + "StdinOnce":false, + "Env":null, + "Cmd": ["/bin/bash"] + ,"Dns":null, + "Image":"base", + "Volumes":null, + "VolumesFrom":"" + } + } + + :statuscode 200: no error + :statuscode 404: no such image + :statuscode 500: server error + + +Get the history of an image +*************************** + +.. http:get:: /images/(name)/history + + Return the history of the image ``name`` + + **Example request**: + + .. sourcecode:: http + + GET /images/base/history HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + [ + { + "Id":"b750fe79269d", + "Created":1364102658, + "CreatedBy":"/bin/bash" + }, + { + "Id":"27cf78414709", + "Created":1364068391, + "CreatedBy":"" + } + ] + + :statuscode 200: no error + :statuscode 404: no such image + :statuscode 500: server error + + +Push an image on the registry +***************************** + +.. http:post:: /images/(name)/push + + Push the image ``name`` on the registry + + **Example request**: + + .. sourcecode:: http + + POST /images/test/push HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/vnd.docker.raw-stream + + {{ STREAM }} + + :query registry: the registry you wan to push, optional + :statuscode 200: no error + :statuscode 404: no such image + :statuscode 500: server error + + +Tag an image into a repository +****************************** + +.. http:post:: /images/(name)/tag + + Tag the image ``name`` into a repository + + **Example request**: + + .. sourcecode:: http + + POST /images/test/tag?repo=myrepo&force=0 HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + + :query repo: The repository to tag in + :query force: 1/True/true or 0/False/false, default false + :statuscode 200: no error + :statuscode 400: bad parameter + :statuscode 404: no such image + :statuscode 500: server error + + +Remove an image +*************** + +.. http:delete:: /images/(name) + + Remove the image ``name`` from the filesystem + + **Example request**: + + .. sourcecode:: http + + DELETE /images/test HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 204 OK + + :statuscode 204: no error + :statuscode 404: no such image + :statuscode 500: server error + + +Search images +************* + +.. http:get:: /images/search + + Search for an image in the docker index + + **Example request**: + + .. sourcecode:: http + + GET /images/search?term=sshd HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + [ + { + "Name":"cespare/sshd", + "Description":"" + }, + { + "Name":"johnfuller/sshd", + "Description":"" + }, + { + "Name":"dhrp/mongodb-sshd", + "Description":"" + } + ] + + :query term: term to search + :statuscode 200: no error + :statuscode 500: server error + + +2.3 Misc +-------- + +Build an image from Dockerfile via stdin +**************************************** + +.. http:post:: /build + + Build an image from Dockerfile via stdin + + **Example request**: + + .. sourcecode:: http + + POST /build HTTP/1.1 + + {{ STREAM }} + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + + {{ STREAM }} + + :statuscode 200: no error + :statuscode 500: server error + + +Get default username and email +****************************** + +.. http:get:: /auth + + Get the default username and email + + **Example request**: + + .. sourcecode:: http + + GET /auth HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + { + "username":"hannibal", + "email":"hannibal@a-team.com" + } + + :statuscode 200: no error + :statuscode 500: server error + + +Set auth configuration +********************** + +.. http:post:: /auth + + Get the default username and email + + **Example request**: + + .. sourcecode:: http + + POST /auth HTTP/1.1 + Content-Type: application/json + + { + "username":"hannibal", + "password:"xxxx", + "email":"hannibal@a-team.com" + } + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + + :statuscode 200: no error + :statuscode 204: no error + :statuscode 500: server error + + +Display system-wide information +******************************* + +.. http:get:: /info + + Display system-wide information + + **Example request**: + + .. sourcecode:: http + + GET /info HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + { + "Containers":11, + "Version":"0.2.2", + "Images":16, + "GoVersion":"go1.0.3", + "Debug":false + } + + :statuscode 200: no error + :statuscode 500: server error + + +Show the docker version information +*********************************** + +.. http:get:: /version + + Show the docker version information + + **Example request**: + + .. sourcecode:: http + + GET /version HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + { + "Version":"0.2.2", + "GitCommit":"5a2a5cc+CHANGES", + "MemoryLimit":true, + "SwapLimit":false + } + + :statuscode 200: no error + :statuscode 500: server error + + +Create a new image from a container's changes +********************************************* + +.. http:post:: /commit + + Create a new image from a container's changes + + **Example request**: + + .. sourcecode:: http + + POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 201 OK + Content-Type: application/vnd.docker.raw-stream + + {"Id":"596069db4bf5"} + + :query container: source container + :query repo: repository + :query tag: tag + :query m: commit message + :query author: author (eg. "John Hannibal Smith ") + :query run: config automatically applied when the image is run. (ex: {"Cmd": ["cat", "/world"], "PortSpecs":["22"]}) + :statuscode 201: no error + :statuscode 404: no such container + :statuscode 500: server error + + +3. Going further +================ + +3.1 Inside 'docker run' +----------------------- + +Here are the steps of 'docker run' : + +* Create the container +* If the status code is 404, it means the image doesn't exists: + * Try to pull it + * Then retry to create the container +* Start the container +* If you are not in detached mode: + * Attach to the container, using logs=1 (to have stdout and stderr from the container's start) and stream=1 +* If in detached mode or only stdin is attached: + * Display the container's id + + +3.2 Hijacking +------------- + +In this first version of the API, some of the endpoints, like /attach, /pull or /push uses hijacking to transport stdin, +stdout and stderr on the same socket. This might change in the future. diff --git a/docs/sources/api/index.rst b/docs/sources/api/index.rst new file mode 100644 index 000000000..4d8ff3fe6 --- /dev/null +++ b/docs/sources/api/index.rst @@ -0,0 +1,17 @@ +:title: API Documentation +:description: docker documentation +:keywords: docker, ipa, documentation + +API's +============= + +This following : + +.. toctree:: + :maxdepth: 3 + + registry_api + index_search_api + docker_remote_api + + diff --git a/docs/sources/index/search.rst b/docs/sources/api/index_search_api.rst similarity index 87% rename from docs/sources/index/search.rst rename to docs/sources/api/index_search_api.rst index 498295fa2..e2f8edc49 100644 --- a/docs/sources/index/search.rst +++ b/docs/sources/api/index_search_api.rst @@ -1,3 +1,8 @@ +:title: Docker Index documentation +:description: Documentation for docker Index +:keywords: docker, index, api + + ======================= Docker Index Search API ======================= @@ -32,7 +37,7 @@ Search {"name": "base2", "description": "A base ubuntu64 image..."}, ] } - + :query q: what you want to search for :statuscode 200: no error :statuscode 500: server error \ No newline at end of file diff --git a/docs/sources/registry/api.rst b/docs/sources/api/registry_api.rst similarity index 96% rename from docs/sources/registry/api.rst rename to docs/sources/api/registry_api.rst index ec2591af4..f33ca187b 100644 --- a/docs/sources/registry/api.rst +++ b/docs/sources/api/registry_api.rst @@ -1,3 +1,8 @@ +:title: Registry Documentation +:description: Documentation for docker Registry and Registry API +:keywords: docker, registry, api, index + + =================== Docker Registry API =================== @@ -44,7 +49,7 @@ We expect that there will be multiple registries out there. To help to grasp the .. 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. + 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:: @@ -80,7 +85,7 @@ On top of being a runtime for LXC, Docker is the Registry client. It supports: 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. +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. @@ -107,7 +112,7 @@ API (pulling repository foo/bar): Jsonified checksums (see part 4.4.1) 3. (Docker -> Registry) GET /v1/repositories/foo/bar/tags/latest - **Headers**: + **Headers**: Authorization: Token signature=123abc,repository=”foo/bar”,access=write 4. (Registry -> Index) GET /v1/repositories/foo/bar/images @@ -121,10 +126,10 @@ API (pulling repository foo/bar): **Action**: ( Lookup token see if they have access to pull.) - If good: + If good: HTTP 200 OK Index will invalidate the token - If bad: + If bad: HTTP 401 Unauthorized 5. (Docker -> Registry) GET /v1/images/928374982374/ancestry @@ -186,9 +191,9 @@ API (pushing repos foo/bar): **Headers**: Authorization: Token signature=123abc,repository=”foo/bar”,access=write **Action**:: - - Index: + - Index: will invalidate the token. - - Registry: + - Registry: grants a session (if token is approved) and fetches the images id 5. (Docker -> Registry) PUT /v1/images/98765432_parent/json @@ -223,7 +228,7 @@ API (pushing repos foo/bar): **Body**: (The image, id’s, tags and checksums) - [{“id”: “9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”, + [{“id”: “9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”, “checksum”: “b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087”}] **Return** HTTP 204 @@ -240,8 +245,8 @@ API (pushing repos foo/bar): 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// + - 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 @@ -296,7 +301,7 @@ POST /v1/users {"email": "sam@dotcloud.com", "password": "toto42", "username": "foobar"'} **Validation**: - - **username** : min 4 character, max 30 characters, all lowercase no special characters. + - **username** : min 4 character, max 30 characters, all lowercase no special characters. - **password**: min 5 characters **Valid**: return HTTP 200 @@ -387,7 +392,7 @@ PUT /v1/repositories///images **Body**: [ {“id”: “9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”, “checksum”: “sha256:b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087”} ] - + **Return** 204 5. Chaining Registries diff --git a/docs/sources/commandline/cli.rst b/docs/sources/commandline/cli.rst index 2657b9177..1a341d3e5 100644 --- a/docs/sources/commandline/cli.rst +++ b/docs/sources/commandline/cli.rst @@ -4,7 +4,7 @@ .. _cli: -Command Line Interface +Overview ====================== Docker Usage @@ -24,9 +24,10 @@ Available Commands ~~~~~~~~~~~~~~~~~~ .. toctree:: - :maxdepth: 1 + :maxdepth: 2 command/attach + command/build command/commit command/diff command/export @@ -46,6 +47,7 @@ Available Commands command/rm command/rmi command/run + command/search command/start command/stop command/tag diff --git a/docs/sources/commandline/command/attach.rst b/docs/sources/commandline/command/attach.rst index ac9a84c0c..4c4c189d8 100644 --- a/docs/sources/commandline/command/attach.rst +++ b/docs/sources/commandline/command/attach.rst @@ -1,3 +1,7 @@ +:title: Attach Command +:description: Attach to a running container +:keywords: attach, container, docker, documentation + =========================================== ``attach`` -- Attach to a running container =========================================== diff --git a/docs/sources/commandline/command/build.rst b/docs/sources/commandline/command/build.rst new file mode 100644 index 000000000..a8d2093a5 --- /dev/null +++ b/docs/sources/commandline/command/build.rst @@ -0,0 +1,13 @@ +:title: Build Command +:description: Build a new image from the Dockerfile passed via stdin +:keywords: build, docker, container, documentation + +======================================================== +``build`` -- Build a container from Dockerfile via stdin +======================================================== + +:: + + Usage: docker build - + Example: cat Dockerfile | docker build - + Build a new image from the Dockerfile passed via stdin diff --git a/docs/sources/commandline/command/commit.rst b/docs/sources/commandline/command/commit.rst index c73f8d189..92f320566 100644 --- a/docs/sources/commandline/command/commit.rst +++ b/docs/sources/commandline/command/commit.rst @@ -1,3 +1,7 @@ +:title: Commit Command +:description: Create a new image from a container's changes +:keywords: commit, docker, container, documentation + =========================================================== ``commit`` -- Create a new image from a container's changes =========================================================== @@ -16,6 +20,7 @@ Full -run example:: {"Hostname": "", "User": "", + "CpuShares": 0, "Memory": 0, "MemorySwap": 0, "PortSpecs": ["22", "80", "443"], diff --git a/docs/sources/commandline/command/diff.rst b/docs/sources/commandline/command/diff.rst index 301da6c49..2901a7f21 100644 --- a/docs/sources/commandline/command/diff.rst +++ b/docs/sources/commandline/command/diff.rst @@ -1,3 +1,7 @@ +:title: Diff Command +:description: Inspect changes on a container's filesystem +:keywords: diff, docker, container, documentation + ======================================================= ``diff`` -- Inspect changes on a container's filesystem ======================================================= diff --git a/docs/sources/commandline/command/export.rst b/docs/sources/commandline/command/export.rst index 88ecd2fd5..9d7e6b369 100644 --- a/docs/sources/commandline/command/export.rst +++ b/docs/sources/commandline/command/export.rst @@ -1,3 +1,7 @@ +:title: Export Command +:description: Export the contents of a filesystem as a tar archive +:keywords: export, docker, container, documentation + ================================================================= ``export`` -- Stream the contents of a container as a tar archive ================================================================= diff --git a/docs/sources/commandline/command/history.rst b/docs/sources/commandline/command/history.rst index 92fad3b48..2f9d3f281 100644 --- a/docs/sources/commandline/command/history.rst +++ b/docs/sources/commandline/command/history.rst @@ -1,3 +1,7 @@ +:title: History Command +:description: Show the history of an image +:keywords: history, docker, container, documentation + =========================================== ``history`` -- Show the history of an image =========================================== diff --git a/docs/sources/commandline/command/images.rst b/docs/sources/commandline/command/images.rst index c21762db0..497bda6e1 100644 --- a/docs/sources/commandline/command/images.rst +++ b/docs/sources/commandline/command/images.rst @@ -1,3 +1,7 @@ +:title: Images Command +:description: List images +:keywords: images, docker, container, documentation + ========================= ``images`` -- List images ========================= @@ -10,3 +14,13 @@ -a=false: show all images -q=false: only show numeric IDs + -viz=false: output in graphviz format + +Displaying images visually +-------------------------- + +:: + + docker images -viz | dot -Tpng -o docker.png + +.. image:: images/docker_images.gif diff --git a/docs/sources/commandline/command/images/docker_images.gif b/docs/sources/commandline/command/images/docker_images.gif new file mode 100644 index 000000000..5894ca270 Binary files /dev/null and b/docs/sources/commandline/command/images/docker_images.gif differ diff --git a/docs/sources/commandline/command/import.rst b/docs/sources/commandline/command/import.rst index 5fe90764b..34a7138e0 100644 --- a/docs/sources/commandline/command/import.rst +++ b/docs/sources/commandline/command/import.rst @@ -1,3 +1,7 @@ +:title: Import Command +:description: Create a new filesystem image from the contents of a tarball +:keywords: import, tarball, docker, url, documentation + ========================================================================== ``import`` -- Create a new filesystem image from the contents of a tarball ========================================================================== diff --git a/docs/sources/commandline/command/info.rst b/docs/sources/commandline/command/info.rst index 10697dba1..6df3486c5 100644 --- a/docs/sources/commandline/command/info.rst +++ b/docs/sources/commandline/command/info.rst @@ -1,3 +1,7 @@ +:title: Info Command +:description: Display system-wide information. +:keywords: info, docker, information, documentation + =========================================== ``info`` -- Display system-wide information =========================================== diff --git a/docs/sources/commandline/command/inspect.rst b/docs/sources/commandline/command/inspect.rst index 34365d1f2..90dbe959e 100644 --- a/docs/sources/commandline/command/inspect.rst +++ b/docs/sources/commandline/command/inspect.rst @@ -1,3 +1,7 @@ +:title: Inspect Command +:description: Return low-level information on a container +:keywords: inspect, container, docker, documentation + ========================================================== ``inspect`` -- Return low-level information on a container ========================================================== diff --git a/docs/sources/commandline/command/kill.rst b/docs/sources/commandline/command/kill.rst index cbd019e1a..f53d3883b 100644 --- a/docs/sources/commandline/command/kill.rst +++ b/docs/sources/commandline/command/kill.rst @@ -1,3 +1,7 @@ +:title: Kill Command +:description: Kill a running container +:keywords: kill, container, docker, documentation + ==================================== ``kill`` -- Kill a running container ==================================== diff --git a/docs/sources/commandline/command/login.rst b/docs/sources/commandline/command/login.rst index b064b4014..bab4fa34e 100644 --- a/docs/sources/commandline/command/login.rst +++ b/docs/sources/commandline/command/login.rst @@ -1,3 +1,7 @@ +:title: Login Command +:description: Register or Login to the docker registry server +:keywords: login, docker, documentation + ============================================================ ``login`` -- Register or Login to the docker registry server ============================================================ diff --git a/docs/sources/commandline/command/logs.rst b/docs/sources/commandline/command/logs.rst index 87f3f95b6..a3423f6e0 100644 --- a/docs/sources/commandline/command/logs.rst +++ b/docs/sources/commandline/command/logs.rst @@ -1,3 +1,7 @@ +:title: Logs Command +:description: Fetch the logs of a container +:keywords: logs, container, docker, documentation + ========================================= ``logs`` -- Fetch the logs of a container ========================================= diff --git a/docs/sources/commandline/command/port.rst b/docs/sources/commandline/command/port.rst index 4fb6d7f81..8d59fedab 100644 --- a/docs/sources/commandline/command/port.rst +++ b/docs/sources/commandline/command/port.rst @@ -1,3 +1,7 @@ +:title: Port Command +:description: Lookup the public-facing port which is NAT-ed to PRIVATE_PORT +:keywords: port, docker, container, documentation + ========================================================================= ``port`` -- Lookup the public-facing port which is NAT-ed to PRIVATE_PORT ========================================================================= diff --git a/docs/sources/commandline/command/ps.rst b/docs/sources/commandline/command/ps.rst index f73177918..597dbd9ae 100644 --- a/docs/sources/commandline/command/ps.rst +++ b/docs/sources/commandline/command/ps.rst @@ -1,3 +1,7 @@ +:title: Ps Command +:description: List containers +:keywords: ps, docker, documentation, container + ========================= ``ps`` -- List containers ========================= diff --git a/docs/sources/commandline/command/pull.rst b/docs/sources/commandline/command/pull.rst index 1c417ddcd..4348f28d0 100644 --- a/docs/sources/commandline/command/pull.rst +++ b/docs/sources/commandline/command/pull.rst @@ -1,3 +1,7 @@ +:title: Pull Command +:description: Pull an image or a repository from the registry +:keywords: pull, image, repo, repository, documentation, docker + ========================================================================= ``pull`` -- Pull an image or a repository from the docker registry server ========================================================================= diff --git a/docs/sources/commandline/command/push.rst b/docs/sources/commandline/command/push.rst index a42296c71..9304f9acc 100644 --- a/docs/sources/commandline/command/push.rst +++ b/docs/sources/commandline/command/push.rst @@ -1,3 +1,7 @@ +:title: Push Command +:description: Push an image or a repository to the registry +:keywords: push, docker, image, repository, documentation, repo + ======================================================================= ``push`` -- Push an image or a repository to the docker registry server ======================================================================= diff --git a/docs/sources/commandline/command/restart.rst b/docs/sources/commandline/command/restart.rst index 24bba5a5a..dfc0dfea6 100644 --- a/docs/sources/commandline/command/restart.rst +++ b/docs/sources/commandline/command/restart.rst @@ -1,3 +1,7 @@ +:title: Restart Command +:description: Restart a running container +:keywords: restart, container, docker, documentation + ========================================== ``restart`` -- Restart a running container ========================================== diff --git a/docs/sources/commandline/command/rm.rst b/docs/sources/commandline/command/rm.rst index f6d6893bf..dc6d91632 100644 --- a/docs/sources/commandline/command/rm.rst +++ b/docs/sources/commandline/command/rm.rst @@ -1,3 +1,7 @@ +:title: Rm Command +:description: Remove a container +:keywords: remove, container, docker, documentation, rm + ============================ ``rm`` -- Remove a container ============================ diff --git a/docs/sources/commandline/command/rmi.rst b/docs/sources/commandline/command/rmi.rst index 3761196f2..a0131886d 100644 --- a/docs/sources/commandline/command/rmi.rst +++ b/docs/sources/commandline/command/rmi.rst @@ -1,3 +1,7 @@ +:title: Rmi Command +:description: Remove an image +:keywords: rmi, remove, image, docker, documentation + ========================== ``rmi`` -- Remove an image ========================== diff --git a/docs/sources/commandline/command/run.rst b/docs/sources/commandline/command/run.rst index d5e571b41..f9bd568b6 100644 --- a/docs/sources/commandline/command/run.rst +++ b/docs/sources/commandline/command/run.rst @@ -1,3 +1,7 @@ +:title: Run Command +:description: Run a command in a new container +:keywords: run, container, docker, documentation + =========================================== ``run`` -- Run a command in a new container =========================================== @@ -9,6 +13,7 @@ Run a command in a new container -a=map[]: Attach to stdin, stdout or stderr. + -c=0: CPU shares (relative weight) -d=false: Detached mode: leave the container running in the background -e=[]: Set environment variables -h="": Container host name diff --git a/docs/sources/commandline/command/search.rst b/docs/sources/commandline/command/search.rst new file mode 100644 index 000000000..2f07e20c3 --- /dev/null +++ b/docs/sources/commandline/command/search.rst @@ -0,0 +1,14 @@ +:title: Search Command +:description: Searches for the TERM parameter on the Docker index and prints out a list of repositories that match. +:keywords: search, docker, image, documentation + +=================================================================== +``search`` -- Search for an image in the docker index +=================================================================== + +:: + + Usage: docker search TERM + + Searches for the TERM parameter on the Docker index and prints out a list of repositories + that match. diff --git a/docs/sources/commandline/command/start.rst b/docs/sources/commandline/command/start.rst index df415ca3d..b70ad21cf 100644 --- a/docs/sources/commandline/command/start.rst +++ b/docs/sources/commandline/command/start.rst @@ -1,3 +1,7 @@ +:title: Start Command +:description: Start a stopped container +:keywords: start, docker, container, documentation + ====================================== ``start`` -- Start a stopped container ====================================== diff --git a/docs/sources/commandline/command/stop.rst b/docs/sources/commandline/command/stop.rst index df6d66ccf..3d571563e 100644 --- a/docs/sources/commandline/command/stop.rst +++ b/docs/sources/commandline/command/stop.rst @@ -1,3 +1,7 @@ +:title: Stop Command +:description: Stop a running container +:keywords: stop, container, docker, documentation + ==================================== ``stop`` -- Stop a running container ==================================== diff --git a/docs/sources/commandline/command/tag.rst b/docs/sources/commandline/command/tag.rst index 59647355e..a9e831aae 100644 --- a/docs/sources/commandline/command/tag.rst +++ b/docs/sources/commandline/command/tag.rst @@ -1,3 +1,7 @@ +:title: Tag Command +:description: Tag an image into a repository +:keywords: tag, docker, image, repository, documentation, repo + ========================================= ``tag`` -- Tag an image into a repository ========================================= diff --git a/docs/sources/commandline/command/version.rst b/docs/sources/commandline/command/version.rst index eedf02f2d..fb3d3b450 100644 --- a/docs/sources/commandline/command/version.rst +++ b/docs/sources/commandline/command/version.rst @@ -1,3 +1,7 @@ +:title: Version Command +:description: +:keywords: version, docker, documentation + ================================================== ``version`` -- Show the docker version information ================================================== diff --git a/docs/sources/commandline/command/wait.rst b/docs/sources/commandline/command/wait.rst index 2959bf880..23bd54513 100644 --- a/docs/sources/commandline/command/wait.rst +++ b/docs/sources/commandline/command/wait.rst @@ -1,3 +1,7 @@ +:title: Wait Command +:description: Block until a container stops, then print its exit code. +:keywords: wait, docker, container, documentation + =================================================================== ``wait`` -- Block until a container stops, then print its exit code =================================================================== diff --git a/docs/sources/commandline/index.rst b/docs/sources/commandline/index.rst index d19d39ab6..f1a3e2da4 100644 --- a/docs/sources/commandline/index.rst +++ b/docs/sources/commandline/index.rst @@ -1,6 +1,6 @@ -:title: docker documentation +:title: Commands :description: -- todo: change me -:keywords: todo: change me +:keywords: todo, commands, command line, help, docker, documentation Commands @@ -9,8 +9,33 @@ Commands Contents: .. toctree:: - :maxdepth: 3 + :maxdepth: 1 - basics - workingwithrepository - cli \ No newline at end of file + cli + attach + build + commit + diff + export + history + images + import + info + inspect + kill + login + logs + port + ps + pull + push + restart + rm + rmi + run + search + start + stop + tag + version + wait \ No newline at end of file diff --git a/docs/sources/commandline/workingwithrepository.rst b/docs/sources/commandline/workingwithrepository.rst deleted file mode 100644 index ae749fdb0..000000000 --- a/docs/sources/commandline/workingwithrepository.rst +++ /dev/null @@ -1,42 +0,0 @@ -.. _working_with_the_repository: - -Working with the repository -============================ - -Connecting to the repository ----------------------------- - -You create a user on the central docker repository by running - -.. code-block:: bash - - docker login - - -If your username does not exist it will prompt you to also enter a password and your e-mail address. It will then -automatically log you in. - - -Committing a container to a named image ---------------------------------------- - -In order to commit to the repository it is required to have committed your container to an image with your namespace. - -.. code-block:: bash - - # for example docker commit $CONTAINER_ID dhrp/kickassapp - docker commit / - - -Pushing a container to the repository ------------------------------------------ - -In order to push an image to the repository you need to have committed your container to a named image (see above) - -Now you can commit this image to the repository - -.. code-block:: bash - - # for example docker push dhrp/kickassapp - docker push - diff --git a/docs/sources/concepts/buildingblocks.rst b/docs/sources/concepts/buildingblocks.rst index 154ef00f4..5f752ea47 100644 --- a/docs/sources/concepts/buildingblocks.rst +++ b/docs/sources/concepts/buildingblocks.rst @@ -1,4 +1,4 @@ -:title: Building blocks +:title: Building Blocks :description: An introduction to docker and standard containers? :keywords: containers, lxc, concepts, explanation diff --git a/docs/sources/concepts/containers.rst b/docs/sources/concepts/containers.rst index f432c4363..e08c3654c 100644 --- a/docs/sources/concepts/containers.rst +++ b/docs/sources/concepts/containers.rst @@ -1,128 +1,8 @@ :title: Introduction :description: An introduction to docker and standard containers? -:keywords: containers, lxc, concepts, explanation +:keywords: containers, lxc, concepts, explanation, docker, documentation :note: This version of the introduction is temporary, just to make sure we don't break the links from the website when the documentation is updated - -Introduction -============ - -Docker - The 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. - - -- **Heterogeneous payloads** Any combination of binaries, libraries, configuration files, scripts, virtualenvs, jars, gems, tarballs, you name it. No more juggling between domain-specific tools. Docker can deploy and run them all. -- **Any server** Docker can run on any x64 machine with a modern linux kernel - whether it's a laptop, a bare metal server or a VM. This makes it perfect for multi-cloud deployments. -- **Isolation** docker isolates processes from each other and from the underlying host, using lightweight containers. -- **Repeatability** Because containers are isolated in their own filesystem, they behave the same regardless of where, when, and alongside what they run. - - - -What is a Standard Container? ------------------------------ - -Docker defines a unit of software delivery called a Standard Container. The goal of a Standard Container is to encapsulate a software component and all its dependencies in -a format that is self-describing and portable, so that any compliant runtime can run it without extra dependency, regardless of the underlying machine and the contents of the container. - -The spec for Standard Containers is currently work in progress, but it is very straightforward. It mostly defines 1) an image format, 2) a set of standard operations, and 3) an execution environment. - -A great analogy for this is the shipping container. Just like Standard Containers are a fundamental unit of software delivery, shipping containers (http://bricks.argz.com/ins/7823-1/12) are a fundamental unit of physical delivery. - -Standard operations -~~~~~~~~~~~~~~~~~~~ - -Just like shipping containers, Standard Containers define a set of STANDARD OPERATIONS. Shipping containers can be lifted, stacked, locked, loaded, unloaded and labelled. Similarly, standard containers can be started, stopped, copied, snapshotted, downloaded, uploaded and tagged. - - -Content-agnostic -~~~~~~~~~~~~~~~~~~~ - -Just like shipping containers, Standard Containers are CONTENT-AGNOSTIC: all standard operations have the same effect regardless of the contents. A shipping container will be stacked in exactly the same way whether it contains Vietnamese powder coffee or spare Maserati parts. Similarly, Standard Containers are started or uploaded in the same way whether they contain a postgres database, a php application with its dependencies and application server, or Java build artifacts. - - -Infrastructure-agnostic -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Both types of containers are INFRASTRUCTURE-AGNOSTIC: they can be transported to thousands of facilities around the world, and manipulated by a wide variety of equipment. A shipping container can be packed in a factory in Ukraine, transported by truck to the nearest routing center, stacked onto a train, loaded into a German boat by an Australian-built crane, stored in a warehouse at a US facility, etc. Similarly, a standard container can be bundled on my laptop, uploaded to S3, downloaded, run and snapshotted by a build server at Equinix in Virginia, uploaded to 10 staging servers in a home-made Openstack cluster, then sent to 30 production instances across 3 EC2 regions. - - -Designed for automation -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Because they offer the same standard operations regardless of content and infrastructure, Standard Containers, just like their physical counterpart, are extremely well-suited for automation. In fact, you could say automation is their secret weapon. - -Many things that once required time-consuming and error-prone human effort can now be programmed. Before shipping containers, a bag of powder coffee was hauled, dragged, dropped, rolled and stacked by 10 different people in 10 different locations by the time it reached its destination. 1 out of 50 disappeared. 1 out of 20 was damaged. The process was slow, inefficient and cost a fortune - and was entirely different depending on the facility and the type of goods. - -Similarly, before Standard Containers, by the time a software component ran in production, it had been individually built, configured, bundled, documented, patched, vendored, templated, tweaked and instrumented by 10 different people on 10 different computers. Builds failed, libraries conflicted, mirrors crashed, post-it notes were lost, logs were misplaced, cluster updates were half-broken. The process was slow, inefficient and cost a fortune - and was entirely different depending on the language and infrastructure provider. - - -Industrial-grade delivery -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -There are 17 million shipping containers in existence, packed with every physical good imaginable. Every single one of them can be loaded on the same boats, by the same cranes, in the same facilities, and sent anywhere in the World with incredible efficiency. It is embarrassing to think that a 30 ton shipment of coffee can safely travel half-way across the World in *less time* than it takes a software team to deliver its code from one datacenter to another sitting 10 miles away. - -With Standard Containers we can put an end to that embarrassment, by making INDUSTRIAL-GRADE DELIVERY of software a reality. - - -Standard Container Specification -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -(TODO) - -Image format -~~~~~~~~~~~~ - -Standard operations -~~~~~~~~~~~~~~~~~~~ - -- Copy -- Run -- Stop -- Wait -- Commit -- Attach standard streams -- List filesystem changes -- ... - -Execution environment -~~~~~~~~~~~~~~~~~~~~~ - -Root filesystem -^^^^^^^^^^^^^^^ - -Environment variables -^^^^^^^^^^^^^^^^^^^^^ - -Process arguments -^^^^^^^^^^^^^^^^^ - -Networking -^^^^^^^^^^ - -Process namespacing -^^^^^^^^^^^^^^^^^^^ - -Resource limits -^^^^^^^^^^^^^^^ - -Process monitoring -^^^^^^^^^^^^^^^^^^ - -Logging -^^^^^^^ - -Signals -^^^^^^^ - -Pseudo-terminal allocation -^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Security -^^^^^^^^ - +This document has been moved to :ref:`introduction`, please update your bookmarks. \ No newline at end of file diff --git a/docs/sources/static_files/lego_docker.jpg b/docs/sources/concepts/images/lego_docker.jpg similarity index 100% rename from docs/sources/static_files/lego_docker.jpg rename to docs/sources/concepts/images/lego_docker.jpg diff --git a/docs/sources/concepts/index.rst b/docs/sources/concepts/index.rst index 915652499..ba1f9f471 100644 --- a/docs/sources/concepts/index.rst +++ b/docs/sources/concepts/index.rst @@ -1,6 +1,6 @@ -:title: docker documentation +:title: Concepts :description: -- todo: change me -:keywords: todo: change me +:keywords: concepts, documentation, docker, containers @@ -12,6 +12,6 @@ Contents: .. toctree:: :maxdepth: 1 - introduction + ../index buildingblocks diff --git a/docs/sources/concepts/introduction.rst b/docs/sources/concepts/introduction.rst index 46698b401..fcdd37a79 100644 --- a/docs/sources/concepts/introduction.rst +++ b/docs/sources/concepts/introduction.rst @@ -2,8 +2,6 @@ :description: An introduction to docker and standard containers? :keywords: containers, lxc, concepts, explanation - - Introduction ============ @@ -20,7 +18,7 @@ Docker is a great building block for automating distributed systems: large-scale - **Isolation** docker isolates processes from each other and from the underlying host, using lightweight containers. - **Repeatability** Because containers are isolated in their own filesystem, they behave the same regardless of where, when, and alongside what they run. -.. image:: http://www.docker.io/_static/lego_docker.jpg +.. image:: images/lego_docker.jpg What is a Standard Container? diff --git a/docs/sources/conf.py b/docs/sources/conf.py index 4c54d8bb6..d443d3405 100644 --- a/docs/sources/conf.py +++ b/docs/sources/conf.py @@ -41,7 +41,7 @@ html_add_permalinks = None # The master toctree document. -master_doc = 'index' +master_doc = 'toctree' # General information about the project. project = u'Docker' diff --git a/docs/sources/contributing/contributing.rst b/docs/sources/contributing/contributing.rst index 7b2b4da2d..c2bd7c80f 100644 --- a/docs/sources/contributing/contributing.rst +++ b/docs/sources/contributing/contributing.rst @@ -1,3 +1,7 @@ +:title: Contribution Guidelines +:description: Contribution guidelines: create issues, convetions, pull requests +:keywords: contributing, docker, documentation, help, guideline + Contributing to Docker ====================== diff --git a/docs/sources/contributing/devenvironment.rst b/docs/sources/contributing/devenvironment.rst index ea5821b7d..0d202596c 100644 --- a/docs/sources/contributing/devenvironment.rst +++ b/docs/sources/contributing/devenvironment.rst @@ -16,7 +16,7 @@ Instructions that have been verified to work on Ubuntu 12.10, mkdir -p $GOPATH/src/github.com/dotcloud cd $GOPATH/src/github.com/dotcloud - git clone git@github.com:dotcloud/docker.git + git clone git://github.com/dotcloud/docker.git cd docker go get -v github.com/dotcloud/docker/... diff --git a/docs/sources/examples/couchdb_data_volumes.rst b/docs/sources/examples/couchdb_data_volumes.rst index 5b50c73e3..1b1d7ff79 100644 --- a/docs/sources/examples/couchdb_data_volumes.rst +++ b/docs/sources/examples/couchdb_data_volumes.rst @@ -2,10 +2,10 @@ :description: Sharing data between 2 couchdb databases :keywords: docker, example, package installation, networking, couchdb, data volumes -.. _running_redis_service: +.. _running_couchdb_service: -Create a redis service -====================== +Create a CouchDB service +======================== .. include:: example_header.inc diff --git a/docs/sources/examples/python_web_app.rst b/docs/sources/examples/python_web_app.rst index 33caa52c1..3dd25015f 100644 --- a/docs/sources/examples/python_web_app.rst +++ b/docs/sources/examples/python_web_app.rst @@ -40,7 +40,7 @@ We attach to the new container to see what is going on. Ctrl-C to disconnect .. code-block:: bash - BUILD_IMG=$(docker commit $BUILD_JOB _/builds/github.com/hykes/helloflask/master) + BUILD_IMG=$(docker commit $BUILD_JOB _/builds/github.com/shykes/helloflask/master) Save the changed we just made in the container to a new image called "_/builds/github.com/hykes/helloflask/master" and save the image id in the BUILD_IMG variable name. diff --git a/docs/sources/faq.rst b/docs/sources/faq.rst index 51fc00b30..901e51ddb 100644 --- a/docs/sources/faq.rst +++ b/docs/sources/faq.rst @@ -1,3 +1,7 @@ +:title: FAQ +:description: Most frequently asked questions. +:keywords: faq, questions, documentation, docker + FAQ === @@ -15,7 +19,7 @@ Most frequently asked questions. 3. **Does Docker run on Mac OS X or Windows?** - Not at this time, Docker currently only runs on Linux, but you can use VirtualBox to run Docker in a virtual machine on your box, and get the best of both worlds. Check out the MacOSX_ and Windows_ intallation guides. + Not at this time, Docker currently only runs on Linux, but you can use VirtualBox to run Docker in a virtual machine on your box, and get the best of both worlds. Check out the MacOSX_ and Windows_ installation guides. 4. **How do containers compare to virtual machines?** @@ -35,8 +39,8 @@ Most frequently asked questions. * `Ask questions on Stackoverflow`_ * `Join the conversation on Twitter`_ - .. _Windows: ../documentation/installation/windows.html - .. _MacOSX: ../documentation/installation/macos.html + .. _Windows: ../installation/windows/ + .. _MacOSX: ../installation/vagrant/ .. _the repo: http://www.github.com/dotcloud/docker .. _IRC\: docker on freenode: irc://chat.freenode.net#docker .. _Github: http://www.github.com/dotcloud/docker diff --git a/docs/sources/index.rst b/docs/sources/index.rst index e6a1482cc..172f82083 100644 --- a/docs/sources/index.rst +++ b/docs/sources/index.rst @@ -1,23 +1,127 @@ -:title: docker documentation -:description: docker documentation -:keywords: +:title: Introduction +:description: An introduction to docker and standard containers? +:keywords: containers, lxc, concepts, explanation -Documentation -============= +.. _introduction: -This documentation has the following resources: +Introduction +============ -.. toctree:: - :maxdepth: 1 +Docker - The Linux container runtime +------------------------------------ - concepts/index - installation/index - examples/index - contributing/index - commandline/index - registry/index - index/index - faq +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. -.. image:: http://www.docker.io/_static/lego_docker.jpg \ No newline at end of file +- **Heterogeneous payloads** Any combination of binaries, libraries, configuration files, scripts, virtualenvs, jars, gems, tarballs, you name it. No more juggling between domain-specific tools. Docker can deploy and run them all. +- **Any server** Docker can run on any x64 machine with a modern linux kernel - whether it's a laptop, a bare metal server or a VM. This makes it perfect for multi-cloud deployments. +- **Isolation** docker isolates processes from each other and from the underlying host, using lightweight containers. +- **Repeatability** Because containers are isolated in their own filesystem, they behave the same regardless of where, when, and alongside what they run. + +.. image:: concepts/images/lego_docker.jpg + + +What is a Standard Container? +----------------------------- + +Docker defines a unit of software delivery called a Standard Container. The goal of a Standard Container is to encapsulate a software component and all its dependencies in +a format that is self-describing and portable, so that any compliant runtime can run it without extra dependency, regardless of the underlying machine and the contents of the container. + +The spec for Standard Containers is currently work in progress, but it is very straightforward. It mostly defines 1) an image format, 2) a set of standard operations, and 3) an execution environment. + +A great analogy for this is the shipping container. Just like Standard Containers are a fundamental unit of software delivery, shipping containers (http://bricks.argz.com/ins/7823-1/12) are a fundamental unit of physical delivery. + +Standard operations +~~~~~~~~~~~~~~~~~~~ + +Just like shipping containers, Standard Containers define a set of STANDARD OPERATIONS. Shipping containers can be lifted, stacked, locked, loaded, unloaded and labelled. Similarly, standard containers can be started, stopped, copied, snapshotted, downloaded, uploaded and tagged. + + +Content-agnostic +~~~~~~~~~~~~~~~~~~~ + +Just like shipping containers, Standard Containers are CONTENT-AGNOSTIC: all standard operations have the same effect regardless of the contents. A shipping container will be stacked in exactly the same way whether it contains Vietnamese powder coffee or spare Maserati parts. Similarly, Standard Containers are started or uploaded in the same way whether they contain a postgres database, a php application with its dependencies and application server, or Java build artifacts. + + +Infrastructure-agnostic +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Both types of containers are INFRASTRUCTURE-AGNOSTIC: they can be transported to thousands of facilities around the world, and manipulated by a wide variety of equipment. A shipping container can be packed in a factory in Ukraine, transported by truck to the nearest routing center, stacked onto a train, loaded into a German boat by an Australian-built crane, stored in a warehouse at a US facility, etc. Similarly, a standard container can be bundled on my laptop, uploaded to S3, downloaded, run and snapshotted by a build server at Equinix in Virginia, uploaded to 10 staging servers in a home-made Openstack cluster, then sent to 30 production instances across 3 EC2 regions. + + +Designed for automation +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Because they offer the same standard operations regardless of content and infrastructure, Standard Containers, just like their physical counterpart, are extremely well-suited for automation. In fact, you could say automation is their secret weapon. + +Many things that once required time-consuming and error-prone human effort can now be programmed. Before shipping containers, a bag of powder coffee was hauled, dragged, dropped, rolled and stacked by 10 different people in 10 different locations by the time it reached its destination. 1 out of 50 disappeared. 1 out of 20 was damaged. The process was slow, inefficient and cost a fortune - and was entirely different depending on the facility and the type of goods. + +Similarly, before Standard Containers, by the time a software component ran in production, it had been individually built, configured, bundled, documented, patched, vendored, templated, tweaked and instrumented by 10 different people on 10 different computers. Builds failed, libraries conflicted, mirrors crashed, post-it notes were lost, logs were misplaced, cluster updates were half-broken. The process was slow, inefficient and cost a fortune - and was entirely different depending on the language and infrastructure provider. + + +Industrial-grade delivery +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +There are 17 million shipping containers in existence, packed with every physical good imaginable. Every single one of them can be loaded on the same boats, by the same cranes, in the same facilities, and sent anywhere in the World with incredible efficiency. It is embarrassing to think that a 30 ton shipment of coffee can safely travel half-way across the World in *less time* than it takes a software team to deliver its code from one datacenter to another sitting 10 miles away. + +With Standard Containers we can put an end to that embarrassment, by making INDUSTRIAL-GRADE DELIVERY of software a reality. + + +Standard Container Specification +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +(TODO) + +Image format +~~~~~~~~~~~~ + +Standard operations +~~~~~~~~~~~~~~~~~~~ + +- Copy +- Run +- Stop +- Wait +- Commit +- Attach standard streams +- List filesystem changes +- ... + +Execution environment +~~~~~~~~~~~~~~~~~~~~~ + +Root filesystem +^^^^^^^^^^^^^^^ + +Environment variables +^^^^^^^^^^^^^^^^^^^^^ + +Process arguments +^^^^^^^^^^^^^^^^^ + +Networking +^^^^^^^^^^ + +Process namespacing +^^^^^^^^^^^^^^^^^^^ + +Resource limits +^^^^^^^^^^^^^^^ + +Process monitoring +^^^^^^^^^^^^^^^^^^ + +Logging +^^^^^^^ + +Signals +^^^^^^^ + +Pseudo-terminal allocation +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Security +^^^^^^^^ + diff --git a/docs/sources/index/index.rst b/docs/sources/index/index.rst deleted file mode 100644 index 7637a4e77..000000000 --- a/docs/sources/index/index.rst +++ /dev/null @@ -1,15 +0,0 @@ -:title: Docker Index documentation -:description: Documentation for docker Index -:keywords: docker, index, api - - - -Index -===== - -Contents: - -.. toctree:: - :maxdepth: 2 - - search diff --git a/docs/sources/index/variable.rst b/docs/sources/index/variable.rst new file mode 100644 index 000000000..90eac3af8 --- /dev/null +++ b/docs/sources/index/variable.rst @@ -0,0 +1,27 @@ +:title: Index Environment Variable +:description: Setting this environment variable on the docker server will change the URL docker index. +:keywords: docker, index environment variable, documentation + +================================= +Docker Index Environment Variable +================================= + +Variable +-------- + +.. code-block:: sh + + DOCKER_INDEX_URL + +Setting this environment variable on the docker server will change the URL docker index. +This address is used in commands such as ``docker login``, ``docker push`` and ``docker pull``. +The docker daemon doesn't need to be restarted for this parameter to take effect. + +Example +------- + +.. code-block:: sh + + docker -d & + export DOCKER_INDEX_URL="https://index.docker.io" + diff --git a/docs/sources/installation/amazon.rst b/docs/sources/installation/amazon.rst index 012c78f40..59896bb63 100644 --- a/docs/sources/installation/amazon.rst +++ b/docs/sources/installation/amazon.rst @@ -1,3 +1,7 @@ +:title: Installation on Amazon EC2 +:description: Docker installation on Amazon EC2 with a single vagrant command. Vagrant 1.1 or higher is required. +:keywords: amazon ec2, virtualization, cloud, docker, documentation, installation + Amazon EC2 ========== @@ -68,7 +72,7 @@ Docker can now be installed on Amazon EC2 with a single vagrant command. Vagrant If it stalls indefinitely on ``[default] Waiting for SSH to become available...``, Double check your default security zone on AWS includes rights to SSH (port 22) to your container. - If you have an advanced AWS setup, you might want to have a look at the https://github.com/mitchellh/vagrant-aws + If you have an advanced AWS setup, you might want to have a look at https://github.com/mitchellh/vagrant-aws 7. Connect to your machine diff --git a/docs/sources/installation/archlinux.rst b/docs/sources/installation/archlinux.rst index db013c6cb..9e3766eb2 100644 --- a/docs/sources/installation/archlinux.rst +++ b/docs/sources/installation/archlinux.rst @@ -1,3 +1,7 @@ +:title: Installation on Arch Linux +:description: Docker installation on Arch Linux. +:keywords: arch linux, virtualization, docker, documentation, installation + .. _arch_linux: Arch Linux diff --git a/docs/sources/installation/binaries.rst b/docs/sources/installation/binaries.rst index 2607f3680..8bab5695c 100644 --- a/docs/sources/installation/binaries.rst +++ b/docs/sources/installation/binaries.rst @@ -1,3 +1,7 @@ +:title: Installation from Binaries +:description: This instruction set is meant for hackers who want to try out Docker on a variety of environments. +:keywords: binaries, installation, docker, documentation, linux + .. _binaries: Binaries @@ -5,48 +9,58 @@ Binaries **Please note this project is currently under heavy development. It should not be used in production.** +**This instruction set is meant for hackers who want to try out Docker on a variety of environments.** Right now, the officially supported distributions are: -- Ubuntu 12.04 (precise LTS) (64-bit) -- Ubuntu 12.10 (quantal) (64-bit) +- :ref:`ubuntu_precise` +- :ref:`ubuntu_raring` -Install dependencies: ---------------------- +But we know people have had success running it under -:: +- Debian +- Suse +- :ref:`arch_linux` - 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. +Dependencies: +------------- -Install the docker binary: +* 3.8 Kernel +* AUFS filesystem support +* lxc +* bsdtar -:: + +Get the docker binary: +---------------------- + +.. code-block:: bash wget http://get.docker.io/builds/Linux/x86_64/docker-latest.tgz tar -xf docker-latest.tgz - sudo cp ./docker-latest/docker /usr/local/bin - -Note: docker currently only supports 64-bit Linux hosts. Run the docker daemon --------------------- -:: +.. code-block:: bash - sudo docker -d & + # start the docker in daemon mode from the directory you unpacked + sudo ./docker -d & Run your first container! ------------------------- -:: +.. code-block:: bash - docker run -i -t ubuntu /bin/bash + # check your docker version + ./docker version + + # run a container and open an interactive shell in the container + ./docker run -i -t ubuntu /bin/bash diff --git a/docs/sources/installation/index.rst b/docs/sources/installation/index.rst index 0726d9b71..9f831091c 100644 --- a/docs/sources/installation/index.rst +++ b/docs/sources/installation/index.rst @@ -1,6 +1,6 @@ -:title: docker documentation +:title: Documentation :description: -- todo: change me -:keywords: todo: change me +:keywords: todo, docker, documentation, installation, OS support @@ -14,8 +14,10 @@ Contents: ubuntulinux binaries - archlinux vagrant windows amazon + rackspace + archlinux upgrading + kernel diff --git a/docs/sources/installation/kernel.rst b/docs/sources/installation/kernel.rst new file mode 100644 index 000000000..61a7bb385 --- /dev/null +++ b/docs/sources/installation/kernel.rst @@ -0,0 +1,153 @@ +:title: Kernel Requirements +:description: Kernel supports +:keywords: kernel requirements, kernel support, docker, installation, cgroups, namespaces + +.. _kernel: + +Kernel Requirements +=================== + + The officially supported kernel is the one recommended by the + :ref:`ubuntu_linux` installation path. It is the one that most developers + will use, and the one that receives the most attention from the core + contributors. If you decide to go with a different kernel and hit a bug, + please try to reproduce it with the official kernels first. + +If for some reason you cannot or do not want to use the "official" kernels, +here is some technical background about the features (both optional and +mandatory) that docker needs to run successfully. + +In short, you need kernel version 3.8 (or above), compiled to include +`AUFS support `_. Of course, you need to +enable cgroups and namespaces. + + +Namespaces and Cgroups +---------------------- + +You need to enable namespaces and cgroups, to the extend of what is needed +to run LXC containers. Technically, while namespaces have been introduced +in the early 2.6 kernels, we do not advise to try any kernel before 2.6.32 +to run LXC containers. Note that 2.6.32 has some documented issues regarding +network namespace setup and teardown; those issues are not a risk if you +run containers in a private environment, but can lead to denial-of-service +attacks if you want to run untrusted code in your containers. For more details, +see `[LP#720095 `_. + +Kernels 2.6.38, and every version since 3.2, have been deployed successfully +to run containerized production workloads. Feature-wise, there is no huge +improvement between 2.6.38 and up to 3.6 (as far as docker is concerned!). + +Starting with version 3.7, the kernel has basic support for +`Checkpoint/Restore In Userspace `_, which is not used by +docker at this point, but allows to suspend the state of a container to +disk and resume it later. + +Version 3.8 provides improvements in stability, which are deemed necessary +for the operation of docker. Versions 3.2 to 3.5 have been shown to +exhibit a reproducible bug (for more details, see issue +`#407 `_). + +Version 3.8 also brings better support for the +`setns() syscall `_ -- but this should not +be a concern since docker does not leverage on this feature for now. + +If you want a technical overview about those concepts, you might +want to check those articles on dotCloud's blog: +`about namespaces `_ +and `about cgroups `_. + + +Important Note About Pre-3.8 Kernels +------------------------------------ + +As mentioned above, kernels before 3.8 are not stable when used with docker. +In some circumstances, you will experience kernel "oopses", or even crashes. +The symptoms include: + +- a container being killed in the middle of an operation (e.g. an ``apt-get`` + command doesn't complete); +- kernel messages including mentioning calls to ``mntput`` or + ``d_hash_and_lookup``; +- kernel crash causing the machine to freeze for a few minutes, or even + completely. + +While it is still possible to use older kernels for development, it is +really not advised to do so. + +Docker checks the kernel version when it starts, and emits a warning if it +detects something older than 3.8. + +See issue `#407 `_ for details. + + +Extra Cgroup Controllers +------------------------ + +Most control groups can be enabled or disabled individually. For instance, +you can decide that you do not want to compile support for the CPU or memory +controller. In some cases, the feature can be enabled or disabled at boot +time. It is worth mentioning that some distributions (like Debian) disable +"expensive" features, like the memory controller, because they can have +a significant performance impact. + +In the specific case of the memory cgroup, docker will detect if the cgroup +is available or not. If it's not, it will print a warning, and it won't +use the feature. If you want to enable that feature -- read on! + + +Memory and Swap Accounting on Debian/Ubuntu +------------------------------------------- + +If you use Debian or Ubuntu kernels, and want to enable memory and swap +accounting, you must add the following command-line parameters to your kernel:: + + cgroup_enable=memory swapaccount + +On Debian or Ubuntu systems, if you use the default GRUB bootloader, you can +add those parameters by editing ``/etc/default/grub`` and extending +``GRUB_CMDLINE_LINUX``. Look for the following line:: + + GRUB_CMDLINE_LINUX="" + +And replace it by the following one:: + + GRUB_CMDLINE_LINUX="cgroup_enable=memory swapaccount" + +Then run ``update-grub``, and reboot. + + +AUFS +---- + +Docker currently relies on AUFS, an unioning filesystem. +While AUFS is included in the kernels built by the Debian and Ubuntu +distributions, is not part of the standard kernel. This means that if +you decide to roll your own kernel, you will have to patch your +kernel tree to add AUFS. The process is documented on +`AUFS webpage `_. + +Note: the AUFS patch is fairly intrusive, but for the record, people have +successfully applied GRSEC and AUFS together, to obtain hardened production +kernels. + +If you want more information about that topic, there is an +`article about AUFS on dotCloud's blog +`_. + + +BTRFS, ZFS, OverlayFS... +------------------------ + +There is ongoing development on docker, to implement support for +`BTRFS `_ +(see github issue `#443 `_). + +People have also showed interest for `ZFS `_ +(using e.g. `ZFS-on-Linux `_) and OverlayFS. +The latter is functionally close to AUFS, and it might end up being included +in the stock kernel; so it's a strong candidate! + +Would you like to `contribute +`_ +support for your favorite filesystem? diff --git a/docs/sources/installation/rackspace.rst b/docs/sources/installation/rackspace.rst new file mode 100644 index 000000000..748240468 --- /dev/null +++ b/docs/sources/installation/rackspace.rst @@ -0,0 +1,95 @@ +:title: Rackspace Cloud Installation +:description: Installing Docker on Ubuntu proviced by Rackspace +:keywords: Rackspace Cloud, installation, docker, linux, ubuntu + +=============== +Rackspace Cloud +=============== + + 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 Docker on Ubuntu proviced by Rackspace is pretty straightforward, and you should mostly be able to follow the +:ref:`ubuntu_linux` installation guide. + +**However, there is one caveat:** + +If you are using any linux not already shipping with the 3.8 kernel you will need to install it. And this is a little +more difficult on Rackspace. + +Rackspace boots their servers using grub's menu.lst and does not like non 'virtual' packages (e.g. xen compatible) +kernels there, although they do work. This makes ``update-grub`` to not have the expected result, and you need to +set the kernel manually. + +**Do not attempt this on a production machine!** + +.. code-block:: bash + + # update apt + apt-get update + + # install the new kernel + apt-get install linux-generic-lts-raring + + +Great, now you have kernel installed in /boot/, next is to make it boot next time. + +.. code-block:: bash + + # find the exact names + find /boot/ -name '*3.8*' + + # this should return some results + + +Now you need to manually edit /boot/grub/menu.lst, you will find a section at the bottom with the existing options. +Copy the top one and substitute the new kernel into that. Make sure the new kernel is on top, and double check kernel +and initrd point to the right files. + +Make special care to double check the kernel and initrd entries. + +.. code-block:: bash + + # now edit /boot/grub/menu.lst + vi /boot/grub/menu.lst + +It will probably look something like this: + +:: + + ## ## End Default Options ## + + title Ubuntu 12.04.2 LTS, kernel 3.8.x generic + root (hd0) + kernel /boot/vmlinuz-3.8.0-19-generic root=/dev/xvda1 ro quiet splash console=hvc0 + initrd /boot/initrd.img-3.8.0-19-generic + + title Ubuntu 12.04.2 LTS, kernel 3.2.0-38-virtual + root (hd0) + kernel /boot/vmlinuz-3.2.0-38-virtual root=/dev/xvda1 ro quiet splash console=hvc0 + initrd /boot/initrd.img-3.2.0-38-virtual + + title Ubuntu 12.04.2 LTS, kernel 3.2.0-38-virtual (recovery mode) + root (hd0) + kernel /boot/vmlinuz-3.2.0-38-virtual root=/dev/xvda1 ro quiet splash single + initrd /boot/initrd.img-3.2.0-38-virtual + + +Reboot server (either via command line or console) + +.. code-block:: bash + + # reboot + +Verify the kernel was updated + +.. code-block:: bash + + uname -a + # Linux docker-12-04 3.8.0-19-generic #30~precise1-Ubuntu SMP Wed May 1 22:26:36 UTC 2013 x86_64 x86_64 x86_64 GNU/Linux + + # nice! 3.8. + + +Now you can finish with the :ref:`ubuntu_linux` instructions. \ No newline at end of file diff --git a/docs/sources/installation/ubuntulinux.rst b/docs/sources/installation/ubuntulinux.rst index 955e8eb3b..6d2d3e671 100644 --- a/docs/sources/installation/ubuntulinux.rst +++ b/docs/sources/installation/ubuntulinux.rst @@ -1,3 +1,7 @@ +:title: Requirements and Installation on Ubuntu Linux +:description: Please note this project is currently under heavy development. It should not be used in production. +:keywords: Docker, Docker documentation, requirements, virtualbox, vagrant, git, ssh, putty, cygwin, linux + .. _ubuntu_linux: Ubuntu Linux @@ -5,20 +9,39 @@ Ubuntu Linux **Please note this project is currently under heavy development. It should not be used in production.** +Right now, the officially supported distribution are: -Right now, the officially supported distributions are: +- :ref:`ubuntu_precise` +- :ref:`ubuntu_raring` + +Docker has the following dependencies + +* Linux kernel 3.8 +* AUFS file system support (we are working on BTRFS support as an alternative) + +.. _ubuntu_precise: + +Ubuntu Precise 12.04 (LTS) (64-bit) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This installation path should work at all times. -- Ubuntu 12.04 (precise LTS) (64-bit) -- Ubuntu 12.10 (quantal) (64-bit) Dependencies ------------ -The linux-image-extra package is only needed on standard Ubuntu EC2 AMIs in order to install the aufs kernel module. +**Linux kernel 3.8** + +Due to a bug in LXC docker works best on the 3.8 kernel. Precise comes with a 3.2 kernel, so we need to upgrade it. The kernel we install comes with AUFS built in. + .. code-block:: bash - sudo apt-get install linux-image-extra-`uname -r` lxc bsdtar + # install the backported kernel + sudo apt-get update && sudo apt-get install linux-image-3.8.0-19-generic + + # reboot + sudo reboot Installation @@ -28,34 +51,77 @@ Docker is available as a Ubuntu PPA (Personal Package Archive), `hosted on launchpad `_ which makes installing Docker on Ubuntu very easy. +.. code-block:: bash + # Add the PPA sources to your apt sources list. + sudo sh -c "echo 'deb http://ppa.launchpad.net/dotcloud/lxc-docker/ubuntu precise main' > /etc/apt/sources.list.d/lxc-docker.list" -Add the custom package sources to your apt sources list. Copy and paste the following lines at once. + # Update your sources, you will see a warning. + sudo apt-get update + + # Install, you will see another warning that the package cannot be authenticated. Confirm install. + sudo apt-get install lxc-docker + +Verify it worked .. code-block:: bash - sudo sh -c "echo 'deb http://ppa.launchpad.net/dotcloud/lxc-docker/ubuntu precise main' >> /etc/apt/sources.list" + # download the base 'ubuntu' container and run bash inside it while setting up an interactive shell + docker run -i -t ubuntu /bin/bash + + # type 'exit' to exit -Update your sources. You will see a warning that GPG signatures cannot be verified. +**Done!**, now continue with the :ref:`hello_world` example. + +.. _ubuntu_raring: + +Ubuntu Raring 13.04 (64 bit) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Dependencies +------------ + +**AUFS filesystem support** + +Ubuntu Raring already comes with the 3.8 kernel, so we don't need to install it. However, not all systems +have AUFS filesystem support enabled, so we need to install it. .. code-block:: bash sudo apt-get update + sudo apt-get install linux-image-extra-`uname -r` + +Installation +------------ + +Docker is available as a Ubuntu PPA (Personal Package Archive), +`hosted on launchpad `_ +which makes installing Docker on Ubuntu very easy. -Now install it, you will see another warning that the package cannot be authenticated. Confirm install. +Add the custom package sources to your apt sources list. .. code-block:: bash - curl get.docker.io | sudo sh -x + # add the sources to your apt + sudo add-apt-repository ppa:dotcloud/lxc-docker + + # update + sudo apt-get update + + # install + sudo apt-get install lxc-docker Verify it worked .. code-block:: bash - docker + # download the base 'ubuntu' container and run bash inside it while setting up an interactive shell + docker run -i -t ubuntu /bin/bash + + # type exit to exit **Done!**, now continue with the :ref:`hello_world` example. diff --git a/docs/sources/installation/upgrading.rst b/docs/sources/installation/upgrading.rst index a5172b6d7..9fa47904b 100644 --- a/docs/sources/installation/upgrading.rst +++ b/docs/sources/installation/upgrading.rst @@ -1,40 +1,59 @@ +:title: Upgrading +:description: These instructions are for upgrading Docker +:keywords: Docker, Docker documentation, upgrading docker, upgrade + .. _upgrading: Upgrading ============ -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. +**These instructions are for upgrading Docker** -Get the latest docker binary: +After normal installation +------------------------- -:: +If you installed Docker normally using apt-get or used Vagrant, use apt-get to upgrade. - wget http://get.docker.io/builds/$(uname -s)/$(uname -m)/docker-latest.tgz +.. code-block:: bash + + # update your sources list + sudo apt-get update + + # install the latest + sudo apt-get install lxc-docker +After manual installation +------------------------- -Unpack it to your current dir +If you installed the Docker binary -:: +.. code-block:: bash + + # kill the running docker daemon + killall docker + + +.. code-block:: bash + + # get the latest binary + wget http://get.docker.io/builds/Linux/x86_64/docker-latest.tgz + + +.. code-block:: bash + + # Unpack it to your current dir tar -xf docker-latest.tgz -Stop your current daemon. How you stop your daemon depends on how you started it. +Start docker in daemon mode (-d) and disconnect (&) starting ./docker will start the version in your current dir rather than a version which +might reside in your path. -- If you started the daemon manually (``sudo docker -d``), you can just kill the process: ``killall docker`` -- If the process was started using upstart (the ubuntu startup daemon), you may need to use that to stop it - - -Start docker in daemon mode (-d) and disconnect (&) starting ./docker will start the version in your current dir rather -than the one in your PATH. - -Now start the daemon - -:: +.. code-block:: bash + # start the new version sudo ./docker -d & diff --git a/docs/sources/installation/vagrant.rst b/docs/sources/installation/vagrant.rst index 465a6c338..24a1e9135 100644 --- a/docs/sources/installation/vagrant.rst +++ b/docs/sources/installation/vagrant.rst @@ -1,14 +1,13 @@ +:title: Using Vagrant (Mac, Linux) +:description: This guide will setup a new virtualbox virtual machine with docker installed on your computer. +:keywords: Docker, Docker documentation, virtualbox, vagrant, git, ssh, putty, cygwin .. _install_using_vagrant: -Using Vagrant -============= +Using Vagrant (Mac, 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. - -**Requirements:** -This guide will setup a new virtual machine with docker installed on your computer. This works on most operating +This guide will setup a new virtualbox 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. diff --git a/docs/sources/installation/windows.rst b/docs/sources/installation/windows.rst index a89d3a901..230ac7805 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 (with Vagrant) -====================== +Using Vagrant (Windows) +======================= 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/registry/index.rst b/docs/sources/registry/index.rst deleted file mode 100644 index d3788f53c..000000000 --- a/docs/sources/registry/index.rst +++ /dev/null @@ -1,15 +0,0 @@ -: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/toctree.rst b/docs/sources/toctree.rst new file mode 100644 index 000000000..ae6d5f010 --- /dev/null +++ b/docs/sources/toctree.rst @@ -0,0 +1,22 @@ +:title: Documentation +:description: -- todo: change me +:keywords: todo, docker, documentation, installation, usage, examples, contributing, faq, command line, concepts + +Documentation +============= + +This documentation has the following resources: + +.. toctree:: + :titlesonly: + + concepts/index + installation/index + use/index + examples/index + commandline/index + contributing/index + api/index + faq + +.. image:: concepts/images/lego_docker.jpg diff --git a/docs/sources/commandline/basics.rst b/docs/sources/use/basics.rst similarity index 93% rename from docs/sources/commandline/basics.rst rename to docs/sources/use/basics.rst index 8dd8ec9de..6ae0711f1 100644 --- a/docs/sources/commandline/basics.rst +++ b/docs/sources/use/basics.rst @@ -1,6 +1,6 @@ -:title: Base commands +:title: Basic Commands :description: Common usage and commands -:keywords: Examples, Usage +:keywords: Examples, Usage, basic commands, docker, documentation, examples The basics @@ -76,8 +76,8 @@ Expose a service on a TCP port echo "Daemon received: $(docker logs $JOB)" -Committing (saving) an image ------------------------------ +Committing (saving) a container state +------------------------------------- Save your containers state to a container image, so the state can be re-used. diff --git a/docs/sources/use/builder.rst b/docs/sources/use/builder.rst new file mode 100644 index 000000000..4e53ed4a7 --- /dev/null +++ b/docs/sources/use/builder.rst @@ -0,0 +1,187 @@ +:title: Docker Builder +:description: Docker Builder specifes a simple DSL which allows you to automate the steps you would normally manually take to create an image. +:keywords: builder, docker, Docker Builder, automation, image creation + +============== +Docker Builder +============== + +.. contents:: Table of Contents + +Docker Builder specifes a simple DSL which allows you to automate the steps you +would normally manually take to create an image. Docker Build will run your +steps and commit them along the way, giving you a final image. + +1. Usage +======== + +To use Docker Builder, assemble the steps into a text file (commonly referred to +as a Dockerfile) and supply this to `docker build` on STDIN, like so: + + ``docker build < Dockerfile`` + +Docker will run your steps one-by-one, committing the result if necessary, +before finally outputting the ID of your new image. + +2. Format +========= + +The Dockerfile format is quite simple: + + ``instruction arguments`` + +The Instruction is not case-sensitive, however convention is for them to be +UPPERCASE in order to distinguish them from arguments more easily. + +Dockerfiles are evaluated in order, therefore the first instruction must be +`FROM` in order to specify the base image from which you are building. + +Docker will ignore lines in Dockerfiles prefixed with "`#`", so you may add +comment lines. A comment marker in the rest of the line will be treated as an +argument. + +2. Instructions +=============== + +Docker builder comes with a set of instructions, described below. + +2.1 FROM +-------- + + ``FROM `` + +The `FROM` instruction sets the base image for subsequent instructions. As such, +a valid Dockerfile must have it as its first instruction. + +`FROM` can be included multiple times within a single Dockerfile in order to +create multiple images. Simply make a note of the last image id output by the +commit before each new `FROM` command. + +2.2 MAINTAINER +-------------- + + ``MAINTAINER `` + +The `MAINTAINER` instruction allows you to set the Author field of the generated +images. + +2.3 RUN +------- + + ``RUN `` + +The `RUN` instruction will execute any commands on the current image and commit +the results. The resulting committed image will be used for the next step in the +Dockerfile. + +Layering `RUN` instructions and generating commits conforms to the +core concepts of Docker where commits are cheap and containers can be created +from any point in an image's history, much like source control. + +2.4 CMD +------- + + ``CMD `` + +The `CMD` instruction sets the command to be executed when running the image. +This is functionally equivalent to running +`docker commit -run '{"Cmd": }'` outside the builder. + +.. note:: + Don't confuse `RUN` with `CMD`. `RUN` actually runs a command and commits + the result; `CMD` does not execute anything at build time, but specifies the + intended command for the image. + +2.5 EXPOSE +---------- + + ``EXPOSE [...]`` + +The `EXPOSE` instruction sets ports to be publicly exposed when running the +image. This is functionally equivalent to running +`docker commit -run '{"PortSpecs": ["", ""]}'` outside the builder. + +2.6 ENV +------- + + ``ENV `` + +The `ENV` instruction sets the environment variable `` to the value +``. This value will be passed to all future ``RUN`` instructions. This is +functionally equivalent to prefixing the command with `=` + +.. note:: + The environment variables will persist when a container is run from the resulting image. + +2.7 INSERT +---------- + + ``INSERT `` + +The `INSERT` instruction will download the file from the given url to the given +path within the image. It is similar to `RUN curl -o `, assuming +curl was installed within the image. + +.. note:: + The path must include the file name. + +.. note:: + This instruction has temporarily disabled + +3. Dockerfile Examples +====================== + +.. code-block:: bash + + # Nginx + # + # VERSION 0.0.1 + + FROM ubuntu + MAINTAINER Guillaume J. Charmes "guillaume@dotcloud.com" + + # make sure the package repository is up to date + RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list + RUN apt-get update + + RUN apt-get install -y inotify-tools nginx apache2 openssh-server + INSERT https://raw.github.com/creack/docker-vps/master/nginx-wrapper.sh /usr/sbin/nginx-wrapper + +.. code-block:: bash + + # Firefox over VNC + # + # VERSION 0.3 + + FROM ubuntu + # make sure the package repository is up to date + RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list + RUN apt-get update + + # Install vnc, xvfb in order to create a 'fake' display and firefox + RUN apt-get install -y x11vnc xvfb firefox + RUN mkdir /.vnc + # Setup a password + RUN x11vnc -storepasswd 1234 ~/.vnc/passwd + # Autostart firefox (might not be the best way, but it does the trick) + RUN bash -c 'echo "firefox" >> /.bashrc' + + EXPOSE 5900 + CMD ["x11vnc", "-forever", "-usepw", "-create"] + +.. code-block:: bash + + # Multiple images example + # + # VERSION 0.1 + + FROM ubuntu + RUN echo foo > bar + # Will output something like ===> 907ad6c2736f + + FROM ubuntu + RUN echo moo > oink + # Will output something like ===> 695d7793cbe4 + + # You'll now have two images, 907ad6c2736f with /bar, and 695d7793cbe4 with + # /oink. diff --git a/docs/sources/use/index.rst b/docs/sources/use/index.rst new file mode 100644 index 000000000..a1086c1fd --- /dev/null +++ b/docs/sources/use/index.rst @@ -0,0 +1,19 @@ +:title: Documentation +:description: -- todo: change me +:keywords: todo, docker, documentation, basic, builder + + + +Use +======== + +Contents: + +.. toctree:: + :maxdepth: 1 + + basics + workingwithrepository + builder + puppet + diff --git a/docs/sources/use/puppet.rst b/docs/sources/use/puppet.rst new file mode 100644 index 000000000..1c48aec8e --- /dev/null +++ b/docs/sources/use/puppet.rst @@ -0,0 +1,112 @@ +:title: Puppet Usage +:description: Installating and using Puppet +:keywords: puppet, installation, usage, docker, documentation + +.. _install_using_puppet: + +Using Puppet +============= + +.. note:: + + 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 +------------ + +To use this guide you'll need a working installation of Puppet from `Puppetlabs `_ . + +The module also currently uses the official PPA so only works with Ubuntu. + +Installation +------------ + +The module is available on the `Puppet Forge `_ +and can be installed using the built-in module tool. + + .. code-block:: bash + + puppet module install garethr/docker + +It can also be found on `GitHub `_ +if you would rather download the source. + +Usage +----- + +The module provides a puppet class for installing docker and two defined types +for managing images and containers. + +Installation +~~~~~~~~~~~~ + + .. code-block:: ruby + + include 'docker' + +Images +~~~~~~ + +The next step is probably to install a docker image, for this we have a +defined type which can be used like so: + + .. code-block:: ruby + + docker::image { 'base': } + +This is equivalent to running: + + .. code-block:: bash + + docker pull base + +Note that it will only if the image of that name does not already exist. +This is downloading a large binary so on first run can take a while. +For that reason this define turns off the default 5 minute timeout +for exec. Note that you can also remove images you no longer need with: + + .. code-block:: ruby + + docker::image { 'base': + ensure => 'absent', + } + +Containers +~~~~~~~~~~ + +Now you have an image you can run commands within a container managed by +docker. + + .. code-block:: ruby + + docker::run { 'helloworld': + image => 'base', + command => '/bin/sh -c "while true; do echo hello world; sleep 1; done"', + } + +This is equivalent to running the following command, but under upstart: + + .. code-block:: bash + + docker run -d base /bin/sh -c "while true; do echo hello world; sleep 1; done" + +Run also contains a number of optional parameters: + + .. code-block:: ruby + + docker::run { 'helloworld': + image => 'base', + command => '/bin/sh -c "while true; do echo hello world; sleep 1; done"', + ports => ['4444', '4555'], + volumes => ['/var/lib/counchdb', '/var/log'], + volumes_from => '6446ea52fbc9', + memory_limit => 10485760, # bytes + username => 'example', + hostname => 'example.com', + env => ['FOO=BAR', 'FOO2=BAR2'], + dns => ['8.8.8.8', '8.8.4.4'], + } + +Note that ports, env, dns and volumes can be set with either a single string +or as above with an array of values. diff --git a/docs/sources/use/workingwithrepository.rst b/docs/sources/use/workingwithrepository.rst new file mode 100644 index 000000000..9a2f96cf0 --- /dev/null +++ b/docs/sources/use/workingwithrepository.rst @@ -0,0 +1,79 @@ +:title: Working With Repositories +:description: Generally, there are two types of repositories: Top-level repositories which are controlled by the people behind Docker, and user repositories. +:keywords: repo, repositiores, usage, pull image, push image, image, documentation + +.. _working_with_the_repository: + +Working with the repository +============================ + + +Top-level repositories and user repositories +-------------------------------------------- + +Generally, there are two types of repositories: Top-level repositories which are controlled by the people behind +Docker, and user repositories. + +* Top-level repositories can easily be recognized by not having a / (slash) in their name. These repositories can + generally be trusted. +* User repositories always come in the form of /. This is what your published images will look like. +* User images are not checked, it is therefore up to you whether or not you trust the creator of this image. + + +Find public images available on the index +----------------------------------------- + +Seach by name, namespace or description + +.. code-block:: bash + + docker search + + +Download them simply by their name + +.. code-block:: bash + + docker pull + + +Very similarly you can search for and browse the index online on https://index.docker.io + + +Connecting to the repository +---------------------------- + +You can create a user on the central docker repository online, or by running + +.. code-block:: bash + + docker login + + +If your username does not exist it will prompt you to also enter a password and your e-mail address. It will then +automatically log you in. + + +Committing a container to a named image +--------------------------------------- + +In order to commit to the repository it is required to have committed your container to an image with your namespace. + +.. code-block:: bash + + # for example docker commit $CONTAINER_ID dhrp/kickassapp + docker commit / + + +Pushing a container to the repository +----------------------------------------- + +In order to push an image to the repository you need to have committed your container to a named image (see above) + +Now you can commit this image to the repository + +.. code-block:: bash + + # for example docker push dhrp/kickassapp + docker push + diff --git a/docs/theme/docker/layout.html b/docs/theme/docker/layout.html index 4da65ac62..d212c9ca8 100755 --- a/docs/theme/docker/layout.html +++ b/docs/theme/docker/layout.html @@ -6,8 +6,9 @@ + - Docker - {{ meta['title'] if meta and meta['title'] else title }} + {{ meta['title'] if meta and meta['title'] else title }} - Docker Documentation @@ -65,7 +66,7 @@
- +
@@ -154,69 +155,87 @@ - + // do nothing + } else { + // collapse children + current.children('ul').hide(); + } + } + + // attached handler on click + $('.sidebar > ul > li > a').not(':last').click(function(){ + + var index = $.inArray(this.href, openmenus) + + if (index > -1) { + console.log(index); + openmenus.splice(index, 1); + + + $(this).parent().children('ul').slideUp(200, function() { + // $(this).parent().removeClass('current'); // toggle after effect + }); + } + else { + openmenus.push(this.href); + console.log(this); + + var current = $(this); + + setTimeout(function() { + $('.sidebar > ul > li').removeClass('current'); + current.parent().addClass('current'); // toggle before effect + current.parent().children('ul').hide(); + current.parent().children('ul').slideDown(200); + }, 100); + } + return false; + }); + + }); + - + + @@ -35,8 +35,8 @@ @@ -76,6 +76,7 @@
  • Ubuntu 12.04 (LTS) (64-bit)
  • or Ubuntu 12.10 (quantal) (64-bit)
  • +
  • The 3.8 Linux Kernel
  1. @@ -105,7 +106,8 @@
    docker run -i -t ubuntu /bin/bash
  2. - Continue with the Hello world example. + Continue with the Hello world example.
    + Or check more detailed installation instructions
@@ -186,7 +188,7 @@ - + - + + + + @@ -34,9 +58,9 @@
-
-
-
- -
-
-
- -
+
-
+
+
+ + +

The Linux container engine

+
+ +
+ +
+ Docker is an open-source engine which automates the deployment of applications as highly portable, self-sufficient containers which are independent of hardware, language, framework, packaging system and hosting provider. +
+ +
+ + + + +
+ +
- +
-
-

Docker

-

The Linux container engine

-
- -
- -

- Docker is an open-source engine which automates the deployment of applications as highly portable, self-sufficient containers. -

- -

- Docker containers are both hardware-agnostic and platform-agnostic. This means that they can run anywhere, from your - laptop to the largest EC2 compute instance and everything in between - and they don't require that you use a particular - language, framework or packaging system. That makes them great building blocks for deploying and scaling web apps, databases - and backend services without depending on a particular stack or provider. -

- -

- Docker is an open-source implementation of the deployment engine which powers dotCloud, a popular Platform-as-a-Service. - It benefits directly from the experience accumulated over several years of large-scale operation and support of hundreds of thousands - of applications and databases. -

- -
- - - - - -
@@ -111,31 +116,72 @@
-
+

Heterogeneous payloads

Any combination of binaries, libraries, configuration files, scripts, virtualenvs, jars, gems, tarballs, you name it. No more juggling between domain-specific tools. Docker can deploy and run them all.

-
-
-
-

Any server

Docker can run on any x64 machine with a modern linux kernel - whether it's a laptop, a bare metal server or a VM. This makes it perfect for multi-cloud deployments.

-
-
-
-

Isolation

-

docker isolates processes from each other and from the underlying host, using lightweight containers.

+

Docker isolates processes from each other and from the underlying host, using lightweight containers.

+

Repeatability

+

Because each container is isolated in its own filesystem, they behave the same regardless of where, when, and alongside what they run.

+
+
+
+
+ +
+
+

Do you think it is cool to hack on docker? Join us!

+
    +
  • Work on open source
  • +
  • Program in Go
  • +
+ read more +
+
+
-
+
-

Repeatability

-

Because containers are isolated in their own filesystem, they behave the same regardless of where, when, and alongside what they run.

+

New! Docker Index

+ On the Docker Index you can find and explore pre-made container images. It allows you to share your images and download them. + +

+ +
+ DOCKER index +
+
+   + + +
+
+
+ Fill out my online form. +
+
+