Conflicts:
runtime.go
Docker-DCO-1.1-Signed-off-by: Michael Crosby <michael@crosbymichael.com> (github: crosbymichael)
| ... | ... |
@@ -6,4 +6,3 @@ Michael Crosby <michael@crosbymichael.com> (@crosbymichael) |
| 6 | 6 |
api.go: Victor Vieux <victor@dotcloud.com> (@vieux) |
| 7 | 7 |
Dockerfile: Tianon Gravi <admwiggin@gmail.com> (@tianon) |
| 8 | 8 |
Makefile: Tianon Gravi <admwiggin@gmail.com> (@tianon) |
| 9 |
-Vagrantfile: Cristian Staretu <cristian.staretu@gmail.com> (@unclejack) |
| 10 | 9 |
deleted file mode 100644 |
| ... | ... |
@@ -1,206 +0,0 @@ |
| 1 |
-# -*- mode: ruby -*- |
|
| 2 |
-# vi: set ft=ruby : |
|
| 3 |
- |
|
| 4 |
-BOX_NAME = ENV['BOX_NAME'] || "ubuntu" |
|
| 5 |
-BOX_URI = ENV['BOX_URI'] || "http://files.vagrantup.com/precise64.box" |
|
| 6 |
-VF_BOX_URI = ENV['BOX_URI'] || "http://files.vagrantup.com/precise64_vmware_fusion.box" |
|
| 7 |
-AWS_BOX_URI = ENV['BOX_URI'] || "https://github.com/mitchellh/vagrant-aws/raw/master/dummy.box" |
|
| 8 |
-AWS_REGION = ENV['AWS_REGION'] || "us-east-1" |
|
| 9 |
-AWS_AMI = ENV['AWS_AMI'] || "ami-69f5a900" |
|
| 10 |
-AWS_INSTANCE_TYPE = ENV['AWS_INSTANCE_TYPE'] || 't1.micro' |
|
| 11 |
-SSH_PRIVKEY_PATH = ENV['SSH_PRIVKEY_PATH'] |
|
| 12 |
-PRIVATE_NETWORK = ENV['PRIVATE_NETWORK'] |
|
| 13 |
- |
|
| 14 |
-# Boolean that forwards the Docker dynamic ports 49000-49900 |
|
| 15 |
-# See http://docs.docker.io/en/latest/use/port_redirection/ for more |
|
| 16 |
-# $ FORWARD_DOCKER_PORTS=1 vagrant [up|reload] |
|
| 17 |
-FORWARD_DOCKER_PORTS = ENV['FORWARD_DOCKER_PORTS'] |
|
| 18 |
-VAGRANT_RAM = ENV['VAGRANT_RAM'] || 512 |
|
| 19 |
-VAGRANT_CORES = ENV['VAGRANT_CORES'] || 1 |
|
| 20 |
- |
|
| 21 |
-# You may also provide a comma-separated list of ports |
|
| 22 |
-# for Vagrant to forward. For example: |
|
| 23 |
-# $ FORWARD_PORTS=8080,27017 vagrant [up|reload] |
|
| 24 |
-FORWARD_PORTS = ENV['FORWARD_PORTS'] |
|
| 25 |
- |
|
| 26 |
-# A script to upgrade from the 12.04 kernel to the raring backport kernel (3.8) |
|
| 27 |
-# and install docker. |
|
| 28 |
-$script = <<SCRIPT |
|
| 29 |
-# The username to add to the docker group will be passed as the first argument |
|
| 30 |
-# to the script. If nothing is passed, default to "vagrant". |
|
| 31 |
-user="$1" |
|
| 32 |
-if [ -z "$user" ]; then |
|
| 33 |
- user=vagrant |
|
| 34 |
-fi |
|
| 35 |
- |
|
| 36 |
-# Enable memory cgroup and swap accounting |
|
| 37 |
-sed -i 's/GRUB_CMDLINE_LINUX=""/GRUB_CMDLINE_LINUX="cgroup_enable=memory swapaccount=1"/g' /etc/default/grub |
|
| 38 |
-update-grub |
|
| 39 |
- |
|
| 40 |
-# Adding an apt gpg key is idempotent. |
|
| 41 |
-apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 36A1D7869245C8950F966E92D8576A8BA88D21E9 |
|
| 42 |
- |
|
| 43 |
-# Creating the docker.list file is idempotent, but it may overwrite desired |
|
| 44 |
-# settings if it already exists. This could be solved with md5sum but it |
|
| 45 |
-# doesn't seem worth it. |
|
| 46 |
-echo 'deb http://get.docker.io/ubuntu docker main' > \ |
|
| 47 |
- /etc/apt/sources.list.d/docker.list |
|
| 48 |
- |
|
| 49 |
-# Update remote package metadata. 'apt-get update' is idempotent. |
|
| 50 |
-apt-get update -q |
|
| 51 |
- |
|
| 52 |
-# Install docker. 'apt-get install' is idempotent. |
|
| 53 |
-apt-get install -q -y lxc-docker |
|
| 54 |
- |
|
| 55 |
-usermod -a -G docker "$user" |
|
| 56 |
- |
|
| 57 |
-tmp=`mktemp -q` && {
|
|
| 58 |
- # Only install the backport kernel, don't bother upgrading if the backport is |
|
| 59 |
- # already installed. We want parse the output of apt so we need to save it |
|
| 60 |
- # with 'tee'. NOTE: The installation of the kernel will trigger dkms to |
|
| 61 |
- # install vboxguest if needed. |
|
| 62 |
- apt-get install -q -y --no-upgrade linux-image-generic-lts-raring | \ |
|
| 63 |
- tee "$tmp" |
|
| 64 |
- |
|
| 65 |
- # Parse the number of installed packages from the output |
|
| 66 |
- NUM_INST=`awk '$2 == "upgraded," && $4 == "newly" { print $3 }' "$tmp"`
|
|
| 67 |
- rm "$tmp" |
|
| 68 |
-} |
|
| 69 |
- |
|
| 70 |
-# If the number of installed packages is greater than 0, we want to reboot (the |
|
| 71 |
-# backport kernel was installed but is not running). |
|
| 72 |
-if [ "$NUM_INST" -gt 0 ]; |
|
| 73 |
-then |
|
| 74 |
- echo "Rebooting down to activate new kernel." |
|
| 75 |
- echo "/vagrant will not be mounted. Use 'vagrant halt' followed by" |
|
| 76 |
- echo "'vagrant up' to ensure /vagrant is mounted." |
|
| 77 |
- shutdown -r now |
|
| 78 |
-fi |
|
| 79 |
-SCRIPT |
|
| 80 |
- |
|
| 81 |
-# We need to install the virtualbox guest additions *before* we do the normal |
|
| 82 |
-# docker installation. As such this script is prepended to the common docker |
|
| 83 |
-# install script above. This allows the install of the backport kernel to |
|
| 84 |
-# trigger dkms to build the virtualbox guest module install. |
|
| 85 |
-$vbox_script = <<VBOX_SCRIPT + $script |
|
| 86 |
-# Install the VirtualBox guest additions if they aren't already installed. |
|
| 87 |
-if [ ! -d /opt/VBoxGuestAdditions-4.3.6/ ]; then |
|
| 88 |
- # Update remote package metadata. 'apt-get update' is idempotent. |
|
| 89 |
- apt-get update -q |
|
| 90 |
- |
|
| 91 |
- # Kernel Headers and dkms are required to build the vbox guest kernel |
|
| 92 |
- # modules. |
|
| 93 |
- apt-get install -q -y linux-headers-generic-lts-raring dkms |
|
| 94 |
- |
|
| 95 |
- echo 'Downloading VBox Guest Additions...' |
|
| 96 |
- wget -cq http://dlc.sun.com.edgesuite.net/virtualbox/4.3.6/VBoxGuestAdditions_4.3.6.iso |
|
| 97 |
- echo "95648fcdb5d028e64145a2fe2f2f28c946d219da366389295a61fed296ca79f0 VBoxGuestAdditions_4.3.6.iso" | sha256sum --check || exit 1 |
|
| 98 |
- |
|
| 99 |
- mount -o loop,ro /home/vagrant/VBoxGuestAdditions_4.3.6.iso /mnt |
|
| 100 |
- /mnt/VBoxLinuxAdditions.run --nox11 |
|
| 101 |
- umount /mnt |
|
| 102 |
-fi |
|
| 103 |
-VBOX_SCRIPT |
|
| 104 |
- |
|
| 105 |
-Vagrant::Config.run do |config| |
|
| 106 |
- # Setup virtual machine box. This VM configuration code is always executed. |
|
| 107 |
- config.vm.box = BOX_NAME |
|
| 108 |
- config.vm.box_url = BOX_URI |
|
| 109 |
- |
|
| 110 |
- # Use the specified private key path if it is specified and not empty. |
|
| 111 |
- if SSH_PRIVKEY_PATH |
|
| 112 |
- config.ssh.private_key_path = SSH_PRIVKEY_PATH |
|
| 113 |
- end |
|
| 114 |
- |
|
| 115 |
- config.ssh.forward_agent = true |
|
| 116 |
-end |
|
| 117 |
- |
|
| 118 |
-# Providers were added on Vagrant >= 1.1.0 |
|
| 119 |
-# |
|
| 120 |
-# NOTE: The vagrant "vm.provision" appends its arguments to a list and executes |
|
| 121 |
-# them in order. If you invoke "vm.provision :shell, :inline => $script" |
|
| 122 |
-# twice then vagrant will run the script two times. Unfortunately when you use |
|
| 123 |
-# providers and the override argument to set up provisioners (like the vbox |
|
| 124 |
-# guest extensions) they 1) don't replace the other provisioners (they append |
|
| 125 |
-# to the end of the list) and 2) you can't control the order the provisioners |
|
| 126 |
-# are executed (you can only append to the list). If you want the virtualbox |
|
| 127 |
-# only script to run before the other script, you have to jump through a lot of |
|
| 128 |
-# hoops. |
|
| 129 |
-# |
|
| 130 |
-# Here is my only repeatable solution: make one script that is common ($script) |
|
| 131 |
-# and another script that is the virtual box guest *prepended* to the common |
|
| 132 |
-# script. Only ever use "vm.provision" *one time* per provider. That means |
|
| 133 |
-# every single provider has an override, and every single one configures |
|
| 134 |
-# "vm.provision". Much saddness, but such is life. |
|
| 135 |
-Vagrant::VERSION >= "1.1.0" and Vagrant.configure("2") do |config|
|
|
| 136 |
- config.vm.provider :aws do |aws, override| |
|
| 137 |
- username = "ubuntu" |
|
| 138 |
- override.vm.box_url = AWS_BOX_URI |
|
| 139 |
- override.vm.provision :shell, :inline => $script, :args => username |
|
| 140 |
- aws.access_key_id = ENV["AWS_ACCESS_KEY"] |
|
| 141 |
- aws.secret_access_key = ENV["AWS_SECRET_KEY"] |
|
| 142 |
- aws.keypair_name = ENV["AWS_KEYPAIR_NAME"] |
|
| 143 |
- override.ssh.username = username |
|
| 144 |
- aws.region = AWS_REGION |
|
| 145 |
- aws.ami = AWS_AMI |
|
| 146 |
- aws.instance_type = AWS_INSTANCE_TYPE |
|
| 147 |
- end |
|
| 148 |
- |
|
| 149 |
- config.vm.provider :rackspace do |rs, override| |
|
| 150 |
- override.vm.provision :shell, :inline => $script |
|
| 151 |
- rs.username = ENV["RS_USERNAME"] |
|
| 152 |
- rs.api_key = ENV["RS_API_KEY"] |
|
| 153 |
- rs.public_key_path = ENV["RS_PUBLIC_KEY"] |
|
| 154 |
- rs.flavor = /512MB/ |
|
| 155 |
- rs.image = /Ubuntu/ |
|
| 156 |
- end |
|
| 157 |
- |
|
| 158 |
- config.vm.provider :vmware_fusion do |f, override| |
|
| 159 |
- override.vm.box_url = VF_BOX_URI |
|
| 160 |
- override.vm.synced_folder ".", "/vagrant", disabled: true |
|
| 161 |
- override.vm.provision :shell, :inline => $script |
|
| 162 |
- f.vmx["displayName"] = "docker" |
|
| 163 |
- end |
|
| 164 |
- |
|
| 165 |
- config.vm.provider :virtualbox do |vb, override| |
|
| 166 |
- override.vm.provision :shell, :inline => $vbox_script |
|
| 167 |
- vb.customize ["modifyvm", :id, "--natdnshostresolver1", "on"] |
|
| 168 |
- vb.customize ["modifyvm", :id, "--natdnsproxy1", "on"] |
|
| 169 |
- vb.customize ["modifyvm", :id, "--memory", VAGRANT_RAM] |
|
| 170 |
- vb.customize ["modifyvm", :id, "--cpus", VAGRANT_CORES] |
|
| 171 |
- end |
|
| 172 |
-end |
|
| 173 |
- |
|
| 174 |
-# If this is a version 1 config, virtualbox is the only option. A version 2 |
|
| 175 |
-# config would have already been set in the above provider section. |
|
| 176 |
-Vagrant::VERSION < "1.1.0" and Vagrant::Config.run do |config| |
|
| 177 |
- config.vm.provision :shell, :inline => $vbox_script |
|
| 178 |
-end |
|
| 179 |
- |
|
| 180 |
-# Setup port forwarding per loaded environment variables |
|
| 181 |
-forward_ports = FORWARD_DOCKER_PORTS.nil? ? [] : [*49153..49900] |
|
| 182 |
-forward_ports += FORWARD_PORTS.split(',').map{|i| i.to_i } if FORWARD_PORTS
|
|
| 183 |
-if forward_ports.any? |
|
| 184 |
- Vagrant::VERSION < "1.1.0" and Vagrant::Config.run do |config| |
|
| 185 |
- forward_ports.each do |port| |
|
| 186 |
- config.vm.forward_port port, port |
|
| 187 |
- end |
|
| 188 |
- end |
|
| 189 |
- |
|
| 190 |
- Vagrant::VERSION >= "1.1.0" and Vagrant.configure("2") do |config|
|
|
| 191 |
- forward_ports.each do |port| |
|
| 192 |
- config.vm.network :forwarded_port, :host => port, :guest => port, auto_correct: true |
|
| 193 |
- end |
|
| 194 |
- end |
|
| 195 |
-end |
|
| 196 |
- |
|
| 197 |
-if !PRIVATE_NETWORK.nil? |
|
| 198 |
- Vagrant::VERSION < "1.1.0" and Vagrant::Config.run do |config| |
|
| 199 |
- config.vm.network :hostonly, PRIVATE_NETWORK |
|
| 200 |
- end |
|
| 201 |
- |
|
| 202 |
- Vagrant::VERSION >= "1.1.0" and Vagrant.configure("2") do |config|
|
|
| 203 |
- config.vm.network "private_network", ip: PRIVATE_NETWORK |
|
| 204 |
- end |
|
| 205 |
-end |
|
| 206 |
- |
| 207 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,1248 +0,0 @@ |
| 1 |
-package api |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "bufio" |
|
| 5 |
- "bytes" |
|
| 6 |
- "code.google.com/p/go.net/websocket" |
|
| 7 |
- "encoding/base64" |
|
| 8 |
- "encoding/json" |
|
| 9 |
- "expvar" |
|
| 10 |
- "fmt" |
|
| 11 |
- "github.com/dotcloud/docker/auth" |
|
| 12 |
- "github.com/dotcloud/docker/engine" |
|
| 13 |
- "github.com/dotcloud/docker/pkg/listenbuffer" |
|
| 14 |
- "github.com/dotcloud/docker/pkg/systemd" |
|
| 15 |
- "github.com/dotcloud/docker/utils" |
|
| 16 |
- "github.com/gorilla/mux" |
|
| 17 |
- "io" |
|
| 18 |
- "io/ioutil" |
|
| 19 |
- "log" |
|
| 20 |
- "mime" |
|
| 21 |
- "net" |
|
| 22 |
- "net/http" |
|
| 23 |
- "net/http/pprof" |
|
| 24 |
- "os" |
|
| 25 |
- "regexp" |
|
| 26 |
- "strconv" |
|
| 27 |
- "strings" |
|
| 28 |
- "syscall" |
|
| 29 |
- "time" |
|
| 30 |
-) |
|
| 31 |
- |
|
| 32 |
-// FIXME: move code common to client and server to common.go |
|
| 33 |
-const ( |
|
| 34 |
- APIVERSION = 1.9 |
|
| 35 |
- DEFAULTHTTPHOST = "127.0.0.1" |
|
| 36 |
- DEFAULTUNIXSOCKET = "/var/run/docker.sock" |
|
| 37 |
-) |
|
| 38 |
- |
|
| 39 |
-var ( |
|
| 40 |
- activationLock chan struct{}
|
|
| 41 |
-) |
|
| 42 |
- |
|
| 43 |
-func ValidateHost(val string) (string, error) {
|
|
| 44 |
- host, err := utils.ParseHost(DEFAULTHTTPHOST, DEFAULTUNIXSOCKET, val) |
|
| 45 |
- if err != nil {
|
|
| 46 |
- return val, err |
|
| 47 |
- } |
|
| 48 |
- return host, nil |
|
| 49 |
-} |
|
| 50 |
- |
|
| 51 |
-type HttpApiFunc func(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error |
|
| 52 |
- |
|
| 53 |
-func init() {
|
|
| 54 |
- engine.Register("serveapi", ServeApi)
|
|
| 55 |
-} |
|
| 56 |
- |
|
| 57 |
-func hijackServer(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) {
|
|
| 58 |
- conn, _, err := w.(http.Hijacker).Hijack() |
|
| 59 |
- if err != nil {
|
|
| 60 |
- return nil, nil, err |
|
| 61 |
- } |
|
| 62 |
- // Flush the options to make sure the client sets the raw mode |
|
| 63 |
- conn.Write([]byte{})
|
|
| 64 |
- return conn, conn, nil |
|
| 65 |
-} |
|
| 66 |
- |
|
| 67 |
-//If we don't do this, POST method without Content-type (even with empty body) will fail |
|
| 68 |
-func parseForm(r *http.Request) error {
|
|
| 69 |
- if r == nil {
|
|
| 70 |
- return nil |
|
| 71 |
- } |
|
| 72 |
- if err := r.ParseForm(); err != nil && !strings.HasPrefix(err.Error(), "mime:") {
|
|
| 73 |
- return err |
|
| 74 |
- } |
|
| 75 |
- return nil |
|
| 76 |
-} |
|
| 77 |
- |
|
| 78 |
-func parseMultipartForm(r *http.Request) error {
|
|
| 79 |
- if err := r.ParseMultipartForm(4096); err != nil && !strings.HasPrefix(err.Error(), "mime:") {
|
|
| 80 |
- return err |
|
| 81 |
- } |
|
| 82 |
- return nil |
|
| 83 |
-} |
|
| 84 |
- |
|
| 85 |
-func httpError(w http.ResponseWriter, err error) {
|
|
| 86 |
- statusCode := http.StatusInternalServerError |
|
| 87 |
- // FIXME: this is brittle and should not be necessary. |
|
| 88 |
- // If we need to differentiate between different possible error types, we should |
|
| 89 |
- // create appropriate error types with clearly defined meaning. |
|
| 90 |
- if strings.Contains(err.Error(), "No such") {
|
|
| 91 |
- statusCode = http.StatusNotFound |
|
| 92 |
- } else if strings.Contains(err.Error(), "Bad parameter") {
|
|
| 93 |
- statusCode = http.StatusBadRequest |
|
| 94 |
- } else if strings.Contains(err.Error(), "Conflict") {
|
|
| 95 |
- statusCode = http.StatusConflict |
|
| 96 |
- } else if strings.Contains(err.Error(), "Impossible") {
|
|
| 97 |
- statusCode = http.StatusNotAcceptable |
|
| 98 |
- } else if strings.Contains(err.Error(), "Wrong login/password") {
|
|
| 99 |
- statusCode = http.StatusUnauthorized |
|
| 100 |
- } else if strings.Contains(err.Error(), "hasn't been activated") {
|
|
| 101 |
- statusCode = http.StatusForbidden |
|
| 102 |
- } |
|
| 103 |
- |
|
| 104 |
- if err != nil {
|
|
| 105 |
- utils.Errorf("HTTP Error: statusCode=%d %s", statusCode, err.Error())
|
|
| 106 |
- http.Error(w, err.Error(), statusCode) |
|
| 107 |
- } |
|
| 108 |
-} |
|
| 109 |
- |
|
| 110 |
-func writeJSON(w http.ResponseWriter, code int, v engine.Env) error {
|
|
| 111 |
- w.Header().Set("Content-Type", "application/json")
|
|
| 112 |
- w.WriteHeader(code) |
|
| 113 |
- return v.Encode(w) |
|
| 114 |
-} |
|
| 115 |
- |
|
| 116 |
-func streamJSON(job *engine.Job, w http.ResponseWriter, flush bool) {
|
|
| 117 |
- w.Header().Set("Content-Type", "application/json")
|
|
| 118 |
- if flush {
|
|
| 119 |
- job.Stdout.Add(utils.NewWriteFlusher(w)) |
|
| 120 |
- } else {
|
|
| 121 |
- job.Stdout.Add(w) |
|
| 122 |
- } |
|
| 123 |
-} |
|
| 124 |
- |
|
| 125 |
-func getBoolParam(value string) (bool, error) {
|
|
| 126 |
- if value == "" {
|
|
| 127 |
- return false, nil |
|
| 128 |
- } |
|
| 129 |
- ret, err := strconv.ParseBool(value) |
|
| 130 |
- if err != nil {
|
|
| 131 |
- return false, fmt.Errorf("Bad parameter")
|
|
| 132 |
- } |
|
| 133 |
- return ret, nil |
|
| 134 |
-} |
|
| 135 |
- |
|
| 136 |
-//TODO remove, used on < 1.5 in getContainersJSON |
|
| 137 |
-func displayablePorts(ports *engine.Table) string {
|
|
| 138 |
- result := []string{}
|
|
| 139 |
- for _, port := range ports.Data {
|
|
| 140 |
- if port.Get("IP") == "" {
|
|
| 141 |
- result = append(result, fmt.Sprintf("%d/%s", port.GetInt("PublicPort"), port.Get("Type")))
|
|
| 142 |
- } else {
|
|
| 143 |
- result = append(result, fmt.Sprintf("%s:%d->%d/%s", port.Get("IP"), port.GetInt("PublicPort"), port.GetInt("PrivatePort"), port.Get("Type")))
|
|
| 144 |
- } |
|
| 145 |
- } |
|
| 146 |
- return strings.Join(result, ", ") |
|
| 147 |
-} |
|
| 148 |
- |
|
| 149 |
-func MatchesContentType(contentType, expectedType string) bool {
|
|
| 150 |
- mimetype, _, err := mime.ParseMediaType(contentType) |
|
| 151 |
- if err != nil {
|
|
| 152 |
- utils.Errorf("Error parsing media type: %s error: %s", contentType, err.Error())
|
|
| 153 |
- } |
|
| 154 |
- return err == nil && mimetype == expectedType |
|
| 155 |
-} |
|
| 156 |
- |
|
| 157 |
-func postAuth(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 158 |
- var ( |
|
| 159 |
- authConfig, err = ioutil.ReadAll(r.Body) |
|
| 160 |
- job = eng.Job("auth")
|
|
| 161 |
- status string |
|
| 162 |
- ) |
|
| 163 |
- if err != nil {
|
|
| 164 |
- return err |
|
| 165 |
- } |
|
| 166 |
- job.Setenv("authConfig", string(authConfig))
|
|
| 167 |
- job.Stdout.AddString(&status) |
|
| 168 |
- if err = job.Run(); err != nil {
|
|
| 169 |
- return err |
|
| 170 |
- } |
|
| 171 |
- if status != "" {
|
|
| 172 |
- var env engine.Env |
|
| 173 |
- env.Set("Status", status)
|
|
| 174 |
- return writeJSON(w, http.StatusOK, env) |
|
| 175 |
- } |
|
| 176 |
- w.WriteHeader(http.StatusNoContent) |
|
| 177 |
- return nil |
|
| 178 |
-} |
|
| 179 |
- |
|
| 180 |
-func getVersion(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 181 |
- w.Header().Set("Content-Type", "application/json")
|
|
| 182 |
- eng.ServeHTTP(w, r) |
|
| 183 |
- return nil |
|
| 184 |
-} |
|
| 185 |
- |
|
| 186 |
-func postContainersKill(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 187 |
- if vars == nil {
|
|
| 188 |
- return fmt.Errorf("Missing parameter")
|
|
| 189 |
- } |
|
| 190 |
- if err := parseForm(r); err != nil {
|
|
| 191 |
- return err |
|
| 192 |
- } |
|
| 193 |
- job := eng.Job("kill", vars["name"])
|
|
| 194 |
- if sig := r.Form.Get("signal"); sig != "" {
|
|
| 195 |
- job.Args = append(job.Args, sig) |
|
| 196 |
- } |
|
| 197 |
- if err := job.Run(); err != nil {
|
|
| 198 |
- return err |
|
| 199 |
- } |
|
| 200 |
- w.WriteHeader(http.StatusNoContent) |
|
| 201 |
- return nil |
|
| 202 |
-} |
|
| 203 |
- |
|
| 204 |
-func getContainersExport(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 205 |
- if vars == nil {
|
|
| 206 |
- return fmt.Errorf("Missing parameter")
|
|
| 207 |
- } |
|
| 208 |
- job := eng.Job("export", vars["name"])
|
|
| 209 |
- job.Stdout.Add(w) |
|
| 210 |
- if err := job.Run(); err != nil {
|
|
| 211 |
- return err |
|
| 212 |
- } |
|
| 213 |
- return nil |
|
| 214 |
-} |
|
| 215 |
- |
|
| 216 |
-func getImagesJSON(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 217 |
- if err := parseForm(r); err != nil {
|
|
| 218 |
- return err |
|
| 219 |
- } |
|
| 220 |
- |
|
| 221 |
- var ( |
|
| 222 |
- err error |
|
| 223 |
- outs *engine.Table |
|
| 224 |
- job = eng.Job("images")
|
|
| 225 |
- ) |
|
| 226 |
- |
|
| 227 |
- job.Setenv("filter", r.Form.Get("filter"))
|
|
| 228 |
- job.Setenv("all", r.Form.Get("all"))
|
|
| 229 |
- |
|
| 230 |
- if version >= 1.7 {
|
|
| 231 |
- streamJSON(job, w, false) |
|
| 232 |
- } else if outs, err = job.Stdout.AddListTable(); err != nil {
|
|
| 233 |
- return err |
|
| 234 |
- } |
|
| 235 |
- |
|
| 236 |
- if err := job.Run(); err != nil {
|
|
| 237 |
- return err |
|
| 238 |
- } |
|
| 239 |
- |
|
| 240 |
- if version < 1.7 && outs != nil { // Convert to legacy format
|
|
| 241 |
- outsLegacy := engine.NewTable("Created", 0)
|
|
| 242 |
- for _, out := range outs.Data {
|
|
| 243 |
- for _, repoTag := range out.GetList("RepoTags") {
|
|
| 244 |
- parts := strings.Split(repoTag, ":") |
|
| 245 |
- outLegacy := &engine.Env{}
|
|
| 246 |
- outLegacy.Set("Repository", parts[0])
|
|
| 247 |
- outLegacy.Set("Tag", parts[1])
|
|
| 248 |
- outLegacy.Set("Id", out.Get("Id"))
|
|
| 249 |
- outLegacy.SetInt64("Created", out.GetInt64("Created"))
|
|
| 250 |
- outLegacy.SetInt64("Size", out.GetInt64("Size"))
|
|
| 251 |
- outLegacy.SetInt64("VirtualSize", out.GetInt64("VirtualSize"))
|
|
| 252 |
- outsLegacy.Add(outLegacy) |
|
| 253 |
- } |
|
| 254 |
- } |
|
| 255 |
- w.Header().Set("Content-Type", "application/json")
|
|
| 256 |
- if _, err := outsLegacy.WriteListTo(w); err != nil {
|
|
| 257 |
- return err |
|
| 258 |
- } |
|
| 259 |
- } |
|
| 260 |
- return nil |
|
| 261 |
-} |
|
| 262 |
- |
|
| 263 |
-func getImagesViz(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 264 |
- if version > 1.6 {
|
|
| 265 |
- w.WriteHeader(http.StatusNotFound) |
|
| 266 |
- return fmt.Errorf("This is now implemented in the client.")
|
|
| 267 |
- } |
|
| 268 |
- eng.ServeHTTP(w, r) |
|
| 269 |
- return nil |
|
| 270 |
-} |
|
| 271 |
- |
|
| 272 |
-func getInfo(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 273 |
- w.Header().Set("Content-Type", "application/json")
|
|
| 274 |
- eng.ServeHTTP(w, r) |
|
| 275 |
- return nil |
|
| 276 |
-} |
|
| 277 |
- |
|
| 278 |
-func getEvents(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 279 |
- if err := parseForm(r); err != nil {
|
|
| 280 |
- return err |
|
| 281 |
- } |
|
| 282 |
- |
|
| 283 |
- var job = eng.Job("events", r.RemoteAddr)
|
|
| 284 |
- streamJSON(job, w, true) |
|
| 285 |
- job.Setenv("since", r.Form.Get("since"))
|
|
| 286 |
- return job.Run() |
|
| 287 |
-} |
|
| 288 |
- |
|
| 289 |
-func getImagesHistory(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 290 |
- if vars == nil {
|
|
| 291 |
- return fmt.Errorf("Missing parameter")
|
|
| 292 |
- } |
|
| 293 |
- |
|
| 294 |
- var job = eng.Job("history", vars["name"])
|
|
| 295 |
- streamJSON(job, w, false) |
|
| 296 |
- |
|
| 297 |
- if err := job.Run(); err != nil {
|
|
| 298 |
- return err |
|
| 299 |
- } |
|
| 300 |
- return nil |
|
| 301 |
-} |
|
| 302 |
- |
|
| 303 |
-func getContainersChanges(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 304 |
- if vars == nil {
|
|
| 305 |
- return fmt.Errorf("Missing parameter")
|
|
| 306 |
- } |
|
| 307 |
- var job = eng.Job("changes", vars["name"])
|
|
| 308 |
- streamJSON(job, w, false) |
|
| 309 |
- |
|
| 310 |
- return job.Run() |
|
| 311 |
-} |
|
| 312 |
- |
|
| 313 |
-func getContainersTop(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 314 |
- if version < 1.4 {
|
|
| 315 |
- return fmt.Errorf("top was improved a lot since 1.3, Please upgrade your docker client.")
|
|
| 316 |
- } |
|
| 317 |
- if vars == nil {
|
|
| 318 |
- return fmt.Errorf("Missing parameter")
|
|
| 319 |
- } |
|
| 320 |
- if err := parseForm(r); err != nil {
|
|
| 321 |
- return err |
|
| 322 |
- } |
|
| 323 |
- |
|
| 324 |
- job := eng.Job("top", vars["name"], r.Form.Get("ps_args"))
|
|
| 325 |
- streamJSON(job, w, false) |
|
| 326 |
- return job.Run() |
|
| 327 |
-} |
|
| 328 |
- |
|
| 329 |
-func getContainersJSON(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 330 |
- if err := parseForm(r); err != nil {
|
|
| 331 |
- return err |
|
| 332 |
- } |
|
| 333 |
- var ( |
|
| 334 |
- err error |
|
| 335 |
- outs *engine.Table |
|
| 336 |
- job = eng.Job("containers")
|
|
| 337 |
- ) |
|
| 338 |
- |
|
| 339 |
- job.Setenv("all", r.Form.Get("all"))
|
|
| 340 |
- job.Setenv("size", r.Form.Get("size"))
|
|
| 341 |
- job.Setenv("since", r.Form.Get("since"))
|
|
| 342 |
- job.Setenv("before", r.Form.Get("before"))
|
|
| 343 |
- job.Setenv("limit", r.Form.Get("limit"))
|
|
| 344 |
- |
|
| 345 |
- if version >= 1.5 {
|
|
| 346 |
- streamJSON(job, w, false) |
|
| 347 |
- } else if outs, err = job.Stdout.AddTable(); err != nil {
|
|
| 348 |
- return err |
|
| 349 |
- } |
|
| 350 |
- if err = job.Run(); err != nil {
|
|
| 351 |
- return err |
|
| 352 |
- } |
|
| 353 |
- if version < 1.5 { // Convert to legacy format
|
|
| 354 |
- for _, out := range outs.Data {
|
|
| 355 |
- ports := engine.NewTable("", 0)
|
|
| 356 |
- ports.ReadListFrom([]byte(out.Get("Ports")))
|
|
| 357 |
- out.Set("Ports", displayablePorts(ports))
|
|
| 358 |
- } |
|
| 359 |
- w.Header().Set("Content-Type", "application/json")
|
|
| 360 |
- if _, err = outs.WriteListTo(w); err != nil {
|
|
| 361 |
- return err |
|
| 362 |
- } |
|
| 363 |
- } |
|
| 364 |
- return nil |
|
| 365 |
-} |
|
| 366 |
- |
|
| 367 |
-func postImagesTag(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 368 |
- if err := parseForm(r); err != nil {
|
|
| 369 |
- return err |
|
| 370 |
- } |
|
| 371 |
- if vars == nil {
|
|
| 372 |
- return fmt.Errorf("Missing parameter")
|
|
| 373 |
- } |
|
| 374 |
- |
|
| 375 |
- job := eng.Job("tag", vars["name"], r.Form.Get("repo"), r.Form.Get("tag"))
|
|
| 376 |
- job.Setenv("force", r.Form.Get("force"))
|
|
| 377 |
- if err := job.Run(); err != nil {
|
|
| 378 |
- return err |
|
| 379 |
- } |
|
| 380 |
- w.WriteHeader(http.StatusCreated) |
|
| 381 |
- return nil |
|
| 382 |
-} |
|
| 383 |
- |
|
| 384 |
-func postCommit(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 385 |
- if err := parseForm(r); err != nil {
|
|
| 386 |
- return err |
|
| 387 |
- } |
|
| 388 |
- var ( |
|
| 389 |
- config engine.Env |
|
| 390 |
- env engine.Env |
|
| 391 |
- job = eng.Job("commit", r.Form.Get("container"))
|
|
| 392 |
- ) |
|
| 393 |
- if err := config.Decode(r.Body); err != nil {
|
|
| 394 |
- utils.Errorf("%s", err)
|
|
| 395 |
- } |
|
| 396 |
- |
|
| 397 |
- job.Setenv("repo", r.Form.Get("repo"))
|
|
| 398 |
- job.Setenv("tag", r.Form.Get("tag"))
|
|
| 399 |
- job.Setenv("author", r.Form.Get("author"))
|
|
| 400 |
- job.Setenv("comment", r.Form.Get("comment"))
|
|
| 401 |
- job.SetenvSubEnv("config", &config)
|
|
| 402 |
- |
|
| 403 |
- var id string |
|
| 404 |
- job.Stdout.AddString(&id) |
|
| 405 |
- if err := job.Run(); err != nil {
|
|
| 406 |
- return err |
|
| 407 |
- } |
|
| 408 |
- env.Set("Id", id)
|
|
| 409 |
- return writeJSON(w, http.StatusCreated, env) |
|
| 410 |
-} |
|
| 411 |
- |
|
| 412 |
-// Creates an image from Pull or from Import |
|
| 413 |
-func postImagesCreate(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 414 |
- if err := parseForm(r); err != nil {
|
|
| 415 |
- return err |
|
| 416 |
- } |
|
| 417 |
- |
|
| 418 |
- var ( |
|
| 419 |
- image = r.Form.Get("fromImage")
|
|
| 420 |
- tag = r.Form.Get("tag")
|
|
| 421 |
- job *engine.Job |
|
| 422 |
- ) |
|
| 423 |
- authEncoded := r.Header.Get("X-Registry-Auth")
|
|
| 424 |
- authConfig := &auth.AuthConfig{}
|
|
| 425 |
- if authEncoded != "" {
|
|
| 426 |
- authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) |
|
| 427 |
- if err := json.NewDecoder(authJson).Decode(authConfig); err != nil {
|
|
| 428 |
- // for a pull it is not an error if no auth was given |
|
| 429 |
- // to increase compatibility with the existing api it is defaulting to be empty |
|
| 430 |
- authConfig = &auth.AuthConfig{}
|
|
| 431 |
- } |
|
| 432 |
- } |
|
| 433 |
- if version > 1.0 {
|
|
| 434 |
- w.Header().Set("Content-Type", "application/json")
|
|
| 435 |
- } |
|
| 436 |
- if image != "" { //pull
|
|
| 437 |
- metaHeaders := map[string][]string{}
|
|
| 438 |
- for k, v := range r.Header {
|
|
| 439 |
- if strings.HasPrefix(k, "X-Meta-") {
|
|
| 440 |
- metaHeaders[k] = v |
|
| 441 |
- } |
|
| 442 |
- } |
|
| 443 |
- job = eng.Job("pull", r.Form.Get("fromImage"), tag)
|
|
| 444 |
- job.SetenvBool("parallel", version > 1.3)
|
|
| 445 |
- job.SetenvJson("metaHeaders", metaHeaders)
|
|
| 446 |
- job.SetenvJson("authConfig", authConfig)
|
|
| 447 |
- } else { //import
|
|
| 448 |
- job = eng.Job("import", r.Form.Get("fromSrc"), r.Form.Get("repo"), tag)
|
|
| 449 |
- job.Stdin.Add(r.Body) |
|
| 450 |
- } |
|
| 451 |
- |
|
| 452 |
- if version > 1.0 {
|
|
| 453 |
- job.SetenvBool("json", true)
|
|
| 454 |
- streamJSON(job, w, true) |
|
| 455 |
- } else {
|
|
| 456 |
- job.Stdout.Add(utils.NewWriteFlusher(w)) |
|
| 457 |
- } |
|
| 458 |
- if err := job.Run(); err != nil {
|
|
| 459 |
- if !job.Stdout.Used() {
|
|
| 460 |
- return err |
|
| 461 |
- } |
|
| 462 |
- sf := utils.NewStreamFormatter(version > 1.0) |
|
| 463 |
- w.Write(sf.FormatError(err)) |
|
| 464 |
- } |
|
| 465 |
- |
|
| 466 |
- return nil |
|
| 467 |
-} |
|
| 468 |
- |
|
| 469 |
-func getImagesSearch(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 470 |
- if err := parseForm(r); err != nil {
|
|
| 471 |
- return err |
|
| 472 |
- } |
|
| 473 |
- var ( |
|
| 474 |
- authEncoded = r.Header.Get("X-Registry-Auth")
|
|
| 475 |
- authConfig = &auth.AuthConfig{}
|
|
| 476 |
- metaHeaders = map[string][]string{}
|
|
| 477 |
- ) |
|
| 478 |
- |
|
| 479 |
- if authEncoded != "" {
|
|
| 480 |
- authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) |
|
| 481 |
- if err := json.NewDecoder(authJson).Decode(authConfig); err != nil {
|
|
| 482 |
- // for a search it is not an error if no auth was given |
|
| 483 |
- // to increase compatibility with the existing api it is defaulting to be empty |
|
| 484 |
- authConfig = &auth.AuthConfig{}
|
|
| 485 |
- } |
|
| 486 |
- } |
|
| 487 |
- for k, v := range r.Header {
|
|
| 488 |
- if strings.HasPrefix(k, "X-Meta-") {
|
|
| 489 |
- metaHeaders[k] = v |
|
| 490 |
- } |
|
| 491 |
- } |
|
| 492 |
- |
|
| 493 |
- var job = eng.Job("search", r.Form.Get("term"))
|
|
| 494 |
- job.SetenvJson("metaHeaders", metaHeaders)
|
|
| 495 |
- job.SetenvJson("authConfig", authConfig)
|
|
| 496 |
- streamJSON(job, w, false) |
|
| 497 |
- |
|
| 498 |
- return job.Run() |
|
| 499 |
-} |
|
| 500 |
- |
|
| 501 |
-func postImagesInsert(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 502 |
- if err := parseForm(r); err != nil {
|
|
| 503 |
- return err |
|
| 504 |
- } |
|
| 505 |
- if vars == nil {
|
|
| 506 |
- return fmt.Errorf("Missing parameter")
|
|
| 507 |
- } |
|
| 508 |
- if version > 1.0 {
|
|
| 509 |
- w.Header().Set("Content-Type", "application/json")
|
|
| 510 |
- } |
|
| 511 |
- |
|
| 512 |
- job := eng.Job("insert", vars["name"], r.Form.Get("url"), r.Form.Get("path"))
|
|
| 513 |
- if version > 1.0 {
|
|
| 514 |
- job.SetenvBool("json", true)
|
|
| 515 |
- streamJSON(job, w, false) |
|
| 516 |
- } else {
|
|
| 517 |
- job.Stdout.Add(w) |
|
| 518 |
- } |
|
| 519 |
- if err := job.Run(); err != nil {
|
|
| 520 |
- if !job.Stdout.Used() {
|
|
| 521 |
- return err |
|
| 522 |
- } |
|
| 523 |
- sf := utils.NewStreamFormatter(version > 1.0) |
|
| 524 |
- w.Write(sf.FormatError(err)) |
|
| 525 |
- } |
|
| 526 |
- |
|
| 527 |
- return nil |
|
| 528 |
-} |
|
| 529 |
- |
|
| 530 |
-func postImagesPush(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 531 |
- if vars == nil {
|
|
| 532 |
- return fmt.Errorf("Missing parameter")
|
|
| 533 |
- } |
|
| 534 |
- |
|
| 535 |
- metaHeaders := map[string][]string{}
|
|
| 536 |
- for k, v := range r.Header {
|
|
| 537 |
- if strings.HasPrefix(k, "X-Meta-") {
|
|
| 538 |
- metaHeaders[k] = v |
|
| 539 |
- } |
|
| 540 |
- } |
|
| 541 |
- if err := parseForm(r); err != nil {
|
|
| 542 |
- return err |
|
| 543 |
- } |
|
| 544 |
- authConfig := &auth.AuthConfig{}
|
|
| 545 |
- |
|
| 546 |
- authEncoded := r.Header.Get("X-Registry-Auth")
|
|
| 547 |
- if authEncoded != "" {
|
|
| 548 |
- // the new format is to handle the authConfig as a header |
|
| 549 |
- authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) |
|
| 550 |
- if err := json.NewDecoder(authJson).Decode(authConfig); err != nil {
|
|
| 551 |
- // to increase compatibility to existing api it is defaulting to be empty |
|
| 552 |
- authConfig = &auth.AuthConfig{}
|
|
| 553 |
- } |
|
| 554 |
- } else {
|
|
| 555 |
- // the old format is supported for compatibility if there was no authConfig header |
|
| 556 |
- if err := json.NewDecoder(r.Body).Decode(authConfig); err != nil {
|
|
| 557 |
- return err |
|
| 558 |
- } |
|
| 559 |
- } |
|
| 560 |
- |
|
| 561 |
- if version > 1.0 {
|
|
| 562 |
- w.Header().Set("Content-Type", "application/json")
|
|
| 563 |
- } |
|
| 564 |
- job := eng.Job("push", vars["name"])
|
|
| 565 |
- job.SetenvJson("metaHeaders", metaHeaders)
|
|
| 566 |
- job.SetenvJson("authConfig", authConfig)
|
|
| 567 |
- if version > 1.0 {
|
|
| 568 |
- job.SetenvBool("json", true)
|
|
| 569 |
- streamJSON(job, w, true) |
|
| 570 |
- } else {
|
|
| 571 |
- job.Stdout.Add(utils.NewWriteFlusher(w)) |
|
| 572 |
- } |
|
| 573 |
- |
|
| 574 |
- if err := job.Run(); err != nil {
|
|
| 575 |
- if !job.Stdout.Used() {
|
|
| 576 |
- return err |
|
| 577 |
- } |
|
| 578 |
- sf := utils.NewStreamFormatter(version > 1.0) |
|
| 579 |
- w.Write(sf.FormatError(err)) |
|
| 580 |
- } |
|
| 581 |
- return nil |
|
| 582 |
-} |
|
| 583 |
- |
|
| 584 |
-func getImagesGet(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 585 |
- if vars == nil {
|
|
| 586 |
- return fmt.Errorf("Missing parameter")
|
|
| 587 |
- } |
|
| 588 |
- if version > 1.0 {
|
|
| 589 |
- w.Header().Set("Content-Type", "application/x-tar")
|
|
| 590 |
- } |
|
| 591 |
- job := eng.Job("image_export", vars["name"])
|
|
| 592 |
- job.Stdout.Add(w) |
|
| 593 |
- return job.Run() |
|
| 594 |
-} |
|
| 595 |
- |
|
| 596 |
-func postImagesLoad(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 597 |
- job := eng.Job("load")
|
|
| 598 |
- job.Stdin.Add(r.Body) |
|
| 599 |
- return job.Run() |
|
| 600 |
-} |
|
| 601 |
- |
|
| 602 |
-func postContainersCreate(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 603 |
- if err := parseForm(r); err != nil {
|
|
| 604 |
- return nil |
|
| 605 |
- } |
|
| 606 |
- var ( |
|
| 607 |
- out engine.Env |
|
| 608 |
- job = eng.Job("create", r.Form.Get("name"))
|
|
| 609 |
- outWarnings []string |
|
| 610 |
- outId string |
|
| 611 |
- warnings = bytes.NewBuffer(nil) |
|
| 612 |
- ) |
|
| 613 |
- if err := job.DecodeEnv(r.Body); err != nil {
|
|
| 614 |
- return err |
|
| 615 |
- } |
|
| 616 |
- // Read container ID from the first line of stdout |
|
| 617 |
- job.Stdout.AddString(&outId) |
|
| 618 |
- // Read warnings from stderr |
|
| 619 |
- job.Stderr.Add(warnings) |
|
| 620 |
- if err := job.Run(); err != nil {
|
|
| 621 |
- return err |
|
| 622 |
- } |
|
| 623 |
- // Parse warnings from stderr |
|
| 624 |
- scanner := bufio.NewScanner(warnings) |
|
| 625 |
- for scanner.Scan() {
|
|
| 626 |
- outWarnings = append(outWarnings, scanner.Text()) |
|
| 627 |
- } |
|
| 628 |
- out.Set("Id", outId)
|
|
| 629 |
- out.SetList("Warnings", outWarnings)
|
|
| 630 |
- return writeJSON(w, http.StatusCreated, out) |
|
| 631 |
-} |
|
| 632 |
- |
|
| 633 |
-func postContainersRestart(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 634 |
- if err := parseForm(r); err != nil {
|
|
| 635 |
- return err |
|
| 636 |
- } |
|
| 637 |
- if vars == nil {
|
|
| 638 |
- return fmt.Errorf("Missing parameter")
|
|
| 639 |
- } |
|
| 640 |
- job := eng.Job("restart", vars["name"])
|
|
| 641 |
- job.Setenv("t", r.Form.Get("t"))
|
|
| 642 |
- if err := job.Run(); err != nil {
|
|
| 643 |
- return err |
|
| 644 |
- } |
|
| 645 |
- w.WriteHeader(http.StatusNoContent) |
|
| 646 |
- return nil |
|
| 647 |
-} |
|
| 648 |
- |
|
| 649 |
-func deleteContainers(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 650 |
- if err := parseForm(r); err != nil {
|
|
| 651 |
- return err |
|
| 652 |
- } |
|
| 653 |
- if vars == nil {
|
|
| 654 |
- return fmt.Errorf("Missing parameter")
|
|
| 655 |
- } |
|
| 656 |
- job := eng.Job("container_delete", vars["name"])
|
|
| 657 |
- job.Setenv("removeVolume", r.Form.Get("v"))
|
|
| 658 |
- job.Setenv("removeLink", r.Form.Get("link"))
|
|
| 659 |
- if err := job.Run(); err != nil {
|
|
| 660 |
- return err |
|
| 661 |
- } |
|
| 662 |
- w.WriteHeader(http.StatusNoContent) |
|
| 663 |
- return nil |
|
| 664 |
-} |
|
| 665 |
- |
|
| 666 |
-func deleteImages(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 667 |
- if err := parseForm(r); err != nil {
|
|
| 668 |
- return err |
|
| 669 |
- } |
|
| 670 |
- if vars == nil {
|
|
| 671 |
- return fmt.Errorf("Missing parameter")
|
|
| 672 |
- } |
|
| 673 |
- var job = eng.Job("image_delete", vars["name"])
|
|
| 674 |
- streamJSON(job, w, false) |
|
| 675 |
- job.SetenvBool("autoPrune", version > 1.1)
|
|
| 676 |
- |
|
| 677 |
- return job.Run() |
|
| 678 |
-} |
|
| 679 |
- |
|
| 680 |
-func postContainersStart(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 681 |
- if vars == nil {
|
|
| 682 |
- return fmt.Errorf("Missing parameter")
|
|
| 683 |
- } |
|
| 684 |
- name := vars["name"] |
|
| 685 |
- job := eng.Job("start", name)
|
|
| 686 |
- // allow a nil body for backwards compatibility |
|
| 687 |
- if r.Body != nil {
|
|
| 688 |
- if MatchesContentType(r.Header.Get("Content-Type"), "application/json") {
|
|
| 689 |
- if err := job.DecodeEnv(r.Body); err != nil {
|
|
| 690 |
- return err |
|
| 691 |
- } |
|
| 692 |
- } |
|
| 693 |
- } |
|
| 694 |
- if err := job.Run(); err != nil {
|
|
| 695 |
- return err |
|
| 696 |
- } |
|
| 697 |
- w.WriteHeader(http.StatusNoContent) |
|
| 698 |
- return nil |
|
| 699 |
-} |
|
| 700 |
- |
|
| 701 |
-func postContainersStop(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 702 |
- if err := parseForm(r); err != nil {
|
|
| 703 |
- return err |
|
| 704 |
- } |
|
| 705 |
- if vars == nil {
|
|
| 706 |
- return fmt.Errorf("Missing parameter")
|
|
| 707 |
- } |
|
| 708 |
- job := eng.Job("stop", vars["name"])
|
|
| 709 |
- job.Setenv("t", r.Form.Get("t"))
|
|
| 710 |
- if err := job.Run(); err != nil {
|
|
| 711 |
- return err |
|
| 712 |
- } |
|
| 713 |
- w.WriteHeader(http.StatusNoContent) |
|
| 714 |
- return nil |
|
| 715 |
-} |
|
| 716 |
- |
|
| 717 |
-func postContainersWait(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 718 |
- if vars == nil {
|
|
| 719 |
- return fmt.Errorf("Missing parameter")
|
|
| 720 |
- } |
|
| 721 |
- var ( |
|
| 722 |
- env engine.Env |
|
| 723 |
- status string |
|
| 724 |
- job = eng.Job("wait", vars["name"])
|
|
| 725 |
- ) |
|
| 726 |
- job.Stdout.AddString(&status) |
|
| 727 |
- if err := job.Run(); err != nil {
|
|
| 728 |
- return err |
|
| 729 |
- } |
|
| 730 |
- // Parse a 16-bit encoded integer to map typical unix exit status. |
|
| 731 |
- _, err := strconv.ParseInt(status, 10, 16) |
|
| 732 |
- if err != nil {
|
|
| 733 |
- return err |
|
| 734 |
- } |
|
| 735 |
- env.Set("StatusCode", status)
|
|
| 736 |
- return writeJSON(w, http.StatusOK, env) |
|
| 737 |
-} |
|
| 738 |
- |
|
| 739 |
-func postContainersResize(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 740 |
- if err := parseForm(r); err != nil {
|
|
| 741 |
- return err |
|
| 742 |
- } |
|
| 743 |
- if vars == nil {
|
|
| 744 |
- return fmt.Errorf("Missing parameter")
|
|
| 745 |
- } |
|
| 746 |
- if err := eng.Job("resize", vars["name"], r.Form.Get("h"), r.Form.Get("w")).Run(); err != nil {
|
|
| 747 |
- return err |
|
| 748 |
- } |
|
| 749 |
- return nil |
|
| 750 |
-} |
|
| 751 |
- |
|
| 752 |
-func postContainersAttach(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 753 |
- if err := parseForm(r); err != nil {
|
|
| 754 |
- return err |
|
| 755 |
- } |
|
| 756 |
- if vars == nil {
|
|
| 757 |
- return fmt.Errorf("Missing parameter")
|
|
| 758 |
- } |
|
| 759 |
- |
|
| 760 |
- var ( |
|
| 761 |
- job = eng.Job("inspect", vars["name"], "container")
|
|
| 762 |
- c, err = job.Stdout.AddEnv() |
|
| 763 |
- ) |
|
| 764 |
- if err != nil {
|
|
| 765 |
- return err |
|
| 766 |
- } |
|
| 767 |
- if err = job.Run(); err != nil {
|
|
| 768 |
- return err |
|
| 769 |
- } |
|
| 770 |
- |
|
| 771 |
- inStream, outStream, err := hijackServer(w) |
|
| 772 |
- if err != nil {
|
|
| 773 |
- return err |
|
| 774 |
- } |
|
| 775 |
- defer func() {
|
|
| 776 |
- if tcpc, ok := inStream.(*net.TCPConn); ok {
|
|
| 777 |
- tcpc.CloseWrite() |
|
| 778 |
- } else {
|
|
| 779 |
- inStream.Close() |
|
| 780 |
- } |
|
| 781 |
- }() |
|
| 782 |
- defer func() {
|
|
| 783 |
- if tcpc, ok := outStream.(*net.TCPConn); ok {
|
|
| 784 |
- tcpc.CloseWrite() |
|
| 785 |
- } else if closer, ok := outStream.(io.Closer); ok {
|
|
| 786 |
- closer.Close() |
|
| 787 |
- } |
|
| 788 |
- }() |
|
| 789 |
- |
|
| 790 |
- var errStream io.Writer |
|
| 791 |
- |
|
| 792 |
- fmt.Fprintf(outStream, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n") |
|
| 793 |
- |
|
| 794 |
- if c.GetSubEnv("Config") != nil && !c.GetSubEnv("Config").GetBool("Tty") && version >= 1.6 {
|
|
| 795 |
- errStream = utils.NewStdWriter(outStream, utils.Stderr) |
|
| 796 |
- outStream = utils.NewStdWriter(outStream, utils.Stdout) |
|
| 797 |
- } else {
|
|
| 798 |
- errStream = outStream |
|
| 799 |
- } |
|
| 800 |
- |
|
| 801 |
- job = eng.Job("attach", vars["name"])
|
|
| 802 |
- job.Setenv("logs", r.Form.Get("logs"))
|
|
| 803 |
- job.Setenv("stream", r.Form.Get("stream"))
|
|
| 804 |
- job.Setenv("stdin", r.Form.Get("stdin"))
|
|
| 805 |
- job.Setenv("stdout", r.Form.Get("stdout"))
|
|
| 806 |
- job.Setenv("stderr", r.Form.Get("stderr"))
|
|
| 807 |
- job.Stdin.Add(inStream) |
|
| 808 |
- job.Stdout.Add(outStream) |
|
| 809 |
- job.Stderr.Set(errStream) |
|
| 810 |
- if err := job.Run(); err != nil {
|
|
| 811 |
- fmt.Fprintf(outStream, "Error: %s\n", err) |
|
| 812 |
- |
|
| 813 |
- } |
|
| 814 |
- return nil |
|
| 815 |
-} |
|
| 816 |
- |
|
| 817 |
-func wsContainersAttach(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 818 |
- if err := parseForm(r); err != nil {
|
|
| 819 |
- return err |
|
| 820 |
- } |
|
| 821 |
- if vars == nil {
|
|
| 822 |
- return fmt.Errorf("Missing parameter")
|
|
| 823 |
- } |
|
| 824 |
- |
|
| 825 |
- if err := eng.Job("inspect", vars["name"], "container").Run(); err != nil {
|
|
| 826 |
- return err |
|
| 827 |
- } |
|
| 828 |
- |
|
| 829 |
- h := websocket.Handler(func(ws *websocket.Conn) {
|
|
| 830 |
- defer ws.Close() |
|
| 831 |
- job := eng.Job("attach", vars["name"])
|
|
| 832 |
- job.Setenv("logs", r.Form.Get("logs"))
|
|
| 833 |
- job.Setenv("stream", r.Form.Get("stream"))
|
|
| 834 |
- job.Setenv("stdin", r.Form.Get("stdin"))
|
|
| 835 |
- job.Setenv("stdout", r.Form.Get("stdout"))
|
|
| 836 |
- job.Setenv("stderr", r.Form.Get("stderr"))
|
|
| 837 |
- job.Stdin.Add(ws) |
|
| 838 |
- job.Stdout.Add(ws) |
|
| 839 |
- job.Stderr.Set(ws) |
|
| 840 |
- if err := job.Run(); err != nil {
|
|
| 841 |
- utils.Errorf("Error: %s", err)
|
|
| 842 |
- } |
|
| 843 |
- }) |
|
| 844 |
- h.ServeHTTP(w, r) |
|
| 845 |
- |
|
| 846 |
- return nil |
|
| 847 |
-} |
|
| 848 |
- |
|
| 849 |
-func getContainersByName(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 850 |
- if vars == nil {
|
|
| 851 |
- return fmt.Errorf("Missing parameter")
|
|
| 852 |
- } |
|
| 853 |
- var job = eng.Job("inspect", vars["name"], "container")
|
|
| 854 |
- streamJSON(job, w, false) |
|
| 855 |
- job.SetenvBool("conflict", true) //conflict=true to detect conflict between containers and images in the job
|
|
| 856 |
- return job.Run() |
|
| 857 |
-} |
|
| 858 |
- |
|
| 859 |
-func getImagesByName(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 860 |
- if vars == nil {
|
|
| 861 |
- return fmt.Errorf("Missing parameter")
|
|
| 862 |
- } |
|
| 863 |
- var job = eng.Job("inspect", vars["name"], "image")
|
|
| 864 |
- streamJSON(job, w, false) |
|
| 865 |
- job.SetenvBool("conflict", true) //conflict=true to detect conflict between containers and images in the job
|
|
| 866 |
- return job.Run() |
|
| 867 |
-} |
|
| 868 |
- |
|
| 869 |
-func postBuild(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 870 |
- if version < 1.3 {
|
|
| 871 |
- return fmt.Errorf("Multipart upload for build is no longer supported. Please upgrade your docker client.")
|
|
| 872 |
- } |
|
| 873 |
- var ( |
|
| 874 |
- authEncoded = r.Header.Get("X-Registry-Auth")
|
|
| 875 |
- authConfig = &auth.AuthConfig{}
|
|
| 876 |
- configFileEncoded = r.Header.Get("X-Registry-Config")
|
|
| 877 |
- configFile = &auth.ConfigFile{}
|
|
| 878 |
- job = eng.Job("build")
|
|
| 879 |
- ) |
|
| 880 |
- |
|
| 881 |
- // This block can be removed when API versions prior to 1.9 are deprecated. |
|
| 882 |
- // Both headers will be parsed and sent along to the daemon, but if a non-empty |
|
| 883 |
- // ConfigFile is present, any value provided as an AuthConfig directly will |
|
| 884 |
- // be overridden. See BuildFile::CmdFrom for details. |
|
| 885 |
- if version < 1.9 && authEncoded != "" {
|
|
| 886 |
- authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) |
|
| 887 |
- if err := json.NewDecoder(authJson).Decode(authConfig); err != nil {
|
|
| 888 |
- // for a pull it is not an error if no auth was given |
|
| 889 |
- // to increase compatibility with the existing api it is defaulting to be empty |
|
| 890 |
- authConfig = &auth.AuthConfig{}
|
|
| 891 |
- } |
|
| 892 |
- } |
|
| 893 |
- |
|
| 894 |
- if configFileEncoded != "" {
|
|
| 895 |
- configFileJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(configFileEncoded)) |
|
| 896 |
- if err := json.NewDecoder(configFileJson).Decode(configFile); err != nil {
|
|
| 897 |
- // for a pull it is not an error if no auth was given |
|
| 898 |
- // to increase compatibility with the existing api it is defaulting to be empty |
|
| 899 |
- configFile = &auth.ConfigFile{}
|
|
| 900 |
- } |
|
| 901 |
- } |
|
| 902 |
- |
|
| 903 |
- if version >= 1.8 {
|
|
| 904 |
- job.SetenvBool("json", true)
|
|
| 905 |
- streamJSON(job, w, true) |
|
| 906 |
- } else {
|
|
| 907 |
- job.Stdout.Add(utils.NewWriteFlusher(w)) |
|
| 908 |
- } |
|
| 909 |
- job.Stdin.Add(r.Body) |
|
| 910 |
- job.Setenv("remote", r.FormValue("remote"))
|
|
| 911 |
- job.Setenv("t", r.FormValue("t"))
|
|
| 912 |
- job.Setenv("q", r.FormValue("q"))
|
|
| 913 |
- job.Setenv("nocache", r.FormValue("nocache"))
|
|
| 914 |
- job.Setenv("rm", r.FormValue("rm"))
|
|
| 915 |
- job.SetenvJson("authConfig", authConfig)
|
|
| 916 |
- job.SetenvJson("configFile", configFile)
|
|
| 917 |
- |
|
| 918 |
- if err := job.Run(); err != nil {
|
|
| 919 |
- if !job.Stdout.Used() {
|
|
| 920 |
- return err |
|
| 921 |
- } |
|
| 922 |
- sf := utils.NewStreamFormatter(version >= 1.8) |
|
| 923 |
- w.Write(sf.FormatError(err)) |
|
| 924 |
- } |
|
| 925 |
- return nil |
|
| 926 |
-} |
|
| 927 |
- |
|
| 928 |
-func postContainersCopy(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 929 |
- if vars == nil {
|
|
| 930 |
- return fmt.Errorf("Missing parameter")
|
|
| 931 |
- } |
|
| 932 |
- |
|
| 933 |
- var copyData engine.Env |
|
| 934 |
- |
|
| 935 |
- if contentType := r.Header.Get("Content-Type"); contentType == "application/json" {
|
|
| 936 |
- if err := copyData.Decode(r.Body); err != nil {
|
|
| 937 |
- return err |
|
| 938 |
- } |
|
| 939 |
- } else {
|
|
| 940 |
- return fmt.Errorf("Content-Type not supported: %s", contentType)
|
|
| 941 |
- } |
|
| 942 |
- |
|
| 943 |
- if copyData.Get("Resource") == "" {
|
|
| 944 |
- return fmt.Errorf("Path cannot be empty")
|
|
| 945 |
- } |
|
| 946 |
- if copyData.Get("Resource")[0] == '/' {
|
|
| 947 |
- copyData.Set("Resource", copyData.Get("Resource")[1:])
|
|
| 948 |
- } |
|
| 949 |
- |
|
| 950 |
- job := eng.Job("container_copy", vars["name"], copyData.Get("Resource"))
|
|
| 951 |
- streamJSON(job, w, false) |
|
| 952 |
- if err := job.Run(); err != nil {
|
|
| 953 |
- utils.Errorf("%s", err.Error())
|
|
| 954 |
- if strings.Contains(err.Error(), "No such container") {
|
|
| 955 |
- w.WriteHeader(http.StatusNotFound) |
|
| 956 |
- } |
|
| 957 |
- } |
|
| 958 |
- return nil |
|
| 959 |
-} |
|
| 960 |
- |
|
| 961 |
-func optionsHandler(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 962 |
- w.WriteHeader(http.StatusOK) |
|
| 963 |
- return nil |
|
| 964 |
-} |
|
| 965 |
-func writeCorsHeaders(w http.ResponseWriter, r *http.Request) {
|
|
| 966 |
- w.Header().Add("Access-Control-Allow-Origin", "*")
|
|
| 967 |
- w.Header().Add("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept")
|
|
| 968 |
- w.Header().Add("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, OPTIONS")
|
|
| 969 |
-} |
|
| 970 |
- |
|
| 971 |
-func makeHttpHandler(eng *engine.Engine, logging bool, localMethod string, localRoute string, handlerFunc HttpApiFunc, enableCors bool, dockerVersion string) http.HandlerFunc {
|
|
| 972 |
- return func(w http.ResponseWriter, r *http.Request) {
|
|
| 973 |
- // log the request |
|
| 974 |
- utils.Debugf("Calling %s %s", localMethod, localRoute)
|
|
| 975 |
- |
|
| 976 |
- if logging {
|
|
| 977 |
- log.Println(r.Method, r.RequestURI) |
|
| 978 |
- } |
|
| 979 |
- |
|
| 980 |
- if strings.Contains(r.Header.Get("User-Agent"), "Docker-Client/") {
|
|
| 981 |
- userAgent := strings.Split(r.Header.Get("User-Agent"), "/")
|
|
| 982 |
- if len(userAgent) == 2 && userAgent[1] != dockerVersion {
|
|
| 983 |
- utils.Debugf("Warning: client and server don't have the same version (client: %s, server: %s)", userAgent[1], dockerVersion)
|
|
| 984 |
- } |
|
| 985 |
- } |
|
| 986 |
- version, err := strconv.ParseFloat(mux.Vars(r)["version"], 64) |
|
| 987 |
- if err != nil {
|
|
| 988 |
- version = APIVERSION |
|
| 989 |
- } |
|
| 990 |
- if enableCors {
|
|
| 991 |
- writeCorsHeaders(w, r) |
|
| 992 |
- } |
|
| 993 |
- |
|
| 994 |
- if version == 0 || version > APIVERSION {
|
|
| 995 |
- http.Error(w, fmt.Errorf("client and server don't have same version (client : %g, server: %g)", version, APIVERSION).Error(), http.StatusNotFound)
|
|
| 996 |
- return |
|
| 997 |
- } |
|
| 998 |
- |
|
| 999 |
- if err := handlerFunc(eng, version, w, r, mux.Vars(r)); err != nil {
|
|
| 1000 |
- utils.Errorf("Error: %s", err)
|
|
| 1001 |
- httpError(w, err) |
|
| 1002 |
- } |
|
| 1003 |
- } |
|
| 1004 |
-} |
|
| 1005 |
- |
|
| 1006 |
-// Replicated from expvar.go as not public. |
|
| 1007 |
-func expvarHandler(w http.ResponseWriter, r *http.Request) {
|
|
| 1008 |
- w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
|
| 1009 |
- fmt.Fprintf(w, "{\n")
|
|
| 1010 |
- first := true |
|
| 1011 |
- expvar.Do(func(kv expvar.KeyValue) {
|
|
| 1012 |
- if !first {
|
|
| 1013 |
- fmt.Fprintf(w, ",\n") |
|
| 1014 |
- } |
|
| 1015 |
- first = false |
|
| 1016 |
- fmt.Fprintf(w, "%q: %s", kv.Key, kv.Value) |
|
| 1017 |
- }) |
|
| 1018 |
- fmt.Fprintf(w, "\n}\n") |
|
| 1019 |
-} |
|
| 1020 |
- |
|
| 1021 |
-func AttachProfiler(router *mux.Router) {
|
|
| 1022 |
- router.HandleFunc("/debug/vars", expvarHandler)
|
|
| 1023 |
- router.HandleFunc("/debug/pprof/", pprof.Index)
|
|
| 1024 |
- router.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
|
|
| 1025 |
- router.HandleFunc("/debug/pprof/profile", pprof.Profile)
|
|
| 1026 |
- router.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
|
|
| 1027 |
- router.HandleFunc("/debug/pprof/heap", pprof.Handler("heap").ServeHTTP)
|
|
| 1028 |
- router.HandleFunc("/debug/pprof/goroutine", pprof.Handler("goroutine").ServeHTTP)
|
|
| 1029 |
- router.HandleFunc("/debug/pprof/threadcreate", pprof.Handler("threadcreate").ServeHTTP)
|
|
| 1030 |
-} |
|
| 1031 |
- |
|
| 1032 |
-func createRouter(eng *engine.Engine, logging, enableCors bool, dockerVersion string) (*mux.Router, error) {
|
|
| 1033 |
- r := mux.NewRouter() |
|
| 1034 |
- if os.Getenv("DEBUG") != "" {
|
|
| 1035 |
- AttachProfiler(r) |
|
| 1036 |
- } |
|
| 1037 |
- m := map[string]map[string]HttpApiFunc{
|
|
| 1038 |
- "GET": {
|
|
| 1039 |
- "/events": getEvents, |
|
| 1040 |
- "/info": getInfo, |
|
| 1041 |
- "/version": getVersion, |
|
| 1042 |
- "/images/json": getImagesJSON, |
|
| 1043 |
- "/images/viz": getImagesViz, |
|
| 1044 |
- "/images/search": getImagesSearch, |
|
| 1045 |
- "/images/{name:.*}/get": getImagesGet,
|
|
| 1046 |
- "/images/{name:.*}/history": getImagesHistory,
|
|
| 1047 |
- "/images/{name:.*}/json": getImagesByName,
|
|
| 1048 |
- "/containers/ps": getContainersJSON, |
|
| 1049 |
- "/containers/json": getContainersJSON, |
|
| 1050 |
- "/containers/{name:.*}/export": getContainersExport,
|
|
| 1051 |
- "/containers/{name:.*}/changes": getContainersChanges,
|
|
| 1052 |
- "/containers/{name:.*}/json": getContainersByName,
|
|
| 1053 |
- "/containers/{name:.*}/top": getContainersTop,
|
|
| 1054 |
- "/containers/{name:.*}/attach/ws": wsContainersAttach,
|
|
| 1055 |
- }, |
|
| 1056 |
- "POST": {
|
|
| 1057 |
- "/auth": postAuth, |
|
| 1058 |
- "/commit": postCommit, |
|
| 1059 |
- "/build": postBuild, |
|
| 1060 |
- "/images/create": postImagesCreate, |
|
| 1061 |
- "/images/{name:.*}/insert": postImagesInsert,
|
|
| 1062 |
- "/images/load": postImagesLoad, |
|
| 1063 |
- "/images/{name:.*}/push": postImagesPush,
|
|
| 1064 |
- "/images/{name:.*}/tag": postImagesTag,
|
|
| 1065 |
- "/containers/create": postContainersCreate, |
|
| 1066 |
- "/containers/{name:.*}/kill": postContainersKill,
|
|
| 1067 |
- "/containers/{name:.*}/restart": postContainersRestart,
|
|
| 1068 |
- "/containers/{name:.*}/start": postContainersStart,
|
|
| 1069 |
- "/containers/{name:.*}/stop": postContainersStop,
|
|
| 1070 |
- "/containers/{name:.*}/wait": postContainersWait,
|
|
| 1071 |
- "/containers/{name:.*}/resize": postContainersResize,
|
|
| 1072 |
- "/containers/{name:.*}/attach": postContainersAttach,
|
|
| 1073 |
- "/containers/{name:.*}/copy": postContainersCopy,
|
|
| 1074 |
- }, |
|
| 1075 |
- "DELETE": {
|
|
| 1076 |
- "/containers/{name:.*}": deleteContainers,
|
|
| 1077 |
- "/images/{name:.*}": deleteImages,
|
|
| 1078 |
- }, |
|
| 1079 |
- "OPTIONS": {
|
|
| 1080 |
- "": optionsHandler, |
|
| 1081 |
- }, |
|
| 1082 |
- } |
|
| 1083 |
- |
|
| 1084 |
- for method, routes := range m {
|
|
| 1085 |
- for route, fct := range routes {
|
|
| 1086 |
- utils.Debugf("Registering %s, %s", method, route)
|
|
| 1087 |
- // NOTE: scope issue, make sure the variables are local and won't be changed |
|
| 1088 |
- localRoute := route |
|
| 1089 |
- localFct := fct |
|
| 1090 |
- localMethod := method |
|
| 1091 |
- |
|
| 1092 |
- // build the handler function |
|
| 1093 |
- f := makeHttpHandler(eng, logging, localMethod, localRoute, localFct, enableCors, dockerVersion) |
|
| 1094 |
- |
|
| 1095 |
- // add the new route |
|
| 1096 |
- if localRoute == "" {
|
|
| 1097 |
- r.Methods(localMethod).HandlerFunc(f) |
|
| 1098 |
- } else {
|
|
| 1099 |
- r.Path("/v{version:[0-9.]+}" + localRoute).Methods(localMethod).HandlerFunc(f)
|
|
| 1100 |
- r.Path(localRoute).Methods(localMethod).HandlerFunc(f) |
|
| 1101 |
- } |
|
| 1102 |
- } |
|
| 1103 |
- } |
|
| 1104 |
- |
|
| 1105 |
- return r, nil |
|
| 1106 |
-} |
|
| 1107 |
- |
|
| 1108 |
-// ServeRequest processes a single http request to the docker remote api. |
|
| 1109 |
-// FIXME: refactor this to be part of Server and not require re-creating a new |
|
| 1110 |
-// router each time. This requires first moving ListenAndServe into Server. |
|
| 1111 |
-func ServeRequest(eng *engine.Engine, apiversion float64, w http.ResponseWriter, req *http.Request) error {
|
|
| 1112 |
- router, err := createRouter(eng, false, true, "") |
|
| 1113 |
- if err != nil {
|
|
| 1114 |
- return err |
|
| 1115 |
- } |
|
| 1116 |
- // Insert APIVERSION into the request as a convenience |
|
| 1117 |
- req.URL.Path = fmt.Sprintf("/v%g%s", apiversion, req.URL.Path)
|
|
| 1118 |
- router.ServeHTTP(w, req) |
|
| 1119 |
- return nil |
|
| 1120 |
-} |
|
| 1121 |
- |
|
| 1122 |
-// ServeFD creates an http.Server and sets it up to serve given a socket activated |
|
| 1123 |
-// argument. |
|
| 1124 |
-func ServeFd(addr string, handle http.Handler) error {
|
|
| 1125 |
- ls, e := systemd.ListenFD(addr) |
|
| 1126 |
- if e != nil {
|
|
| 1127 |
- return e |
|
| 1128 |
- } |
|
| 1129 |
- |
|
| 1130 |
- chErrors := make(chan error, len(ls)) |
|
| 1131 |
- |
|
| 1132 |
- // Since ListenFD will return one or more sockets we have |
|
| 1133 |
- // to create a go func to spawn off multiple serves |
|
| 1134 |
- for i := range ls {
|
|
| 1135 |
- listener := ls[i] |
|
| 1136 |
- go func() {
|
|
| 1137 |
- httpSrv := http.Server{Handler: handle}
|
|
| 1138 |
- chErrors <- httpSrv.Serve(listener) |
|
| 1139 |
- }() |
|
| 1140 |
- } |
|
| 1141 |
- |
|
| 1142 |
- for i := 0; i < len(ls); i += 1 {
|
|
| 1143 |
- err := <-chErrors |
|
| 1144 |
- if err != nil {
|
|
| 1145 |
- return err |
|
| 1146 |
- } |
|
| 1147 |
- } |
|
| 1148 |
- |
|
| 1149 |
- return nil |
|
| 1150 |
-} |
|
| 1151 |
- |
|
| 1152 |
-// ListenAndServe sets up the required http.Server and gets it listening for |
|
| 1153 |
-// each addr passed in and does protocol specific checking. |
|
| 1154 |
-func ListenAndServe(proto, addr string, eng *engine.Engine, logging, enableCors bool, dockerVersion string) error {
|
|
| 1155 |
- r, err := createRouter(eng, logging, enableCors, dockerVersion) |
|
| 1156 |
- if err != nil {
|
|
| 1157 |
- return err |
|
| 1158 |
- } |
|
| 1159 |
- |
|
| 1160 |
- if proto == "fd" {
|
|
| 1161 |
- return ServeFd(addr, r) |
|
| 1162 |
- } |
|
| 1163 |
- |
|
| 1164 |
- if proto == "unix" {
|
|
| 1165 |
- if err := syscall.Unlink(addr); err != nil && !os.IsNotExist(err) {
|
|
| 1166 |
- return err |
|
| 1167 |
- } |
|
| 1168 |
- } |
|
| 1169 |
- |
|
| 1170 |
- l, err := listenbuffer.NewListenBuffer(proto, addr, activationLock, 15*time.Minute) |
|
| 1171 |
- if err != nil {
|
|
| 1172 |
- return err |
|
| 1173 |
- } |
|
| 1174 |
- |
|
| 1175 |
- // Basic error and sanity checking |
|
| 1176 |
- switch proto {
|
|
| 1177 |
- case "tcp": |
|
| 1178 |
- if !strings.HasPrefix(addr, "127.0.0.1") {
|
|
| 1179 |
- log.Println("/!\\ DON'T BIND ON ANOTHER IP ADDRESS THAN 127.0.0.1 IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\")
|
|
| 1180 |
- } |
|
| 1181 |
- case "unix": |
|
| 1182 |
- if err := os.Chmod(addr, 0660); err != nil {
|
|
| 1183 |
- return err |
|
| 1184 |
- } |
|
| 1185 |
- |
|
| 1186 |
- groups, err := ioutil.ReadFile("/etc/group")
|
|
| 1187 |
- if err != nil {
|
|
| 1188 |
- return err |
|
| 1189 |
- } |
|
| 1190 |
- re := regexp.MustCompile("(^|\n)docker:.*?:([0-9]+)")
|
|
| 1191 |
- if gidMatch := re.FindStringSubmatch(string(groups)); gidMatch != nil {
|
|
| 1192 |
- gid, err := strconv.Atoi(gidMatch[2]) |
|
| 1193 |
- if err != nil {
|
|
| 1194 |
- return err |
|
| 1195 |
- } |
|
| 1196 |
- utils.Debugf("docker group found. gid: %d", gid)
|
|
| 1197 |
- if err := os.Chown(addr, 0, gid); err != nil {
|
|
| 1198 |
- return err |
|
| 1199 |
- } |
|
| 1200 |
- } |
|
| 1201 |
- default: |
|
| 1202 |
- return fmt.Errorf("Invalid protocol format.")
|
|
| 1203 |
- } |
|
| 1204 |
- |
|
| 1205 |
- httpSrv := http.Server{Addr: addr, Handler: r}
|
|
| 1206 |
- return httpSrv.Serve(l) |
|
| 1207 |
-} |
|
| 1208 |
- |
|
| 1209 |
-// ServeApi loops through all of the protocols sent in to docker and spawns |
|
| 1210 |
-// off a go routine to setup a serving http.Server for each. |
|
| 1211 |
-func ServeApi(job *engine.Job) engine.Status {
|
|
| 1212 |
- var ( |
|
| 1213 |
- protoAddrs = job.Args |
|
| 1214 |
- chErrors = make(chan error, len(protoAddrs)) |
|
| 1215 |
- ) |
|
| 1216 |
- activationLock = make(chan struct{})
|
|
| 1217 |
- |
|
| 1218 |
- if err := job.Eng.Register("acceptconnections", AcceptConnections); err != nil {
|
|
| 1219 |
- return job.Error(err) |
|
| 1220 |
- } |
|
| 1221 |
- |
|
| 1222 |
- for _, protoAddr := range protoAddrs {
|
|
| 1223 |
- protoAddrParts := strings.SplitN(protoAddr, "://", 2) |
|
| 1224 |
- go func() {
|
|
| 1225 |
- log.Printf("Listening for HTTP on %s (%s)\n", protoAddrParts[0], protoAddrParts[1])
|
|
| 1226 |
- chErrors <- ListenAndServe(protoAddrParts[0], protoAddrParts[1], job.Eng, job.GetenvBool("Logging"), job.GetenvBool("EnableCors"), job.Getenv("Version"))
|
|
| 1227 |
- }() |
|
| 1228 |
- } |
|
| 1229 |
- |
|
| 1230 |
- for i := 0; i < len(protoAddrs); i += 1 {
|
|
| 1231 |
- err := <-chErrors |
|
| 1232 |
- if err != nil {
|
|
| 1233 |
- return job.Error(err) |
|
| 1234 |
- } |
|
| 1235 |
- } |
|
| 1236 |
- |
|
| 1237 |
- return engine.StatusOK |
|
| 1238 |
-} |
|
| 1239 |
- |
|
| 1240 |
-func AcceptConnections(job *engine.Job) engine.Status {
|
|
| 1241 |
- // Tell the init daemon we are accepting requests |
|
| 1242 |
- go systemd.SdNotify("READY=1")
|
|
| 1243 |
- |
|
| 1244 |
- // close the lock so the listeners start accepting connections |
|
| 1245 |
- close(activationLock) |
|
| 1246 |
- |
|
| 1247 |
- return engine.StatusOK |
|
| 1248 |
-} |
| 1249 | 1 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,44 @@ |
| 0 |
+package api |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "github.com/dotcloud/docker/engine" |
|
| 5 |
+ "github.com/dotcloud/docker/utils" |
|
| 6 |
+ "mime" |
|
| 7 |
+ "strings" |
|
| 8 |
+) |
|
| 9 |
+ |
|
| 10 |
+const ( |
|
| 11 |
+ APIVERSION = 1.9 |
|
| 12 |
+ DEFAULTHTTPHOST = "127.0.0.1" |
|
| 13 |
+ DEFAULTUNIXSOCKET = "/var/run/docker.sock" |
|
| 14 |
+) |
|
| 15 |
+ |
|
| 16 |
+func ValidateHost(val string) (string, error) {
|
|
| 17 |
+ host, err := utils.ParseHost(DEFAULTHTTPHOST, DEFAULTUNIXSOCKET, val) |
|
| 18 |
+ if err != nil {
|
|
| 19 |
+ return val, err |
|
| 20 |
+ } |
|
| 21 |
+ return host, nil |
|
| 22 |
+} |
|
| 23 |
+ |
|
| 24 |
+//TODO remove, used on < 1.5 in getContainersJSON |
|
| 25 |
+func displayablePorts(ports *engine.Table) string {
|
|
| 26 |
+ result := []string{}
|
|
| 27 |
+ for _, port := range ports.Data {
|
|
| 28 |
+ if port.Get("IP") == "" {
|
|
| 29 |
+ result = append(result, fmt.Sprintf("%d/%s", port.GetInt("PublicPort"), port.Get("Type")))
|
|
| 30 |
+ } else {
|
|
| 31 |
+ result = append(result, fmt.Sprintf("%s:%d->%d/%s", port.Get("IP"), port.GetInt("PublicPort"), port.GetInt("PrivatePort"), port.Get("Type")))
|
|
| 32 |
+ } |
|
| 33 |
+ } |
|
| 34 |
+ return strings.Join(result, ", ") |
|
| 35 |
+} |
|
| 36 |
+ |
|
| 37 |
+func MatchesContentType(contentType, expectedType string) bool {
|
|
| 38 |
+ mimetype, _, err := mime.ParseMediaType(contentType) |
|
| 39 |
+ if err != nil {
|
|
| 40 |
+ utils.Errorf("Error parsing media type: %s error: %s", contentType, err.Error())
|
|
| 41 |
+ } |
|
| 42 |
+ return err == nil && mimetype == expectedType |
|
| 43 |
+} |
| 0 | 44 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,1207 @@ |
| 0 |
+package api |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "bufio" |
|
| 4 |
+ "bytes" |
|
| 5 |
+ "code.google.com/p/go.net/websocket" |
|
| 6 |
+ "encoding/base64" |
|
| 7 |
+ "encoding/json" |
|
| 8 |
+ "expvar" |
|
| 9 |
+ "fmt" |
|
| 10 |
+ "github.com/dotcloud/docker/auth" |
|
| 11 |
+ "github.com/dotcloud/docker/engine" |
|
| 12 |
+ "github.com/dotcloud/docker/pkg/listenbuffer" |
|
| 13 |
+ "github.com/dotcloud/docker/pkg/systemd" |
|
| 14 |
+ "github.com/dotcloud/docker/utils" |
|
| 15 |
+ "github.com/gorilla/mux" |
|
| 16 |
+ "io" |
|
| 17 |
+ "io/ioutil" |
|
| 18 |
+ "log" |
|
| 19 |
+ "net" |
|
| 20 |
+ "net/http" |
|
| 21 |
+ "net/http/pprof" |
|
| 22 |
+ "os" |
|
| 23 |
+ "regexp" |
|
| 24 |
+ "strconv" |
|
| 25 |
+ "strings" |
|
| 26 |
+ "syscall" |
|
| 27 |
+ "time" |
|
| 28 |
+) |
|
| 29 |
+ |
|
| 30 |
+var ( |
|
| 31 |
+ activationLock chan struct{}
|
|
| 32 |
+) |
|
| 33 |
+ |
|
| 34 |
+type HttpApiFunc func(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error |
|
| 35 |
+ |
|
| 36 |
+func hijackServer(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) {
|
|
| 37 |
+ conn, _, err := w.(http.Hijacker).Hijack() |
|
| 38 |
+ if err != nil {
|
|
| 39 |
+ return nil, nil, err |
|
| 40 |
+ } |
|
| 41 |
+ // Flush the options to make sure the client sets the raw mode |
|
| 42 |
+ conn.Write([]byte{})
|
|
| 43 |
+ return conn, conn, nil |
|
| 44 |
+} |
|
| 45 |
+ |
|
| 46 |
+//If we don't do this, POST method without Content-type (even with empty body) will fail |
|
| 47 |
+func parseForm(r *http.Request) error {
|
|
| 48 |
+ if r == nil {
|
|
| 49 |
+ return nil |
|
| 50 |
+ } |
|
| 51 |
+ if err := r.ParseForm(); err != nil && !strings.HasPrefix(err.Error(), "mime:") {
|
|
| 52 |
+ return err |
|
| 53 |
+ } |
|
| 54 |
+ return nil |
|
| 55 |
+} |
|
| 56 |
+ |
|
| 57 |
+func parseMultipartForm(r *http.Request) error {
|
|
| 58 |
+ if err := r.ParseMultipartForm(4096); err != nil && !strings.HasPrefix(err.Error(), "mime:") {
|
|
| 59 |
+ return err |
|
| 60 |
+ } |
|
| 61 |
+ return nil |
|
| 62 |
+} |
|
| 63 |
+ |
|
| 64 |
+func httpError(w http.ResponseWriter, err error) {
|
|
| 65 |
+ statusCode := http.StatusInternalServerError |
|
| 66 |
+ // FIXME: this is brittle and should not be necessary. |
|
| 67 |
+ // If we need to differentiate between different possible error types, we should |
|
| 68 |
+ // create appropriate error types with clearly defined meaning. |
|
| 69 |
+ if strings.Contains(err.Error(), "No such") {
|
|
| 70 |
+ statusCode = http.StatusNotFound |
|
| 71 |
+ } else if strings.Contains(err.Error(), "Bad parameter") {
|
|
| 72 |
+ statusCode = http.StatusBadRequest |
|
| 73 |
+ } else if strings.Contains(err.Error(), "Conflict") {
|
|
| 74 |
+ statusCode = http.StatusConflict |
|
| 75 |
+ } else if strings.Contains(err.Error(), "Impossible") {
|
|
| 76 |
+ statusCode = http.StatusNotAcceptable |
|
| 77 |
+ } else if strings.Contains(err.Error(), "Wrong login/password") {
|
|
| 78 |
+ statusCode = http.StatusUnauthorized |
|
| 79 |
+ } else if strings.Contains(err.Error(), "hasn't been activated") {
|
|
| 80 |
+ statusCode = http.StatusForbidden |
|
| 81 |
+ } |
|
| 82 |
+ |
|
| 83 |
+ if err != nil {
|
|
| 84 |
+ utils.Errorf("HTTP Error: statusCode=%d %s", statusCode, err.Error())
|
|
| 85 |
+ http.Error(w, err.Error(), statusCode) |
|
| 86 |
+ } |
|
| 87 |
+} |
|
| 88 |
+ |
|
| 89 |
+func writeJSON(w http.ResponseWriter, code int, v engine.Env) error {
|
|
| 90 |
+ w.Header().Set("Content-Type", "application/json")
|
|
| 91 |
+ w.WriteHeader(code) |
|
| 92 |
+ return v.Encode(w) |
|
| 93 |
+} |
|
| 94 |
+ |
|
| 95 |
+func streamJSON(job *engine.Job, w http.ResponseWriter, flush bool) {
|
|
| 96 |
+ w.Header().Set("Content-Type", "application/json")
|
|
| 97 |
+ if flush {
|
|
| 98 |
+ job.Stdout.Add(utils.NewWriteFlusher(w)) |
|
| 99 |
+ } else {
|
|
| 100 |
+ job.Stdout.Add(w) |
|
| 101 |
+ } |
|
| 102 |
+} |
|
| 103 |
+ |
|
| 104 |
+func getBoolParam(value string) (bool, error) {
|
|
| 105 |
+ if value == "" {
|
|
| 106 |
+ return false, nil |
|
| 107 |
+ } |
|
| 108 |
+ ret, err := strconv.ParseBool(value) |
|
| 109 |
+ if err != nil {
|
|
| 110 |
+ return false, fmt.Errorf("Bad parameter")
|
|
| 111 |
+ } |
|
| 112 |
+ return ret, nil |
|
| 113 |
+} |
|
| 114 |
+ |
|
| 115 |
+func postAuth(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 116 |
+ var ( |
|
| 117 |
+ authConfig, err = ioutil.ReadAll(r.Body) |
|
| 118 |
+ job = eng.Job("auth")
|
|
| 119 |
+ status string |
|
| 120 |
+ ) |
|
| 121 |
+ if err != nil {
|
|
| 122 |
+ return err |
|
| 123 |
+ } |
|
| 124 |
+ job.Setenv("authConfig", string(authConfig))
|
|
| 125 |
+ job.Stdout.AddString(&status) |
|
| 126 |
+ if err = job.Run(); err != nil {
|
|
| 127 |
+ return err |
|
| 128 |
+ } |
|
| 129 |
+ if status != "" {
|
|
| 130 |
+ var env engine.Env |
|
| 131 |
+ env.Set("Status", status)
|
|
| 132 |
+ return writeJSON(w, http.StatusOK, env) |
|
| 133 |
+ } |
|
| 134 |
+ w.WriteHeader(http.StatusNoContent) |
|
| 135 |
+ return nil |
|
| 136 |
+} |
|
| 137 |
+ |
|
| 138 |
+func getVersion(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 139 |
+ w.Header().Set("Content-Type", "application/json")
|
|
| 140 |
+ eng.ServeHTTP(w, r) |
|
| 141 |
+ return nil |
|
| 142 |
+} |
|
| 143 |
+ |
|
| 144 |
+func postContainersKill(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 145 |
+ if vars == nil {
|
|
| 146 |
+ return fmt.Errorf("Missing parameter")
|
|
| 147 |
+ } |
|
| 148 |
+ if err := parseForm(r); err != nil {
|
|
| 149 |
+ return err |
|
| 150 |
+ } |
|
| 151 |
+ job := eng.Job("kill", vars["name"])
|
|
| 152 |
+ if sig := r.Form.Get("signal"); sig != "" {
|
|
| 153 |
+ job.Args = append(job.Args, sig) |
|
| 154 |
+ } |
|
| 155 |
+ if err := job.Run(); err != nil {
|
|
| 156 |
+ return err |
|
| 157 |
+ } |
|
| 158 |
+ w.WriteHeader(http.StatusNoContent) |
|
| 159 |
+ return nil |
|
| 160 |
+} |
|
| 161 |
+ |
|
| 162 |
+func getContainersExport(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 163 |
+ if vars == nil {
|
|
| 164 |
+ return fmt.Errorf("Missing parameter")
|
|
| 165 |
+ } |
|
| 166 |
+ job := eng.Job("export", vars["name"])
|
|
| 167 |
+ job.Stdout.Add(w) |
|
| 168 |
+ if err := job.Run(); err != nil {
|
|
| 169 |
+ return err |
|
| 170 |
+ } |
|
| 171 |
+ return nil |
|
| 172 |
+} |
|
| 173 |
+ |
|
| 174 |
+func getImagesJSON(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 175 |
+ if err := parseForm(r); err != nil {
|
|
| 176 |
+ return err |
|
| 177 |
+ } |
|
| 178 |
+ |
|
| 179 |
+ var ( |
|
| 180 |
+ err error |
|
| 181 |
+ outs *engine.Table |
|
| 182 |
+ job = eng.Job("images")
|
|
| 183 |
+ ) |
|
| 184 |
+ |
|
| 185 |
+ job.Setenv("filter", r.Form.Get("filter"))
|
|
| 186 |
+ job.Setenv("all", r.Form.Get("all"))
|
|
| 187 |
+ |
|
| 188 |
+ if version >= 1.7 {
|
|
| 189 |
+ streamJSON(job, w, false) |
|
| 190 |
+ } else if outs, err = job.Stdout.AddListTable(); err != nil {
|
|
| 191 |
+ return err |
|
| 192 |
+ } |
|
| 193 |
+ |
|
| 194 |
+ if err := job.Run(); err != nil {
|
|
| 195 |
+ return err |
|
| 196 |
+ } |
|
| 197 |
+ |
|
| 198 |
+ if version < 1.7 && outs != nil { // Convert to legacy format
|
|
| 199 |
+ outsLegacy := engine.NewTable("Created", 0)
|
|
| 200 |
+ for _, out := range outs.Data {
|
|
| 201 |
+ for _, repoTag := range out.GetList("RepoTags") {
|
|
| 202 |
+ parts := strings.Split(repoTag, ":") |
|
| 203 |
+ outLegacy := &engine.Env{}
|
|
| 204 |
+ outLegacy.Set("Repository", parts[0])
|
|
| 205 |
+ outLegacy.Set("Tag", parts[1])
|
|
| 206 |
+ outLegacy.Set("Id", out.Get("Id"))
|
|
| 207 |
+ outLegacy.SetInt64("Created", out.GetInt64("Created"))
|
|
| 208 |
+ outLegacy.SetInt64("Size", out.GetInt64("Size"))
|
|
| 209 |
+ outLegacy.SetInt64("VirtualSize", out.GetInt64("VirtualSize"))
|
|
| 210 |
+ outsLegacy.Add(outLegacy) |
|
| 211 |
+ } |
|
| 212 |
+ } |
|
| 213 |
+ w.Header().Set("Content-Type", "application/json")
|
|
| 214 |
+ if _, err := outsLegacy.WriteListTo(w); err != nil {
|
|
| 215 |
+ return err |
|
| 216 |
+ } |
|
| 217 |
+ } |
|
| 218 |
+ return nil |
|
| 219 |
+} |
|
| 220 |
+ |
|
| 221 |
+func getImagesViz(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 222 |
+ if version > 1.6 {
|
|
| 223 |
+ w.WriteHeader(http.StatusNotFound) |
|
| 224 |
+ return fmt.Errorf("This is now implemented in the client.")
|
|
| 225 |
+ } |
|
| 226 |
+ eng.ServeHTTP(w, r) |
|
| 227 |
+ return nil |
|
| 228 |
+} |
|
| 229 |
+ |
|
| 230 |
+func getInfo(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 231 |
+ w.Header().Set("Content-Type", "application/json")
|
|
| 232 |
+ eng.ServeHTTP(w, r) |
|
| 233 |
+ return nil |
|
| 234 |
+} |
|
| 235 |
+ |
|
| 236 |
+func getEvents(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 237 |
+ if err := parseForm(r); err != nil {
|
|
| 238 |
+ return err |
|
| 239 |
+ } |
|
| 240 |
+ |
|
| 241 |
+ var job = eng.Job("events", r.RemoteAddr)
|
|
| 242 |
+ streamJSON(job, w, true) |
|
| 243 |
+ job.Setenv("since", r.Form.Get("since"))
|
|
| 244 |
+ return job.Run() |
|
| 245 |
+} |
|
| 246 |
+ |
|
| 247 |
+func getImagesHistory(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 248 |
+ if vars == nil {
|
|
| 249 |
+ return fmt.Errorf("Missing parameter")
|
|
| 250 |
+ } |
|
| 251 |
+ |
|
| 252 |
+ var job = eng.Job("history", vars["name"])
|
|
| 253 |
+ streamJSON(job, w, false) |
|
| 254 |
+ |
|
| 255 |
+ if err := job.Run(); err != nil {
|
|
| 256 |
+ return err |
|
| 257 |
+ } |
|
| 258 |
+ return nil |
|
| 259 |
+} |
|
| 260 |
+ |
|
| 261 |
+func getContainersChanges(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 262 |
+ if vars == nil {
|
|
| 263 |
+ return fmt.Errorf("Missing parameter")
|
|
| 264 |
+ } |
|
| 265 |
+ var job = eng.Job("changes", vars["name"])
|
|
| 266 |
+ streamJSON(job, w, false) |
|
| 267 |
+ |
|
| 268 |
+ return job.Run() |
|
| 269 |
+} |
|
| 270 |
+ |
|
| 271 |
+func getContainersTop(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 272 |
+ if version < 1.4 {
|
|
| 273 |
+ return fmt.Errorf("top was improved a lot since 1.3, Please upgrade your docker client.")
|
|
| 274 |
+ } |
|
| 275 |
+ if vars == nil {
|
|
| 276 |
+ return fmt.Errorf("Missing parameter")
|
|
| 277 |
+ } |
|
| 278 |
+ if err := parseForm(r); err != nil {
|
|
| 279 |
+ return err |
|
| 280 |
+ } |
|
| 281 |
+ |
|
| 282 |
+ job := eng.Job("top", vars["name"], r.Form.Get("ps_args"))
|
|
| 283 |
+ streamJSON(job, w, false) |
|
| 284 |
+ return job.Run() |
|
| 285 |
+} |
|
| 286 |
+ |
|
| 287 |
+func getContainersJSON(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 288 |
+ if err := parseForm(r); err != nil {
|
|
| 289 |
+ return err |
|
| 290 |
+ } |
|
| 291 |
+ var ( |
|
| 292 |
+ err error |
|
| 293 |
+ outs *engine.Table |
|
| 294 |
+ job = eng.Job("containers")
|
|
| 295 |
+ ) |
|
| 296 |
+ |
|
| 297 |
+ job.Setenv("all", r.Form.Get("all"))
|
|
| 298 |
+ job.Setenv("size", r.Form.Get("size"))
|
|
| 299 |
+ job.Setenv("since", r.Form.Get("since"))
|
|
| 300 |
+ job.Setenv("before", r.Form.Get("before"))
|
|
| 301 |
+ job.Setenv("limit", r.Form.Get("limit"))
|
|
| 302 |
+ |
|
| 303 |
+ if version >= 1.5 {
|
|
| 304 |
+ streamJSON(job, w, false) |
|
| 305 |
+ } else if outs, err = job.Stdout.AddTable(); err != nil {
|
|
| 306 |
+ return err |
|
| 307 |
+ } |
|
| 308 |
+ if err = job.Run(); err != nil {
|
|
| 309 |
+ return err |
|
| 310 |
+ } |
|
| 311 |
+ if version < 1.5 { // Convert to legacy format
|
|
| 312 |
+ for _, out := range outs.Data {
|
|
| 313 |
+ ports := engine.NewTable("", 0)
|
|
| 314 |
+ ports.ReadListFrom([]byte(out.Get("Ports")))
|
|
| 315 |
+ out.Set("Ports", displayablePorts(ports))
|
|
| 316 |
+ } |
|
| 317 |
+ w.Header().Set("Content-Type", "application/json")
|
|
| 318 |
+ if _, err = outs.WriteListTo(w); err != nil {
|
|
| 319 |
+ return err |
|
| 320 |
+ } |
|
| 321 |
+ } |
|
| 322 |
+ return nil |
|
| 323 |
+} |
|
| 324 |
+ |
|
| 325 |
+func postImagesTag(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 326 |
+ if err := parseForm(r); err != nil {
|
|
| 327 |
+ return err |
|
| 328 |
+ } |
|
| 329 |
+ if vars == nil {
|
|
| 330 |
+ return fmt.Errorf("Missing parameter")
|
|
| 331 |
+ } |
|
| 332 |
+ |
|
| 333 |
+ job := eng.Job("tag", vars["name"], r.Form.Get("repo"), r.Form.Get("tag"))
|
|
| 334 |
+ job.Setenv("force", r.Form.Get("force"))
|
|
| 335 |
+ if err := job.Run(); err != nil {
|
|
| 336 |
+ return err |
|
| 337 |
+ } |
|
| 338 |
+ w.WriteHeader(http.StatusCreated) |
|
| 339 |
+ return nil |
|
| 340 |
+} |
|
| 341 |
+ |
|
| 342 |
+func postCommit(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 343 |
+ if err := parseForm(r); err != nil {
|
|
| 344 |
+ return err |
|
| 345 |
+ } |
|
| 346 |
+ var ( |
|
| 347 |
+ config engine.Env |
|
| 348 |
+ env engine.Env |
|
| 349 |
+ job = eng.Job("commit", r.Form.Get("container"))
|
|
| 350 |
+ ) |
|
| 351 |
+ if err := config.Decode(r.Body); err != nil {
|
|
| 352 |
+ utils.Errorf("%s", err)
|
|
| 353 |
+ } |
|
| 354 |
+ |
|
| 355 |
+ job.Setenv("repo", r.Form.Get("repo"))
|
|
| 356 |
+ job.Setenv("tag", r.Form.Get("tag"))
|
|
| 357 |
+ job.Setenv("author", r.Form.Get("author"))
|
|
| 358 |
+ job.Setenv("comment", r.Form.Get("comment"))
|
|
| 359 |
+ job.SetenvSubEnv("config", &config)
|
|
| 360 |
+ |
|
| 361 |
+ var id string |
|
| 362 |
+ job.Stdout.AddString(&id) |
|
| 363 |
+ if err := job.Run(); err != nil {
|
|
| 364 |
+ return err |
|
| 365 |
+ } |
|
| 366 |
+ env.Set("Id", id)
|
|
| 367 |
+ return writeJSON(w, http.StatusCreated, env) |
|
| 368 |
+} |
|
| 369 |
+ |
|
| 370 |
+// Creates an image from Pull or from Import |
|
| 371 |
+func postImagesCreate(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 372 |
+ if err := parseForm(r); err != nil {
|
|
| 373 |
+ return err |
|
| 374 |
+ } |
|
| 375 |
+ |
|
| 376 |
+ var ( |
|
| 377 |
+ image = r.Form.Get("fromImage")
|
|
| 378 |
+ tag = r.Form.Get("tag")
|
|
| 379 |
+ job *engine.Job |
|
| 380 |
+ ) |
|
| 381 |
+ authEncoded := r.Header.Get("X-Registry-Auth")
|
|
| 382 |
+ authConfig := &auth.AuthConfig{}
|
|
| 383 |
+ if authEncoded != "" {
|
|
| 384 |
+ authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) |
|
| 385 |
+ if err := json.NewDecoder(authJson).Decode(authConfig); err != nil {
|
|
| 386 |
+ // for a pull it is not an error if no auth was given |
|
| 387 |
+ // to increase compatibility with the existing api it is defaulting to be empty |
|
| 388 |
+ authConfig = &auth.AuthConfig{}
|
|
| 389 |
+ } |
|
| 390 |
+ } |
|
| 391 |
+ if version > 1.0 {
|
|
| 392 |
+ w.Header().Set("Content-Type", "application/json")
|
|
| 393 |
+ } |
|
| 394 |
+ if image != "" { //pull
|
|
| 395 |
+ metaHeaders := map[string][]string{}
|
|
| 396 |
+ for k, v := range r.Header {
|
|
| 397 |
+ if strings.HasPrefix(k, "X-Meta-") {
|
|
| 398 |
+ metaHeaders[k] = v |
|
| 399 |
+ } |
|
| 400 |
+ } |
|
| 401 |
+ job = eng.Job("pull", r.Form.Get("fromImage"), tag)
|
|
| 402 |
+ job.SetenvBool("parallel", version > 1.3)
|
|
| 403 |
+ job.SetenvJson("metaHeaders", metaHeaders)
|
|
| 404 |
+ job.SetenvJson("authConfig", authConfig)
|
|
| 405 |
+ } else { //import
|
|
| 406 |
+ job = eng.Job("import", r.Form.Get("fromSrc"), r.Form.Get("repo"), tag)
|
|
| 407 |
+ job.Stdin.Add(r.Body) |
|
| 408 |
+ } |
|
| 409 |
+ |
|
| 410 |
+ if version > 1.0 {
|
|
| 411 |
+ job.SetenvBool("json", true)
|
|
| 412 |
+ streamJSON(job, w, true) |
|
| 413 |
+ } else {
|
|
| 414 |
+ job.Stdout.Add(utils.NewWriteFlusher(w)) |
|
| 415 |
+ } |
|
| 416 |
+ if err := job.Run(); err != nil {
|
|
| 417 |
+ if !job.Stdout.Used() {
|
|
| 418 |
+ return err |
|
| 419 |
+ } |
|
| 420 |
+ sf := utils.NewStreamFormatter(version > 1.0) |
|
| 421 |
+ w.Write(sf.FormatError(err)) |
|
| 422 |
+ } |
|
| 423 |
+ |
|
| 424 |
+ return nil |
|
| 425 |
+} |
|
| 426 |
+ |
|
| 427 |
+func getImagesSearch(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 428 |
+ if err := parseForm(r); err != nil {
|
|
| 429 |
+ return err |
|
| 430 |
+ } |
|
| 431 |
+ var ( |
|
| 432 |
+ authEncoded = r.Header.Get("X-Registry-Auth")
|
|
| 433 |
+ authConfig = &auth.AuthConfig{}
|
|
| 434 |
+ metaHeaders = map[string][]string{}
|
|
| 435 |
+ ) |
|
| 436 |
+ |
|
| 437 |
+ if authEncoded != "" {
|
|
| 438 |
+ authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) |
|
| 439 |
+ if err := json.NewDecoder(authJson).Decode(authConfig); err != nil {
|
|
| 440 |
+ // for a search it is not an error if no auth was given |
|
| 441 |
+ // to increase compatibility with the existing api it is defaulting to be empty |
|
| 442 |
+ authConfig = &auth.AuthConfig{}
|
|
| 443 |
+ } |
|
| 444 |
+ } |
|
| 445 |
+ for k, v := range r.Header {
|
|
| 446 |
+ if strings.HasPrefix(k, "X-Meta-") {
|
|
| 447 |
+ metaHeaders[k] = v |
|
| 448 |
+ } |
|
| 449 |
+ } |
|
| 450 |
+ |
|
| 451 |
+ var job = eng.Job("search", r.Form.Get("term"))
|
|
| 452 |
+ job.SetenvJson("metaHeaders", metaHeaders)
|
|
| 453 |
+ job.SetenvJson("authConfig", authConfig)
|
|
| 454 |
+ streamJSON(job, w, false) |
|
| 455 |
+ |
|
| 456 |
+ return job.Run() |
|
| 457 |
+} |
|
| 458 |
+ |
|
| 459 |
+func postImagesInsert(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 460 |
+ if err := parseForm(r); err != nil {
|
|
| 461 |
+ return err |
|
| 462 |
+ } |
|
| 463 |
+ if vars == nil {
|
|
| 464 |
+ return fmt.Errorf("Missing parameter")
|
|
| 465 |
+ } |
|
| 466 |
+ if version > 1.0 {
|
|
| 467 |
+ w.Header().Set("Content-Type", "application/json")
|
|
| 468 |
+ } |
|
| 469 |
+ |
|
| 470 |
+ job := eng.Job("insert", vars["name"], r.Form.Get("url"), r.Form.Get("path"))
|
|
| 471 |
+ if version > 1.0 {
|
|
| 472 |
+ job.SetenvBool("json", true)
|
|
| 473 |
+ streamJSON(job, w, false) |
|
| 474 |
+ } else {
|
|
| 475 |
+ job.Stdout.Add(w) |
|
| 476 |
+ } |
|
| 477 |
+ if err := job.Run(); err != nil {
|
|
| 478 |
+ if !job.Stdout.Used() {
|
|
| 479 |
+ return err |
|
| 480 |
+ } |
|
| 481 |
+ sf := utils.NewStreamFormatter(version > 1.0) |
|
| 482 |
+ w.Write(sf.FormatError(err)) |
|
| 483 |
+ } |
|
| 484 |
+ |
|
| 485 |
+ return nil |
|
| 486 |
+} |
|
| 487 |
+ |
|
| 488 |
+func postImagesPush(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 489 |
+ if vars == nil {
|
|
| 490 |
+ return fmt.Errorf("Missing parameter")
|
|
| 491 |
+ } |
|
| 492 |
+ |
|
| 493 |
+ metaHeaders := map[string][]string{}
|
|
| 494 |
+ for k, v := range r.Header {
|
|
| 495 |
+ if strings.HasPrefix(k, "X-Meta-") {
|
|
| 496 |
+ metaHeaders[k] = v |
|
| 497 |
+ } |
|
| 498 |
+ } |
|
| 499 |
+ if err := parseForm(r); err != nil {
|
|
| 500 |
+ return err |
|
| 501 |
+ } |
|
| 502 |
+ authConfig := &auth.AuthConfig{}
|
|
| 503 |
+ |
|
| 504 |
+ authEncoded := r.Header.Get("X-Registry-Auth")
|
|
| 505 |
+ if authEncoded != "" {
|
|
| 506 |
+ // the new format is to handle the authConfig as a header |
|
| 507 |
+ authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) |
|
| 508 |
+ if err := json.NewDecoder(authJson).Decode(authConfig); err != nil {
|
|
| 509 |
+ // to increase compatibility to existing api it is defaulting to be empty |
|
| 510 |
+ authConfig = &auth.AuthConfig{}
|
|
| 511 |
+ } |
|
| 512 |
+ } else {
|
|
| 513 |
+ // the old format is supported for compatibility if there was no authConfig header |
|
| 514 |
+ if err := json.NewDecoder(r.Body).Decode(authConfig); err != nil {
|
|
| 515 |
+ return err |
|
| 516 |
+ } |
|
| 517 |
+ } |
|
| 518 |
+ |
|
| 519 |
+ if version > 1.0 {
|
|
| 520 |
+ w.Header().Set("Content-Type", "application/json")
|
|
| 521 |
+ } |
|
| 522 |
+ job := eng.Job("push", vars["name"])
|
|
| 523 |
+ job.SetenvJson("metaHeaders", metaHeaders)
|
|
| 524 |
+ job.SetenvJson("authConfig", authConfig)
|
|
| 525 |
+ if version > 1.0 {
|
|
| 526 |
+ job.SetenvBool("json", true)
|
|
| 527 |
+ streamJSON(job, w, true) |
|
| 528 |
+ } else {
|
|
| 529 |
+ job.Stdout.Add(utils.NewWriteFlusher(w)) |
|
| 530 |
+ } |
|
| 531 |
+ |
|
| 532 |
+ if err := job.Run(); err != nil {
|
|
| 533 |
+ if !job.Stdout.Used() {
|
|
| 534 |
+ return err |
|
| 535 |
+ } |
|
| 536 |
+ sf := utils.NewStreamFormatter(version > 1.0) |
|
| 537 |
+ w.Write(sf.FormatError(err)) |
|
| 538 |
+ } |
|
| 539 |
+ return nil |
|
| 540 |
+} |
|
| 541 |
+ |
|
| 542 |
+func getImagesGet(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 543 |
+ if vars == nil {
|
|
| 544 |
+ return fmt.Errorf("Missing parameter")
|
|
| 545 |
+ } |
|
| 546 |
+ if version > 1.0 {
|
|
| 547 |
+ w.Header().Set("Content-Type", "application/x-tar")
|
|
| 548 |
+ } |
|
| 549 |
+ job := eng.Job("image_export", vars["name"])
|
|
| 550 |
+ job.Stdout.Add(w) |
|
| 551 |
+ return job.Run() |
|
| 552 |
+} |
|
| 553 |
+ |
|
| 554 |
+func postImagesLoad(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 555 |
+ job := eng.Job("load")
|
|
| 556 |
+ job.Stdin.Add(r.Body) |
|
| 557 |
+ return job.Run() |
|
| 558 |
+} |
|
| 559 |
+ |
|
| 560 |
+func postContainersCreate(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 561 |
+ if err := parseForm(r); err != nil {
|
|
| 562 |
+ return nil |
|
| 563 |
+ } |
|
| 564 |
+ var ( |
|
| 565 |
+ out engine.Env |
|
| 566 |
+ job = eng.Job("create", r.Form.Get("name"))
|
|
| 567 |
+ outWarnings []string |
|
| 568 |
+ outId string |
|
| 569 |
+ warnings = bytes.NewBuffer(nil) |
|
| 570 |
+ ) |
|
| 571 |
+ if err := job.DecodeEnv(r.Body); err != nil {
|
|
| 572 |
+ return err |
|
| 573 |
+ } |
|
| 574 |
+ // Read container ID from the first line of stdout |
|
| 575 |
+ job.Stdout.AddString(&outId) |
|
| 576 |
+ // Read warnings from stderr |
|
| 577 |
+ job.Stderr.Add(warnings) |
|
| 578 |
+ if err := job.Run(); err != nil {
|
|
| 579 |
+ return err |
|
| 580 |
+ } |
|
| 581 |
+ // Parse warnings from stderr |
|
| 582 |
+ scanner := bufio.NewScanner(warnings) |
|
| 583 |
+ for scanner.Scan() {
|
|
| 584 |
+ outWarnings = append(outWarnings, scanner.Text()) |
|
| 585 |
+ } |
|
| 586 |
+ out.Set("Id", outId)
|
|
| 587 |
+ out.SetList("Warnings", outWarnings)
|
|
| 588 |
+ return writeJSON(w, http.StatusCreated, out) |
|
| 589 |
+} |
|
| 590 |
+ |
|
| 591 |
+func postContainersRestart(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 592 |
+ if err := parseForm(r); err != nil {
|
|
| 593 |
+ return err |
|
| 594 |
+ } |
|
| 595 |
+ if vars == nil {
|
|
| 596 |
+ return fmt.Errorf("Missing parameter")
|
|
| 597 |
+ } |
|
| 598 |
+ job := eng.Job("restart", vars["name"])
|
|
| 599 |
+ job.Setenv("t", r.Form.Get("t"))
|
|
| 600 |
+ if err := job.Run(); err != nil {
|
|
| 601 |
+ return err |
|
| 602 |
+ } |
|
| 603 |
+ w.WriteHeader(http.StatusNoContent) |
|
| 604 |
+ return nil |
|
| 605 |
+} |
|
| 606 |
+ |
|
| 607 |
+func deleteContainers(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 608 |
+ if err := parseForm(r); err != nil {
|
|
| 609 |
+ return err |
|
| 610 |
+ } |
|
| 611 |
+ if vars == nil {
|
|
| 612 |
+ return fmt.Errorf("Missing parameter")
|
|
| 613 |
+ } |
|
| 614 |
+ job := eng.Job("container_delete", vars["name"])
|
|
| 615 |
+ job.Setenv("removeVolume", r.Form.Get("v"))
|
|
| 616 |
+ job.Setenv("removeLink", r.Form.Get("link"))
|
|
| 617 |
+ if err := job.Run(); err != nil {
|
|
| 618 |
+ return err |
|
| 619 |
+ } |
|
| 620 |
+ w.WriteHeader(http.StatusNoContent) |
|
| 621 |
+ return nil |
|
| 622 |
+} |
|
| 623 |
+ |
|
| 624 |
+func deleteImages(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 625 |
+ if err := parseForm(r); err != nil {
|
|
| 626 |
+ return err |
|
| 627 |
+ } |
|
| 628 |
+ if vars == nil {
|
|
| 629 |
+ return fmt.Errorf("Missing parameter")
|
|
| 630 |
+ } |
|
| 631 |
+ var job = eng.Job("image_delete", vars["name"])
|
|
| 632 |
+ streamJSON(job, w, false) |
|
| 633 |
+ job.SetenvBool("autoPrune", version > 1.1)
|
|
| 634 |
+ |
|
| 635 |
+ return job.Run() |
|
| 636 |
+} |
|
| 637 |
+ |
|
| 638 |
+func postContainersStart(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 639 |
+ if vars == nil {
|
|
| 640 |
+ return fmt.Errorf("Missing parameter")
|
|
| 641 |
+ } |
|
| 642 |
+ name := vars["name"] |
|
| 643 |
+ job := eng.Job("start", name)
|
|
| 644 |
+ // allow a nil body for backwards compatibility |
|
| 645 |
+ if r.Body != nil {
|
|
| 646 |
+ if MatchesContentType(r.Header.Get("Content-Type"), "application/json") {
|
|
| 647 |
+ if err := job.DecodeEnv(r.Body); err != nil {
|
|
| 648 |
+ return err |
|
| 649 |
+ } |
|
| 650 |
+ } |
|
| 651 |
+ } |
|
| 652 |
+ if err := job.Run(); err != nil {
|
|
| 653 |
+ return err |
|
| 654 |
+ } |
|
| 655 |
+ w.WriteHeader(http.StatusNoContent) |
|
| 656 |
+ return nil |
|
| 657 |
+} |
|
| 658 |
+ |
|
| 659 |
+func postContainersStop(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 660 |
+ if err := parseForm(r); err != nil {
|
|
| 661 |
+ return err |
|
| 662 |
+ } |
|
| 663 |
+ if vars == nil {
|
|
| 664 |
+ return fmt.Errorf("Missing parameter")
|
|
| 665 |
+ } |
|
| 666 |
+ job := eng.Job("stop", vars["name"])
|
|
| 667 |
+ job.Setenv("t", r.Form.Get("t"))
|
|
| 668 |
+ if err := job.Run(); err != nil {
|
|
| 669 |
+ return err |
|
| 670 |
+ } |
|
| 671 |
+ w.WriteHeader(http.StatusNoContent) |
|
| 672 |
+ return nil |
|
| 673 |
+} |
|
| 674 |
+ |
|
| 675 |
+func postContainersWait(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 676 |
+ if vars == nil {
|
|
| 677 |
+ return fmt.Errorf("Missing parameter")
|
|
| 678 |
+ } |
|
| 679 |
+ var ( |
|
| 680 |
+ env engine.Env |
|
| 681 |
+ status string |
|
| 682 |
+ job = eng.Job("wait", vars["name"])
|
|
| 683 |
+ ) |
|
| 684 |
+ job.Stdout.AddString(&status) |
|
| 685 |
+ if err := job.Run(); err != nil {
|
|
| 686 |
+ return err |
|
| 687 |
+ } |
|
| 688 |
+ // Parse a 16-bit encoded integer to map typical unix exit status. |
|
| 689 |
+ _, err := strconv.ParseInt(status, 10, 16) |
|
| 690 |
+ if err != nil {
|
|
| 691 |
+ return err |
|
| 692 |
+ } |
|
| 693 |
+ env.Set("StatusCode", status)
|
|
| 694 |
+ return writeJSON(w, http.StatusOK, env) |
|
| 695 |
+} |
|
| 696 |
+ |
|
| 697 |
+func postContainersResize(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 698 |
+ if err := parseForm(r); err != nil {
|
|
| 699 |
+ return err |
|
| 700 |
+ } |
|
| 701 |
+ if vars == nil {
|
|
| 702 |
+ return fmt.Errorf("Missing parameter")
|
|
| 703 |
+ } |
|
| 704 |
+ if err := eng.Job("resize", vars["name"], r.Form.Get("h"), r.Form.Get("w")).Run(); err != nil {
|
|
| 705 |
+ return err |
|
| 706 |
+ } |
|
| 707 |
+ return nil |
|
| 708 |
+} |
|
| 709 |
+ |
|
| 710 |
+func postContainersAttach(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 711 |
+ if err := parseForm(r); err != nil {
|
|
| 712 |
+ return err |
|
| 713 |
+ } |
|
| 714 |
+ if vars == nil {
|
|
| 715 |
+ return fmt.Errorf("Missing parameter")
|
|
| 716 |
+ } |
|
| 717 |
+ |
|
| 718 |
+ var ( |
|
| 719 |
+ job = eng.Job("inspect", vars["name"], "container")
|
|
| 720 |
+ c, err = job.Stdout.AddEnv() |
|
| 721 |
+ ) |
|
| 722 |
+ if err != nil {
|
|
| 723 |
+ return err |
|
| 724 |
+ } |
|
| 725 |
+ if err = job.Run(); err != nil {
|
|
| 726 |
+ return err |
|
| 727 |
+ } |
|
| 728 |
+ |
|
| 729 |
+ inStream, outStream, err := hijackServer(w) |
|
| 730 |
+ if err != nil {
|
|
| 731 |
+ return err |
|
| 732 |
+ } |
|
| 733 |
+ defer func() {
|
|
| 734 |
+ if tcpc, ok := inStream.(*net.TCPConn); ok {
|
|
| 735 |
+ tcpc.CloseWrite() |
|
| 736 |
+ } else {
|
|
| 737 |
+ inStream.Close() |
|
| 738 |
+ } |
|
| 739 |
+ }() |
|
| 740 |
+ defer func() {
|
|
| 741 |
+ if tcpc, ok := outStream.(*net.TCPConn); ok {
|
|
| 742 |
+ tcpc.CloseWrite() |
|
| 743 |
+ } else if closer, ok := outStream.(io.Closer); ok {
|
|
| 744 |
+ closer.Close() |
|
| 745 |
+ } |
|
| 746 |
+ }() |
|
| 747 |
+ |
|
| 748 |
+ var errStream io.Writer |
|
| 749 |
+ |
|
| 750 |
+ fmt.Fprintf(outStream, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n") |
|
| 751 |
+ |
|
| 752 |
+ if c.GetSubEnv("Config") != nil && !c.GetSubEnv("Config").GetBool("Tty") && version >= 1.6 {
|
|
| 753 |
+ errStream = utils.NewStdWriter(outStream, utils.Stderr) |
|
| 754 |
+ outStream = utils.NewStdWriter(outStream, utils.Stdout) |
|
| 755 |
+ } else {
|
|
| 756 |
+ errStream = outStream |
|
| 757 |
+ } |
|
| 758 |
+ |
|
| 759 |
+ job = eng.Job("attach", vars["name"])
|
|
| 760 |
+ job.Setenv("logs", r.Form.Get("logs"))
|
|
| 761 |
+ job.Setenv("stream", r.Form.Get("stream"))
|
|
| 762 |
+ job.Setenv("stdin", r.Form.Get("stdin"))
|
|
| 763 |
+ job.Setenv("stdout", r.Form.Get("stdout"))
|
|
| 764 |
+ job.Setenv("stderr", r.Form.Get("stderr"))
|
|
| 765 |
+ job.Stdin.Add(inStream) |
|
| 766 |
+ job.Stdout.Add(outStream) |
|
| 767 |
+ job.Stderr.Set(errStream) |
|
| 768 |
+ if err := job.Run(); err != nil {
|
|
| 769 |
+ fmt.Fprintf(outStream, "Error: %s\n", err) |
|
| 770 |
+ |
|
| 771 |
+ } |
|
| 772 |
+ return nil |
|
| 773 |
+} |
|
| 774 |
+ |
|
| 775 |
+func wsContainersAttach(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 776 |
+ if err := parseForm(r); err != nil {
|
|
| 777 |
+ return err |
|
| 778 |
+ } |
|
| 779 |
+ if vars == nil {
|
|
| 780 |
+ return fmt.Errorf("Missing parameter")
|
|
| 781 |
+ } |
|
| 782 |
+ |
|
| 783 |
+ if err := eng.Job("inspect", vars["name"], "container").Run(); err != nil {
|
|
| 784 |
+ return err |
|
| 785 |
+ } |
|
| 786 |
+ |
|
| 787 |
+ h := websocket.Handler(func(ws *websocket.Conn) {
|
|
| 788 |
+ defer ws.Close() |
|
| 789 |
+ job := eng.Job("attach", vars["name"])
|
|
| 790 |
+ job.Setenv("logs", r.Form.Get("logs"))
|
|
| 791 |
+ job.Setenv("stream", r.Form.Get("stream"))
|
|
| 792 |
+ job.Setenv("stdin", r.Form.Get("stdin"))
|
|
| 793 |
+ job.Setenv("stdout", r.Form.Get("stdout"))
|
|
| 794 |
+ job.Setenv("stderr", r.Form.Get("stderr"))
|
|
| 795 |
+ job.Stdin.Add(ws) |
|
| 796 |
+ job.Stdout.Add(ws) |
|
| 797 |
+ job.Stderr.Set(ws) |
|
| 798 |
+ if err := job.Run(); err != nil {
|
|
| 799 |
+ utils.Errorf("Error: %s", err)
|
|
| 800 |
+ } |
|
| 801 |
+ }) |
|
| 802 |
+ h.ServeHTTP(w, r) |
|
| 803 |
+ |
|
| 804 |
+ return nil |
|
| 805 |
+} |
|
| 806 |
+ |
|
| 807 |
+func getContainersByName(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 808 |
+ if vars == nil {
|
|
| 809 |
+ return fmt.Errorf("Missing parameter")
|
|
| 810 |
+ } |
|
| 811 |
+ var job = eng.Job("inspect", vars["name"], "container")
|
|
| 812 |
+ streamJSON(job, w, false) |
|
| 813 |
+ job.SetenvBool("conflict", true) //conflict=true to detect conflict between containers and images in the job
|
|
| 814 |
+ return job.Run() |
|
| 815 |
+} |
|
| 816 |
+ |
|
| 817 |
+func getImagesByName(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 818 |
+ if vars == nil {
|
|
| 819 |
+ return fmt.Errorf("Missing parameter")
|
|
| 820 |
+ } |
|
| 821 |
+ var job = eng.Job("inspect", vars["name"], "image")
|
|
| 822 |
+ streamJSON(job, w, false) |
|
| 823 |
+ job.SetenvBool("conflict", true) //conflict=true to detect conflict between containers and images in the job
|
|
| 824 |
+ return job.Run() |
|
| 825 |
+} |
|
| 826 |
+ |
|
| 827 |
+func postBuild(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 828 |
+ if version < 1.3 {
|
|
| 829 |
+ return fmt.Errorf("Multipart upload for build is no longer supported. Please upgrade your docker client.")
|
|
| 830 |
+ } |
|
| 831 |
+ var ( |
|
| 832 |
+ authEncoded = r.Header.Get("X-Registry-Auth")
|
|
| 833 |
+ authConfig = &auth.AuthConfig{}
|
|
| 834 |
+ configFileEncoded = r.Header.Get("X-Registry-Config")
|
|
| 835 |
+ configFile = &auth.ConfigFile{}
|
|
| 836 |
+ job = eng.Job("build")
|
|
| 837 |
+ ) |
|
| 838 |
+ |
|
| 839 |
+ // This block can be removed when API versions prior to 1.9 are deprecated. |
|
| 840 |
+ // Both headers will be parsed and sent along to the daemon, but if a non-empty |
|
| 841 |
+ // ConfigFile is present, any value provided as an AuthConfig directly will |
|
| 842 |
+ // be overridden. See BuildFile::CmdFrom for details. |
|
| 843 |
+ if version < 1.9 && authEncoded != "" {
|
|
| 844 |
+ authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) |
|
| 845 |
+ if err := json.NewDecoder(authJson).Decode(authConfig); err != nil {
|
|
| 846 |
+ // for a pull it is not an error if no auth was given |
|
| 847 |
+ // to increase compatibility with the existing api it is defaulting to be empty |
|
| 848 |
+ authConfig = &auth.AuthConfig{}
|
|
| 849 |
+ } |
|
| 850 |
+ } |
|
| 851 |
+ |
|
| 852 |
+ if configFileEncoded != "" {
|
|
| 853 |
+ configFileJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(configFileEncoded)) |
|
| 854 |
+ if err := json.NewDecoder(configFileJson).Decode(configFile); err != nil {
|
|
| 855 |
+ // for a pull it is not an error if no auth was given |
|
| 856 |
+ // to increase compatibility with the existing api it is defaulting to be empty |
|
| 857 |
+ configFile = &auth.ConfigFile{}
|
|
| 858 |
+ } |
|
| 859 |
+ } |
|
| 860 |
+ |
|
| 861 |
+ if version >= 1.8 {
|
|
| 862 |
+ job.SetenvBool("json", true)
|
|
| 863 |
+ streamJSON(job, w, true) |
|
| 864 |
+ } else {
|
|
| 865 |
+ job.Stdout.Add(utils.NewWriteFlusher(w)) |
|
| 866 |
+ } |
|
| 867 |
+ job.Stdin.Add(r.Body) |
|
| 868 |
+ job.Setenv("remote", r.FormValue("remote"))
|
|
| 869 |
+ job.Setenv("t", r.FormValue("t"))
|
|
| 870 |
+ job.Setenv("q", r.FormValue("q"))
|
|
| 871 |
+ job.Setenv("nocache", r.FormValue("nocache"))
|
|
| 872 |
+ job.Setenv("rm", r.FormValue("rm"))
|
|
| 873 |
+ job.SetenvJson("authConfig", authConfig)
|
|
| 874 |
+ job.SetenvJson("configFile", configFile)
|
|
| 875 |
+ |
|
| 876 |
+ if err := job.Run(); err != nil {
|
|
| 877 |
+ if !job.Stdout.Used() {
|
|
| 878 |
+ return err |
|
| 879 |
+ } |
|
| 880 |
+ sf := utils.NewStreamFormatter(version >= 1.8) |
|
| 881 |
+ w.Write(sf.FormatError(err)) |
|
| 882 |
+ } |
|
| 883 |
+ return nil |
|
| 884 |
+} |
|
| 885 |
+ |
|
| 886 |
+func postContainersCopy(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 887 |
+ if vars == nil {
|
|
| 888 |
+ return fmt.Errorf("Missing parameter")
|
|
| 889 |
+ } |
|
| 890 |
+ |
|
| 891 |
+ var copyData engine.Env |
|
| 892 |
+ |
|
| 893 |
+ if contentType := r.Header.Get("Content-Type"); contentType == "application/json" {
|
|
| 894 |
+ if err := copyData.Decode(r.Body); err != nil {
|
|
| 895 |
+ return err |
|
| 896 |
+ } |
|
| 897 |
+ } else {
|
|
| 898 |
+ return fmt.Errorf("Content-Type not supported: %s", contentType)
|
|
| 899 |
+ } |
|
| 900 |
+ |
|
| 901 |
+ if copyData.Get("Resource") == "" {
|
|
| 902 |
+ return fmt.Errorf("Path cannot be empty")
|
|
| 903 |
+ } |
|
| 904 |
+ if copyData.Get("Resource")[0] == '/' {
|
|
| 905 |
+ copyData.Set("Resource", copyData.Get("Resource")[1:])
|
|
| 906 |
+ } |
|
| 907 |
+ |
|
| 908 |
+ job := eng.Job("container_copy", vars["name"], copyData.Get("Resource"))
|
|
| 909 |
+ streamJSON(job, w, false) |
|
| 910 |
+ if err := job.Run(); err != nil {
|
|
| 911 |
+ utils.Errorf("%s", err.Error())
|
|
| 912 |
+ if strings.Contains(err.Error(), "No such container") {
|
|
| 913 |
+ w.WriteHeader(http.StatusNotFound) |
|
| 914 |
+ } |
|
| 915 |
+ } |
|
| 916 |
+ return nil |
|
| 917 |
+} |
|
| 918 |
+ |
|
| 919 |
+func optionsHandler(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 920 |
+ w.WriteHeader(http.StatusOK) |
|
| 921 |
+ return nil |
|
| 922 |
+} |
|
| 923 |
+func writeCorsHeaders(w http.ResponseWriter, r *http.Request) {
|
|
| 924 |
+ w.Header().Add("Access-Control-Allow-Origin", "*")
|
|
| 925 |
+ w.Header().Add("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept")
|
|
| 926 |
+ w.Header().Add("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, OPTIONS")
|
|
| 927 |
+} |
|
| 928 |
+ |
|
| 929 |
+func makeHttpHandler(eng *engine.Engine, logging bool, localMethod string, localRoute string, handlerFunc HttpApiFunc, enableCors bool, dockerVersion string) http.HandlerFunc {
|
|
| 930 |
+ return func(w http.ResponseWriter, r *http.Request) {
|
|
| 931 |
+ // log the request |
|
| 932 |
+ utils.Debugf("Calling %s %s", localMethod, localRoute)
|
|
| 933 |
+ |
|
| 934 |
+ if logging {
|
|
| 935 |
+ log.Println(r.Method, r.RequestURI) |
|
| 936 |
+ } |
|
| 937 |
+ |
|
| 938 |
+ if strings.Contains(r.Header.Get("User-Agent"), "Docker-Client/") {
|
|
| 939 |
+ userAgent := strings.Split(r.Header.Get("User-Agent"), "/")
|
|
| 940 |
+ if len(userAgent) == 2 && userAgent[1] != dockerVersion {
|
|
| 941 |
+ utils.Debugf("Warning: client and server don't have the same version (client: %s, server: %s)", userAgent[1], dockerVersion)
|
|
| 942 |
+ } |
|
| 943 |
+ } |
|
| 944 |
+ version, err := strconv.ParseFloat(mux.Vars(r)["version"], 64) |
|
| 945 |
+ if err != nil {
|
|
| 946 |
+ version = APIVERSION |
|
| 947 |
+ } |
|
| 948 |
+ if enableCors {
|
|
| 949 |
+ writeCorsHeaders(w, r) |
|
| 950 |
+ } |
|
| 951 |
+ |
|
| 952 |
+ if version == 0 || version > APIVERSION {
|
|
| 953 |
+ http.Error(w, fmt.Errorf("client and server don't have same version (client : %g, server: %g)", version, APIVERSION).Error(), http.StatusNotFound)
|
|
| 954 |
+ return |
|
| 955 |
+ } |
|
| 956 |
+ |
|
| 957 |
+ if err := handlerFunc(eng, version, w, r, mux.Vars(r)); err != nil {
|
|
| 958 |
+ utils.Errorf("Error: %s", err)
|
|
| 959 |
+ httpError(w, err) |
|
| 960 |
+ } |
|
| 961 |
+ } |
|
| 962 |
+} |
|
| 963 |
+ |
|
| 964 |
+// Replicated from expvar.go as not public. |
|
| 965 |
+func expvarHandler(w http.ResponseWriter, r *http.Request) {
|
|
| 966 |
+ w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
|
| 967 |
+ fmt.Fprintf(w, "{\n")
|
|
| 968 |
+ first := true |
|
| 969 |
+ expvar.Do(func(kv expvar.KeyValue) {
|
|
| 970 |
+ if !first {
|
|
| 971 |
+ fmt.Fprintf(w, ",\n") |
|
| 972 |
+ } |
|
| 973 |
+ first = false |
|
| 974 |
+ fmt.Fprintf(w, "%q: %s", kv.Key, kv.Value) |
|
| 975 |
+ }) |
|
| 976 |
+ fmt.Fprintf(w, "\n}\n") |
|
| 977 |
+} |
|
| 978 |
+ |
|
| 979 |
+func AttachProfiler(router *mux.Router) {
|
|
| 980 |
+ router.HandleFunc("/debug/vars", expvarHandler)
|
|
| 981 |
+ router.HandleFunc("/debug/pprof/", pprof.Index)
|
|
| 982 |
+ router.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
|
|
| 983 |
+ router.HandleFunc("/debug/pprof/profile", pprof.Profile)
|
|
| 984 |
+ router.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
|
|
| 985 |
+ router.HandleFunc("/debug/pprof/heap", pprof.Handler("heap").ServeHTTP)
|
|
| 986 |
+ router.HandleFunc("/debug/pprof/goroutine", pprof.Handler("goroutine").ServeHTTP)
|
|
| 987 |
+ router.HandleFunc("/debug/pprof/threadcreate", pprof.Handler("threadcreate").ServeHTTP)
|
|
| 988 |
+} |
|
| 989 |
+ |
|
| 990 |
+func createRouter(eng *engine.Engine, logging, enableCors bool, dockerVersion string) (*mux.Router, error) {
|
|
| 991 |
+ r := mux.NewRouter() |
|
| 992 |
+ if os.Getenv("DEBUG") != "" {
|
|
| 993 |
+ AttachProfiler(r) |
|
| 994 |
+ } |
|
| 995 |
+ m := map[string]map[string]HttpApiFunc{
|
|
| 996 |
+ "GET": {
|
|
| 997 |
+ "/events": getEvents, |
|
| 998 |
+ "/info": getInfo, |
|
| 999 |
+ "/version": getVersion, |
|
| 1000 |
+ "/images/json": getImagesJSON, |
|
| 1001 |
+ "/images/viz": getImagesViz, |
|
| 1002 |
+ "/images/search": getImagesSearch, |
|
| 1003 |
+ "/images/{name:.*}/get": getImagesGet,
|
|
| 1004 |
+ "/images/{name:.*}/history": getImagesHistory,
|
|
| 1005 |
+ "/images/{name:.*}/json": getImagesByName,
|
|
| 1006 |
+ "/containers/ps": getContainersJSON, |
|
| 1007 |
+ "/containers/json": getContainersJSON, |
|
| 1008 |
+ "/containers/{name:.*}/export": getContainersExport,
|
|
| 1009 |
+ "/containers/{name:.*}/changes": getContainersChanges,
|
|
| 1010 |
+ "/containers/{name:.*}/json": getContainersByName,
|
|
| 1011 |
+ "/containers/{name:.*}/top": getContainersTop,
|
|
| 1012 |
+ "/containers/{name:.*}/attach/ws": wsContainersAttach,
|
|
| 1013 |
+ }, |
|
| 1014 |
+ "POST": {
|
|
| 1015 |
+ "/auth": postAuth, |
|
| 1016 |
+ "/commit": postCommit, |
|
| 1017 |
+ "/build": postBuild, |
|
| 1018 |
+ "/images/create": postImagesCreate, |
|
| 1019 |
+ "/images/{name:.*}/insert": postImagesInsert,
|
|
| 1020 |
+ "/images/load": postImagesLoad, |
|
| 1021 |
+ "/images/{name:.*}/push": postImagesPush,
|
|
| 1022 |
+ "/images/{name:.*}/tag": postImagesTag,
|
|
| 1023 |
+ "/containers/create": postContainersCreate, |
|
| 1024 |
+ "/containers/{name:.*}/kill": postContainersKill,
|
|
| 1025 |
+ "/containers/{name:.*}/restart": postContainersRestart,
|
|
| 1026 |
+ "/containers/{name:.*}/start": postContainersStart,
|
|
| 1027 |
+ "/containers/{name:.*}/stop": postContainersStop,
|
|
| 1028 |
+ "/containers/{name:.*}/wait": postContainersWait,
|
|
| 1029 |
+ "/containers/{name:.*}/resize": postContainersResize,
|
|
| 1030 |
+ "/containers/{name:.*}/attach": postContainersAttach,
|
|
| 1031 |
+ "/containers/{name:.*}/copy": postContainersCopy,
|
|
| 1032 |
+ }, |
|
| 1033 |
+ "DELETE": {
|
|
| 1034 |
+ "/containers/{name:.*}": deleteContainers,
|
|
| 1035 |
+ "/images/{name:.*}": deleteImages,
|
|
| 1036 |
+ }, |
|
| 1037 |
+ "OPTIONS": {
|
|
| 1038 |
+ "": optionsHandler, |
|
| 1039 |
+ }, |
|
| 1040 |
+ } |
|
| 1041 |
+ |
|
| 1042 |
+ for method, routes := range m {
|
|
| 1043 |
+ for route, fct := range routes {
|
|
| 1044 |
+ utils.Debugf("Registering %s, %s", method, route)
|
|
| 1045 |
+ // NOTE: scope issue, make sure the variables are local and won't be changed |
|
| 1046 |
+ localRoute := route |
|
| 1047 |
+ localFct := fct |
|
| 1048 |
+ localMethod := method |
|
| 1049 |
+ |
|
| 1050 |
+ // build the handler function |
|
| 1051 |
+ f := makeHttpHandler(eng, logging, localMethod, localRoute, localFct, enableCors, dockerVersion) |
|
| 1052 |
+ |
|
| 1053 |
+ // add the new route |
|
| 1054 |
+ if localRoute == "" {
|
|
| 1055 |
+ r.Methods(localMethod).HandlerFunc(f) |
|
| 1056 |
+ } else {
|
|
| 1057 |
+ r.Path("/v{version:[0-9.]+}" + localRoute).Methods(localMethod).HandlerFunc(f)
|
|
| 1058 |
+ r.Path(localRoute).Methods(localMethod).HandlerFunc(f) |
|
| 1059 |
+ } |
|
| 1060 |
+ } |
|
| 1061 |
+ } |
|
| 1062 |
+ |
|
| 1063 |
+ return r, nil |
|
| 1064 |
+} |
|
| 1065 |
+ |
|
| 1066 |
+// ServeRequest processes a single http request to the docker remote api. |
|
| 1067 |
+// FIXME: refactor this to be part of Server and not require re-creating a new |
|
| 1068 |
+// router each time. This requires first moving ListenAndServe into Server. |
|
| 1069 |
+func ServeRequest(eng *engine.Engine, apiversion float64, w http.ResponseWriter, req *http.Request) error {
|
|
| 1070 |
+ router, err := createRouter(eng, false, true, "") |
|
| 1071 |
+ if err != nil {
|
|
| 1072 |
+ return err |
|
| 1073 |
+ } |
|
| 1074 |
+ // Insert APIVERSION into the request as a convenience |
|
| 1075 |
+ req.URL.Path = fmt.Sprintf("/v%g%s", apiversion, req.URL.Path)
|
|
| 1076 |
+ router.ServeHTTP(w, req) |
|
| 1077 |
+ return nil |
|
| 1078 |
+} |
|
| 1079 |
+ |
|
| 1080 |
+// ServeFD creates an http.Server and sets it up to serve given a socket activated |
|
| 1081 |
+// argument. |
|
| 1082 |
+func ServeFd(addr string, handle http.Handler) error {
|
|
| 1083 |
+ ls, e := systemd.ListenFD(addr) |
|
| 1084 |
+ if e != nil {
|
|
| 1085 |
+ return e |
|
| 1086 |
+ } |
|
| 1087 |
+ |
|
| 1088 |
+ chErrors := make(chan error, len(ls)) |
|
| 1089 |
+ |
|
| 1090 |
+ // Since ListenFD will return one or more sockets we have |
|
| 1091 |
+ // to create a go func to spawn off multiple serves |
|
| 1092 |
+ for i := range ls {
|
|
| 1093 |
+ listener := ls[i] |
|
| 1094 |
+ go func() {
|
|
| 1095 |
+ httpSrv := http.Server{Handler: handle}
|
|
| 1096 |
+ chErrors <- httpSrv.Serve(listener) |
|
| 1097 |
+ }() |
|
| 1098 |
+ } |
|
| 1099 |
+ |
|
| 1100 |
+ for i := 0; i < len(ls); i += 1 {
|
|
| 1101 |
+ err := <-chErrors |
|
| 1102 |
+ if err != nil {
|
|
| 1103 |
+ return err |
|
| 1104 |
+ } |
|
| 1105 |
+ } |
|
| 1106 |
+ |
|
| 1107 |
+ return nil |
|
| 1108 |
+} |
|
| 1109 |
+ |
|
| 1110 |
+// ListenAndServe sets up the required http.Server and gets it listening for |
|
| 1111 |
+// each addr passed in and does protocol specific checking. |
|
| 1112 |
+func ListenAndServe(proto, addr string, eng *engine.Engine, logging, enableCors bool, dockerVersion string) error {
|
|
| 1113 |
+ r, err := createRouter(eng, logging, enableCors, dockerVersion) |
|
| 1114 |
+ if err != nil {
|
|
| 1115 |
+ return err |
|
| 1116 |
+ } |
|
| 1117 |
+ |
|
| 1118 |
+ if proto == "fd" {
|
|
| 1119 |
+ return ServeFd(addr, r) |
|
| 1120 |
+ } |
|
| 1121 |
+ |
|
| 1122 |
+ if proto == "unix" {
|
|
| 1123 |
+ if err := syscall.Unlink(addr); err != nil && !os.IsNotExist(err) {
|
|
| 1124 |
+ return err |
|
| 1125 |
+ } |
|
| 1126 |
+ } |
|
| 1127 |
+ |
|
| 1128 |
+ l, err := listenbuffer.NewListenBuffer(proto, addr, activationLock, 15*time.Minute) |
|
| 1129 |
+ if err != nil {
|
|
| 1130 |
+ return err |
|
| 1131 |
+ } |
|
| 1132 |
+ |
|
| 1133 |
+ // Basic error and sanity checking |
|
| 1134 |
+ switch proto {
|
|
| 1135 |
+ case "tcp": |
|
| 1136 |
+ if !strings.HasPrefix(addr, "127.0.0.1") {
|
|
| 1137 |
+ log.Println("/!\\ DON'T BIND ON ANOTHER IP ADDRESS THAN 127.0.0.1 IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\")
|
|
| 1138 |
+ } |
|
| 1139 |
+ case "unix": |
|
| 1140 |
+ if err := os.Chmod(addr, 0660); err != nil {
|
|
| 1141 |
+ return err |
|
| 1142 |
+ } |
|
| 1143 |
+ |
|
| 1144 |
+ groups, err := ioutil.ReadFile("/etc/group")
|
|
| 1145 |
+ if err != nil {
|
|
| 1146 |
+ return err |
|
| 1147 |
+ } |
|
| 1148 |
+ re := regexp.MustCompile("(^|\n)docker:.*?:([0-9]+)")
|
|
| 1149 |
+ if gidMatch := re.FindStringSubmatch(string(groups)); gidMatch != nil {
|
|
| 1150 |
+ gid, err := strconv.Atoi(gidMatch[2]) |
|
| 1151 |
+ if err != nil {
|
|
| 1152 |
+ return err |
|
| 1153 |
+ } |
|
| 1154 |
+ utils.Debugf("docker group found. gid: %d", gid)
|
|
| 1155 |
+ if err := os.Chown(addr, 0, gid); err != nil {
|
|
| 1156 |
+ return err |
|
| 1157 |
+ } |
|
| 1158 |
+ } |
|
| 1159 |
+ default: |
|
| 1160 |
+ return fmt.Errorf("Invalid protocol format.")
|
|
| 1161 |
+ } |
|
| 1162 |
+ |
|
| 1163 |
+ httpSrv := http.Server{Addr: addr, Handler: r}
|
|
| 1164 |
+ return httpSrv.Serve(l) |
|
| 1165 |
+} |
|
| 1166 |
+ |
|
| 1167 |
+// ServeApi loops through all of the protocols sent in to docker and spawns |
|
| 1168 |
+// off a go routine to setup a serving http.Server for each. |
|
| 1169 |
+func ServeApi(job *engine.Job) engine.Status {
|
|
| 1170 |
+ var ( |
|
| 1171 |
+ protoAddrs = job.Args |
|
| 1172 |
+ chErrors = make(chan error, len(protoAddrs)) |
|
| 1173 |
+ ) |
|
| 1174 |
+ activationLock = make(chan struct{})
|
|
| 1175 |
+ |
|
| 1176 |
+ if err := job.Eng.Register("acceptconnections", AcceptConnections); err != nil {
|
|
| 1177 |
+ return job.Error(err) |
|
| 1178 |
+ } |
|
| 1179 |
+ |
|
| 1180 |
+ for _, protoAddr := range protoAddrs {
|
|
| 1181 |
+ protoAddrParts := strings.SplitN(protoAddr, "://", 2) |
|
| 1182 |
+ go func() {
|
|
| 1183 |
+ log.Printf("Listening for HTTP on %s (%s)\n", protoAddrParts[0], protoAddrParts[1])
|
|
| 1184 |
+ chErrors <- ListenAndServe(protoAddrParts[0], protoAddrParts[1], job.Eng, job.GetenvBool("Logging"), job.GetenvBool("EnableCors"), job.Getenv("Version"))
|
|
| 1185 |
+ }() |
|
| 1186 |
+ } |
|
| 1187 |
+ |
|
| 1188 |
+ for i := 0; i < len(protoAddrs); i += 1 {
|
|
| 1189 |
+ err := <-chErrors |
|
| 1190 |
+ if err != nil {
|
|
| 1191 |
+ return job.Error(err) |
|
| 1192 |
+ } |
|
| 1193 |
+ } |
|
| 1194 |
+ |
|
| 1195 |
+ return engine.StatusOK |
|
| 1196 |
+} |
|
| 1197 |
+ |
|
| 1198 |
+func AcceptConnections(job *engine.Job) engine.Status {
|
|
| 1199 |
+ // Tell the init daemon we are accepting requests |
|
| 1200 |
+ go systemd.SdNotify("READY=1")
|
|
| 1201 |
+ |
|
| 1202 |
+ // close the lock so the listeners start accepting connections |
|
| 1203 |
+ close(activationLock) |
|
| 1204 |
+ |
|
| 1205 |
+ return engine.StatusOK |
|
| 1206 |
+} |
| ... | ... |
@@ -110,7 +110,7 @@ func (b *buildFile) CmdFrom(name string) error {
|
| 110 | 110 |
b.config = image.Config |
| 111 | 111 |
} |
| 112 | 112 |
if b.config.Env == nil || len(b.config.Env) == 0 {
|
| 113 |
- b.config.Env = append(b.config.Env, "HOME=/", "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin") |
|
| 113 |
+ b.config.Env = append(b.config.Env, "HOME=/", "PATH="+defaultPathEnv) |
|
| 114 | 114 |
} |
| 115 | 115 |
// Process ONBUILD triggers if they exist |
| 116 | 116 |
if nTriggers := len(b.config.OnBuild); nTriggers != 0 {
|
| 117 | 117 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,40 @@ |
| 0 |
+package builtins |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "github.com/dotcloud/docker/engine" |
|
| 4 |
+ |
|
| 5 |
+ "github.com/dotcloud/docker" |
|
| 6 |
+ "github.com/dotcloud/docker/api" |
|
| 7 |
+ "github.com/dotcloud/docker/networkdriver/lxc" |
|
| 8 |
+) |
|
| 9 |
+ |
|
| 10 |
+func Register(eng *engine.Engine) {
|
|
| 11 |
+ daemon(eng) |
|
| 12 |
+ remote(eng) |
|
| 13 |
+} |
|
| 14 |
+ |
|
| 15 |
+// remote: a RESTful api for cross-docker communication |
|
| 16 |
+func remote(eng *engine.Engine) {
|
|
| 17 |
+ eng.Register("serveapi", api.ServeApi)
|
|
| 18 |
+} |
|
| 19 |
+ |
|
| 20 |
+// daemon: a default execution and storage backend for Docker on Linux, |
|
| 21 |
+// with the following underlying components: |
|
| 22 |
+// |
|
| 23 |
+// * Pluggable storage drivers including aufs, vfs, lvm and btrfs. |
|
| 24 |
+// * Pluggable execution drivers including lxc and chroot. |
|
| 25 |
+// |
|
| 26 |
+// In practice `daemon` still includes most core Docker components, including: |
|
| 27 |
+// |
|
| 28 |
+// * The reference registry client implementation |
|
| 29 |
+// * Image management |
|
| 30 |
+// * The build facility |
|
| 31 |
+// * Logging |
|
| 32 |
+// |
|
| 33 |
+// These components should be broken off into plugins of their own. |
|
| 34 |
+// |
|
| 35 |
+func daemon(eng *engine.Engine) {
|
|
| 36 |
+ eng.Register("initserver", docker.InitServer)
|
|
| 37 |
+ eng.Register("init_networkdriver", lxc.InitDriver)
|
|
| 38 |
+ eng.Register("version", docker.GetVersion)
|
|
| 39 |
+} |
| ... | ... |
@@ -25,6 +25,7 @@ type DaemonConfig struct {
|
| 25 | 25 |
BridgeIP string |
| 26 | 26 |
InterContainerCommunication bool |
| 27 | 27 |
GraphDriver string |
| 28 |
+ ExecDriver string |
|
| 28 | 29 |
Mtu int |
| 29 | 30 |
DisableNetwork bool |
| 30 | 31 |
} |
| ... | ... |
@@ -43,6 +44,7 @@ func DaemonConfigFromJob(job *engine.Job) *DaemonConfig {
|
| 43 | 43 |
DefaultIp: net.ParseIP(job.Getenv("DefaultIp")),
|
| 44 | 44 |
InterContainerCommunication: job.GetenvBool("InterContainerCommunication"),
|
| 45 | 45 |
GraphDriver: job.Getenv("GraphDriver"),
|
| 46 |
+ ExecDriver: job.Getenv("ExecDriver"),
|
|
| 46 | 47 |
} |
| 47 | 48 |
if dns := job.GetenvList("Dns"); dns != nil {
|
| 48 | 49 |
config.Dns = dns |
| ... | ... |
@@ -23,6 +23,8 @@ import ( |
| 23 | 23 |
"time" |
| 24 | 24 |
) |
| 25 | 25 |
|
| 26 |
+const defaultPathEnv = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" |
|
| 27 |
+ |
|
| 26 | 28 |
var ( |
| 27 | 29 |
ErrNotATTY = errors.New("The PTY is not a file")
|
| 28 | 30 |
ErrNoTTY = errors.New("No PTY found")
|
| ... | ... |
@@ -447,7 +449,7 @@ func (container *Container) Start() (err error) {
|
| 447 | 447 |
// Setup environment |
| 448 | 448 |
env := []string{
|
| 449 | 449 |
"HOME=/", |
| 450 |
- "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", |
|
| 450 |
+ "PATH=" + defaultPathEnv, |
|
| 451 | 451 |
"HOSTNAME=" + container.Config.Hostname, |
| 452 | 452 |
} |
| 453 | 453 |
|
| ... | ... |
@@ -44,6 +44,8 @@ debianStable=wheezy |
| 44 | 44 |
debianUnstable=sid |
| 45 | 45 |
# this should match the name found at http://releases.ubuntu.com/ |
| 46 | 46 |
ubuntuLatestLTS=precise |
| 47 |
+# this should match the name found at http://releases.tanglu.org/ |
|
| 48 |
+tangluLatest=aequorea |
|
| 47 | 49 |
|
| 48 | 50 |
while getopts v:i:a:p:dst name; do |
| 49 | 51 |
case "$name" in |
| ... | ... |
@@ -201,6 +203,17 @@ if [ -z "$strictDebootstrap" ]; then |
| 201 | 201 |
s/ $suite-updates main/ ${suite}-security main/
|
| 202 | 202 |
" etc/apt/sources.list |
| 203 | 203 |
;; |
| 204 |
+ Tanglu) |
|
| 205 |
+ # add the updates repository |
|
| 206 |
+ if [ "$suite" = "$tangluLatest" ]; then |
|
| 207 |
+ # ${suite}-updates only applies to stable Tanglu versions
|
|
| 208 |
+ sudo sed -i "p; s/ $suite main$/ ${suite}-updates main/" etc/apt/sources.list
|
|
| 209 |
+ fi |
|
| 210 |
+ ;; |
|
| 211 |
+ SteamOS) |
|
| 212 |
+ # add contrib and non-free |
|
| 213 |
+ sudo sed -i "s/ $suite main$/ $suite main contrib non-free/" etc/apt/sources.list |
|
| 214 |
+ ;; |
|
| 204 | 215 |
esac |
| 205 | 216 |
fi |
| 206 | 217 |
|
| ... | ... |
@@ -248,6 +261,28 @@ else |
| 248 | 248 |
fi |
| 249 | 249 |
fi |
| 250 | 250 |
;; |
| 251 |
+ Tanglu) |
|
| 252 |
+ if [ "$suite" = "$tangluLatest" ]; then |
|
| 253 |
+ # tag latest |
|
| 254 |
+ $docker tag $repo:$suite $repo:latest |
|
| 255 |
+ fi |
|
| 256 |
+ if [ -r etc/lsb-release ]; then |
|
| 257 |
+ lsbRelease="$(. etc/lsb-release && echo "$DISTRIB_RELEASE")" |
|
| 258 |
+ if [ "$lsbRelease" ]; then |
|
| 259 |
+ # tag specific Tanglu version number, if available (1.0, 2.0, etc.) |
|
| 260 |
+ $docker tag $repo:$suite $repo:$lsbRelease |
|
| 261 |
+ fi |
|
| 262 |
+ fi |
|
| 263 |
+ ;; |
|
| 264 |
+ SteamOS) |
|
| 265 |
+ if [ -r etc/lsb-release ]; then |
|
| 266 |
+ lsbRelease="$(. etc/lsb-release && echo "$DISTRIB_RELEASE")" |
|
| 267 |
+ if [ "$lsbRelease" ]; then |
|
| 268 |
+ # tag specific SteamOS version number, if available (1.0, 2.0, etc.) |
|
| 269 |
+ $docker tag $repo:$suite $repo:$lsbRelease |
|
| 270 |
+ fi |
|
| 271 |
+ fi |
|
| 272 |
+ ;; |
|
| 251 | 273 |
esac |
| 252 | 274 |
fi |
| 253 | 275 |
fi |
| ... | ... |
@@ -6,8 +6,8 @@ import ( |
| 6 | 6 |
"os" |
| 7 | 7 |
"strings" |
| 8 | 8 |
|
| 9 |
- _ "github.com/dotcloud/docker" |
|
| 10 | 9 |
"github.com/dotcloud/docker/api" |
| 10 |
+ "github.com/dotcloud/docker/builtins" |
|
| 11 | 11 |
"github.com/dotcloud/docker/dockerversion" |
| 12 | 12 |
"github.com/dotcloud/docker/engine" |
| 13 | 13 |
flag "github.com/dotcloud/docker/pkg/mflag" |
| ... | ... |
@@ -39,6 +39,7 @@ func main() {
|
| 39 | 39 |
flDefaultIp = flag.String([]string{"#ip", "-ip"}, "0.0.0.0", "Default IP address to use when binding container ports")
|
| 40 | 40 |
flInterContainerComm = flag.Bool([]string{"#icc", "-icc"}, true, "Enable inter-container communication")
|
| 41 | 41 |
flGraphDriver = flag.String([]string{"s", "-storage-driver"}, "", "Force the docker runtime to use a specific storage driver")
|
| 42 |
+ flExecDriver = flag.String([]string{"e", "-exec-driver"}, "", "Force the docker runtime to use a specific exec driver")
|
|
| 42 | 43 |
flHosts = opts.NewListOpts(api.ValidateHost) |
| 43 | 44 |
flMtu = flag.Int([]string{"#mtu", "-mtu"}, 0, "Set the containers network MTU; if no value is provided: default to the default route MTU or 1500 if no default route is available")
|
| 44 | 45 |
) |
| ... | ... |
@@ -81,6 +82,8 @@ func main() {
|
| 81 | 81 |
if err != nil {
|
| 82 | 82 |
log.Fatal(err) |
| 83 | 83 |
} |
| 84 |
+ // Load builtins |
|
| 85 |
+ builtins.Register(eng) |
|
| 84 | 86 |
// load the daemon in the background so we can immediately start |
| 85 | 87 |
// the http api so that connections don't fail while the daemon |
| 86 | 88 |
// is booting |
| ... | ... |
@@ -98,6 +101,7 @@ func main() {
|
| 98 | 98 |
job.Setenv("DefaultIp", *flDefaultIp)
|
| 99 | 99 |
job.SetenvBool("InterContainerCommunication", *flInterContainerComm)
|
| 100 | 100 |
job.Setenv("GraphDriver", *flGraphDriver)
|
| 101 |
+ job.Setenv("ExecDriver", *flExecDriver)
|
|
| 101 | 102 |
job.SetenvInt("Mtu", *flMtu)
|
| 102 | 103 |
if err := job.Run(); err != nil {
|
| 103 | 104 |
log.Fatal(err) |
| ... | ... |
@@ -92,14 +92,6 @@ To execute the test cases, run this command: |
| 92 | 92 |
|
| 93 | 93 |
sudo make test |
| 94 | 94 |
|
| 95 |
- |
|
| 96 |
-Note: if you're running the tests in vagrant, you need to specify a dns entry in |
|
| 97 |
-the command (either edit the Makefile, or run the step manually): |
|
| 98 |
- |
|
| 99 |
-.. code-block:: bash |
|
| 100 |
- |
|
| 101 |
- sudo docker run -dns 8.8.8.8 -privileged -v `pwd`:/go/src/github.com/dotcloud/docker docker hack/make.sh test |
|
| 102 |
- |
|
| 103 | 95 |
If the test are successful then the tail of the output should look something like this |
| 104 | 96 |
|
| 105 | 97 |
.. code-block:: bash |
| ... | ... |
@@ -25,9 +25,9 @@ Does Docker run on Mac OS X or Windows? |
| 25 | 25 |
|
| 26 | 26 |
Not at this time, Docker currently only runs on Linux, but you can |
| 27 | 27 |
use VirtualBox to run Docker in a virtual machine on your box, and |
| 28 |
- get the best of both worlds. Check out the |
|
| 29 |
- :ref:`macosx` and :ref:`windows` installation |
|
| 30 |
- guides. |
|
| 28 |
+ get the best of both worlds. Check out the :ref:`macosx` and |
|
| 29 |
+ :ref:`windows` installation guides. The small Linux distribution boot2docker |
|
| 30 |
+ can be run inside virtual machines on these two operating systems. |
|
| 31 | 31 |
|
| 32 | 32 |
How do containers compare to virtual machines? |
| 33 | 33 |
.............................................. |
| ... | ... |
@@ -24,6 +24,6 @@ For a high-level overview of Docker, please see the `Introduction |
| 24 | 24 |
Docker, we have a `quick start <http://www.docker.io/gettingstarted>`_ |
| 25 | 25 |
and a more in-depth guide to :ref:`ubuntu_linux` and other |
| 26 | 26 |
:ref:`installation_list` paths including prebuilt binaries, |
| 27 |
-Vagrant-created VMs, Rackspace and Amazon instances. |
|
| 27 |
+Rackspace and Amazon instances. |
|
| 28 | 28 |
|
| 29 | 29 |
Enough reading! :ref:`Try it out! <running_examples>` |
| ... | ... |
@@ -10,8 +10,7 @@ Amazon EC2 |
| 10 | 10 |
There are several ways to install Docker on AWS EC2: |
| 11 | 11 |
|
| 12 | 12 |
* :ref:`amazonquickstart` or |
| 13 |
-* :ref:`amazonstandard` or |
|
| 14 |
-* :ref:`amazonvagrant` |
|
| 13 |
+* :ref:`amazonstandard` |
|
| 15 | 14 |
|
| 16 | 15 |
**You'll need an** `AWS account <http://aws.amazon.com/>`_ **first, of course.** |
| 17 | 16 |
|
| ... | ... |
@@ -73,112 +72,4 @@ running Ubuntu. Just follow Step 1 from :ref:`amazonquickstart` to |
| 73 | 73 |
pick an image (or use one of your own) and skip the step with the |
| 74 | 74 |
*User Data*. Then continue with the :ref:`ubuntu_linux` instructions. |
| 75 | 75 |
|
| 76 |
-.. _amazonvagrant: |
|
| 77 |
- |
|
| 78 |
-Use Vagrant |
|
| 79 |
- |
|
| 80 |
-.. include:: install_unofficial.inc |
|
| 81 |
- |
|
| 82 |
-And finally, if you prefer to work through Vagrant, you can install |
|
| 83 |
-Docker that way too. Vagrant 1.1 or higher is required. |
|
| 84 |
- |
|
| 85 |
-1. Install vagrant from http://www.vagrantup.com/ (or use your package manager) |
|
| 86 |
-2. Install the vagrant aws plugin |
|
| 87 |
- |
|
| 88 |
- :: |
|
| 89 |
- |
|
| 90 |
- vagrant plugin install vagrant-aws |
|
| 91 |
- |
|
| 92 |
- |
|
| 93 |
-3. Get the docker sources, this will give you the latest Vagrantfile. |
|
| 94 |
- |
|
| 95 |
- :: |
|
| 96 |
- |
|
| 97 |
- git clone https://github.com/dotcloud/docker.git |
|
| 98 |
- |
|
| 99 |
- |
|
| 100 |
-4. Check your AWS environment. |
|
| 101 |
- |
|
| 102 |
- Create a keypair specifically for EC2, give it a name and save it |
|
| 103 |
- to your disk. *I usually store these in my ~/.ssh/ folder*. |
|
| 104 |
- |
|
| 105 |
- Check that your default security group has an inbound rule to |
|
| 106 |
- accept SSH (port 22) connections. |
|
| 107 |
- |
|
| 108 |
-5. Inform Vagrant of your settings |
|
| 109 |
- |
|
| 110 |
- Vagrant will read your access credentials from your environment, so |
|
| 111 |
- we need to set them there first. Make sure you have everything on |
|
| 112 |
- amazon aws setup so you can (manually) deploy a new image to EC2. |
|
| 113 |
- |
|
| 114 |
- Note that where possible these variables are the same as those honored by |
|
| 115 |
- the ec2 api tools. |
|
| 116 |
- :: |
|
| 117 |
- |
|
| 118 |
- export AWS_ACCESS_KEY=xxx |
|
| 119 |
- export AWS_SECRET_KEY=xxx |
|
| 120 |
- export AWS_KEYPAIR_NAME=xxx |
|
| 121 |
- export SSH_PRIVKEY_PATH=xxx |
|
| 122 |
- |
|
| 123 |
- export BOX_NAME=xxx |
|
| 124 |
- export AWS_REGION=xxx |
|
| 125 |
- export AWS_AMI=xxx |
|
| 126 |
- export AWS_INSTANCE_TYPE=xxx |
|
| 127 |
- |
|
| 128 |
- The required environment variables are: |
|
| 129 |
- |
|
| 130 |
- * ``AWS_ACCESS_KEY`` - The API key used to make requests to AWS |
|
| 131 |
- * ``AWS_SECRET_KEY`` - The secret key to make AWS API requests |
|
| 132 |
- * ``AWS_KEYPAIR_NAME`` - The name of the keypair used for this EC2 instance |
|
| 133 |
- * ``SSH_PRIVKEY_PATH`` - The path to the private key for the named |
|
| 134 |
- keypair, for example ``~/.ssh/docker.pem`` |
|
| 135 |
- |
|
| 136 |
- There are a number of optional environment variables: |
|
| 137 |
- |
|
| 138 |
- * ``BOX_NAME`` - The name of the vagrant box to use. Defaults to |
|
| 139 |
- ``ubuntu``. |
|
| 140 |
- * ``AWS_REGION`` - The aws region to spawn the vm in. Defaults to |
|
| 141 |
- ``us-east-1``. |
|
| 142 |
- * ``AWS_AMI`` - The aws AMI to start with as a base. This must be |
|
| 143 |
- be an ubuntu 12.04 precise image. You must change this value if |
|
| 144 |
- ``AWS_REGION`` is set to a value other than ``us-east-1``. |
|
| 145 |
- This is because AMIs are region specific. Defaults to ``ami-69f5a900``. |
|
| 146 |
- * ``AWS_INSTANCE_TYPE`` - The aws instance type. Defaults to ``t1.micro``. |
|
| 147 |
- |
|
| 148 |
- You can check if they are set correctly by doing something like |
|
| 149 |
- |
|
| 150 |
- :: |
|
| 151 |
- |
|
| 152 |
- echo $AWS_ACCESS_KEY |
|
| 153 |
- |
|
| 154 |
-6. Do the magic! |
|
| 155 |
- |
|
| 156 |
- :: |
|
| 157 |
- |
|
| 158 |
- vagrant up --provider=aws |
|
| 159 |
- |
|
| 160 |
- |
|
| 161 |
- If it stalls indefinitely on ``[default] Waiting for SSH to become |
|
| 162 |
- available...``, Double check your default security zone on AWS |
|
| 163 |
- includes rights to SSH (port 22) to your container. |
|
| 164 |
- |
|
| 165 |
- If you have an advanced AWS setup, you might want to have a look at |
|
| 166 |
- `vagrant-aws <https://github.com/mitchellh/vagrant-aws>`_. |
|
| 167 |
- |
|
| 168 |
-7. Connect to your machine |
|
| 169 |
- |
|
| 170 |
- .. code-block:: bash |
|
| 171 |
- |
|
| 172 |
- vagrant ssh |
|
| 173 |
- |
|
| 174 |
-8. Your first command |
|
| 175 |
- |
|
| 176 |
- Now you are in the VM, run docker |
|
| 177 |
- |
|
| 178 |
- .. code-block:: bash |
|
| 179 |
- |
|
| 180 |
- sudo docker |
|
| 181 |
- |
|
| 182 |
- |
|
| 183 | 76 |
Continue with the :ref:`hello_world` example. |
| ... | ... |
@@ -1,223 +1,72 @@ |
| 1 | 1 |
:title: Installation on Windows |
| 2 | 2 |
:description: Please note this project is currently under heavy development. It should not be used in production. |
| 3 |
-:keywords: Docker, Docker documentation, Windows, requirements, virtualbox, vagrant, git, ssh, putty, cygwin |
|
| 3 |
+:keywords: Docker, Docker documentation, Windows, requirements, virtualbox, boot2docker |
|
| 4 | 4 |
|
| 5 | 5 |
.. _windows: |
| 6 | 6 |
|
| 7 | 7 |
Windows |
| 8 | 8 |
======= |
| 9 | 9 |
|
| 10 |
-Docker can run on Windows using a VM like VirtualBox. You then run |
|
| 11 |
-Linux within the VM. |
|
| 10 |
+Docker can run on Windows using a virtualization platform like VirtualBox. A Linux |
|
| 11 |
+distribution is run inside a virtual machine and that's where Docker will run. |
|
| 12 | 12 |
|
| 13 | 13 |
Installation |
| 14 | 14 |
------------ |
| 15 | 15 |
|
| 16 | 16 |
.. include:: install_header.inc |
| 17 | 17 |
|
| 18 |
-.. include:: install_unofficial.inc |
|
| 18 |
+1. Install virtualbox from https://www.virtualbox.org - or follow this `tutorial <http://www.slideshare.net/julienbarbier42/install-virtualbox-on-windows-7>`_. |
|
| 19 | 19 |
|
| 20 |
-1. Install virtualbox from https://www.virtualbox.org - or follow this tutorial__ |
|
| 20 |
+2. Download the latest boot2docker.iso from https://github.com/boot2docker/boot2docker/releases. |
|
| 21 | 21 |
|
| 22 |
-.. __: http://www.slideshare.net/julienbarbier42/install-virtualbox-on-windows-7 |
|
| 22 |
+3. Start VirtualBox. |
|
| 23 | 23 |
|
| 24 |
-2. Install vagrant from http://www.vagrantup.com - or follow this tutorial__ |
|
| 24 |
+4. Create a new Virtual machine with the following settings: |
|
| 25 | 25 |
|
| 26 |
-.. __: http://www.slideshare.net/julienbarbier42/install-vagrant-on-windows-7 |
|
| 26 |
+ - `Name: boot2docker` |
|
| 27 |
+ - `Type: Linux` |
|
| 28 |
+ - `Version: Linux 2.6 (64 bit)` |
|
| 29 |
+ - `Memory size: 1024 MB` |
|
| 30 |
+ - `Hard drive: Do not add a virtual hard drive` |
|
| 27 | 31 |
|
| 28 |
-3. Install git with ssh from http://git-scm.com/downloads - or follow this tutorial__ |
|
| 32 |
+5. Open the settings of the virtual machine: |
|
| 29 | 33 |
|
| 30 |
-.. __: http://www.slideshare.net/julienbarbier42/install-git-with-ssh-on-windows-7 |
|
| 34 |
+ 5.1. go to Storage |
|
| 31 | 35 |
|
| 36 |
+ 5.2. click the empty slot below `Controller: IDE` |
|
| 32 | 37 |
|
| 33 |
-We recommend having at least 2Gb of free disk space and 2Gb of RAM (or more). |
|
| 38 |
+ 5.3. click the disc icon on the right of `IDE Secondary Master` |
|
| 34 | 39 |
|
| 35 |
-Opening a command prompt |
|
| 40 |
+ 5.4. click `Choose a virtual CD/DVD disk file` |
|
| 36 | 41 |
|
| 37 |
-First open a cmd prompt. Press Windows key and then press “R” |
|
| 38 |
-key. This will open the RUN dialog box for you. Type “cmd” and press |
|
| 39 |
-Enter. Or you can click on Start, type “cmd” in the “Search programs |
|
| 40 |
-and files” field, and click on cmd.exe. |
|
| 42 |
+6. Browse to the path where you've saved the `boot2docker.iso`, select the `boot2docker.iso` and click open. |
|
| 41 | 43 |
|
| 42 |
-.. image:: images/win/_01.gif |
|
| 43 |
- :alt: Git install |
|
| 44 |
- :align: center |
|
| 44 |
+7. Click OK on the Settings dialog to save the changes and close the window. |
|
| 45 | 45 |
|
| 46 |
-This should open a cmd prompt window. |
|
| 46 |
+8. Start the virtual machine by clicking the green start button. |
|
| 47 | 47 |
|
| 48 |
-.. image:: images/win/_02.gif |
|
| 49 |
- :alt: run docker |
|
| 50 |
- :align: center |
|
| 51 |
- |
|
| 52 |
-Alternatively, you can also use a Cygwin terminal, or Git Bash (or any |
|
| 53 |
-other command line program you are usually using). The next steps |
|
| 54 |
-would be the same. |
|
| 55 |
- |
|
| 56 |
-.. _launch_ubuntu: |
|
| 57 |
- |
|
| 58 |
-Launch an Ubuntu virtual server |
|
| 59 |
- |
|
| 60 |
-Let’s download and run an Ubuntu image with docker binaries already |
|
| 61 |
-installed. |
|
| 62 |
- |
|
| 63 |
-.. code-block:: bash |
|
| 64 |
- |
|
| 65 |
- git clone https://github.com/dotcloud/docker.git |
|
| 66 |
- cd docker |
|
| 67 |
- vagrant up |
|
| 68 |
- |
|
| 69 |
-.. image:: images/win/run_02_.gif |
|
| 70 |
- :alt: run docker |
|
| 71 |
- :align: center |
|
| 72 |
- |
|
| 73 |
-Congratulations! You are running an Ubuntu server with docker |
|
| 74 |
-installed on it. You do not see it though, because it is running in |
|
| 75 |
-the background. |
|
| 76 |
- |
|
| 77 |
-Log onto your Ubuntu server |
|
| 78 |
- |
|
| 79 |
-Let’s log into your Ubuntu server now. To do so you have two choices: |
|
| 80 |
- |
|
| 81 |
-- Use Vagrant on Windows command prompt OR |
|
| 82 |
-- Use SSH |
|
| 83 |
- |
|
| 84 |
-Using Vagrant on Windows Command Prompt |
|
| 85 |
-``````````````````````````````````````` |
|
| 86 |
- |
|
| 87 |
-Run the following command |
|
| 88 |
- |
|
| 89 |
-.. code-block:: bash |
|
| 90 |
- |
|
| 91 |
- vagrant ssh |
|
| 92 |
- |
|
| 93 |
-You may see an error message starting with “`ssh` executable not |
|
| 94 |
-found”. In this case it means that you do not have SSH in your |
|
| 95 |
-PATH. If you do not have SSH in your PATH you can set it up with the |
|
| 96 |
-“set” command. For instance, if your ssh.exe is in the folder named |
|
| 97 |
-“C:\Program Files (x86)\Git\bin”, then you can run the following |
|
| 98 |
-command: |
|
| 99 |
- |
|
| 100 |
-.. code-block:: bash |
|
| 101 |
- |
|
| 102 |
- set PATH=%PATH%;C:\Program Files (x86)\Git\bin |
|
| 103 |
- |
|
| 104 |
-.. image:: images/win/run_03.gif |
|
| 105 |
- :alt: run docker |
|
| 106 |
- :align: center |
|
| 107 |
- |
|
| 108 |
-Using SSH |
|
| 109 |
-````````` |
|
| 110 |
- |
|
| 111 |
-First step is to get the IP and port of your Ubuntu server. Simply run: |
|
| 112 |
- |
|
| 113 |
-.. code-block:: bash |
|
| 114 |
- |
|
| 115 |
- vagrant ssh-config |
|
| 116 |
- |
|
| 117 |
-You should see an output with HostName and Port information. In this |
|
| 118 |
-example, HostName is 127.0.0.1 and port is 2222. And the User is |
|
| 119 |
-“vagrant”. The password is not shown, but it is also “vagrant”. |
|
| 120 |
- |
|
| 121 |
-.. image:: images/win/ssh-config.gif |
|
| 122 |
- :alt: run docker |
|
| 123 |
- :align: center |
|
| 124 |
- |
|
| 125 |
-You can now use this information for connecting via SSH to your |
|
| 126 |
-server. To do so you can: |
|
| 127 |
- |
|
| 128 |
-- Use putty.exe OR |
|
| 129 |
-- Use SSH from a terminal |
|
| 130 |
- |
|
| 131 |
-Use putty.exe |
|
| 132 |
-''''''''''''' |
|
| 133 |
- |
|
| 134 |
-You can download putty.exe from this page |
|
| 135 |
-http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html Launch |
|
| 136 |
-putty.exe and simply enter the information you got from last step. |
|
| 137 |
- |
|
| 138 |
-.. image:: images/win/putty.gif |
|
| 139 |
- :alt: run docker |
|
| 140 |
- :align: center |
|
| 141 |
- |
|
| 142 |
-Open, and enter user = vagrant and password = vagrant. |
|
| 143 |
- |
|
| 144 |
-.. image:: images/win/putty_2.gif |
|
| 145 |
- :alt: run docker |
|
| 146 |
- :align: center |
|
| 147 |
- |
|
| 148 |
-SSH from a terminal |
|
| 149 |
-''''''''''''''''''' |
|
| 150 |
- |
|
| 151 |
-You can also run this command on your favorite terminal (windows |
|
| 152 |
-prompt, cygwin, git-bash, …). Make sure to adapt the IP and port from |
|
| 153 |
-what you got from the vagrant ssh-config command. |
|
| 154 |
- |
|
| 155 |
-.. code-block:: bash |
|
| 156 |
- |
|
| 157 |
- ssh vagrant@127.0.0.1 –p 2222 |
|
| 158 |
- |
|
| 159 |
-Enter user = vagrant and password = vagrant. |
|
| 160 |
- |
|
| 161 |
-.. image:: images/win/cygwin.gif |
|
| 162 |
- :alt: run docker |
|
| 163 |
- :align: center |
|
| 164 |
- |
|
| 165 |
-Congratulations, you are now logged onto your Ubuntu Server, running |
|
| 166 |
-on top of your Windows machine ! |
|
| 48 |
+9. The boot2docker virtual machine should boot now. |
|
| 167 | 49 |
|
| 168 | 50 |
Running Docker |
| 169 | 51 |
-------------- |
| 170 | 52 |
|
| 171 |
-First you have to be root in order to run docker. Simply run the |
|
| 172 |
-following command: |
|
| 173 |
- |
|
| 174 |
-.. code-block:: bash |
|
| 53 |
+boot2docker will log you in automatically so you can start using Docker right |
|
| 54 |
+away. |
|
| 175 | 55 |
|
| 176 |
- sudo su |
|
| 177 |
- |
|
| 178 |
-You are now ready for the docker’s “hello world” example. Run |
|
| 56 |
+Let's try the “hello world” example. Run |
|
| 179 | 57 |
|
| 180 | 58 |
.. code-block:: bash |
| 181 | 59 |
|
| 182 | 60 |
docker run busybox echo hello world |
| 183 | 61 |
|
| 184 |
-.. image:: images/win/run_04.gif |
|
| 185 |
- :alt: run docker |
|
| 186 |
- :align: center |
|
| 187 |
- |
|
| 188 |
-All done! |
|
| 189 |
- |
|
| 190 |
-Now you can continue with the :ref:`hello_world` example. |
|
| 62 |
+This will download the small busybox image and print hello world. |
|
| 191 | 63 |
|
| 192 |
-Troubleshooting |
|
| 193 | 64 |
|
| 194 |
-VM does not boot |
|
| 195 |
-```````````````` |
|
| 196 |
- |
|
| 197 |
-.. image:: images/win/ts_go_bios.JPG |
|
| 198 |
- |
|
| 199 |
-If you run into this error message "The VM failed to remain in the |
|
| 200 |
-'running' state while attempting to boot", please check that your |
|
| 201 |
-computer has virtualization technology available and activated by |
|
| 202 |
-going to the BIOS. Here's an example for an HP computer (System |
|
| 203 |
-configuration / Device configuration) |
|
| 204 |
- |
|
| 205 |
-.. image:: images/win/hp_bios_vm.JPG |
|
| 206 |
- |
|
| 207 |
-On some machines the BIOS menu can only be accessed before startup. |
|
| 208 |
-To access BIOS in this scenario you should restart your computer and |
|
| 209 |
-press ESC/Enter when prompted to access the boot and BIOS controls. Typically |
|
| 210 |
-the option to allow virtualization is contained within the BIOS/Security menu. |
|
| 211 |
- |
|
| 212 |
-Docker is not installed |
|
| 213 |
-``````````````````````` |
|
| 65 |
+Observations |
|
| 66 |
+------------ |
|
| 214 | 67 |
|
| 215 |
-.. image:: images/win/ts_no_docker.JPG |
|
| 68 |
+Persistent storage |
|
| 69 |
+`````````````````` |
|
| 216 | 70 |
|
| 217 |
-If you run into this error message "The program 'docker' is currently |
|
| 218 |
-not installed", try deleting the docker folder and restart from |
|
| 219 |
-:ref:`launch_ubuntu` |
|
| 71 |
+The virtual machine created above lacks any persistent data storage. All images |
|
| 72 |
+and containers will be lost when shutting down or rebooting the VM. |
| ... | ... |
@@ -118,6 +118,7 @@ Create a container |
| 118 | 118 |
"User":"", |
| 119 | 119 |
"Memory":0, |
| 120 | 120 |
"MemorySwap":0, |
| 121 |
+ "CpuShares":0, |
|
| 121 | 122 |
"AttachStdin":false, |
| 122 | 123 |
"AttachStdout":true, |
| 123 | 124 |
"AttachStderr":true, |
| ... | ... |
@@ -153,7 +154,15 @@ Create a container |
| 153 | 153 |
"Warnings":[] |
| 154 | 154 |
} |
| 155 | 155 |
|
| 156 |
- :jsonparam config: the container's configuration |
|
| 156 |
+ :jsonparam Hostname: Container host name |
|
| 157 |
+ :jsonparam User: Username or UID |
|
| 158 |
+ :jsonparam Memory: Memory Limit in bytes |
|
| 159 |
+ :jsonparam CpuShares: CPU shares (relative weight) |
|
| 160 |
+ :jsonparam AttachStdin: 1/True/true or 0/False/false, attach to standard input. Default false |
|
| 161 |
+ :jsonparam AttachStdout: 1/True/true or 0/False/false, attach to standard output. Default false |
|
| 162 |
+ :jsonparam AttachStderr: 1/True/true or 0/False/false, attach to standard error. Default false |
|
| 163 |
+ :jsonparam Tty: 1/True/true or 0/False/false, allocate a pseudo-tty. Default false |
|
| 164 |
+ :jsonparam OpenStdin: 1/True/true or 0/False/false, keep stdin open even if not attached. Default false |
|
| 157 | 165 |
:query name: Assign the specified name to the container. Must match ``/?[a-zA-Z0-9_-]+``. |
| 158 | 166 |
:statuscode 201: no error |
| 159 | 167 |
:statuscode 404: no such container |
| ... | ... |
@@ -394,7 +403,11 @@ Start a container |
| 394 | 394 |
HTTP/1.1 204 No Content |
| 395 | 395 |
Content-Type: text/plain |
| 396 | 396 |
|
| 397 |
- :jsonparam hostConfig: the container's host configuration (optional) |
|
| 397 |
+ :jsonparam Binds: Create a bind mount to a directory or file with [host-path]:[container-path]:[rw|ro]. If a directory "container-path" is missing, then docker creates a new volume. |
|
| 398 |
+ :jsonparam LxcConf: Map of custom lxc options |
|
| 399 |
+ :jsonparam PortBindings: Expose ports from the container, optionally publishing them via the HostPort flag |
|
| 400 |
+ :jsonparam PublishAllPorts: 1/True/true or 0/False/false, publish all exposed ports to the host interfaces. Default false |
|
| 401 |
+ :jsonparam Privileged: 1/True/true or 0/False/false, give extended privileges to this container. Default false |
|
| 398 | 402 |
:statuscode 204: no error |
| 399 | 403 |
:statuscode 404: no such container |
| 400 | 404 |
:statuscode 500: server error |
| ... | ... |
@@ -118,6 +118,7 @@ Create a container |
| 118 | 118 |
"User":"", |
| 119 | 119 |
"Memory":0, |
| 120 | 120 |
"MemorySwap":0, |
| 121 |
+ "CpuShares":0, |
|
| 121 | 122 |
"AttachStdin":false, |
| 122 | 123 |
"AttachStdout":true, |
| 123 | 124 |
"AttachStderr":true, |
| ... | ... |
@@ -153,7 +154,15 @@ Create a container |
| 153 | 153 |
"Warnings":[] |
| 154 | 154 |
} |
| 155 | 155 |
|
| 156 |
- :jsonparam config: the container's configuration |
|
| 156 |
+ :jsonparam Hostname: Container host name |
|
| 157 |
+ :jsonparam User: Username or UID |
|
| 158 |
+ :jsonparam Memory: Memory Limit in bytes |
|
| 159 |
+ :jsonparam CpuShares: CPU shares (relative weight) |
|
| 160 |
+ :jsonparam AttachStdin: 1/True/true or 0/False/false, attach to standard input. Default false |
|
| 161 |
+ :jsonparam AttachStdout: 1/True/true or 0/False/false, attach to standard output. Default false |
|
| 162 |
+ :jsonparam AttachStderr: 1/True/true or 0/False/false, attach to standard error. Default false |
|
| 163 |
+ :jsonparam Tty: 1/True/true or 0/False/false, allocate a pseudo-tty. Default false |
|
| 164 |
+ :jsonparam OpenStdin: 1/True/true or 0/False/false, keep stdin open even if not attached. Default false |
|
| 157 | 165 |
:query name: Assign the specified name to the container. Must match ``/?[a-zA-Z0-9_-]+``. |
| 158 | 166 |
:statuscode 201: no error |
| 159 | 167 |
:statuscode 404: no such container |
| ... | ... |
@@ -394,7 +403,11 @@ Start a container |
| 394 | 394 |
HTTP/1.1 204 No Content |
| 395 | 395 |
Content-Type: text/plain |
| 396 | 396 |
|
| 397 |
- :jsonparam hostConfig: the container's host configuration (optional) |
|
| 397 |
+ :jsonparam Binds: Create a bind mount to a directory or file with [host-path]:[container-path]:[rw|ro]. If a directory "container-path" is missing, then docker creates a new volume. |
|
| 398 |
+ :jsonparam LxcConf: Map of custom lxc options |
|
| 399 |
+ :jsonparam PortBindings: Expose ports from the container, optionally publishing them via the HostPort flag |
|
| 400 |
+ :jsonparam PublishAllPorts: 1/True/true or 0/False/false, publish all exposed ports to the host interfaces. Default false |
|
| 401 |
+ :jsonparam Privileged: 1/True/true or 0/False/false, give extended privileges to this container. Default false |
|
| 398 | 402 |
:statuscode 204: no error |
| 399 | 403 |
:statuscode 404: no such container |
| 400 | 404 |
:statuscode 500: server error |
| ... | ... |
@@ -79,6 +79,7 @@ Commands |
| 79 | 79 |
-p, --pidfile="/var/run/docker.pid": Path to use for daemon PID file |
| 80 | 80 |
-r, --restart=true: Restart previously running containers |
| 81 | 81 |
-s, --storage-driver="": Force the docker runtime to use a specific storage driver |
| 82 |
+ -e, --exec-driver="": Force the docker runtime to use a specific exec driver |
|
| 82 | 83 |
-v, --version=false: Print version information and quit |
| 83 | 84 |
--mtu=0: Set the containers network MTU; if no value is provided: default to the default route MTU or 1500 if no default route is available |
| 84 | 85 |
|
| ... | ... |
@@ -1,6 +1,7 @@ |
| 1 | 1 |
package engine |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
+ "bufio" |
|
| 4 | 5 |
"fmt" |
| 5 | 6 |
"github.com/dotcloud/docker/utils" |
| 6 | 7 |
"io" |
| ... | ... |
@@ -136,6 +137,48 @@ func (eng *Engine) Job(name string, args ...string) *Job {
|
| 136 | 136 |
return job |
| 137 | 137 |
} |
| 138 | 138 |
|
| 139 |
+// ParseJob creates a new job from a text description using a shell-like syntax. |
|
| 140 |
+// |
|
| 141 |
+// The following syntax is used to parse `input`: |
|
| 142 |
+// |
|
| 143 |
+// * Words are separated using standard whitespaces as separators. |
|
| 144 |
+// * Quotes and backslashes are not interpreted. |
|
| 145 |
+// * Words of the form 'KEY=[VALUE]' are added to the job environment. |
|
| 146 |
+// * All other words are added to the job arguments. |
|
| 147 |
+// |
|
| 148 |
+// For example: |
|
| 149 |
+// |
|
| 150 |
+// job, _ := eng.ParseJob("VERBOSE=1 echo hello TEST=true world")
|
|
| 151 |
+// |
|
| 152 |
+// The resulting job will have: |
|
| 153 |
+// job.Args={"echo", "hello", "world"}
|
|
| 154 |
+// job.Env={"VERBOSE":"1", "TEST":"true"}
|
|
| 155 |
+// |
|
| 156 |
+func (eng *Engine) ParseJob(input string) (*Job, error) {
|
|
| 157 |
+ // FIXME: use a full-featured command parser |
|
| 158 |
+ scanner := bufio.NewScanner(strings.NewReader(input)) |
|
| 159 |
+ scanner.Split(bufio.ScanWords) |
|
| 160 |
+ var ( |
|
| 161 |
+ cmd []string |
|
| 162 |
+ env Env |
|
| 163 |
+ ) |
|
| 164 |
+ for scanner.Scan() {
|
|
| 165 |
+ word := scanner.Text() |
|
| 166 |
+ kv := strings.SplitN(word, "=", 2) |
|
| 167 |
+ if len(kv) == 2 {
|
|
| 168 |
+ env.Set(kv[0], kv[1]) |
|
| 169 |
+ } else {
|
|
| 170 |
+ cmd = append(cmd, word) |
|
| 171 |
+ } |
|
| 172 |
+ } |
|
| 173 |
+ if len(cmd) == 0 {
|
|
| 174 |
+ return nil, fmt.Errorf("empty command: '%s'", input)
|
|
| 175 |
+ } |
|
| 176 |
+ job := eng.Job(cmd[0], cmd[1:]...) |
|
| 177 |
+ job.Env().Init(&env) |
|
| 178 |
+ return job, nil |
|
| 179 |
+} |
|
| 180 |
+ |
|
| 139 | 181 |
func (eng *Engine) Logf(format string, args ...interface{}) (n int, err error) {
|
| 140 | 182 |
if os.Getenv("TEST") == "" {
|
| 141 | 183 |
prefixedFormat := fmt.Sprintf("[%s] %s\n", eng, strings.TrimRight(format, "\n"))
|
| ... | ... |
@@ -5,6 +5,7 @@ import ( |
| 5 | 5 |
"os" |
| 6 | 6 |
"path" |
| 7 | 7 |
"path/filepath" |
| 8 |
+ "strings" |
|
| 8 | 9 |
"testing" |
| 9 | 10 |
) |
| 10 | 11 |
|
| ... | ... |
@@ -114,3 +115,40 @@ func TestEngineLogf(t *testing.T) {
|
| 114 | 114 |
t.Fatalf("Test: Logf() should print at least as much as the input\ninput=%d\nprinted=%d", len(input), n)
|
| 115 | 115 |
} |
| 116 | 116 |
} |
| 117 |
+ |
|
| 118 |
+func TestParseJob(t *testing.T) {
|
|
| 119 |
+ eng := newTestEngine(t) |
|
| 120 |
+ defer os.RemoveAll(eng.Root()) |
|
| 121 |
+ // Verify that the resulting job calls to the right place |
|
| 122 |
+ var called bool |
|
| 123 |
+ eng.Register("echo", func(job *Job) Status {
|
|
| 124 |
+ called = true |
|
| 125 |
+ return StatusOK |
|
| 126 |
+ }) |
|
| 127 |
+ input := "echo DEBUG=1 hello world VERBOSITY=42" |
|
| 128 |
+ job, err := eng.ParseJob(input) |
|
| 129 |
+ if err != nil {
|
|
| 130 |
+ t.Fatal(err) |
|
| 131 |
+ } |
|
| 132 |
+ if job.Name != "echo" {
|
|
| 133 |
+ t.Fatalf("Invalid job name: %v", job.Name)
|
|
| 134 |
+ } |
|
| 135 |
+ if strings.Join(job.Args, ":::") != "hello:::world" {
|
|
| 136 |
+ t.Fatalf("Invalid job args: %v", job.Args)
|
|
| 137 |
+ } |
|
| 138 |
+ if job.Env().Get("DEBUG") != "1" {
|
|
| 139 |
+ t.Fatalf("Invalid job env: %v", job.Env)
|
|
| 140 |
+ } |
|
| 141 |
+ if job.Env().Get("VERBOSITY") != "42" {
|
|
| 142 |
+ t.Fatalf("Invalid job env: %v", job.Env)
|
|
| 143 |
+ } |
|
| 144 |
+ if len(job.Env().Map()) != 2 {
|
|
| 145 |
+ t.Fatalf("Invalid job env: %v", job.Env)
|
|
| 146 |
+ } |
|
| 147 |
+ if err := job.Run(); err != nil {
|
|
| 148 |
+ t.Fatal(err) |
|
| 149 |
+ } |
|
| 150 |
+ if !called {
|
|
| 151 |
+ t.Fatalf("Job was not called")
|
|
| 152 |
+ } |
|
| 153 |
+} |
| ... | ... |
@@ -36,6 +36,13 @@ func (env *Env) Exists(key string) bool {
|
| 36 | 36 |
return exists |
| 37 | 37 |
} |
| 38 | 38 |
|
| 39 |
+func (env *Env) Init(src *Env) {
|
|
| 40 |
+ (*env) = make([]string, 0, len(*src)) |
|
| 41 |
+ for _, val := range *src {
|
|
| 42 |
+ (*env) = append((*env), val) |
|
| 43 |
+ } |
|
| 44 |
+} |
|
| 45 |
+ |
|
| 39 | 46 |
func (env *Env) GetBool(key string) (value bool) {
|
| 40 | 47 |
s := strings.ToLower(strings.Trim(env.Get(key), " \t")) |
| 41 | 48 |
if s == "" || s == "0" || s == "no" || s == "false" || s == "none" {
|
| ... | ... |
@@ -74,7 +74,7 @@ func (job *Job) Run() error {
|
| 74 | 74 |
return err |
| 75 | 75 |
} |
| 76 | 76 |
if job.status != 0 {
|
| 77 |
- return fmt.Errorf("%s: %s", job.Name, errorMessage)
|
|
| 77 |
+ return fmt.Errorf("%s", errorMessage)
|
|
| 78 | 78 |
} |
| 79 | 79 |
return nil |
| 80 | 80 |
} |
| ... | ... |
@@ -102,6 +102,10 @@ func (job *Job) String() string {
|
| 102 | 102 |
return fmt.Sprintf("%s.%s%s", job.Eng, job.CallString(), job.StatusString())
|
| 103 | 103 |
} |
| 104 | 104 |
|
| 105 |
+func (job *Job) Env() *Env {
|
|
| 106 |
+ return job.env |
|
| 107 |
+} |
|
| 108 |
+ |
|
| 105 | 109 |
func (job *Job) EnvExists(key string) (value bool) {
|
| 106 | 110 |
return job.env.Exists(key) |
| 107 | 111 |
} |
| ... | ... |
@@ -197,11 +201,14 @@ func (job *Job) Printf(format string, args ...interface{}) (n int, err error) {
|
| 197 | 197 |
} |
| 198 | 198 |
|
| 199 | 199 |
func (job *Job) Errorf(format string, args ...interface{}) Status {
|
| 200 |
+ if format[len(format)-1] != '\n' {
|
|
| 201 |
+ format = format + "\n" |
|
| 202 |
+ } |
|
| 200 | 203 |
fmt.Fprintf(job.Stderr, format, args...) |
| 201 | 204 |
return StatusErr |
| 202 | 205 |
} |
| 203 | 206 |
|
| 204 | 207 |
func (job *Job) Error(err error) Status {
|
| 205 |
- fmt.Fprintf(job.Stderr, "%s", err) |
|
| 208 |
+ fmt.Fprintf(job.Stderr, "%s\n", err) |
|
| 206 | 209 |
return StatusErr |
| 207 | 210 |
} |
| ... | ... |
@@ -301,9 +301,8 @@ func (d *driver) Info(id string) execdriver.Info {
|
| 301 | 301 |
func (d *driver) GetPidsForContainer(id string) ([]int, error) {
|
| 302 | 302 |
pids := []int{}
|
| 303 | 303 |
|
| 304 |
- // memory is chosen randomly, any cgroup used by docker works |
|
| 305 |
- subsystem := "memory" |
|
| 306 |
- |
|
| 304 |
+ // cpu is chosen because it is the only non optional subsystem in cgroups |
|
| 305 |
+ subsystem := "cpu" |
|
| 307 | 306 |
cgroupRoot, err := cgroups.FindCgroupMountpoint(subsystem) |
| 308 | 307 |
if err != nil {
|
| 309 | 308 |
return pids, err |
| ... | ... |
@@ -12,5 +12,5 @@ bundle_test_integration() {
|
| 12 | 12 |
# this "grep" hides some really irritating warnings that "go test -coverpkg" |
| 13 | 13 |
# spews when it is given packages that aren't used |
| 14 | 14 |
bundle_test_integration 2>&1 \ |
| 15 |
- | grep -v '^warning: no packages being tested depend on ' \ |
|
| 15 |
+ | grep --line-buffered -v '^warning: no packages being tested depend on ' \ |
|
| 16 | 16 |
| tee $DEST/test.log |
| ... | ... |
@@ -123,19 +123,8 @@ func init() {
|
| 123 | 123 |
} |
| 124 | 124 |
|
| 125 | 125 |
func setupBaseImage() {
|
| 126 |
- eng, err := engine.New(unitTestStoreBase) |
|
| 127 |
- if err != nil {
|
|
| 128 |
- log.Fatalf("Can't initialize engine at %s: %s", unitTestStoreBase, err)
|
|
| 129 |
- } |
|
| 130 |
- job := eng.Job("initserver")
|
|
| 131 |
- job.Setenv("Root", unitTestStoreBase)
|
|
| 132 |
- job.SetenvBool("Autorestart", false)
|
|
| 133 |
- job.Setenv("BridgeIface", unitTestNetworkBridge)
|
|
| 134 |
- if err := job.Run(); err != nil {
|
|
| 135 |
- log.Fatalf("Unable to create a runtime for tests: %s", err)
|
|
| 136 |
- } |
|
| 137 |
- |
|
| 138 |
- job = eng.Job("inspect", unitTestImageName, "image")
|
|
| 126 |
+ eng := newTestEngine(log.New(os.Stderr, "", 0), false, unitTestStoreBase) |
|
| 127 |
+ job := eng.Job("inspect", unitTestImageName, "image")
|
|
| 139 | 128 |
img, _ := job.Stdout.AddEnv() |
| 140 | 129 |
// If the unit test is not found, try to download it. |
| 141 | 130 |
if err := job.Run(); err != nil || img.Get("id") != unitTestImageID {
|
| ... | ... |
@@ -575,18 +564,7 @@ func TestRestore(t *testing.T) {
|
| 575 | 575 |
|
| 576 | 576 |
// Here are are simulating a docker restart - that is, reloading all containers |
| 577 | 577 |
// from scratch |
| 578 |
- root := eng.Root() |
|
| 579 |
- eng, err := engine.New(root) |
|
| 580 |
- if err != nil {
|
|
| 581 |
- t.Fatal(err) |
|
| 582 |
- } |
|
| 583 |
- job := eng.Job("initserver")
|
|
| 584 |
- job.Setenv("Root", eng.Root())
|
|
| 585 |
- job.SetenvBool("Autorestart", false)
|
|
| 586 |
- if err := job.Run(); err != nil {
|
|
| 587 |
- t.Fatal(err) |
|
| 588 |
- } |
|
| 589 |
- |
|
| 578 |
+ eng = newTestEngine(t, false, eng.Root()) |
|
| 590 | 579 |
runtime2 := mkRuntimeFromEngine(eng, t) |
| 591 | 580 |
if len(runtime2.List()) != 2 {
|
| 592 | 581 |
t.Errorf("Expected 2 container, %v found", len(runtime2.List()))
|
| ... | ... |
@@ -612,22 +590,14 @@ func TestRestore(t *testing.T) {
|
| 612 | 612 |
} |
| 613 | 613 |
|
| 614 | 614 |
func TestReloadContainerLinks(t *testing.T) {
|
| 615 |
- // FIXME: here we don't use NewTestEngine because it calls initserver with Autorestart=false, |
|
| 616 |
- // and we want to set it to true. |
|
| 617 | 615 |
root, err := newTestDirectory(unitTestStoreBase) |
| 618 | 616 |
if err != nil {
|
| 619 | 617 |
t.Fatal(err) |
| 620 | 618 |
} |
| 621 |
- eng, err := engine.New(root) |
|
| 622 |
- if err != nil {
|
|
| 623 |
- t.Fatal(err) |
|
| 624 |
- } |
|
| 625 |
- job := eng.Job("initserver")
|
|
| 626 |
- job.Setenv("Root", eng.Root())
|
|
| 627 |
- job.SetenvBool("Autorestart", true)
|
|
| 628 |
- if err := job.Run(); err != nil {
|
|
| 629 |
- t.Fatal(err) |
|
| 630 |
- } |
|
| 619 |
+ // FIXME: here we don't use NewTestEngine because it calls initserver with Autorestart=false, |
|
| 620 |
+ // and we want to set it to true. |
|
| 621 |
+ |
|
| 622 |
+ eng := newTestEngine(t, true, root) |
|
| 631 | 623 |
|
| 632 | 624 |
runtime1 := mkRuntimeFromEngine(eng, t) |
| 633 | 625 |
defer nuke(runtime1) |
| ... | ... |
@@ -668,17 +638,7 @@ func TestReloadContainerLinks(t *testing.T) {
|
| 668 | 668 |
|
| 669 | 669 |
// Here are are simulating a docker restart - that is, reloading all containers |
| 670 | 670 |
// from scratch |
| 671 |
- eng, err = engine.New(root) |
|
| 672 |
- if err != nil {
|
|
| 673 |
- t.Fatal(err) |
|
| 674 |
- } |
|
| 675 |
- job = eng.Job("initserver")
|
|
| 676 |
- job.Setenv("Root", eng.Root())
|
|
| 677 |
- job.SetenvBool("Autorestart", false)
|
|
| 678 |
- if err := job.Run(); err != nil {
|
|
| 679 |
- t.Fatal(err) |
|
| 680 |
- } |
|
| 681 |
- |
|
| 671 |
+ eng = newTestEngine(t, false, root) |
|
| 682 | 672 |
runtime2 := mkRuntimeFromEngine(eng, t) |
| 683 | 673 |
if len(runtime2.List()) != 2 {
|
| 684 | 674 |
t.Errorf("Expected 2 container, %v found", len(runtime2.List()))
|
| ... | ... |
@@ -2,7 +2,6 @@ package docker |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 | 4 |
"github.com/dotcloud/docker" |
| 5 |
- "github.com/dotcloud/docker/engine" |
|
| 6 | 5 |
"github.com/dotcloud/docker/runconfig" |
| 7 | 6 |
"strings" |
| 8 | 7 |
"testing" |
| ... | ... |
@@ -258,20 +257,7 @@ func TestRestartKillWait(t *testing.T) {
|
| 258 | 258 |
t.Fatal(err) |
| 259 | 259 |
} |
| 260 | 260 |
|
| 261 |
- eng, err = engine.New(eng.Root()) |
|
| 262 |
- if err != nil {
|
|
| 263 |
- t.Fatal(err) |
|
| 264 |
- } |
|
| 265 |
- |
|
| 266 |
- job = eng.Job("initserver")
|
|
| 267 |
- job.Setenv("Root", eng.Root())
|
|
| 268 |
- job.SetenvBool("AutoRestart", false)
|
|
| 269 |
- // TestGetEnabledCors and TestOptionsRoute require EnableCors=true |
|
| 270 |
- job.SetenvBool("EnableCors", true)
|
|
| 271 |
- if err := job.Run(); err != nil {
|
|
| 272 |
- t.Fatal(err) |
|
| 273 |
- } |
|
| 274 |
- |
|
| 261 |
+ eng = newTestEngine(t, false, eng.Root()) |
|
| 275 | 262 |
srv = mkServerFromEngine(eng, t) |
| 276 | 263 |
|
| 277 | 264 |
job = srv.Eng.Job("containers")
|
| ... | ... |
@@ -15,6 +15,7 @@ import ( |
| 15 | 15 |
"time" |
| 16 | 16 |
|
| 17 | 17 |
"github.com/dotcloud/docker" |
| 18 |
+ "github.com/dotcloud/docker/builtins" |
|
| 18 | 19 |
"github.com/dotcloud/docker/engine" |
| 19 | 20 |
"github.com/dotcloud/docker/runconfig" |
| 20 | 21 |
"github.com/dotcloud/docker/utils" |
| ... | ... |
@@ -27,26 +28,12 @@ import ( |
| 27 | 27 |
// Create a temporary runtime suitable for unit testing. |
| 28 | 28 |
// Call t.Fatal() at the first error. |
| 29 | 29 |
func mkRuntime(f utils.Fataler) *docker.Runtime {
|
| 30 |
- root, err := newTestDirectory(unitTestStoreBase) |
|
| 31 |
- if err != nil {
|
|
| 32 |
- f.Fatal(err) |
|
| 33 |
- } |
|
| 34 |
- config := &docker.DaemonConfig{
|
|
| 35 |
- Root: root, |
|
| 36 |
- AutoRestart: false, |
|
| 37 |
- Mtu: docker.GetDefaultNetworkMtu(), |
|
| 38 |
- } |
|
| 39 |
- |
|
| 40 |
- eng, err := engine.New(root) |
|
| 41 |
- if err != nil {
|
|
| 42 |
- f.Fatal(err) |
|
| 43 |
- } |
|
| 44 |
- |
|
| 45 |
- r, err := docker.NewRuntimeFromDirectory(config, eng) |
|
| 46 |
- if err != nil {
|
|
| 47 |
- f.Fatal(err) |
|
| 48 |
- } |
|
| 49 |
- return r |
|
| 30 |
+ eng := newTestEngine(f, false, "") |
|
| 31 |
+ return mkRuntimeFromEngine(eng, f) |
|
| 32 |
+ // FIXME: |
|
| 33 |
+ // [...] |
|
| 34 |
+ // Mtu: docker.GetDefaultNetworkMtu(), |
|
| 35 |
+ // [...] |
|
| 50 | 36 |
} |
| 51 | 37 |
|
| 52 | 38 |
func createNamedTestContainer(eng *engine.Engine, config *runconfig.Config, f utils.Fataler, name string) (shortId string) {
|
| ... | ... |
@@ -185,20 +172,24 @@ func mkRuntimeFromEngine(eng *engine.Engine, t utils.Fataler) *docker.Runtime {
|
| 185 | 185 |
return runtime |
| 186 | 186 |
} |
| 187 | 187 |
|
| 188 |
-func NewTestEngine(t utils.Fataler) *engine.Engine {
|
|
| 189 |
- root, err := newTestDirectory(unitTestStoreBase) |
|
| 190 |
- if err != nil {
|
|
| 191 |
- t.Fatal(err) |
|
| 188 |
+func newTestEngine(t utils.Fataler, autorestart bool, root string) *engine.Engine {
|
|
| 189 |
+ if root == "" {
|
|
| 190 |
+ if dir, err := newTestDirectory(unitTestStoreBase); err != nil {
|
|
| 191 |
+ t.Fatal(err) |
|
| 192 |
+ } else {
|
|
| 193 |
+ root = dir |
|
| 194 |
+ } |
|
| 192 | 195 |
} |
| 193 | 196 |
eng, err := engine.New(root) |
| 194 | 197 |
if err != nil {
|
| 195 | 198 |
t.Fatal(err) |
| 196 | 199 |
} |
| 197 | 200 |
// Load default plugins |
| 201 |
+ builtins.Register(eng) |
|
| 198 | 202 |
// (This is manually copied and modified from main() until we have a more generic plugin system) |
| 199 | 203 |
job := eng.Job("initserver")
|
| 200 | 204 |
job.Setenv("Root", root)
|
| 201 |
- job.SetenvBool("AutoRestart", false)
|
|
| 205 |
+ job.SetenvBool("AutoRestart", autorestart)
|
|
| 202 | 206 |
// TestGetEnabledCors and TestOptionsRoute require EnableCors=true |
| 203 | 207 |
job.SetenvBool("EnableCors", true)
|
| 204 | 208 |
if err := job.Run(); err != nil {
|
| ... | ... |
@@ -207,6 +198,10 @@ func NewTestEngine(t utils.Fataler) *engine.Engine {
|
| 207 | 207 |
return eng |
| 208 | 208 |
} |
| 209 | 209 |
|
| 210 |
+func NewTestEngine(t utils.Fataler) *engine.Engine {
|
|
| 211 |
+ return newTestEngine(t, false, "") |
|
| 212 |
+} |
|
| 213 |
+ |
|
| 210 | 214 |
func newTestDirectory(templateDir string) (dir string, err error) {
|
| 211 | 215 |
return utils.TestDirectory(templateDir) |
| 212 | 216 |
} |
| ... | ... |
@@ -72,7 +72,7 @@ func (l *Link) ToEnv() []string {
|
| 72 | 72 |
if len(parts) != 2 {
|
| 73 | 73 |
continue |
| 74 | 74 |
} |
| 75 |
- // Ignore a few variables that are added during docker build |
|
| 75 |
+ // Ignore a few variables that are added during docker build (and not really relevant to linked containers) |
|
| 76 | 76 |
if parts[0] == "HOME" || parts[0] == "PATH" {
|
| 77 | 77 |
continue |
| 78 | 78 |
} |
| ... | ... |
@@ -57,12 +57,6 @@ var ( |
| 57 | 57 |
currentInterfaces = make(map[string]*networkInterface) |
| 58 | 58 |
) |
| 59 | 59 |
|
| 60 |
-func init() {
|
|
| 61 |
- if err := engine.Register("init_networkdriver", InitDriver); err != nil {
|
|
| 62 |
- panic(err) |
|
| 63 |
- } |
|
| 64 |
-} |
|
| 65 |
- |
|
| 66 | 60 |
func InitDriver(job *engine.Job) engine.Status {
|
| 67 | 61 |
var ( |
| 68 | 62 |
network *net.IPNet |
| ... | ... |
@@ -8,7 +8,7 @@ import ( |
| 8 | 8 |
"github.com/dotcloud/docker/engine" |
| 9 | 9 |
"github.com/dotcloud/docker/execdriver" |
| 10 | 10 |
"github.com/dotcloud/docker/execdriver/docker" |
| 11 |
- _ "github.com/dotcloud/docker/execdriver/lxc" |
|
| 11 |
+ "github.com/dotcloud/docker/execdriver/lxc" |
|
| 12 | 12 |
"github.com/dotcloud/docker/graphdriver" |
| 13 | 13 |
"github.com/dotcloud/docker/graphdriver/aufs" |
| 14 | 14 |
_ "github.com/dotcloud/docker/graphdriver/btrfs" |
| ... | ... |
@@ -704,7 +704,16 @@ func NewRuntimeFromDirectory(config *DaemonConfig, eng *engine.Engine) (*Runtime |
| 704 | 704 |
|
| 705 | 705 |
sysInfo := sysinfo.New(false) |
| 706 | 706 |
|
| 707 |
- ed, err := docker.NewDriver(config.Root) |
|
| 707 |
+ var ed execdriver.Driver |
|
| 708 |
+ utils.Debugf("execDriver: provided %s", config.ExecDriver)
|
|
| 709 |
+ if config.ExecDriver == "chroot" && false {
|
|
| 710 |
+ // chroot is presently a noop driver https://github.com/dotcloud/docker/pull/4189#issuecomment-35330655 |
|
| 711 |
+ ed, err = chroot.NewDriver() |
|
| 712 |
+ utils.Debugf("execDriver: using chroot")
|
|
| 713 |
+ } else {
|
|
| 714 |
+ ed, err = lxc.NewDriver(config.Root, sysInfo.AppArmor) |
|
| 715 |
+ utils.Debugf("execDriver: using lxc")
|
|
| 716 |
+ } |
|
| 708 | 717 |
if err != nil {
|
| 709 | 718 |
return nil, err |
| 710 | 719 |
} |
| ... | ... |
@@ -34,14 +34,10 @@ func (srv *Server) Close() error {
|
| 34 | 34 |
return srv.runtime.Close() |
| 35 | 35 |
} |
| 36 | 36 |
|
| 37 |
-func init() {
|
|
| 38 |
- engine.Register("initserver", jobInitServer)
|
|
| 39 |
-} |
|
| 40 |
- |
|
| 41 | 37 |
// jobInitApi runs the remote api server `srv` as a daemon, |
| 42 | 38 |
// Only one api server can run at the same time - this is enforced by a pidfile. |
| 43 | 39 |
// The signals SIGINT, SIGQUIT and SIGTERM are intercepted for cleanup. |
| 44 |
-func jobInitServer(job *engine.Job) engine.Status {
|
|
| 40 |
+func InitServer(job *engine.Job) engine.Status {
|
|
| 45 | 41 |
job.Logf("Creating server")
|
| 46 | 42 |
srv, err := NewServer(job.Eng, DaemonConfigFromJob(job)) |
| 47 | 43 |
if err != nil {
|
| ... | ... |
@@ -7,11 +7,7 @@ import ( |
| 7 | 7 |
"runtime" |
| 8 | 8 |
) |
| 9 | 9 |
|
| 10 |
-func init() {
|
|
| 11 |
- engine.Register("version", jobVersion)
|
|
| 12 |
-} |
|
| 13 |
- |
|
| 14 |
-func jobVersion(job *engine.Job) engine.Status {
|
|
| 10 |
+func GetVersion(job *engine.Job) engine.Status {
|
|
| 15 | 11 |
if _, err := dockerVersion().WriteTo(job.Stdout); err != nil {
|
| 16 | 12 |
job.Errorf("%s", err)
|
| 17 | 13 |
return engine.StatusErr |