Merge branch 'master' into fix-auth

This commit is contained in:
Victor Vieux 2013-06-21 09:18:03 +00:00
commit 8f2a80804c
70 changed files with 1216 additions and 808 deletions

View File

@ -19,3 +19,7 @@ Andy Smith <github@anarkystic.com>
<victor.vieux@dotcloud.com> <dev@vvieux.com> <victor.vieux@dotcloud.com> <dev@vvieux.com>
<dominik@honnef.co> <dominikh@fork-bomb.org> <dominik@honnef.co> <dominikh@fork-bomb.org>
Thatcher Peskens <thatcher@dotcloud.com> Thatcher Peskens <thatcher@dotcloud.com>
<ehanchrow@ine.com> <eric.hanchrow@gmail.com>
Walter Stanish <walter@pratyeka.org>
<daniel@gasienica.ch> <dgasienica@zynga.com>
Roberto Hashioka <roberto_hashioka@hotmail.com>

23
AUTHORS
View File

@ -6,6 +6,8 @@
Al Tobey <al@ooyala.com> Al Tobey <al@ooyala.com>
Alexey Shamrin <shamrin@gmail.com> Alexey Shamrin <shamrin@gmail.com>
Andrea Luzzardi <aluzzardi@gmail.com> Andrea Luzzardi <aluzzardi@gmail.com>
Andreas Tiefenthaler <at@an-ti.eu>
Andrew Munsell <andrew@wizardapps.net>
Andy Rothfusz <github@metaliveblog.com> Andy Rothfusz <github@metaliveblog.com>
Andy Smith <github@anarkystic.com> Andy Smith <github@anarkystic.com>
Antony Messerli <amesserl@rackspace.com> Antony Messerli <amesserl@rackspace.com>
@ -14,7 +16,9 @@ Brandon Liu <bdon@bdon.org>
Brian McCallister <brianm@skife.org> Brian McCallister <brianm@skife.org>
Bruno Bigras <bigras.bruno@gmail.com> Bruno Bigras <bigras.bruno@gmail.com>
Caleb Spare <cespare@gmail.com> Caleb Spare <cespare@gmail.com>
Calen Pennington <cale@edx.org>
Charles Hooper <charles.hooper@dotcloud.com> Charles Hooper <charles.hooper@dotcloud.com>
Christopher Currie <codemonkey+github@gmail.com>
Daniel Gasienica <daniel@gasienica.ch> Daniel Gasienica <daniel@gasienica.ch>
Daniel Mizyrycki <daniel.mizyrycki@dotcloud.com> Daniel Mizyrycki <daniel.mizyrycki@dotcloud.com>
Daniel Robinson <gottagetmac@gmail.com> Daniel Robinson <gottagetmac@gmail.com>
@ -22,11 +26,14 @@ Daniel Von Fange <daniel@leancoder.com>
Dominik Honnef <dominik@honnef.co> Dominik Honnef <dominik@honnef.co>
Don Spaulding <donspauldingii@gmail.com> Don Spaulding <donspauldingii@gmail.com>
Dr Nic Williams <drnicwilliams@gmail.com> Dr Nic Williams <drnicwilliams@gmail.com>
Elias Probst <mail@eliasprobst.eu>
Eric Hanchrow <ehanchrow@ine.com>
Evan Wies <evan@neomantra.net> Evan Wies <evan@neomantra.net>
ezbercih <cem.ezberci@gmail.com> ezbercih <cem.ezberci@gmail.com>
Flavio Castelli <fcastelli@suse.com> Flavio Castelli <fcastelli@suse.com>
Francisco Souza <f@souza.cc> Francisco Souza <f@souza.cc>
Frederick F. Kautz IV <fkautz@alumni.cmu.edu> Frederick F. Kautz IV <fkautz@alumni.cmu.edu>
Gareth Rushgrove <gareth@morethanseven.net>
Guillaume J. Charmes <guillaume.charmes@dotcloud.com> Guillaume J. Charmes <guillaume.charmes@dotcloud.com>
Harley Laue <losinggeneration@gmail.com> Harley Laue <losinggeneration@gmail.com>
Hunter Blanks <hunter@twilio.com> Hunter Blanks <hunter@twilio.com>
@ -34,14 +41,21 @@ Jeff Lindsay <progrium@gmail.com>
Jeremy Grosser <jeremy@synack.me> Jeremy Grosser <jeremy@synack.me>
Joffrey F <joffrey@dotcloud.com> Joffrey F <joffrey@dotcloud.com>
John Costa <john.costa@gmail.com> John Costa <john.costa@gmail.com>
Jon Wedaman <jweede@gmail.com>
Jonas Pfenniger <jonas@pfenniger.name> Jonas Pfenniger <jonas@pfenniger.name>
Jonathan Rudenberg <jonathan@titanous.com> Jonathan Rudenberg <jonathan@titanous.com>
Joseph Anthony Pasquale Holsten <joseph@josephholsten.com>
Julien Barbier <write0@gmail.com> Julien Barbier <write0@gmail.com>
Jérôme Petazzoni <jerome.petazzoni@dotcloud.com> Jérôme Petazzoni <jerome.petazzoni@dotcloud.com>
Ken Cochrane <kencochrane@gmail.com> Ken Cochrane <kencochrane@gmail.com>
Kevin J. Lynagh <kevin@keminglabs.com> Kevin J. Lynagh <kevin@keminglabs.com>
kim0 <email.ahmedkamal@googlemail.com>
Kiran Gangadharan <kiran.daredevil@gmail.com>
Louis Opter <kalessin@kalessin.fr> Louis Opter <kalessin@kalessin.fr>
Marcus Farkas <toothlessgear@finitebox.com>
Mark McGranaghan <mmcgrana@gmail.com>
Maxim Treskin <zerthurd@gmail.com> Maxim Treskin <zerthurd@gmail.com>
meejah <meejah@meejah.ca>
Michael Crosby <crosby.michael@gmail.com> Michael Crosby <crosby.michael@gmail.com>
Mikhail Sobolev <mss@mawhrin.net> Mikhail Sobolev <mss@mawhrin.net>
Nate Jones <nate@endot.org> Nate Jones <nate@endot.org>
@ -50,18 +64,25 @@ Niall O'Higgins <niallo@unworkable.org>
odk- <github@odkurzacz.org> odk- <github@odkurzacz.org>
Paul Bowsher <pbowsher@globalpersonals.co.uk> Paul Bowsher <pbowsher@globalpersonals.co.uk>
Paul Hammond <paul@paulhammond.org> Paul Hammond <paul@paulhammond.org>
Phil Spitler <pspitler@gmail.com>
Piotr Bogdan <ppbogdan@gmail.com> Piotr Bogdan <ppbogdan@gmail.com>
Renato Riccieri Santos Zannon <renato.riccieri@gmail.com>
Robert Obryk <robryk@gmail.com> Robert Obryk <robryk@gmail.com>
Roberto Hashioka <roberto_hashioka@hotmail.com>
Sam Alba <sam.alba@gmail.com> Sam Alba <sam.alba@gmail.com>
Sam J Sharpe <sam.sharpe@digital.cabinet-office.gov.uk>
Shawn Siefkas <shawn.siefkas@meredith.com> Shawn Siefkas <shawn.siefkas@meredith.com>
Silas Sewell <silas@sewell.org> Silas Sewell <silas@sewell.org>
Solomon Hykes <solomon@dotcloud.com> Solomon Hykes <solomon@dotcloud.com>
Sridhar Ratnakumar <sridharr@activestate.com> Sridhar Ratnakumar <sridharr@activestate.com>
Thatcher Peskens <thatcher@dotcloud.com> Thatcher Peskens <thatcher@dotcloud.com>
Thomas Bikeev <thomas.bikeev@mac.com> Thomas Bikeev <thomas.bikeev@mac.com>
Thomas Hansen <thomas.hansen@gmail.com>
Tianon Gravi <admwiggin@gmail.com> Tianon Gravi <admwiggin@gmail.com>
Tim Terhorst <mynamewastaken+git@gmail.com> Tim Terhorst <mynamewastaken+git@gmail.com>
Troy Howard <thoward37@gmail.com> Tobias Bieniek <Tobias.Bieniek@gmx.de>
unclejack <unclejacksons@gmail.com> unclejack <unclejacksons@gmail.com>
Victor Vieux <victor.vieux@dotcloud.com> Victor Vieux <victor.vieux@dotcloud.com>
Vivek Agarwal <me@vivek.im> Vivek Agarwal <me@vivek.im>
Walter Stanish <walter@pratyeka.org>
Will Dietz <w@wdtz.org>

View File

@ -1,5 +1,48 @@
# Changelog # Changelog
## 0.4.4 (2013-06-19)
- Builder: fix a regression introduced in 0.4.3 which caused builds to fail on new clients.
## 0.4.3 (2013-06-19)
+ Builder: ADD of a local file will detect tar archives and unpack them
* Runtime: Remove bsdtar dependency
* Runtime: Add unix socket and multiple -H support
* Runtime: Prevent rm of running containers
* Runtime: Use go1.1 cookiejar
* Builder: ADD improvements: use tar for copy + automatically unpack local archives
* Builder: ADD uses tar/untar for copies instead of calling 'cp -ar'
* Builder: nicer output for 'docker build'
* Builder: fixed the behavior of ADD to be (mostly) reverse-compatible, predictable and well-documented.
* Client: HumanReadable ProgressBar sizes in pull
* Client: Fix docker version's git commit output
* API: Send all tags on History API call
* API: Add tag lookup to history command. Fixes #882
- Runtime: Fix issue detaching from running TTY container
- Runtime: Forbid parralel push/pull for a single image/repo. Fixes #311
- Runtime: Fix race condition within Run command when attaching.
- Builder: fix a bug which caused builds to fail if ADD was the first command
- Documentation: fix missing command in irc bouncer example
## 0.4.2 (2013-06-17)
- Packaging: Bumped version to work around an Ubuntu bug
## 0.4.1 (2013-06-17)
+ Remote Api: Add flag to enable cross domain requests
+ Remote Api/Client: Add images and containers sizes in docker ps and docker images
+ Runtime: Configure dns configuration host-wide with 'docker -d -dns'
+ Runtime: Detect faulty DNS configuration and replace it with a public default
+ Runtime: allow docker run <name>:<id>
+ Runtime: you can now specify public port (ex: -p 80:4500)
* Client: allow multiple params in inspect
* Client: Print the container id before the hijack in `docker run`
* Registry: add regexp check on repo's name
* Registry: Move auth to the client
* Runtime: improved image removal to garbage-collect unreferenced parents
* Vagrantfile: Add the rest api port to vagrantfile's port_forward
* Upgrade to Go 1.1
- Builder: don't ignore last line in Dockerfile when it doesn't end with \n
- Registry: Remove login check on pull
## 0.4.0 (2013-06-03) ## 0.4.0 (2013-06-03)
+ Introducing Builder: 'docker build' builds a container, layer by layer, from a source repository containing a Dockerfile + Introducing Builder: 'docker build' builds a container, layer by layer, from a source repository containing a Dockerfile
+ Introducing Remote API: control Docker programmatically using a simple HTTP/json API + Introducing Remote API: control Docker programmatically using a simple HTTP/json API

17
FIXME
View File

@ -16,3 +16,20 @@ to put them - so we put them here :)
* Unify build commands and regular commands * Unify build commands and regular commands
* Move source code into src/ subdir for clarity * Move source code into src/ subdir for clarity
* Clean up the Makefile, it's a mess * Clean up the Makefile, it's a mess
* docker build: on non-existent local path for ADD, don't show full absolute path on the host
* mount into /dockerinit rather than /sbin/init
* docker tag foo REPO:TAG
* use size header for progress bar in pull
* Clean up context upload in build!!!
* Parallel pull
* Ensure /proc/sys/net/ipv4/ip_forward is 1
* Force DNS to public!
* Always generate a resolv.conf per container, to avoid changing resolv.conf under thne container's feet
* Save metadata with import/export
* Upgrade dockerd without stopping containers
* bring back git revision info, looks like it was lost
* Simple command to remove all untagged images
* Simple command to clean up containers for disk space
* Caching after an ADD
* entry point config
* bring back git revision info, looks like it was lost

View File

@ -17,7 +17,7 @@ endif
GIT_COMMIT = $(shell git rev-parse --short HEAD) GIT_COMMIT = $(shell git rev-parse --short HEAD)
GIT_STATUS = $(shell test -n "`git status --porcelain`" && echo "+CHANGES") GIT_STATUS = $(shell test -n "`git status --porcelain`" && echo "+CHANGES")
BUILD_OPTIONS = -ldflags "-X main.GIT_COMMIT $(GIT_COMMIT)$(GIT_STATUS)" BUILD_OPTIONS = -ldflags "-X main.GITCOMMIT $(GIT_COMMIT)$(GIT_STATUS)"
SRC_DIR := $(GOPATH)/src SRC_DIR := $(GOPATH)/src
@ -46,6 +46,7 @@ whichrelease:
release: $(BINRELEASE) release: $(BINRELEASE)
s3cmd -P put $(BINRELEASE) s3://get.docker.io/builds/`uname -s`/`uname -m`/docker-$(RELEASE_VERSION).tgz s3cmd -P put $(BINRELEASE) s3://get.docker.io/builds/`uname -s`/`uname -m`/docker-$(RELEASE_VERSION).tgz
s3cmd -P put docker-latest.tgz s3://get.docker.io/builds/`uname -s`/`uname -m`/docker-latest.tgz
srcrelease: $(SRCRELEASE) srcrelease: $(SRCRELEASE)
deps: $(DOCKER_DIR) deps: $(DOCKER_DIR)
@ -60,6 +61,7 @@ $(SRCRELEASE):
$(BINRELEASE): $(SRCRELEASE) $(BINRELEASE): $(SRCRELEASE)
rm -f $(BINRELEASE) rm -f $(BINRELEASE)
cd $(SRCRELEASE); make; cp -R bin docker-$(RELEASE_VERSION); tar -f ../$(BINRELEASE) -zv -c docker-$(RELEASE_VERSION) cd $(SRCRELEASE); make; cp -R bin docker-$(RELEASE_VERSION); tar -f ../$(BINRELEASE) -zv -c docker-$(RELEASE_VERSION)
cd $(SRCRELEASE); cp -R bin docker-latest; tar -f ../docker-latest.tgz -zv -c docker-latest
clean: clean:
@rm -rf $(dir $(DOCKER_BIN)) @rm -rf $(dir $(DOCKER_BIN))
@ -72,6 +74,9 @@ endif
test: all test: all
@(cd $(DOCKER_DIR); sudo -E go test $(GO_OPTIONS)) @(cd $(DOCKER_DIR); sudo -E go test $(GO_OPTIONS))
testall: all
@(cd $(DOCKER_DIR); sudo -E go test ./... $(GO_OPTIONS))
fmt: fmt:
@gofmt -s -l -w . @gofmt -s -l -w .

View File

@ -97,7 +97,7 @@ Quick install on Ubuntu 12.04 and 12.10
--------------------------------------- ---------------------------------------
```bash ```bash
curl get.docker.io | sh -x curl get.docker.io | sudo sh -x
``` ```
Binary installs Binary installs
@ -108,7 +108,7 @@ Note that some methods are community contributions and not yet officially suppor
* [Ubuntu 12.04 and 12.10 (officially supported)](http://docs.docker.io/en/latest/installation/ubuntulinux/) * [Ubuntu 12.04 and 12.10 (officially supported)](http://docs.docker.io/en/latest/installation/ubuntulinux/)
* [Arch Linux](http://docs.docker.io/en/latest/installation/archlinux/) * [Arch Linux](http://docs.docker.io/en/latest/installation/archlinux/)
* [MacOS X (with Vagrant)](http://docs.docker.io/en/latest/installation/macos/) * [Mac OS X (with Vagrant)](http://docs.docker.io/en/latest/installation/vagrant/)
* [Windows (with Vagrant)](http://docs.docker.io/en/latest/installation/windows/) * [Windows (with Vagrant)](http://docs.docker.io/en/latest/installation/windows/)
* [Amazon EC2 (with Vagrant)](http://docs.docker.io/en/latest/installation/amazon/) * [Amazon EC2 (with Vagrant)](http://docs.docker.io/en/latest/installation/amazon/)
@ -181,7 +181,7 @@ Running an irc bouncer
---------------------- ----------------------
```bash ```bash
BOUNCER_ID=$(docker run -d -p 6667 -u irc shykes/znc $USER $PASSWORD) BOUNCER_ID=$(docker run -d -p 6667 -u irc shykes/znc zncrun $USER $PASSWORD)
echo "Configure your irc client to connect to port $(docker port $BOUNCER_ID 6667) of this machine" echo "Configure your irc client to connect to port $(docker port $BOUNCER_ID 6667) of this machine"
``` ```
@ -216,7 +216,8 @@ PORT=$(docker port $JOB 4444)
# Connect to the public port via the host's public address # Connect to the public port via the host's public address
# Please note that because of how routing works connecting to localhost or 127.0.0.1 $PORT will not work. # Please note that because of how routing works connecting to localhost or 127.0.0.1 $PORT will not work.
IP=$(ifconfig eth0 | perl -n -e 'if (m/inet addr:([\d\.]+)/g) { print $1 }') # Replace *eth0* according to your local interface name.
IP=$(ip -o -4 addr list eth0 | perl -n -e 'if (m{inet\s([\d\.]+)\/\d+\s}xms) { print $1 }')
echo hello world | nc $IP $PORT echo hello world | nc $IP $PORT
# Verify that the network connection worked # Verify that the network connection worked
@ -262,14 +263,14 @@ Setting up a dev environment
Instructions that have been verified to work on Ubuntu 12.10, Instructions that have been verified to work on Ubuntu 12.10,
```bash ```bash
sudo apt-get -y install lxc wget bsdtar curl golang git sudo apt-get -y install lxc curl xz-utils golang git
export GOPATH=~/go/ export GOPATH=~/go/
export PATH=$GOPATH/bin:$PATH export PATH=$GOPATH/bin:$PATH
mkdir -p $GOPATH/src/github.com/dotcloud mkdir -p $GOPATH/src/github.com/dotcloud
cd $GOPATH/src/github.com/dotcloud cd $GOPATH/src/github.com/dotcloud
git clone git@github.com:dotcloud/docker.git git clone https://github.com/dotcloud/docker.git
cd docker cd docker
go get -v github.com/dotcloud/docker/... go get -v github.com/dotcloud/docker/...

24
Vagrantfile vendored
View File

@ -3,13 +3,16 @@
BOX_NAME = ENV['BOX_NAME'] || "ubuntu" BOX_NAME = ENV['BOX_NAME'] || "ubuntu"
BOX_URI = ENV['BOX_URI'] || "http://files.vagrantup.com/precise64.box" BOX_URI = ENV['BOX_URI'] || "http://files.vagrantup.com/precise64.box"
VF_BOX_URI = ENV['BOX_URI'] || "http://files.vagrantup.com/precise64_vmware_fusion.box"
AWS_REGION = ENV['AWS_REGION'] || "us-east-1" AWS_REGION = ENV['AWS_REGION'] || "us-east-1"
AWS_AMI = ENV['AWS_AMI'] || "ami-d0f89fb9" AWS_AMI = ENV['AWS_AMI'] || "ami-d0f89fb9"
FORWARD_DOCKER_PORTS = ENV['FORWARD_DOCKER_PORTS']
Vagrant::Config.run do |config| Vagrant::Config.run do |config|
# Setup virtual machine box. This VM configuration code is always executed. # Setup virtual machine box. This VM configuration code is always executed.
config.vm.box = BOX_NAME config.vm.box = BOX_NAME
config.vm.box_url = BOX_URI config.vm.box_url = BOX_URI
config.vm.forward_port 4243, 4243
# Provision docker and new kernel if deployment was not done # Provision docker and new kernel if deployment was not done
if Dir.glob("#{File.dirname(__FILE__)}/.vagrant/machines/default/*/id").empty? if Dir.glob("#{File.dirname(__FILE__)}/.vagrant/machines/default/*/id").empty?
@ -65,8 +68,29 @@ Vagrant::VERSION >= "1.1.0" and Vagrant.configure("2") do |config|
rs.image = /Ubuntu/ rs.image = /Ubuntu/
end end
config.vm.provider :vmware_fusion do |f, override|
override.vm.box = BOX_NAME
override.vm.box_url = VF_BOX_URI
override.vm.synced_folder ".", "/vagrant", disabled: true
f.vmx["displayName"] = "docker"
end
config.vm.provider :virtualbox do |vb| config.vm.provider :virtualbox do |vb|
config.vm.box = BOX_NAME config.vm.box = BOX_NAME
config.vm.box_url = BOX_URI config.vm.box_url = BOX_URI
end end
end end
if !FORWARD_DOCKER_PORTS.nil?
Vagrant::VERSION < "1.1.0" and Vagrant::Config.run do |config|
(49000..49900).each do |port|
config.vm.forward_port port, port
end
end
Vagrant::VERSION >= "1.1.0" and Vagrant.configure("2") do |config|
(49000..49900).each do |port|
config.vm.network :forwarded_port, :host => port, :guest => port
end
end
end

34
api.go
View File

@ -8,12 +8,16 @@ import (
"github.com/gorilla/mux" "github.com/gorilla/mux"
"io" "io"
"log" "log"
"net"
"net/http" "net/http"
"os"
"strconv" "strconv"
"strings" "strings"
) )
const APIVERSION = 1.2 const APIVERSION = 1.2
const DEFAULTHTTPHOST string = "127.0.0.1"
const DEFAULTHTTPPORT int = 4243
func hijackServer(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) { func hijackServer(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) {
conn, _, err := w.(http.Hijacker).Hijack() conn, _, err := w.(http.Hijacker).Hijack()
@ -438,17 +442,23 @@ func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http
func postContainersCreate(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { func postContainersCreate(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
config := &Config{} config := &Config{}
out := &APIRun{}
if err := json.NewDecoder(r.Body).Decode(config); err != nil { if err := json.NewDecoder(r.Body).Decode(config); err != nil {
return err return err
} }
if len(config.Dns) == 0 && len(srv.runtime.Dns) == 0 && utils.CheckLocalDns() {
out.Warnings = append(out.Warnings, fmt.Sprintf("Docker detected local DNS server on resolv.conf. Using default external servers: %v", defaultDns))
config.Dns = defaultDns
}
id, err := srv.ContainerCreate(config) id, err := srv.ContainerCreate(config)
if err != nil { if err != nil {
return err return err
} }
out.ID = id
out := &APIRun{
ID: id,
}
if config.Memory > 0 && !srv.runtime.capabilities.MemoryLimit { if config.Memory > 0 && !srv.runtime.capabilities.MemoryLimit {
log.Println("WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.") log.Println("WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.")
out.Warnings = append(out.Warnings, "Your kernel does not support memory limit capabilities. Limitation discarded.") out.Warnings = append(out.Warnings, "Your kernel does not support memory limit capabilities. Limitation discarded.")
@ -457,6 +467,7 @@ func postContainersCreate(srv *Server, version float64, w http.ResponseWriter, r
log.Println("WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.") log.Println("WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.")
out.Warnings = append(out.Warnings, "Your kernel does not support memory swap capabilities. Limitation discarded.") out.Warnings = append(out.Warnings, "Your kernel does not support memory swap capabilities. Limitation discarded.")
} }
b, err := json.Marshal(out) b, err := json.Marshal(out)
if err != nil { if err != nil {
return err return err
@ -809,6 +820,7 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
localFct := fct localFct := fct
f := func(w http.ResponseWriter, r *http.Request) { f := func(w http.ResponseWriter, r *http.Request) {
utils.Debugf("Calling %s %s", localMethod, localRoute) utils.Debugf("Calling %s %s", localMethod, localRoute)
if logging { if logging {
log.Println(r.Method, r.RequestURI) log.Println(r.Method, r.RequestURI)
} }
@ -829,6 +841,7 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
w.WriteHeader(http.StatusNotFound) w.WriteHeader(http.StatusNotFound)
return return
} }
if err := localFct(srv, version, w, r, mux.Vars(r)); err != nil { if err := localFct(srv, version, w, r, mux.Vars(r)); err != nil {
httpError(w, err) httpError(w, err)
} }
@ -845,12 +858,21 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
return r, nil return r, nil
} }
func ListenAndServe(addr string, srv *Server, logging bool) error { func ListenAndServe(proto, addr string, srv *Server, logging bool) error {
log.Printf("Listening for HTTP on %s\n", addr) log.Printf("Listening for HTTP on %s (%s)\n", addr, proto)
r, err := createRouter(srv, logging) r, err := createRouter(srv, logging)
if err != nil { if err != nil {
return err return err
} }
return http.ListenAndServe(addr, r) l, e := net.Listen(proto, addr)
if e != nil {
return e
}
//as the daemon is launched as root, change to permission of the socket to allow non-root to connect
if proto == "unix" {
os.Chmod(addr, 0777)
}
httpSrv := http.Server{Addr: addr, Handler: r}
return httpSrv.Serve(l)
} }

View File

@ -1,16 +1,19 @@
package docker package docker
type APIHistory struct { type APIHistory struct {
ID string `json:"Id"` ID string `json:"Id"`
Tags []string `json:",omitempty"`
Created int64 Created int64
CreatedBy string `json:",omitempty"` CreatedBy string `json:",omitempty"`
} }
type APIImages struct { type APIImages struct {
Repository string `json:",omitempty"` Repository string `json:",omitempty"`
Tag string `json:",omitempty"` Tag string `json:",omitempty"`
ID string `json:"Id"` ID string `json:"Id"`
Created int64 Created int64
Size int64
VirtualSize int64
} }
type APIInfo struct { type APIInfo struct {
@ -29,12 +32,14 @@ type APIRmi struct {
} }
type APIContainers struct { type APIContainers struct {
ID string `json:"Id"` ID string `json:"Id"`
Image string Image string
Command string Command string
Created int64 Created int64
Status string Status string
Ports string Ports string
SizeRw int64
SizeRootFs int64
} }
type APISearch struct { type APISearch struct {

View File

@ -1,12 +1,15 @@
package docker package docker
import ( import (
"bufio"
"errors" "errors"
"fmt" "fmt"
"github.com/dotcloud/docker/utils"
"io" "io"
"io/ioutil" "io/ioutil"
"os" "os"
"os/exec" "os/exec"
"path"
) )
type Archive io.Reader type Archive io.Reader
@ -20,6 +23,37 @@ const (
Xz Xz
) )
func DetectCompression(source []byte) Compression {
for _, c := range source[:10] {
utils.Debugf("%x", c)
}
sourceLen := len(source)
for compression, m := range map[Compression][]byte{
Bzip2: {0x42, 0x5A, 0x68},
Gzip: {0x1F, 0x8B, 0x08},
Xz: {0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00},
} {
fail := false
if len(m) > sourceLen {
utils.Debugf("Len too short")
continue
}
i := 0
for _, b := range m {
if b != source[i] {
fail = true
break
}
i++
}
if !fail {
return compression
}
}
return Uncompressed
}
func (compression *Compression) Flag() string { func (compression *Compression) Flag() string {
switch *compression { switch *compression {
case Bzip2: case Bzip2:
@ -46,14 +80,43 @@ func (compression *Compression) Extension() string {
return "" return ""
} }
// Tar creates an archive from the directory at `path`, and returns it as a
// stream of bytes.
func Tar(path string, compression Compression) (io.Reader, error) { func Tar(path string, compression Compression) (io.Reader, error) {
cmd := exec.Command("bsdtar", "-f", "-", "-C", path, "-c"+compression.Flag(), ".") return TarFilter(path, compression, nil)
return CmdStream(cmd)
} }
// Tar creates an archive from the directory at `path`, only including files whose relative
// paths are included in `filter`. If `filter` is nil, then all files are included.
func TarFilter(path string, compression Compression, filter []string) (io.Reader, error) {
args := []string{"tar", "-f", "-", "-C", path}
if filter == nil {
filter = []string{"."}
}
for _, f := range filter {
args = append(args, "-c"+compression.Flag(), f)
}
return CmdStream(exec.Command(args[0], args[1:]...))
}
// Untar reads a stream of bytes from `archive`, parses it as a tar archive,
// and unpacks it into the directory at `path`.
// The archive may be compressed with one of the following algorithgms:
// identity (uncompressed), gzip, bzip2, xz.
// FIXME: specify behavior when target path exists vs. doesn't exist.
func Untar(archive io.Reader, path string) error { func Untar(archive io.Reader, path string) error {
cmd := exec.Command("bsdtar", "-f", "-", "-C", path, "-x")
cmd.Stdin = archive bufferedArchive := bufio.NewReaderSize(archive, 10)
buf, err := bufferedArchive.Peek(10)
if err != nil {
return err
}
compression := DetectCompression(buf)
utils.Debugf("Archive compression detected: %s", compression.Extension())
cmd := exec.Command("tar", "-f", "-", "-C", path, "-x"+compression.Flag())
cmd.Stdin = bufferedArchive
// Hardcode locale environment for predictable outcome regardless of host configuration. // Hardcode locale environment for predictable outcome regardless of host configuration.
// (see https://github.com/dotcloud/docker/issues/355) // (see https://github.com/dotcloud/docker/issues/355)
cmd.Env = []string{"LANG=en_US.utf-8", "LC_ALL=en_US.utf-8"} cmd.Env = []string{"LANG=en_US.utf-8", "LC_ALL=en_US.utf-8"}
@ -64,6 +127,86 @@ func Untar(archive io.Reader, path string) error {
return nil return nil
} }
// TarUntar is a convenience function which calls Tar and Untar, with
// the output of one piped into the other. If either Tar or Untar fails,
// TarUntar aborts and returns the error.
func TarUntar(src string, filter []string, dst string) error {
utils.Debugf("TarUntar(%s %s %s)", src, filter, dst)
archive, err := TarFilter(src, Uncompressed, filter)
if err != nil {
return err
}
return Untar(archive, dst)
}
// UntarPath is a convenience function which looks for an archive
// at filesystem path `src`, and unpacks it at `dst`.
func UntarPath(src, dst string) error {
if archive, err := os.Open(src); err != nil {
return err
} else if err := Untar(archive, dst); err != nil {
return err
}
return nil
}
// CopyWithTar creates a tar archive of filesystem path `src`, and
// unpacks it at filesystem path `dst`.
// The archive is streamed directly with fixed buffering and no
// intermediary disk IO.
//
func CopyWithTar(src, dst string) error {
srcSt, err := os.Stat(src)
if err != nil {
return err
}
var dstExists bool
dstSt, err := os.Stat(dst)
if err != nil {
if !os.IsNotExist(err) {
return err
}
} else {
dstExists = true
}
// Things that can go wrong if the source is a directory
if srcSt.IsDir() {
// The destination exists and is a regular file
if dstExists && !dstSt.IsDir() {
return fmt.Errorf("Can't copy a directory over a regular file")
}
// Things that can go wrong if the source is a regular file
} else {
utils.Debugf("The destination exists, it's a directory, and doesn't end in /")
// The destination exists, it's a directory, and doesn't end in /
if dstExists && dstSt.IsDir() && dst[len(dst)-1] != '/' {
return fmt.Errorf("Can't copy a regular file over a directory %s |%s|", dst, dst[len(dst)-1])
}
}
// Create the destination
var dstDir string
if srcSt.IsDir() || dst[len(dst)-1] == '/' {
// The destination ends in /, or the source is a directory
// --> dst is the holding directory and needs to be created for -C
dstDir = dst
} else {
// The destination doesn't end in /
// --> dst is the file
dstDir = path.Dir(dst)
}
if !dstExists {
// Create the holding directory if necessary
utils.Debugf("Creating the holding directory %s", dstDir)
if err := os.MkdirAll(dstDir, 0700); err != nil && !os.IsExist(err) {
return err
}
}
if !srcSt.IsDir() {
return TarUntar(path.Dir(src), []string{path.Base(src)}, dstDir)
}
return TarUntar(src, nil, dstDir)
}
// CmdStream executes a command, and returns its stdout as a stream. // CmdStream executes a command, and returns its stdout as a stream.
// If the command fails to run or doesn't complete successfully, an error // If the command fails to run or doesn't complete successfully, an error
// will be returned, including anything written on stderr. // will be returned, including anything written on stderr.

View File

@ -1,10 +1,13 @@
package docker package docker
import ( import (
"bytes"
"fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"os" "os"
"os/exec" "os/exec"
"path"
"testing" "testing"
"time" "time"
) )
@ -58,20 +61,58 @@ func TestCmdStreamGood(t *testing.T) {
} }
} }
func TestTarUntar(t *testing.T) { func tarUntar(t *testing.T, origin string, compression Compression) error {
archive, err := Tar(".", Uncompressed) archive, err := Tar(origin, compression)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
buf := make([]byte, 10)
if _, err := archive.Read(buf); err != nil {
return err
}
archive = io.MultiReader(bytes.NewReader(buf), archive)
detectedCompression := DetectCompression(buf)
if detectedCompression.Extension() != compression.Extension() {
return fmt.Errorf("Wrong compression detected. Actual compression: %s, found %s", compression.Extension(), detectedCompression.Extension())
}
tmp, err := ioutil.TempDir("", "docker-test-untar") tmp, err := ioutil.TempDir("", "docker-test-untar")
if err != nil { if err != nil {
t.Fatal(err) return err
} }
defer os.RemoveAll(tmp) defer os.RemoveAll(tmp)
if err := Untar(archive, tmp); err != nil { if err := Untar(archive, tmp); err != nil {
t.Fatal(err) return err
} }
if _, err := os.Stat(tmp); err != nil { if _, err := os.Stat(tmp); err != nil {
t.Fatalf("Error stating %s: %s", tmp, err.Error()) return err
}
return nil
}
func TestTarUntar(t *testing.T) {
origin, err := ioutil.TempDir("", "docker-test-untar-origin")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(origin)
if err := ioutil.WriteFile(path.Join(origin, "1"), []byte("hello world"), 0700); err != nil {
t.Fatal(err)
}
if err := ioutil.WriteFile(path.Join(origin, "2"), []byte("welcome!"), 0700); err != nil {
t.Fatal(err)
}
for _, c := range []Compression{
Uncompressed,
Gzip,
Bzip2,
Xz,
} {
if err := tarUntar(t, origin, c); err != nil {
t.Fatalf("Error tar/untar for compression %s: %s", c.Extension(), err)
}
} }
} }

View File

@ -82,7 +82,7 @@ func decodeAuth(authStr string) (*AuthConfig, error) {
func LoadConfig(rootPath string) (*AuthConfig, error) { func LoadConfig(rootPath string) (*AuthConfig, error) {
confFile := path.Join(rootPath, CONFIGFILE) confFile := path.Join(rootPath, CONFIGFILE)
if _, err := os.Stat(confFile); err != nil { if _, err := os.Stat(confFile); err != nil {
return &AuthConfig{rootPath:rootPath}, ErrConfigFileMissing return &AuthConfig{rootPath: rootPath}, ErrConfigFileMissing
} }
b, err := ioutil.ReadFile(confFile) b, err := ioutil.ReadFile(confFile)
if err != nil { if err != nil {

View File

@ -10,8 +10,8 @@ import (
func TestEncodeAuth(t *testing.T) { func TestEncodeAuth(t *testing.T) {
newAuthConfig := &AuthConfig{Username: "ken", Password: "test", Email: "test@example.com"} newAuthConfig := &AuthConfig{Username: "ken", Password: "test", Email: "test@example.com"}
authStr := EncodeAuth(newAuthConfig) authStr := encodeAuth(newAuthConfig)
decAuthConfig, err := DecodeAuth(authStr) decAuthConfig, err := decodeAuth(authStr)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -30,7 +30,7 @@ func TestLogin(t *testing.T) {
os.Setenv("DOCKER_INDEX_URL", "https://indexstaging-docker.dotcloud.com") os.Setenv("DOCKER_INDEX_URL", "https://indexstaging-docker.dotcloud.com")
defer os.Setenv("DOCKER_INDEX_URL", "") defer os.Setenv("DOCKER_INDEX_URL", "")
authConfig := NewAuthConfig("unittester", "surlautrerivejetattendrai", "noise+unittester@dotcloud.com", "/tmp") authConfig := NewAuthConfig("unittester", "surlautrerivejetattendrai", "noise+unittester@dotcloud.com", "/tmp")
status, err := Login(authConfig) status, err := Login(authConfig, false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -50,7 +50,7 @@ func TestCreateAccount(t *testing.T) {
token := hex.EncodeToString(tokenBuffer)[:12] token := hex.EncodeToString(tokenBuffer)[:12]
username := "ut" + token username := "ut" + token
authConfig := NewAuthConfig(username, "test42", "docker-ut+"+token+"@example.com", "/tmp") authConfig := NewAuthConfig(username, "test42", "docker-ut+"+token+"@example.com", "/tmp")
status, err := Login(authConfig) status, err := Login(authConfig, false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -60,7 +60,7 @@ func TestCreateAccount(t *testing.T) {
t.Fatalf("Expected status: \"%s\", found \"%s\" instead.", expectedStatus, status) t.Fatalf("Expected status: \"%s\", found \"%s\" instead.", expectedStatus, status)
} }
status, err = Login(authConfig) status, err = Login(authConfig, false)
if err == nil { if err == nil {
t.Fatalf("Expected error but found nil instead") t.Fatalf("Expected error but found nil instead")
} }

View File

@ -2,11 +2,14 @@ package docker
import ( import (
"fmt" "fmt"
"github.com/dotcloud/docker/utils"
"os" "os"
"path" "path"
"time" "time"
) )
var defaultDns = []string{"8.8.8.8", "8.8.4.4"}
type Builder struct { type Builder struct {
runtime *Runtime runtime *Runtime
repositories *TagStore repositories *TagStore
@ -66,15 +69,26 @@ func (builder *Builder) Create(config *Config) (*Container, error) {
return nil, err return nil, err
} }
if len(config.Dns) == 0 && len(builder.runtime.Dns) == 0 && utils.CheckLocalDns() {
//"WARNING: Docker detected local DNS server on resolv.conf. Using default external servers: %v", defaultDns
builder.runtime.Dns = defaultDns
}
// If custom dns exists, then create a resolv.conf for the container // If custom dns exists, then create a resolv.conf for the container
if len(config.Dns) > 0 { if len(config.Dns) > 0 || len(builder.runtime.Dns) > 0 {
var dns []string
if len(config.Dns) > 0 {
dns = config.Dns
} else {
dns = builder.runtime.Dns
}
container.ResolvConfPath = path.Join(container.root, "resolv.conf") container.ResolvConfPath = path.Join(container.root, "resolv.conf")
f, err := os.Create(container.ResolvConfPath) f, err := os.Create(container.ResolvConfPath)
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer f.Close() defer f.Close()
for _, dns := range config.Dns { for _, dns := range dns {
if _, err := f.Write([]byte("nameserver " + dns + "\n")); err != nil { if _, err := f.Write([]byte("nameserver " + dns + "\n")); err != nil {
return nil, err return nil, err
} }

View File

@ -304,9 +304,9 @@ func (b *builderClient) Build(dockerfile, context io.Reader) (string, error) {
return "", fmt.Errorf("An error occured during the build\n") return "", fmt.Errorf("An error occured during the build\n")
} }
func NewBuilderClient(addr string, port int) BuildFile { func NewBuilderClient(proto, addr string) BuildFile {
return &builderClient{ return &builderClient{
cli: NewDockerCli(addr, port), cli: NewDockerCli(proto, addr),
config: &Config{}, config: &Config{},
tmpContainers: make(map[string]struct{}), tmpContainers: make(map[string]struct{}),
tmpImages: make(map[string]struct{}), tmpImages: make(map[string]struct{}),

View File

@ -101,6 +101,7 @@ func (b *buildFile) CmdRun(args string) error {
if cache, err := b.srv.ImageGetCached(b.image, b.config); err != nil { if cache, err := b.srv.ImageGetCached(b.image, b.config); err != nil {
return err return err
} else if cache != nil { } else if cache != nil {
fmt.Fprintf(b.out, " ---> Using cache\n")
utils.Debugf("[BUILDER] Use cached version") utils.Debugf("[BUILDER] Use cached version")
b.image = cache.ID b.image = cache.ID
return nil return nil
@ -178,11 +179,14 @@ func (b *buildFile) CmdAdd(args string) error {
cmd := b.config.Cmd cmd := b.config.Cmd
// Create the container and start it // Create the container and start it
b.config.Cmd = []string{"/bin/sh", "-c", fmt.Sprintf("#(nop) ADD %s in %s", orig, dest)}
b.config.Image = b.image
container, err := b.builder.Create(b.config) container, err := b.builder.Create(b.config)
if err != nil { if err != nil {
return err return err
} }
b.tmpContainers[container.ID] = struct{}{} b.tmpContainers[container.ID] = struct{}{}
fmt.Fprintf(b.out, " ---> Running in %s\n", utils.TruncateID(container.ID))
if err := container.EnsureMounted(); err != nil { if err := container.EnsureMounted(); err != nil {
return err return err
@ -191,30 +195,26 @@ func (b *buildFile) CmdAdd(args string) error {
origPath := path.Join(b.context, orig) origPath := path.Join(b.context, orig)
destPath := path.Join(container.RootfsPath(), dest) destPath := path.Join(container.RootfsPath(), dest)
// Preserve the trailing '/'
if dest[len(dest)-1] == '/' {
destPath = destPath + "/"
}
fi, err := os.Stat(origPath) fi, err := os.Stat(origPath)
if err != nil { if err != nil {
return err return err
} }
if fi.IsDir() { if fi.IsDir() {
if err := os.MkdirAll(destPath, 0700); err != nil { if err := CopyWithTar(origPath, destPath); err != nil {
return err return err
} }
// First try to unpack the source as an archive
files, err := ioutil.ReadDir(path.Join(b.context, orig)) } else if err := UntarPath(origPath, destPath); err != nil {
if err != nil { utils.Debugf("Couldn't untar %s to %s: %s", origPath, destPath, err)
return err // If that fails, just copy it as a regular file
}
for _, fi := range files {
if err := utils.CopyDirectory(path.Join(origPath, fi.Name()), path.Join(destPath, fi.Name())); err != nil {
return err
}
}
} else {
if err := os.MkdirAll(path.Dir(destPath), 0700); err != nil { if err := os.MkdirAll(path.Dir(destPath), 0700); err != nil {
return err return err
} }
if err := utils.CopyDirectory(origPath, destPath); err != nil { if err := CopyWithTar(origPath, destPath); err != nil {
return err return err
} }
} }
@ -237,6 +237,7 @@ func (b *buildFile) run() (string, error) {
return "", err return "", err
} }
b.tmpContainers[c.ID] = struct{}{} b.tmpContainers[c.ID] = struct{}{}
fmt.Fprintf(b.out, " ---> Running in %s\n", utils.TruncateID(c.ID))
//start the container //start the container
if err := c.Start(); err != nil { if err := c.Start(); err != nil {
@ -263,6 +264,7 @@ func (b *buildFile) commit(id string, autoCmd []string, comment string) error {
if cache, err := b.srv.ImageGetCached(b.image, b.config); err != nil { if cache, err := b.srv.ImageGetCached(b.image, b.config); err != nil {
return err return err
} else if cache != nil { } else if cache != nil {
fmt.Fprintf(b.out, " ---> Using cache\n")
utils.Debugf("[BUILDER] Use cached version") utils.Debugf("[BUILDER] Use cached version")
b.image = cache.ID b.image = cache.ID
return nil return nil
@ -276,6 +278,7 @@ func (b *buildFile) commit(id string, autoCmd []string, comment string) error {
return err return err
} }
b.tmpContainers[container.ID] = struct{}{} b.tmpContainers[container.ID] = struct{}{}
fmt.Fprintf(b.out, " ---> Running in %s\n", utils.TruncateID(container.ID))
if err := container.EnsureMounted(); err != nil { if err := container.EnsureMounted(); err != nil {
return err return err
@ -316,6 +319,7 @@ func (b *buildFile) Build(dockerfile, context io.Reader) (string, error) {
b.context = name b.context = name
} }
file := bufio.NewReader(dockerfile) file := bufio.NewReader(dockerfile)
stepN := 0
for { for {
line, err := file.ReadString('\n') line, err := file.ReadString('\n')
if err != nil { if err != nil {
@ -336,12 +340,13 @@ func (b *buildFile) Build(dockerfile, context io.Reader) (string, error) {
} }
instruction := strings.ToLower(strings.Trim(tmp[0], " ")) instruction := strings.ToLower(strings.Trim(tmp[0], " "))
arguments := strings.Trim(tmp[1], " ") arguments := strings.Trim(tmp[1], " ")
stepN += 1
fmt.Fprintf(b.out, "%s %s (%s)\n", strings.ToUpper(instruction), arguments, b.image) // FIXME: only count known instructions as build steps
fmt.Fprintf(b.out, "Step %d : %s %s\n", stepN, strings.ToUpper(instruction), arguments)
method, exists := reflect.TypeOf(b).MethodByName("Cmd" + strings.ToUpper(instruction[:1]) + strings.ToLower(instruction[1:])) method, exists := reflect.TypeOf(b).MethodByName("Cmd" + strings.ToUpper(instruction[:1]) + strings.ToLower(instruction[1:]))
if !exists { if !exists {
fmt.Fprintf(b.out, "Skipping unknown instruction %s\n", strings.ToUpper(instruction)) fmt.Fprintf(b.out, "# Skipping unknown instruction %s\n", strings.ToUpper(instruction))
continue continue
} }
ret := method.Func.Call([]reflect.Value{reflect.ValueOf(b), reflect.ValueOf(arguments)})[0].Interface() ret := method.Func.Call([]reflect.Value{reflect.ValueOf(b), reflect.ValueOf(arguments)})[0].Interface()
@ -349,10 +354,10 @@ func (b *buildFile) Build(dockerfile, context io.Reader) (string, error) {
return "", ret.(error) return "", ret.(error)
} }
fmt.Fprintf(b.out, "===> %v\n", b.image) fmt.Fprintf(b.out, " ---> %v\n", utils.TruncateID(b.image))
} }
if b.image != "" { if b.image != "" {
fmt.Fprintf(b.out, "Build successful.\n===> %s\n", b.image) fmt.Fprintf(b.out, "Successfully built %s\n", utils.TruncateID(b.image))
return b.image, nil return b.image, nil
} }
return "", fmt.Errorf("An error occured during the build\n") return "", fmt.Errorf("An error occured during the build\n")

View File

@ -23,6 +23,12 @@ from ` + unitTestImageName + `
run sh -c 'echo root:testpass > /tmp/passwd' run sh -c 'echo root:testpass > /tmp/passwd'
run mkdir -p /var/run/sshd` run mkdir -p /var/run/sshd`
// FIXME: test building with a context
// FIXME: test building with a local ADD as first command
// FIXME: test building with 2 successive overlapping ADD commands
func TestBuild(t *testing.T) { func TestBuild(t *testing.T) {
dockerfiles := []string{Dockerfile, DockerfileNoNewLine} dockerfiles := []string{Dockerfile, DockerfileNoNewLine}
for _, Dockerfile := range dockerfiles { for _, Dockerfile := range dockerfiles {

View File

@ -20,6 +20,7 @@ import (
"path" "path"
"path/filepath" "path/filepath"
"reflect" "reflect"
"regexp"
"strconv" "strconv"
"strings" "strings"
"syscall" "syscall"
@ -28,7 +29,7 @@ import (
"unicode" "unicode"
) )
const VERSION = "0.4.0" const VERSION = "0.4.4"
var ( var (
GITCOMMIT string GITCOMMIT string
@ -39,8 +40,8 @@ func (cli *DockerCli) getMethod(name string) (reflect.Method, bool) {
return reflect.TypeOf(cli).MethodByName(methodName) return reflect.TypeOf(cli).MethodByName(methodName)
} }
func ParseCommands(addr string, port int, args ...string) error { func ParseCommands(proto, addr string, args ...string) error {
cli := NewDockerCli(addr, port) cli := NewDockerCli(proto, addr)
if len(args) > 0 { if len(args) > 0 {
method, exists := cli.getMethod(args[0]) method, exists := cli.getMethod(args[0])
@ -73,7 +74,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
return nil return nil
} }
} }
help := fmt.Sprintf("Usage: docker [OPTIONS] COMMAND [arg...]\n -H=\"%s:%d\": Host:port to bind/connect to\n\nA self-sufficient runtime for linux containers.\n\nCommands:\n", cli.host, cli.port) help := fmt.Sprintf("Usage: docker [OPTIONS] COMMAND [arg...]\n -H=[tcp://%s:%d]: tcp://host:port to bind/connect to or unix://path/to/socker to use\n\nA self-sufficient runtime for linux containers.\n\nCommands:\n", DEFAULTHTTPHOST, DEFAULTHTTPPORT)
for _, command := range [][2]string{ for _, command := range [][2]string{
{"attach", "Attach to a running container"}, {"attach", "Attach to a running container"},
{"build", "Build a container from a Dockerfile"}, {"build", "Build a container from a Dockerfile"},
@ -196,7 +197,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
v := &url.Values{} v := &url.Values{}
v.Set("t", *tag) v.Set("t", *tag)
// Send the multipart request with correct content-type // Send the multipart request with correct content-type
req, err := http.NewRequest("POST", fmt.Sprintf("http://%s:%d%s?%s", cli.host, cli.port, "/build", v.Encode()), multipartBody) req, err := http.NewRequest("POST", fmt.Sprintf("/v%g/build?%s", APIVERSION, v.Encode()), multipartBody)
if err != nil { if err != nil {
return err return err
} }
@ -205,8 +206,13 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
req.Header.Set("X-Docker-Context-Compression", compression.Flag()) req.Header.Set("X-Docker-Context-Compression", compression.Flag())
fmt.Println("Uploading Context...") fmt.Println("Uploading Context...")
} }
dial, err := net.Dial(cli.proto, cli.addr)
resp, err := http.DefaultClient.Do(req) if err != nil {
return err
}
clientconn := httputil.NewClientConn(dial, nil)
resp, err := clientconn.Do(req)
defer clientconn.Close()
if err != nil { if err != nil {
return err return err
} }
@ -634,7 +640,10 @@ func (cli *DockerCli) CmdHistory(args ...string) error {
fmt.Fprintln(w, "ID\tCREATED\tCREATED BY") fmt.Fprintln(w, "ID\tCREATED\tCREATED BY")
for _, out := range outs { for _, out := range outs {
fmt.Fprintf(w, "%s\t%s ago\t%s\n", out.ID, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.CreatedBy) if out.Tags != nil {
out.ID = out.Tags[0]
}
fmt.Fprintf(w, "%s \t%s ago\t%s\n", out.ID, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.CreatedBy)
} }
w.Flush() w.Flush()
return nil return nil
@ -735,6 +744,15 @@ func (cli *DockerCli) CmdPush(args ...string) error {
if err != nil { if err != nil {
return err return err
} }
nameParts := strings.SplitN(name, "/", 2)
validNamespace := regexp.MustCompile(`^([a-z0-9_]{4,30})$`)
if !validNamespace.MatchString(nameParts[0]) {
return fmt.Errorf("Invalid namespace name (%s), only [a-z0-9_] are allowed, size between 4 and 30", nameParts[0])
}
validRepo := regexp.MustCompile(`^([a-zA-Z0-9-_.]+)$`)
if !validRepo.MatchString(nameParts[1]) {
return fmt.Errorf("Invalid repository name (%s), only [a-zA-Z0-9-_.] are allowed", nameParts[1])
}
v := url.Values{} v := url.Values{}
v.Set("registry", *registry) v.Set("registry", *registry)
@ -819,7 +837,7 @@ func (cli *DockerCli) CmdImages(args ...string) error {
w := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0) w := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0)
if !*quiet { if !*quiet {
fmt.Fprintln(w, "REPOSITORY\tTAG\tID\tCREATED") fmt.Fprintln(w, "REPOSITORY\tTAG\tID\tCREATED\tSIZE")
} }
for _, out := range outs { for _, out := range outs {
@ -837,7 +855,12 @@ func (cli *DockerCli) CmdImages(args ...string) error {
} else { } else {
fmt.Fprintf(w, "%s\t", utils.TruncateID(out.ID)) fmt.Fprintf(w, "%s\t", utils.TruncateID(out.ID))
} }
fmt.Fprintf(w, "%s ago\n", utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0)))) fmt.Fprintf(w, "%s ago\t", utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))))
if out.VirtualSize > 0 {
fmt.Fprintf(w, "%s (virtual %s)\n", utils.HumanSize(out.Size), utils.HumanSize(out.VirtualSize))
} else {
fmt.Fprintf(w, "%s\n", utils.HumanSize(out.Size))
}
} else { } else {
if *noTrunc { if *noTrunc {
fmt.Fprintln(w, out.ID) fmt.Fprintln(w, out.ID)
@ -896,15 +919,20 @@ func (cli *DockerCli) CmdPs(args ...string) error {
} }
w := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0) w := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0)
if !*quiet { if !*quiet {
fmt.Fprintln(w, "ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS") fmt.Fprintln(w, "ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS\tSIZE")
} }
for _, out := range outs { for _, out := range outs {
if !*quiet { if !*quiet {
if *noTrunc { if *noTrunc {
fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\n", out.ID, out.Image, out.Command, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, out.Ports) fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t", out.ID, out.Image, out.Command, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, out.Ports)
} else { } else {
fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\n", utils.TruncateID(out.ID), out.Image, utils.Trunc(out.Command, 20), utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, out.Ports) fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t", utils.TruncateID(out.ID), out.Image, utils.Trunc(out.Command, 20), utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, out.Ports)
}
if out.SizeRootFs > 0 {
fmt.Fprintf(w, "%s (virtual %s)\n", utils.HumanSize(out.SizeRw), utils.HumanSize(out.SizeRootFs))
} else {
fmt.Fprintf(w, "%s\n", utils.HumanSize(out.SizeRw))
} }
} else { } else {
if *noTrunc { if *noTrunc {
@ -1016,10 +1044,10 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
return nil return nil
} }
if err := cli.stream("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stdout=1", nil, os.Stdout); err != nil { if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stdout=1", false, nil, os.Stdout); err != nil {
return err return err
} }
if err := cli.stream("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stderr=1", nil, os.Stderr); err != nil { if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stderr=1", false, nil, os.Stderr); err != nil {
return err return err
} }
return nil return nil
@ -1046,37 +1074,22 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
return err return err
} }
splitStderr := container.Config.Tty if !container.State.Running {
return fmt.Errorf("Impossible to attach to a stopped container, start it first")
connections := 1
if splitStderr {
connections += 1
} }
chErrors := make(chan error, connections)
if container.Config.Tty { if container.Config.Tty {
cli.monitorTtySize(cmd.Arg(0)) cli.monitorTtySize(cmd.Arg(0))
} }
if splitStderr {
go func() {
chErrors <- cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?stream=1&stderr=1", false, nil, os.Stderr)
}()
}
v := url.Values{} v := url.Values{}
v.Set("stream", "1") v.Set("stream", "1")
v.Set("stdin", "1") v.Set("stdin", "1")
v.Set("stdout", "1") v.Set("stdout", "1")
if !splitStderr { v.Set("stderr", "1")
v.Set("stderr", "1")
} if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), container.Config.Tty, os.Stdin, os.Stdout); err != nil {
go func() { return err
chErrors <- cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), container.Config.Tty, os.Stdin, os.Stdout)
}()
for connections > 0 {
err := <-chErrors
if err != nil {
return err
}
connections -= 1
} }
return nil return nil
} }
@ -1245,16 +1258,6 @@ func (cli *DockerCli) CmdRun(args ...string) error {
fmt.Fprintln(os.Stderr, "WARNING: ", warning) fmt.Fprintln(os.Stderr, "WARNING: ", warning)
} }
splitStderr := !config.Tty
connections := 0
if config.AttachStdin || config.AttachStdout || (!splitStderr && config.AttachStderr) {
connections += 1
}
if splitStderr && config.AttachStderr {
connections += 1
}
//start the container //start the container
_, _, err = cli.call("POST", "/containers/"+out.ID+"/start", nil) _, _, err = cli.call("POST", "/containers/"+out.ID+"/start", nil)
if err != nil { if err != nil {
@ -1263,19 +1266,11 @@ func (cli *DockerCli) CmdRun(args ...string) error {
if !config.AttachStdout && !config.AttachStderr { if !config.AttachStdout && !config.AttachStderr {
fmt.Println(out.ID) fmt.Println(out.ID)
} } else {
if connections > 0 {
chErrors := make(chan error, connections)
if config.Tty { if config.Tty {
cli.monitorTtySize(out.ID) cli.monitorTtySize(out.ID)
} }
if splitStderr && config.AttachStderr {
go func() {
chErrors <- cli.hijack("POST", "/containers/"+out.ID+"/attach?logs=1&stream=1&stderr=1", config.Tty, nil, os.Stderr)
}()
}
v := url.Values{} v := url.Values{}
v.Set("logs", "1") v.Set("logs", "1")
v.Set("stream", "1") v.Set("stream", "1")
@ -1286,19 +1281,12 @@ func (cli *DockerCli) CmdRun(args ...string) error {
if config.AttachStdout { if config.AttachStdout {
v.Set("stdout", "1") v.Set("stdout", "1")
} }
if !splitStderr && config.AttachStderr { if config.AttachStderr {
v.Set("stderr", "1") v.Set("stderr", "1")
} }
go func() { if err := cli.hijack("POST", "/containers/"+out.ID+"/attach?"+v.Encode(), config.Tty, os.Stdin, os.Stdout); err != nil {
chErrors <- cli.hijack("POST", "/containers/"+out.ID+"/attach?"+v.Encode(), config.Tty, os.Stdin, os.Stdout) utils.Debugf("Error hijack: %s", err)
}() return err
for connections > 0 {
err := <-chErrors
if err != nil {
utils.Debugf("Error hijack: %s", err)
return err
}
connections -= 1
} }
} }
return nil return nil
@ -1327,7 +1315,7 @@ func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int,
params = bytes.NewBuffer(buf) params = bytes.NewBuffer(buf)
} }
req, err := http.NewRequest(method, fmt.Sprintf("http://%s:%d/v%g%s", cli.host, cli.port, APIVERSION, path), params) req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), params)
if err != nil { if err != nil {
return nil, -1, err return nil, -1, err
} }
@ -1337,7 +1325,13 @@ func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int,
} else if method == "POST" { } else if method == "POST" {
req.Header.Set("Content-Type", "plain/text") req.Header.Set("Content-Type", "plain/text")
} }
resp, err := http.DefaultClient.Do(req) dial, err := net.Dial(cli.proto, cli.addr)
if err != nil {
return nil, -1, err
}
clientconn := httputil.NewClientConn(dial, nil)
resp, err := clientconn.Do(req)
defer clientconn.Close()
if err != nil { if err != nil {
if strings.Contains(err.Error(), "connection refused") { if strings.Contains(err.Error(), "connection refused") {
return nil, -1, fmt.Errorf("Can't connect to docker daemon. Is 'docker -d' running on this host?") return nil, -1, fmt.Errorf("Can't connect to docker daemon. Is 'docker -d' running on this host?")
@ -1362,7 +1356,7 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e
if (method == "POST" || method == "PUT") && in == nil { if (method == "POST" || method == "PUT") && in == nil {
in = bytes.NewReader([]byte{}) in = bytes.NewReader([]byte{})
} }
req, err := http.NewRequest(method, fmt.Sprintf("http://%s:%d/v%g%s", cli.host, cli.port, APIVERSION, path), in) req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), in)
if err != nil { if err != nil {
return err return err
} }
@ -1370,7 +1364,13 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e
if method == "POST" { if method == "POST" {
req.Header.Set("Content-Type", "plain/text") req.Header.Set("Content-Type", "plain/text")
} }
resp, err := http.DefaultClient.Do(req) dial, err := net.Dial(cli.proto, cli.addr)
if err != nil {
return err
}
clientconn := httputil.NewClientConn(dial, nil)
resp, err := clientconn.Do(req)
defer clientconn.Close()
if err != nil { if err != nil {
if strings.Contains(err.Error(), "connection refused") { if strings.Contains(err.Error(), "connection refused") {
return fmt.Errorf("Can't connect to docker daemon. Is 'docker -d' running on this host?") return fmt.Errorf("Can't connect to docker daemon. Is 'docker -d' running on this host?")
@ -1378,6 +1378,7 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e
return err return err
} }
defer resp.Body.Close() defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 400 { if resp.StatusCode < 200 || resp.StatusCode >= 400 {
body, err := ioutil.ReadAll(resp.Body) body, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
@ -1415,19 +1416,24 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e
} }
func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in *os.File, out io.Writer) error { func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in *os.File, out io.Writer) error {
req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), nil) req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), nil)
if err != nil { if err != nil {
return err return err
} }
req.Header.Set("User-Agent", "Docker-Client/"+VERSION)
req.Header.Set("Content-Type", "plain/text") req.Header.Set("Content-Type", "plain/text")
dial, err := net.Dial("tcp", fmt.Sprintf("%s:%d", cli.host, cli.port))
dial, err := net.Dial(cli.proto, cli.addr)
if err != nil { if err != nil {
return err return err
} }
clientconn := httputil.NewClientConn(dial, nil) clientconn := httputil.NewClientConn(dial, nil)
clientconn.Do(req)
defer clientconn.Close() defer clientconn.Close()
// Server hijacks the connection, error 'connection closed' expected
clientconn.Do(req)
rwc, br := clientconn.Hijack() rwc, br := clientconn.Hijack()
defer rwc.Close() defer rwc.Close()
@ -1503,13 +1509,13 @@ func Subcmd(name, signature, description string) *flag.FlagSet {
return flags return flags
} }
func NewDockerCli(addr string, port int) *DockerCli { func NewDockerCli(proto, addr string) *DockerCli {
authConfig, _ := auth.LoadConfig(os.Getenv("HOME")) authConfig, _ := auth.LoadConfig(os.Getenv("HOME"))
return &DockerCli{addr, port, authConfig} return &DockerCli{proto, addr, authConfig}
} }
type DockerCli struct { type DockerCli struct {
host string proto string
port int addr string
authConfig *auth.AuthConfig authConfig *auth.AuthConfig
} }

View File

@ -13,6 +13,7 @@ import (
"os" "os"
"os/exec" "os/exec"
"path" "path"
"path/filepath"
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
@ -919,3 +920,26 @@ func validateID(id string) error {
} }
return nil return nil
} }
// GetSize, return real size, virtual size
func (container *Container) GetSize() (int64, int64) {
var sizeRw, sizeRootfs int64
filepath.Walk(container.rwPath(), func(path string, fileInfo os.FileInfo, err error) error {
if fileInfo != nil {
sizeRw += fileInfo.Size()
}
return nil
})
_, err := os.Stat(container.RootfsPath())
if err == nil {
filepath.Walk(container.RootfsPath(), func(path string, fileInfo os.FileInfo, err error) error {
if fileInfo != nil {
sizeRootfs += fileInfo.Size()
}
return nil
})
}
return sizeRw, sizeRootfs
}

View File

@ -8,7 +8,7 @@
echo "Ensuring basic dependencies are installed..." echo "Ensuring basic dependencies are installed..."
apt-get -qq update apt-get -qq update
apt-get -qq install lxc wget bsdtar apt-get -qq install lxc wget
echo "Looking in /proc/filesystems to see if we have AUFS support..." echo "Looking in /proc/filesystems to see if we have AUFS support..."
if grep -q aufs /proc/filesystems if grep -q aufs /proc/filesystems

View File

@ -24,39 +24,29 @@ func main() {
docker.SysInit() docker.SysInit()
return return
} }
host := "127.0.0.1"
port := 4243
// FIXME: Switch d and D ? (to be more sshd like) // FIXME: Switch d and D ? (to be more sshd like)
flDaemon := flag.Bool("d", false, "Daemon mode") flDaemon := flag.Bool("d", false, "Daemon mode")
flDebug := flag.Bool("D", false, "Debug mode") flDebug := flag.Bool("D", false, "Debug mode")
flAutoRestart := flag.Bool("r", false, "Restart previously running containers") flAutoRestart := flag.Bool("r", false, "Restart previously running containers")
bridgeName := flag.String("b", "", "Attach containers to a pre-existing network bridge") bridgeName := flag.String("b", "", "Attach containers to a pre-existing network bridge")
pidfile := flag.String("p", "/var/run/docker.pid", "File containing process PID") pidfile := flag.String("p", "/var/run/docker.pid", "File containing process PID")
flHost := flag.String("H", fmt.Sprintf("%s:%d", host, port), "Host:port to bind/connect to")
flEnableCors := flag.Bool("api-enable-cors", false, "Enable CORS requests in the remote api.") flEnableCors := flag.Bool("api-enable-cors", false, "Enable CORS requests in the remote api.")
flDns := flag.String("dns", "", "Set custom dns servers")
flHosts := docker.ListOpts{fmt.Sprintf("tcp://%s:%d", docker.DEFAULTHTTPHOST, docker.DEFAULTHTTPPORT)}
flag.Var(&flHosts, "H", "tcp://host:port to bind/connect to or unix://path/to/socket to use")
flag.Parse() flag.Parse()
if len(flHosts) > 1 {
flHosts = flHosts[1:len(flHosts)] //trick to display a nice defaul value in the usage
}
for i, flHost := range flHosts {
flHosts[i] = utils.ParseHost(docker.DEFAULTHTTPHOST, docker.DEFAULTHTTPPORT, flHost)
}
if *bridgeName != "" { if *bridgeName != "" {
docker.NetworkBridgeIface = *bridgeName docker.NetworkBridgeIface = *bridgeName
} else { } else {
docker.NetworkBridgeIface = docker.DefaultNetworkBridge docker.NetworkBridgeIface = docker.DefaultNetworkBridge
} }
if strings.Contains(*flHost, ":") {
hostParts := strings.Split(*flHost, ":")
if len(hostParts) != 2 {
log.Fatal("Invalid bind address format.")
os.Exit(-1)
}
if hostParts[0] != "" {
host = hostParts[0]
}
if p, err := strconv.Atoi(hostParts[1]); err == nil {
port = p
}
} else {
host = *flHost
}
if *flDebug { if *flDebug {
os.Setenv("DEBUG", "1") os.Setenv("DEBUG", "1")
} }
@ -66,12 +56,17 @@ func main() {
flag.Usage() flag.Usage()
return return
} }
if err := daemon(*pidfile, host, port, *flAutoRestart, *flEnableCors); err != nil { if err := daemon(*pidfile, flHosts, *flAutoRestart, *flEnableCors, *flDns); err != nil {
log.Fatal(err) log.Fatal(err)
os.Exit(-1) os.Exit(-1)
} }
} else { } else {
if err := docker.ParseCommands(host, port, flag.Args()...); err != nil { if len(flHosts) > 1 {
log.Fatal("Please specify only one -H")
return
}
protoAddrParts := strings.SplitN(flHosts[0], "://", 2)
if err := docker.ParseCommands(protoAddrParts[0], protoAddrParts[1], flag.Args()...); err != nil {
log.Fatal(err) log.Fatal(err)
os.Exit(-1) os.Exit(-1)
} }
@ -105,10 +100,7 @@ func removePidFile(pidfile string) {
} }
} }
func daemon(pidfile, addr string, port int, autoRestart, enableCors bool) error { func daemon(pidfile string, protoAddrs []string, autoRestart, enableCors bool, flDns string) error {
if addr != "127.0.0.1" {
log.Println("/!\\ DON'T BIND ON ANOTHER IP ADDRESS THAN 127.0.0.1 IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\")
}
if err := createPidFile(pidfile); err != nil { if err := createPidFile(pidfile); err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -122,11 +114,37 @@ func daemon(pidfile, addr string, port int, autoRestart, enableCors bool) error
removePidFile(pidfile) removePidFile(pidfile)
os.Exit(0) os.Exit(0)
}() }()
var dns []string
server, err := docker.NewServer(autoRestart, enableCors) if flDns != "" {
dns = []string{flDns}
}
server, err := docker.NewServer(autoRestart, enableCors, dns)
if err != nil { if err != nil {
return err return err
} }
chErrors := make(chan error, len(protoAddrs))
return docker.ListenAndServe(fmt.Sprintf("%s:%d", addr, port), server, true) for _, protoAddr := range protoAddrs {
protoAddrParts := strings.SplitN(protoAddr, "://", 2)
if protoAddrParts[0] == "unix" {
syscall.Unlink(protoAddrParts[1]);
} else if protoAddrParts[0] == "tcp" {
if !strings.HasPrefix(protoAddrParts[1], "127.0.0.1") {
log.Println("/!\\ DON'T BIND ON ANOTHER IP ADDRESS THAN 127.0.0.1 IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\")
}
} else {
log.Fatal("Invalid protocol format.")
os.Exit(-1)
}
go func() {
chErrors <- docker.ListenAndServe(protoAddrParts[0], protoAddrParts[1], server, true)
}()
}
for i :=0 ; i < len(protoAddrs); i+=1 {
err := <-chErrors
if err != nil {
return err
}
}
return nil
} }

View File

@ -36,6 +36,7 @@ The client should send it's authConfig as POST on each call of /images/(name)/pu
.. http:post:: /auth only checks the configuration but doesn't store it on the server .. http:post:: /auth only checks the configuration but doesn't store it on the server
Deleting an image is now improved, will only untag the image if it has chidrens and remove all the untagged parents if has any. Deleting an image is now improved, will only untag the image if it has chidrens and remove all the untagged parents if has any.
.. http:post:: /images/<name>/delete now returns a JSON with the list of images deleted/untagged .. http:post:: /images/<name>/delete now returns a JSON with the list of images deleted/untagged

View File

@ -47,28 +47,40 @@ List containers
"Image": "base:latest", "Image": "base:latest",
"Command": "echo 1", "Command": "echo 1",
"Created": 1367854155, "Created": 1367854155,
"Status": "Exit 0" "Status": "Exit 0",
"Ports":"",
"SizeRw":12288,
"SizeRootFs":0
}, },
{ {
"Id": "9cd87474be90", "Id": "9cd87474be90",
"Image": "base:latest", "Image": "base:latest",
"Command": "echo 222222", "Command": "echo 222222",
"Created": 1367854155, "Created": 1367854155,
"Status": "Exit 0" "Status": "Exit 0",
"Ports":"",
"SizeRw":12288,
"SizeRootFs":0
}, },
{ {
"Id": "3176a2479c92", "Id": "3176a2479c92",
"Image": "base:latest", "Image": "base:latest",
"Command": "echo 3333333333333333", "Command": "echo 3333333333333333",
"Created": 1367854154, "Created": 1367854154,
"Status": "Exit 0" "Status": "Exit 0",
"Ports":"",
"SizeRw":12288,
"SizeRootFs":0
}, },
{ {
"Id": "4cb07b47f9fb", "Id": "4cb07b47f9fb",
"Image": "base:latest", "Image": "base:latest",
"Command": "echo 444444444444444444444444444444444", "Command": "echo 444444444444444444444444444444444",
"Created": 1367854152, "Created": 1367854152,
"Status": "Exit 0" "Status": "Exit 0",
"Ports":"",
"SizeRw":12288,
"SizeRootFs":0
} }
] ]
@ -488,13 +500,17 @@ List Images
"Repository":"base", "Repository":"base",
"Tag":"ubuntu-12.10", "Tag":"ubuntu-12.10",
"Id":"b750fe79269d", "Id":"b750fe79269d",
"Created":1364102658 "Created":1364102658,
"Size":24653,
"VirtualSize":180116135
}, },
{ {
"Repository":"base", "Repository":"base",
"Tag":"ubuntu-quantal", "Tag":"ubuntu-quantal",
"Id":"b750fe79269d", "Id":"b750fe79269d",
"Created":1364102658 "Created":1364102658,
"Size":24653,
"VirtualSize":180116135
} }
] ]
@ -643,7 +659,8 @@ Inspect an image
"Image":"base", "Image":"base",
"Volumes":null, "Volumes":null,
"VolumesFrom":"" "VolumesFrom":""
} },
"Size": 6824592
} }
:statuscode 200: no error :statuscode 200: no error
@ -674,6 +691,7 @@ Get the history of an image
[ [
{ {
"Id":"b750fe79269d", "Id":"b750fe79269d",
"Tag":["base:latest"],
"Created":1364102658, "Created":1364102658,
"CreatedBy":"/bin/bash" "CreatedBy":"/bin/bash"
}, },
@ -1016,5 +1034,5 @@ In this version of the API, /attach, uses hijacking to transport stdin, stdout a
To enable cross origin requests to the remote api add the flag "-api-enable-cors" when running docker in daemon mode. To enable cross origin requests to the remote api add the flag "-api-enable-cors" when running docker in daemon mode.
docker -d -H="192.168.1.9:4243" -api-enable-cors docker -d -H="tcp://192.168.1.9:4243" -api-enable-cors

View File

@ -15,7 +15,7 @@ To list available commands, either run ``docker`` with no parameters or execute
$ docker $ docker
Usage: docker [OPTIONS] COMMAND [arg...] Usage: docker [OPTIONS] COMMAND [arg...]
-H="127.0.0.1:4243": Host:port to bind/connect to -H=[tcp://127.0.0.1:4243]: tcp://host:port to bind/connect to or unix://path/to/socket to use
A self-sufficient runtime for linux containers. A self-sufficient runtime for linux containers.

View File

@ -8,6 +8,33 @@
:: ::
Usage: docker import [OPTIONS] URL|- [REPOSITORY [TAG]] Usage: docker import URL|- [REPOSITORY [TAG]]
Create a new filesystem image from the contents of a tarball Create a new filesystem image from the contents of a tarball
At this time, the URL must start with ``http`` and point to a single file archive (.tar, .tar.gz, .bzip)
containing a root filesystem. If you would like to import from a local directory or archive,
you can use the ``-`` parameter to take the data from standard in.
Examples
--------
Import from a remote location
.............................
``$ docker import http://example.com/exampleimage.tgz exampleimagerepo``
Import from a local file
........................
Import to docker via pipe and standard in
``$ cat exampleimage.tgz | docker import - exampleimagelocal``
Import from a local directory
.............................
``$ sudo tar -c . | docker import - exampleimagedir``
Note the ``sudo`` in this example -- you must preserve the ownership of the files (especially root ownership)
during the archiving with tar. If you are not root (or sudo) when you tar, then the ownerships might not get preserved.

View File

@ -1,8 +0,0 @@
:title: Introduction
:description: An introduction to docker and standard containers?
:keywords: containers, lxc, concepts, explanation, docker, documentation
:note: This version of the introduction is temporary, just to make sure we don't break the links from the website when the documentation is updated
This document has been moved to :ref:`introduction`, please update your bookmarks.

View File

@ -1,125 +0,0 @@
:title: Introduction
:description: An introduction to docker and standard containers?
:keywords: containers, lxc, concepts, explanation
Introduction
============
Docker -- The Linux container runtime
-------------------------------------
Docker complements LXC with a high-level API which operates at the process level. It runs unix processes with strong guarantees of isolation and repeatability across servers.
Docker is a great building block for automating distributed systems: large-scale web deployments, database clusters, continuous deployment systems, private PaaS, service-oriented architectures, etc.
- **Heterogeneous payloads** Any combination of binaries, libraries, configuration files, scripts, virtualenvs, jars, gems, tarballs, you name it. No more juggling between domain-specific tools. Docker can deploy and run them all.
- **Any server** Docker can run on any x64 machine with a modern linux kernel - whether it's a laptop, a bare metal server or a VM. This makes it perfect for multi-cloud deployments.
- **Isolation** docker isolates processes from each other and from the underlying host, using lightweight containers.
- **Repeatability** Because containers are isolated in their own filesystem, they behave the same regardless of where, when, and alongside what they run.
.. image:: images/lego_docker.jpg
What is a Standard Container?
-----------------------------
Docker defines a unit of software delivery called a Standard Container. The goal of a Standard Container is to encapsulate a software component and all its dependencies in
a format that is self-describing and portable, so that any compliant runtime can run it without extra dependency, regardless of the underlying machine and the contents of the container.
The spec for Standard Containers is currently work in progress, but it is very straightforward. It mostly defines 1) an image format, 2) a set of standard operations, and 3) an execution environment.
A great analogy for this is the shipping container. Just like Standard Containers are a fundamental unit of software delivery, shipping containers (http://bricks.argz.com/ins/7823-1/12) are a fundamental unit of physical delivery.
Standard operations
~~~~~~~~~~~~~~~~~~~
Just like shipping containers, Standard Containers define a set of STANDARD OPERATIONS. Shipping containers can be lifted, stacked, locked, loaded, unloaded and labelled. Similarly, standard containers can be started, stopped, copied, snapshotted, downloaded, uploaded and tagged.
Content-agnostic
~~~~~~~~~~~~~~~~~~~
Just like shipping containers, Standard Containers are CONTENT-AGNOSTIC: all standard operations have the same effect regardless of the contents. A shipping container will be stacked in exactly the same way whether it contains Vietnamese powder coffee or spare Maserati parts. Similarly, Standard Containers are started or uploaded in the same way whether they contain a postgres database, a php application with its dependencies and application server, or Java build artifacts.
Infrastructure-agnostic
~~~~~~~~~~~~~~~~~~~~~~~~~~
Both types of containers are INFRASTRUCTURE-AGNOSTIC: they can be transported to thousands of facilities around the world, and manipulated by a wide variety of equipment. A shipping container can be packed in a factory in Ukraine, transported by truck to the nearest routing center, stacked onto a train, loaded into a German boat by an Australian-built crane, stored in a warehouse at a US facility, etc. Similarly, a standard container can be bundled on my laptop, uploaded to S3, downloaded, run and snapshotted by a build server at Equinix in Virginia, uploaded to 10 staging servers in a home-made Openstack cluster, then sent to 30 production instances across 3 EC2 regions.
Designed for automation
~~~~~~~~~~~~~~~~~~~~~~~~~~
Because they offer the same standard operations regardless of content and infrastructure, Standard Containers, just like their physical counterpart, are extremely well-suited for automation. In fact, you could say automation is their secret weapon.
Many things that once required time-consuming and error-prone human effort can now be programmed. Before shipping containers, a bag of powder coffee was hauled, dragged, dropped, rolled and stacked by 10 different people in 10 different locations by the time it reached its destination. 1 out of 50 disappeared. 1 out of 20 was damaged. The process was slow, inefficient and cost a fortune - and was entirely different depending on the facility and the type of goods.
Similarly, before Standard Containers, by the time a software component ran in production, it had been individually built, configured, bundled, documented, patched, vendored, templated, tweaked and instrumented by 10 different people on 10 different computers. Builds failed, libraries conflicted, mirrors crashed, post-it notes were lost, logs were misplaced, cluster updates were half-broken. The process was slow, inefficient and cost a fortune - and was entirely different depending on the language and infrastructure provider.
Industrial-grade delivery
~~~~~~~~~~~~~~~~~~~~~~~~~~
There are 17 million shipping containers in existence, packed with every physical good imaginable. Every single one of them can be loaded on the same boats, by the same cranes, in the same facilities, and sent anywhere in the World with incredible efficiency. It is embarrassing to think that a 30 ton shipment of coffee can safely travel half-way across the World in *less time* than it takes a software team to deliver its code from one datacenter to another sitting 10 miles away.
With Standard Containers we can put an end to that embarrassment, by making INDUSTRIAL-GRADE DELIVERY of software a reality.
Standard Container Specification
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
(TODO)
Image format
~~~~~~~~~~~~
Standard operations
~~~~~~~~~~~~~~~~~~~
- Copy
- Run
- Stop
- Wait
- Commit
- Attach standard streams
- List filesystem changes
- ...
Execution environment
~~~~~~~~~~~~~~~~~~~~~
Root filesystem
^^^^^^^^^^^^^^^
Environment variables
^^^^^^^^^^^^^^^^^^^^^
Process arguments
^^^^^^^^^^^^^^^^^
Networking
^^^^^^^^^^
Process namespacing
^^^^^^^^^^^^^^^^^^^
Resource limits
^^^^^^^^^^^^^^^
Process monitoring
^^^^^^^^^^^^^^^^^^
Logging
^^^^^^^
Signals
^^^^^^^
Pseudo-terminal allocation
^^^^^^^^^^^^^^^^^^^^^^^^^^
Security
^^^^^^^^

View File

@ -20,6 +20,20 @@ import sys, os
# -- General configuration ----------------------------------------------------- # -- General configuration -----------------------------------------------------
# Additional templates that should be rendered to pages, maps page names to
# template names.
# the 'redirect_home.html' page redirects using a http meta refresh which, according
# to official sources is more or less equivalent of a 301.
html_additional_pages = {
'concepts/containers': 'redirect_home.html',
'concepts/introduction': 'redirect_home.html',
}
# If your documentation needs a minimal Sphinx version, state it here. # If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0' #needs_sphinx = '1.0'
@ -120,7 +134,11 @@ html_theme_path = ['../theme']
# The name of an image file (within the static path) to use as favicon of the # The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large. # pixels large.
#html_favicon = None
# We use a png favicon. This is not compatible with internet explorer, but looks
# much better on all other browsers. However, sphynx doesn't like it (it likes
# .ico better) so we have just put it in the template rather than used this setting
# html_favicon = 'favicon.png'
# Add any paths that contain custom static files (such as style sheets) here, # Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files, # relative to this directory. They are copied after the builtin static files,
@ -138,10 +156,6 @@ html_static_path = ['static_files']
# Custom sidebar templates, maps document names to template names. # Custom sidebar templates, maps document names to template names.
#html_sidebars = {} #html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}
# If false, no module index is generated. # If false, no module index is generated.
#html_domain_indices = True #html_domain_indices = True

View File

@ -33,7 +33,7 @@ Installation
sudo apt-get install python-software-properties sudo apt-get install python-software-properties
sudo add-apt-repository ppa:gophers/go sudo add-apt-repository ppa:gophers/go
sudo apt-get update sudo apt-get update
sudo apt-get -y install lxc wget bsdtar curl golang-stable git sudo apt-get -y install lxc xz-utils curl golang-stable git aufs-tools
export GOPATH=~/go/ export GOPATH=~/go/
export PATH=$GOPATH/bin:$PATH export PATH=$GOPATH/bin:$PATH

View File

@ -72,7 +72,7 @@ Connect to the host os with the redis-cli.
docker ps # grab the new container id docker ps # grab the new container id
docker port <container_id> 6379 # grab the external port docker port <container_id> 6379 # grab the external port
ifconfig # grab the host ip address ip addr show # grab the host ip address
redis-cli -h <host ipaddress> -p <external port> redis-cli -h <host ipaddress> -p <external port>
redis 192.168.0.1:49153> set docker awesome redis 192.168.0.1:49153> set docker awesome
OK OK

View File

@ -59,6 +59,7 @@ The password is 'screencast'
# it has now given us a port to connect to # it has now given us a port to connect to
# we have to connect using a public ip of our host # we have to connect using a public ip of our host
$ hostname $ hostname
# *ifconfig* is deprecated, better use *ip addr show* now
$ ifconfig $ ifconfig
$ ssh root@192.168.33.10 -p 49153 $ ssh root@192.168.33.10 -p 49153
# Ah! forgot to set root passwd # Ah! forgot to set root passwd
@ -70,6 +71,7 @@ The password is 'screencast'
$ docker commit 9e863f0ca0af31c8b951048ba87641d67c382d08d655c2e4879c51410e0fedc1 dhrp/sshd $ docker commit 9e863f0ca0af31c8b951048ba87641d67c382d08d655c2e4879c51410e0fedc1 dhrp/sshd
$ docker run -d -p 22 dhrp/sshd /usr/sbin/sshd -D $ docker run -d -p 22 dhrp/sshd /usr/sbin/sshd -D
$ docker port a0aaa9558c90cf5c7782648df904a82365ebacce523e4acc085ac1213bfe2206 22 $ docker port a0aaa9558c90cf5c7782648df904a82365ebacce523e4acc085ac1213bfe2206 22
# *ifconfig* is deprecated, better use *ip addr show* now
$ ifconfig $ ifconfig
$ ssh root@192.168.33.10 -p 49154 $ ssh root@192.168.33.10 -p 49154
# Thanks for watching, Thatcher thatcher@dotcloud.com # Thanks for watching, Thatcher thatcher@dotcloud.com

View File

@ -1,27 +0,0 @@
:title: Index Environment Variable
:description: Setting this environment variable on the docker server will change the URL docker index.
:keywords: docker, index environment variable, documentation
=================================
Docker Index Environment Variable
=================================
Variable
--------
.. code-block:: sh
DOCKER_INDEX_URL
Setting this environment variable on the docker server will change the URL docker index.
This address is used in commands such as ``docker login``, ``docker push`` and ``docker pull``.
The docker daemon doesn't need to be restarted for this parameter to take effect.
Example
-------
.. code-block:: sh
docker -d &
export DOCKER_INDEX_URL="https://index.docker.io"

View File

@ -30,8 +30,7 @@ Dependencies:
* 3.8 Kernel (read more about :ref:`kernel`) * 3.8 Kernel (read more about :ref:`kernel`)
* AUFS filesystem support * AUFS filesystem support
* lxc * lxc
* bsdtar * xz-utils
Get the docker binary: Get the docker binary:
---------------------- ----------------------

View File

@ -100,7 +100,7 @@ Memory and Swap Accounting on Debian/Ubuntu
If you use Debian or Ubuntu kernels, and want to enable memory and swap If you use Debian or Ubuntu kernels, and want to enable memory and swap
accounting, you must add the following command-line parameters to your kernel:: accounting, you must add the following command-line parameters to your kernel::
cgroup_enable=memory swapaccount cgroup_enable=memory swapaccount=1
On Debian or Ubuntu systems, if you use the default GRUB bootloader, you can On Debian or Ubuntu systems, if you use the default GRUB bootloader, you can
add those parameters by editing ``/etc/default/grub`` and extending add those parameters by editing ``/etc/default/grub`` and extending
@ -110,6 +110,6 @@ add those parameters by editing ``/etc/default/grub`` and extending
And replace it by the following one:: And replace it by the following one::
GRUB_CMDLINE_LINUX="cgroup_enable=memory swapaccount" GRUB_CMDLINE_LINUX="cgroup_enable=memory swapaccount=1"
Then run ``update-grub``, and reboot. Then run ``update-grub``, and reboot.

View File

@ -33,11 +33,20 @@ Running an interactive shell
# allocate a tty, attach stdin and stdout # allocate a tty, attach stdin and stdout
docker run -i -t base /bin/bash docker run -i -t base /bin/bash
Bind Docker to another host/port Bind Docker to another host/port or a unix socket
-------------------------------- -------------------------------------------------
If you want Docker to listen to another port and bind to another ip With -H it is possible to make the Docker daemon to listen on a specific ip and port. By default, it will listen on 127.0.0.1:4243 to allow only local connections but you can set it to 0.0.0.0:4243 or a specific host ip to give access to everybody.
use -host and -port on both deamon and client
Similarly, the Docker client can use -H to connect to a custom port.
-H accepts host and port assignment in the following format: tcp://[host][:port] or unix://path
For example:
* tcp://host -> tcp connection on host:4243
* tcp://host:port -> tcp connection on host:port
* tcp://:port -> tcp connection on 127.0.0.1:port
* unix://path/to/socket -> unix socket located at path/to/socket
.. code-block:: bash .. code-block:: bash
@ -46,6 +55,17 @@ use -host and -port on both deamon and client
# Download a base image # Download a base image
docker -H :5555 pull base docker -H :5555 pull base
You can use multiple -H, for example, if you want to listen
on both tcp and a unix socket
.. code-block:: bash
# Run docker in daemon mode
sudo <path to>/docker -H tcp://127.0.0.1:4243 -H unix:///var/run/docker.sock
# Download a base image
docker pull base
# OR
docker -H unix:///var/run/docker.sock pull base
Starting a long-running worker process Starting a long-running worker process
-------------------------------------- --------------------------------------
@ -82,7 +102,8 @@ Expose a service on a TCP port
# Connect to the public port via the host's public address # Connect to the public port via the host's public address
# Please note that because of how routing works connecting to localhost or 127.0.0.1 $PORT will not work. # Please note that because of how routing works connecting to localhost or 127.0.0.1 $PORT will not work.
IP=$(ifconfig eth0 | perl -n -e 'if (m/inet addr:([\d\.]+)/g) { print $1 }') # Replace *eth0* according to your local interface name.
IP=$(ip -o -4 addr list eth0 | perl -n -e 'if (m{inet\s([\d\.]+)\/\d+\s}xms) { print $1 }')
echo hello world | nc $IP $PORT echo hello world | nc $IP $PORT
# Verify that the network connection worked # Verify that the network connection worked

View File

@ -15,10 +15,18 @@ steps and commit them along the way, giving you a final image.
1. Usage 1. Usage
======== ========
To use Docker Builder, assemble the steps into a text file (commonly referred to To build an image from a source repository, create a description file called `Dockerfile`
as a Dockerfile) and supply this to `docker build` on STDIN, like so: at the root of your repository. This file will describe the steps to assemble
the image.
``docker build - < Dockerfile`` Then call `docker build` with the path of your source repository as argument:
``docker build .``
You can specify a repository and tag at which to save the new image if the
build succeeds:
``docker build -t shykes/myapp .``
Docker will run your steps one-by-one, committing the result if necessary, Docker will run your steps one-by-one, committing the result if necessary,
before finally outputting the ID of your new image. before finally outputting the ID of your new image.
@ -130,9 +138,32 @@ curl was installed within the image.
``ADD <src> <dest>`` ``ADD <src> <dest>``
The `ADD` instruction will insert the files from the `<src>` path of the context into `<dest>` path The `ADD` instruction will copy new files from <src> and add them to the container's filesystem at path `<dest>`.
of the container.
The context must be set in order to use this instruction. (see examples) `<src>` must be the path to a file or directory relative to the source directory being built (also called the
context of the build).
`<dest>` is the path at which the source will be copied in the destination container.
The copy obeys the following rules:
If `<src>` is a directory, the entire directory is copied, including filesystem metadata.
If `<src>` is a tar archive in a recognized compression format (identity, gzip, bzip2 or xz), it
is unpacked as a directory.
When a directory is copied or unpacked, it has the same behavior as 'tar -x': the result is the union of
a) whatever existed at the destination path and b) the contents of the source tree, with conflicts resolved
in favor of b on a file-by-file basis.
If `<src>` is any other kind of file, it is copied individually along with its metadata. In this case,
if `<dst>` ends with a trailing slash '/', it will be considered a directory and the contents of `<src>`
will be written at `<dst>/base(<src>)`.
If `<dst>` does not end with a trailing slash, it will be considered a regular file and the contents
of `<src>` will be written at `<dst>`.
If `<dest>` doesn't exist, it is created along with all missing directories in its path. All new
files and directories are created with mode 0700, uid and gid 0.
3. Dockerfile Examples 3. Dockerfile Examples
====================== ======================

View File

@ -14,6 +14,7 @@ Contents:
basics basics
workingwithrepository workingwithrepository
port_redirection
builder builder
puppet puppet

View File

@ -0,0 +1,25 @@
:title: Port redirection
:description: usage about port redirection
:keywords: Usage, basic port, docker, documentation, examples
Port redirection
================
Docker can redirect public tcp ports to your container, so it can be reached over the network.
Port redirection is done on ``docker run`` using the -p flag.
A port redirect is specified as PUBLIC:PRIVATE, where tcp port PUBLIC will be redirected to
tcp port PRIVATE. As a special case, the public port can be omitted, in which case a random
public port will be allocated.
.. code-block:: bash
# A random PUBLIC port is redirected to PRIVATE port 80 on the container
docker run -p 80 <image> <cmd>
# PUBLIC port 80 is redirected to PRIVATE port 80
docker run -p 80:80 <image> <cmd>
Default port redirects can be built into a container with the EXPOSE build command.

View File

@ -77,3 +77,28 @@ Now you can commit this image to the repository
# for example docker push dhrp/kickassapp # for example docker push dhrp/kickassapp
docker push <image-name> docker push <image-name>
Changing the server to connect to
----------------------------------
When you are running your own index and/or registry, You can change the server the docker client will connect to.
Variable
^^^^^^^^
.. code-block:: sh
DOCKER_INDEX_URL
Setting this environment variable on the docker server will change the URL docker index.
This address is used in commands such as ``docker login``, ``docker push`` and ``docker pull``.
The docker daemon doesn't need to be restarted for this parameter to take effect.
Example
^^^^^^^
.. code-block:: sh
docker -d &
export DOCKER_INDEX_URL="https://index.docker.io"

View File

@ -40,6 +40,8 @@
{%- set script_files = script_files + ['_static/js/docs.js'] %} {%- set script_files = script_files + ['_static/js/docs.js'] %}
<link rel="canonical" href="http://docs.docker.io/en/latest/{{ pagename }}/">
{%- for cssfile in css_files %} {%- for cssfile in css_files %}
<link rel="stylesheet" href="{{ pathto(cssfile, 1) }}" type="text/css" /> <link rel="stylesheet" href="{{ pathto(cssfile, 1) }}" type="text/css" />
{%- endfor %} {%- endfor %}
@ -48,9 +50,8 @@
<script type="text/javascript" src="{{ pathto(scriptfile, 1) }}"></script> <script type="text/javascript" src="{{ pathto(scriptfile, 1) }}"></script>
{%- endfor %} {%- endfor %}
{%- if favicon %} <link rel="shortcut icon" href="{{ pathto('_static/favicon.png', 1) }}"/>
<link rel="shortcut icon" href="{{ pathto('_static/' + favicon, 1) }}"/>
{%- endif %}
{%- block extrahead %}{% endblock %} {%- block extrahead %}{% endblock %}
@ -104,11 +105,8 @@
<!-- Docs nav <!-- Docs nav
================================================== --> ================================================== -->
<div class="row" style="position: relative"> <div class="row" style="position: relative">
<div class="span3" style="height:100%;" >
</div> <div class="span3 sidebar bs-docs-sidebar">
<div class="span3 sidebar bs-docs-sidebar" style="position: absolute">
{{ toctree(collapse=False, maxdepth=3) }} {{ toctree(collapse=False, maxdepth=3) }}
</div> </div>

12
docs/theme/docker/redirect_home.html vendored Normal file
View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<head>
<title>Page Moved</title>
<meta http-equiv="refresh" content="0; url=http://docks.docker.io/en/latest/">
</head>
<body>
This page has moved. Perhaps you should visit the <a href="http://docs.docker.io/" title="documentation homepage">Documentation Homepage</a>
</body>
</html>

View File

@ -168,10 +168,13 @@ section.header {
.sidebar { .sidebar {
font-weight: normal; font-weight: normal;
float: left; float: left;
min-height: 475px; /* min-height: 475px;*/
background: #ececec; background: #ececec;
border-left: 1px solid #bbbbbb; /* border-left: 1px solid #bbbbbb;*/
border-right: 1px solid #cccccc;
/* border-right: 1px solid #cccccc;*/
position: relative; position: relative;
} }
.sidebar ul { .sidebar ul {
@ -357,7 +360,6 @@ section.header {
#global { #global {
/* TODO: Fix this to be relative to the navigation size */ /* TODO: Fix this to be relative to the navigation size */
padding-top: 600px;
} }
#fork-us { #fork-us {
display: none; display: none;

View File

@ -226,20 +226,21 @@ section.header {
} }
.sidebar {
// font-family: "Maven Pro";
font-weight: normal;
// margin-top: 38px;
float: left;
// width: 220px;
/* min-height: 475px;*/
// margin-bottom: 28px;
// padding-bottom: 120px;
background: #ececec;
/* border-left: 1px solid #bbbbbb;*/
/* border-right: 1px solid #cccccc;*/
position: relative;
.sidebar {
// font-family: "Maven Pro";
font-weight: normal;
// margin-top: 38px;
float: left;
// width: 220px;
min-height: 475px;
// margin-bottom: 28px;
// padding-bottom: 120px;
background: #ececec;
border-left: 1px solid #bbbbbb;
border-right: 1px solid #cccccc;
position: relative;
ul { ul {
padding: 0px; padding: 0px;
@ -471,7 +472,7 @@ section.header {
} }
#global { #global {
/* TODO: Fix this to be relative to the navigation size */ /* TODO: Fix this to be relative to the navigation size */
padding-top: 600px; // padding-top: 600px;
} }
#fork-us { #fork-us {
display: none; display: none;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

BIN
docs/theme/docker/static/favicon.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 404 B

View File

@ -1,10 +0,0 @@
package docker
import (
"fmt"
"github.com/dotcloud/docker/utils"
)
func getKernelVersion() (*utils.KernelVersionInfo, error) {
return nil, fmt.Errorf("Kernel version detection is not available on darwin")
}

View File

@ -1,71 +0,0 @@
package docker
import (
"bytes"
"github.com/dotcloud/docker/utils"
"strconv"
"strings"
"syscall"
)
// FIXME: Move this to utils package
func getKernelVersion() (*utils.KernelVersionInfo, error) {
var (
uts syscall.Utsname
flavor string
kernel, major, minor int
err error
)
if err := syscall.Uname(&uts); err != nil {
return nil, err
}
release := make([]byte, len(uts.Release))
i := 0
for _, c := range uts.Release {
release[i] = byte(c)
i++
}
// Remove the \x00 from the release for Atoi to parse correctly
release = release[:bytes.IndexByte(release, 0)]
tmp := strings.SplitN(string(release), "-", 2)
tmp2 := strings.SplitN(tmp[0], ".", 3)
if len(tmp2) > 0 {
kernel, err = strconv.Atoi(tmp2[0])
if err != nil {
return nil, err
}
}
if len(tmp2) > 1 {
major, err = strconv.Atoi(tmp2[1])
if err != nil {
return nil, err
}
}
if len(tmp2) > 2 {
minor, err = strconv.Atoi(tmp2[2])
if err != nil {
return nil, err
}
}
if len(tmp) == 2 {
flavor = tmp[1]
} else {
flavor = ""
}
return &utils.KernelVersionInfo{
Kernel: kernel,
Major: major,
Minor: minor,
Flavor: flavor,
}, nil
}

View File

@ -90,6 +90,15 @@ func (graph *Graph) Get(name string) (*Image, error) {
return nil, fmt.Errorf("Image stored at '%s' has wrong id '%s'", id, img.ID) return nil, fmt.Errorf("Image stored at '%s' has wrong id '%s'", id, img.ID)
} }
img.graph = graph img.graph = graph
if img.Size == 0 {
root, err := img.root()
if err != nil {
return nil, err
}
if err := StoreSize(img, root); err != nil {
return nil, err
}
}
graph.lockSumMap.Lock() graph.lockSumMap.Lock()
defer graph.lockSumMap.Unlock() defer graph.lockSumMap.Unlock()
if _, exists := graph.checksumLock[img.ID]; !exists { if _, exists := graph.checksumLock[img.ID]; !exists {

View File

@ -192,11 +192,19 @@ func TestDelete(t *testing.T) {
} }
assertNImages(graph, t, 0) assertNImages(graph, t, 0)
archive, err = fakeTar()
if err != nil {
t.Fatal(err)
}
// Test 2 create (same name) / 1 delete // Test 2 create (same name) / 1 delete
img1, err := graph.Create(archive, nil, "Testing", "", nil) img1, err := graph.Create(archive, nil, "Testing", "", nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
archive, err = fakeTar()
if err != nil {
t.Fatal(err)
}
if _, err = graph.Create(archive, nil, "Testing", "", nil); err != nil { if _, err = graph.Create(archive, nil, "Testing", "", nil); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -212,6 +220,10 @@ func TestDelete(t *testing.T) {
} }
assertNImages(graph, t, 1) assertNImages(graph, t, 1)
archive, err = fakeTar()
if err != nil {
t.Fatal(err)
}
// Test delete twice (pull -> rm -> pull -> rm) // Test delete twice (pull -> rm -> pull -> rm)
if err := graph.Register(archive, false, img1); err != nil { if err := graph.Register(archive, false, img1); err != nil {
t.Fatal(err) t.Fatal(err)

2
hack/Vagrantfile vendored
View File

@ -22,7 +22,7 @@ Vagrant::Config.run do |config|
pkg_cmd = "touch #{DOCKER_PATH}; " pkg_cmd = "touch #{DOCKER_PATH}; "
# Install docker dependencies # Install docker dependencies
pkg_cmd << "export DEBIAN_FRONTEND=noninteractive; apt-get -qq update; " \ pkg_cmd << "export DEBIAN_FRONTEND=noninteractive; apt-get -qq update; " \
"apt-get install -q -y lxc bsdtar git golang make linux-image-extra-3.8.0-19-generic; " \ "apt-get install -q -y lxc git aufs-tools golang make linux-image-extra-3.8.0-19-generic; " \
"chown -R #{USER}.#{USER} #{GOPATH}; " \ "chown -R #{USER}.#{USER} #{GOPATH}; " \
"install -m 0664 #{CFG_PATH}/bash_profile /home/#{USER}/.bash_profile" "install -m 0664 #{CFG_PATH}/bash_profile /home/#{USER}/.bash_profile"
config.vm.provision :shell, :inline => pkg_cmd config.vm.provision :shell, :inline => pkg_cmd

View File

@ -1,5 +1,13 @@
# This will build a container capable of producing an official binary build of docker and # DESCRIPTION Build a container capable of producing official binary and
# uploading it to S3 # PPA packages and uploading them to S3 and Launchpad
# VERSION 1.2
# DOCKER_VERSION 0.4
# AUTHOR Solomon Hykes <solomon@dotcloud.com>
# Daniel Mizyrycki <daniel@dotcloud.net>
# BUILD_CMD docker build -t dockerbuilder .
# RUN_CMD docker run -e AWS_ID="$AWS_ID" -e AWS_KEY="$AWS_KEY" -e GPG_KEY="$GPG_KEY" dockerbuilder
#
#
from ubuntu:12.04 from ubuntu:12.04
maintainer Solomon Hykes <solomon@dotcloud.com> maintainer Solomon Hykes <solomon@dotcloud.com>
# Workaround the upstart issue # Workaround the upstart issue
@ -8,24 +16,18 @@ run ln -s /bin/true /sbin/initctl
# Enable universe and gophers PPA # Enable universe and gophers PPA
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q python-software-properties run DEBIAN_FRONTEND=noninteractive apt-get install -y -q python-software-properties
run add-apt-repository "deb http://archive.ubuntu.com/ubuntu $(lsb_release -sc) universe" run add-apt-repository "deb http://archive.ubuntu.com/ubuntu $(lsb_release -sc) universe"
run add-apt-repository -y ppa:gophers/go/ubuntu run add-apt-repository -y ppa:dotcloud/docker-golang/ubuntu
run apt-get update run apt-get update
# Packages required to checkout, build and upload docker # Packages required to checkout, build and upload docker
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q s3cmd run DEBIAN_FRONTEND=noninteractive apt-get install -y -q s3cmd curl
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q curl
run curl -s -o /go.tar.gz https://go.googlecode.com/files/go1.1.1.linux-amd64.tar.gz run curl -s -o /go.tar.gz https://go.googlecode.com/files/go1.1.1.linux-amd64.tar.gz
run tar -C /usr/local -xzf /go.tar.gz run tar -C /usr/local -xzf /go.tar.gz
run echo "export PATH=/usr/local/go/bin:$PATH" > /.bashrc run echo "export PATH=/usr/local/go/bin:$PATH" > /.bashrc
run echo "export PATH=/usr/local/go/bin:$PATH" > /.bash_profile run echo "export PATH=/usr/local/go/bin:$PATH" > /.bash_profile
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q git run DEBIAN_FRONTEND=noninteractive apt-get install -y -q git build-essential
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q build-essential
# Packages required to build an ubuntu package # Packages required to build an ubuntu package
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q golang-stable run DEBIAN_FRONTEND=noninteractive apt-get install -y -q golang-stable debhelper autotools-dev devscripts
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q debhelper
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q autotools-dev
run apt-get install -y -q devscripts
# Copy dockerbuilder files into the container # Copy dockerbuilder files into the container
add . /src add . /src
run cp /src/dockerbuilder /usr/local/bin/ && chmod +x /usr/local/bin/dockerbuilder run cp /src/dockerbuilder /usr/local/bin/ && chmod +x /usr/local/bin/dockerbuilder
run cp /src/s3cfg /.s3cfg
cmd ["dockerbuilder"] cmd ["dockerbuilder"]

View File

@ -13,12 +13,10 @@ fi
export REVISION=$1 export REVISION=$1
if [ -z "$AWS_ID" ]; then if [ -z "$AWS_ID" -o -z "$AWS_KEY" ]; then
echo "Warning: environment variable AWS_ID is not set. Won't upload to S3." echo "Warning: either AWS_ID or AWS_KEY environment variable not set. Won't upload to S3."
fi else
/bin/echo -e "[default]\naccess_key = $AWS_ID\nsecret_key = $AWS_KEY\n" > /.s3cfg
if [ -z "$AWS_KEY" ]; then
echo "Warning: environment variable AWS_KEY is not set. Won't upload to S3."
fi fi
if [ -z "$GPG_KEY" ]; then if [ -z "$GPG_KEY" ]; then
@ -35,6 +33,9 @@ else
make release RELEASE_VERSION=$REVISION make release RELEASE_VERSION=$REVISION
fi fi
# Remove credentials from container
rm -f /.s3cfg
if [ -z "$NO_UBUNTU" ]; then if [ -z "$NO_UBUNTU" ]; then
export PATH=`echo $PATH | sed 's#/usr/local/go/bin:##g'` export PATH=`echo $PATH | sed 's#/usr/local/go/bin:##g'`
(cd packaging/ubuntu && make ubuntu) (cd packaging/ubuntu && make ubuntu)

View File

@ -1,3 +0,0 @@
[default]
access_key = $AWS_ID
secret_key = $AWS_KEY

View File

@ -13,6 +13,7 @@ import (
"os" "os"
"os/exec" "os/exec"
"path" "path"
"path/filepath"
"strings" "strings"
"time" "time"
) )
@ -29,6 +30,7 @@ type Image struct {
Config *Config `json:"config,omitempty"` Config *Config `json:"config,omitempty"`
Architecture string `json:"architecture,omitempty"` Architecture string `json:"architecture,omitempty"`
graph *Graph graph *Graph
Size int64
} }
func LoadImage(root string) (*Image, error) { func LoadImage(root string) (*Image, error) {
@ -94,6 +96,18 @@ func StoreImage(img *Image, layerData Archive, root string, store bool) error {
if err := Untar(layerData, layer); err != nil { if err := Untar(layerData, layer); err != nil {
return err return err
} }
return StoreSize(img, root)
}
func StoreSize(img *Image, root string) error {
layer := layerPath(root)
filepath.Walk(layer, func(path string, fileInfo os.FileInfo, err error) error {
img.Size += fileInfo.Size()
return nil
})
// Store the json ball // Store the json ball
jsonData, err := json.Marshal(img) jsonData, err := json.Marshal(img)
if err != nil { if err != nil {
@ -363,6 +377,15 @@ func (img *Image) Checksum() (string, error) {
return hash, nil return hash, nil
} }
func (img *Image) getParentsSize(size int64) int64 {
parentImage, err := img.GetParent()
if err != nil || parentImage == nil {
return size
}
size += parentImage.Size
return parentImage.getParentsSize(size)
}
// Build an Image object from raw json data // Build an Image object from raw json data
func NewImgJSON(src []byte) (*Image, error) { func NewImgJSON(src []byte) (*Image, error) {
ret := &Image{} ret := &Image{}

View File

@ -67,7 +67,11 @@ lxc.cgroup.devices.allow = c 10:200 rwm
# standard mount point # standard mount point
# WARNING: procfs is a known attack vector and should probably be disabled
# if your userspace allows it. eg. see http://blog.zx2c4.com/749
lxc.mount.entry = proc {{$ROOTFS}}/proc proc nosuid,nodev,noexec 0 0 lxc.mount.entry = proc {{$ROOTFS}}/proc proc nosuid,nodev,noexec 0 0
# WARNING: sysfs is a known attack vector and should probably be disabled
# if your userspace allows it. eg. see http://bit.ly/T9CkqJ
lxc.mount.entry = sysfs {{$ROOTFS}}/sys sysfs nosuid,nodev,noexec 0 0 lxc.mount.entry = sysfs {{$ROOTFS}}/sys sysfs nosuid,nodev,noexec 0 0
lxc.mount.entry = devpts {{$ROOTFS}}/dev/pts devpts newinstance,ptmxmode=0666,nosuid,noexec 0 0 lxc.mount.entry = devpts {{$ROOTFS}}/dev/pts devpts newinstance,ptmxmode=0666,nosuid,noexec 0 0
#lxc.mount.entry = varrun {{$ROOTFS}}/var/run tmpfs mode=755,size=4096k,nosuid,nodev,noexec 0 0 #lxc.mount.entry = varrun {{$ROOTFS}}/var/run tmpfs mode=755,size=4096k,nosuid,nodev,noexec 0 0
@ -86,6 +90,9 @@ lxc.mount.entry = {{$realPath}} {{$ROOTFS}}/{{$virtualPath}} none bind,rw 0 0
{{end}} {{end}}
# drop linux capabilities (apply mainly to the user root in the container) # drop linux capabilities (apply mainly to the user root in the container)
# (Note: 'lxc.cap.keep' is coming soon and should replace this under the
# security principle 'deny all unless explicitly permitted', see
# http://sourceforge.net/mailarchive/message.php?msg_id=31054627 )
lxc.cap.drop = audit_control audit_write mac_admin mac_override mknod setfcap setpcap sys_admin sys_boot sys_module sys_nice sys_pacct sys_rawio sys_resource sys_time sys_tty_config lxc.cap.drop = audit_control audit_write mac_admin mac_override mknod setfcap setpcap sys_admin sys_boot sys_module sys_nice sys_pacct sys_rawio sys_resource sys_time sys_tty_config
# limits # limits

View File

@ -2,13 +2,18 @@ package docker
import ( import (
"fmt" "fmt"
"github.com/dotcloud/docker/utils"
"os" "os"
"os/exec"
"path/filepath" "path/filepath"
"syscall" "syscall"
"time" "time"
) )
func Unmount(target string) error { func Unmount(target string) error {
if err := exec.Command("auplink", target, "flush").Run(); err != nil {
utils.Debugf("[warning]: couldn't run auplink before unmount: %s", err)
}
if err := syscall.Unmount(target, 0); err != nil { if err := syscall.Unmount(target, 0); err != nil {
return err return err
} }

View File

@ -10,7 +10,7 @@ Homepage: http://github.com/dotcloud/docker
Package: lxc-docker Package: lxc-docker
Architecture: linux-any Architecture: linux-any
Depends: ${shlibs:Depends}, ${misc:Depends}, lxc, bsdtar Depends: ${shlibs:Depends}, ${misc:Depends}, lxc, bsdtar, aufs-tools
Description: Linux container runtime Description: Linux container runtime
Docker complements LXC with a high-level API which operates at the process Docker complements LXC with a high-level API which operates at the process
level. It runs unix processes with strong guarantees of isolation and level. It runs unix processes with strong guarantees of isolation and

View File

@ -1,20 +1,17 @@
# Ubuntu package Makefile # Ubuntu package Makefile
# #
# Dependencies: debhelper autotools-dev devscripts golang # Dependencies: debhelper autotools-dev devscripts golang-stable
# Notes: # Notes:
# Use 'make ubuntu' to create the ubuntu package # Use 'make ubuntu' to create the ubuntu package and push it to stating PPA by
# GPG_KEY environment variable needs to contain a GPG private key for package to be signed # default. To push to production, set PUBLISH_PPA=1 before doing 'make ubuntu'
# and uploaded to docker PPA. # GPG_KEY environment variable needs to contain a GPG private key for package
# If GPG_KEY is not defined, make ubuntu will create docker package and exit with # to be signed and uploaded to docker PPA. If GPG_KEY is not defined,
# status code 2 # make ubuntu will create docker package and exit with status code 2
PKG_NAME=lxc-docker PKG_NAME=lxc-docker
VERSION=$(shell head -1 changelog | sed 's/^.\+(\(.\+\)..).\+$$/\1/')
GITHUB_PATH=github.com/dotcloud/docker GITHUB_PATH=github.com/dotcloud/docker
DOCKER_VERSION=${PKG_NAME}_${VERSION}
DOCKER_FVERSION=${PKG_NAME}_$(shell head -1 changelog | sed 's/^.\+(\(.\+\)).\+$$/\1/')
BUILD_SRC=${CURDIR}/../../build_src BUILD_SRC=${CURDIR}/../../build_src
VERSION_TAG=v$(shell head -1 changelog | sed 's/^.\+(\(.\+\)-[0-9]\+).\+$$/\1/') VERSION=$(shell sed -En '0,/^\#\# /{s/^\#\# ([^ ]+).+/\1/p}' ../../CHANGELOG.md)
all: all:
# Compile docker. Used by dpkg-buildpackage. # Compile docker. Used by dpkg-buildpackage.
@ -35,18 +32,19 @@ ubuntu:
# Retrieve docker project and its go structure from internet # Retrieve docker project and its go structure from internet
rm -rf ${BUILD_SRC} rm -rf ${BUILD_SRC}
git clone $(shell git rev-parse --show-toplevel) ${BUILD_SRC}/${GITHUB_PATH} git clone $(shell git rev-parse --show-toplevel) ${BUILD_SRC}/${GITHUB_PATH}
cd ${BUILD_SRC}/${GITHUB_PATH}; git checkout ${VERSION_TAG} && GOPATH=${BUILD_SRC} go get -d cd ${BUILD_SRC}/${GITHUB_PATH}; git checkout v${VERSION} && GOPATH=${BUILD_SRC} go get -d
# Add debianization # Add debianization
mkdir ${BUILD_SRC}/debian mkdir ${BUILD_SRC}/debian
cp Makefile ${BUILD_SRC} cp Makefile ${BUILD_SRC}
cp -r * ${BUILD_SRC}/debian cp -r * ${BUILD_SRC}/debian
cp ../../README.md ${BUILD_SRC} cp ../../README.md ${BUILD_SRC}
./parse_changelog.py < ../../CHANGELOG.md > ${BUILD_SRC}/debian/changelog
# Cleanup # Cleanup
for d in `find ${BUILD_SRC} -name '.git*'`; do rm -rf $$d; done for d in `find ${BUILD_SRC} -name '.git*'`; do rm -rf $$d; done
rm -rf ${BUILD_SRC}/../${DOCKER_VERSION}.orig.tar.gz rm -rf ${BUILD_SRC}/../${PKG_NAME}_${VERSION}.orig.tar.gz
rm -rf ${BUILD_SRC}/pkg rm -rf ${BUILD_SRC}/pkg
# Create docker debian files # Create docker debian files
cd ${BUILD_SRC}; tar czf ../${DOCKER_VERSION}.orig.tar.gz . cd ${BUILD_SRC}; tar czf ../${PKG_NAME}_${VERSION}.orig.tar.gz .
cd ${BUILD_SRC}; dpkg-buildpackage -us -uc cd ${BUILD_SRC}; dpkg-buildpackage -us -uc
rm -rf ${BUILD_SRC} rm -rf ${BUILD_SRC}
# Sign package and upload it to PPA if GPG_KEY environment variable # Sign package and upload it to PPA if GPG_KEY environment variable
@ -54,9 +52,11 @@ ubuntu:
if /usr/bin/test "$${GPG_KEY}" == ""; then exit 2; fi if /usr/bin/test "$${GPG_KEY}" == ""; then exit 2; fi
mkdir ${BUILD_SRC} mkdir ${BUILD_SRC}
# Import gpg signing key # Import gpg signing key
echo "$${GPG_KEY}" | gpg --allow-secret-key-import --import echo "$${GPG_KEY}" | gpg --allow-secret-key-import --import || true
# Sign the package # Sign the package
cd ${BUILD_SRC}; dpkg-source -x ${BUILD_SRC}/../${DOCKER_FVERSION}.dsc cd ${BUILD_SRC}; dpkg-source -x ${BUILD_SRC}/../${PKG_NAME}_${VERSION}-1.dsc
cd ${BUILD_SRC}/${PKG_NAME}-${VERSION}; debuild -S -sa cd ${BUILD_SRC}/${PKG_NAME}-${VERSION}; debuild -S -sa
cd ${BUILD_SRC};dput ppa:dotcloud/lxc-docker ${DOCKER_FVERSION}_source.changes # Upload to PPA
if [ "${PUBLISH_PPA}" = "1" ]; then cd ${BUILD_SRC};dput ppa:dotcloud/lxc-docker ${PKG_NAME}_${VERSION}-1_source.changes; fi
if [ "${PUBLISH_PPA}" != "1" ]; then cd ${BUILD_SRC};dput ppa:dotcloud/docker-staging ${PKG_NAME}_${VERSION}-1_source.changes; fi
rm -rf ${BUILD_SRC} rm -rf ${BUILD_SRC}

View File

@ -1,222 +0,0 @@
lxc-docker (0.4.0-1) precise; urgency=low
- Introducing Builder: 'docker build' builds a container, layer by layer, from a source repository containing a Dockerfile
- Introducing Remote API: control Docker programmatically using a simple HTTP/json API
- Runtime: various reliability and usability improvements
-- dotCloud <ops@dotcloud.com> Mon, 03 Jun 2013 00:00:00 -0700
lxc-docker (0.3.4-1) precise; urgency=low
- Builder: 'docker build' builds a container, layer by layer, from a source repository containing a Dockerfile
- Builder: 'docker build -t FOO' applies the tag FOO to the newly built container.
- Runtime: interactive TTYs correctly handle window resize
- Runtime: fix how configuration is merged between layers
- Remote API: split stdout and stderr on 'docker run'
- Remote API: optionally listen on a different IP and port (use at your own risk)
- Documentation: improved install instructions.
-- dotCloud <ops@dotcloud.com> Thu, 30 May 2013 00:00:00 -0700
lxc-docker (0.3.3-1) precise; urgency=low
- Registry: Fix push regression
- Various bugfixes
-- dotCloud <ops@dotcloud.com> Thu, 23 May 2013 00:00:00 -0700
lxc-docker (0.3.2-1) precise; urgency=low
- Runtime: Store the actual archive on commit
- Registry: Improve the checksum process
- Registry: Use the size to have a good progress bar while pushing
- Registry: Use the actual archive if it exists in order to speed up the push
- Registry: Fix error 400 on push
-- dotCloud <ops@dotcloud.com> Fri, 9 May 2013 00:00:00 -0700
lxc-docker (0.3.1-1) precise; urgency=low
- Builder: Implement the autorun capability within docker builder
- Builder: Add caching to docker builder
- Builder: Add support for docker builder with native API as top level command
- Runtime: Add go version to debug infos
- Builder: Implement ENV within docker builder
- Registry: Add docker search top level command in order to search a repository
- Images: output graph of images to dot (graphviz)
- Documentation: new introduction and high-level overview
- Documentation: Add the documentation for docker builder
- Website: new high-level overview
- Makefile: Swap "go get" for "go get -d", especially to compile on go1.1rc
- Images: fix ByParent function
- Builder: Check the command existance prior create and add Unit tests for the case
- Registry: Fix pull for official images with specific tag
- Registry: Fix issue when login in with a different user and trying to push
- Documentation: CSS fix for docker documentation to make REST API docs look better.
- Documentation: Fixed CouchDB example page header mistake
- Documentation: fixed README formatting
- Registry: Improve checksum - async calculation
- Runtime: kernel version - don't show the dash if flavor is empty
- Documentation: updated www.docker.io website.
- Builder: use any whitespaces instead of tabs
- Packaging: packaging ubuntu; issue #510: Use goland-stable PPA package to build docker
-- dotCloud <ops@dotcloud.com> Fri, 8 May 2013 00:00:00 -0700
lxc-docker (0.3.0-1) precise; urgency=low
- Registry: Implement the new registry
- Documentation: new example: sharing data between 2 couchdb databases
- Runtime: Fix the command existance check
- Runtime: strings.Split may return an empty string on no match
- Runtime: Fix an index out of range crash if cgroup memory is not
- Documentation: Various improvments
- Vagrant: Use only one deb line in /etc/apt
-- dotCloud <ops@dotcloud.com> Fri, 5 May 2013 00:00:00 -0700
lxc-docker (0.2.2-1) precise; urgency=low
- Support for data volumes ('docker run -v=PATH')
- Share data volumes between containers ('docker run -volumes-from')
- Improved documentation
- Upgrade to Go 1.0.3
- Various upgrades to the dev environment for contributors
-- dotCloud <ops@dotcloud.com> Fri, 3 May 2013 00:00:00 -0700
lxc-docker (0.2.1-1) precise; urgency=low
- 'docker commit -run' bundles a layer with default runtime options: command, ports etc.
- Improve install process on Vagrant
- New Dockerfile operation: "maintainer"
- New Dockerfile operation: "expose"
- New Dockerfile operation: "cmd"
- Contrib script to build a Debian base layer
- 'docker -d -r': restart crashed containers at daemon startup
- Runtime: improve test coverage
-- dotCloud <ops@dotcloud.com> Wed, 1 May 2013 00:00:00 -0700
lxc-docker (0.2.0-1) precise; urgency=low
- Runtime: ghost containers can be killed and waited for
- Documentation: update install intructions
- Packaging: fix Vagrantfile
- Development: automate releasing binaries and ubuntu packages
- Add a changelog
- Various bugfixes
-- dotCloud <ops@dotcloud.com> Mon, 23 Apr 2013 00:00:00 -0700
lxc-docker (0.1.8-1) precise; urgency=low
- Dynamically detect cgroup capabilities
- Issue stability warning on kernels <3.8
- 'docker push' buffers on disk instead of memory
- Fix 'docker diff' for removed files
- Fix 'docker stop' for ghost containers
- Fix handling of pidfile
- Various bugfixes and stability improvements
-- dotCloud <ops@dotcloud.com> Mon, 22 Apr 2013 00:00:00 -0700
lxc-docker (0.1.7-1) precise; urgency=low
- Container ports are available on localhost
- 'docker ps' shows allocated TCP ports
- Contributors can run 'make hack' to start a continuous integration VM
- Streamline ubuntu packaging & uploading
- Various bugfixes and stability improvements
-- dotCloud <ops@dotcloud.com> Thu, 18 Apr 2013 00:00:00 -0700
lxc-docker (0.1.6-1) precise; urgency=low
- Record the author an image with 'docker commit -author'
-- dotCloud <ops@dotcloud.com> Wed, 17 Apr 2013 00:00:00 -0700
lxc-docker (0.1.5-1) precise; urgency=low
- Disable standalone mode
- Use a custom DNS resolver with 'docker -d -dns'
- Detect ghost containers
- Improve diagnosis of missing system capabilities
- Allow disabling memory limits at compile time
- Add debian packaging
- Documentation: installing on Arch Linux
- Documentation: running Redis on docker
- Fixed lxc 0.9 compatibility
- Automatically load aufs module
- Various bugfixes and stability improvements
-- dotCloud <ops@dotcloud.com> Wed, 17 Apr 2013 00:00:00 -0700
lxc-docker (0.1.4-1) precise; urgency=low
- Full support for TTY emulation
- Detach from a TTY session with the escape sequence `C-p C-q`
- Various bugfixes and stability improvements
- Minor UI improvements
- Automatically create our own bridge interface 'docker0'
-- dotCloud <ops@dotcloud.com> Tue, 9 Apr 2013 00:00:00 -0700
lxc-docker (0.1.3-1) precise; urgency=low
- Choose TCP frontend port with '-p :PORT'
- Layer format is versioned
- Major reliability improvements to the process manager
- Various bugfixes and stability improvements
-- dotCloud <ops@dotcloud.com> Thu, 4 Apr 2013 00:00:00 -0700
lxc-docker (0.1.2-1) precise; urgency=low
- Set container hostname with 'docker run -h'
- Selective attach at run with 'docker run -a [stdin[,stdout[,stderr]]]'
- Various bugfixes and stability improvements
- UI polish
- Progress bar on push/pull
- Use XZ compression by default
- Make IP allocator lazy
-- dotCloud <ops@dotcloud.com> Wed, 3 Apr 2013 00:00:00 -0700
lxc-docker (0.1.1-1) precise; urgency=low
- Display shorthand IDs for convenience
- Stabilize process management
- Layers can include a commit message
- Simplified 'docker attach'
- Fixed support for re-attaching
- Various bugfixes and stability improvements
- Auto-download at run
- Auto-login on push
- Beefed up documentation
-- dotCloud <ops@dotcloud.com> Sun, 31 Mar 2013 00:00:00 -0700
lxc-docker (0.1.0-1) precise; urgency=low
- First release
- Implement registry in order to push/pull images
- TCP port allocation
- Fix termcaps on Linux
- Add documentation
- Add Vagrant support with Vagrantfile
- Add unit tests
- Add repository/tags to ease image management
- Improve the layer implementation
-- dotCloud <ops@dotcloud.com> Sat, 23 Mar 2013 00:00:00 -0700

View File

@ -8,7 +8,7 @@ Homepage: http://github.com/dotcloud/docker
Package: lxc-docker Package: lxc-docker
Architecture: linux-any Architecture: linux-any
Depends: ${misc:Depends},${shlibs:Depends},lxc,bsdtar Depends: ${misc:Depends},${shlibs:Depends},lxc,bsdtar,aufs-tools
Conflicts: docker Conflicts: docker
Description: lxc-docker is a Linux container runtime Description: lxc-docker is a Linux container runtime
Docker complements LXC with a high-level API which operates at the process Docker complements LXC with a high-level API which operates at the process

View File

@ -0,0 +1,23 @@
#!/usr/bin/env python
'Parse main CHANGELOG.md from stdin outputing on stdout the ubuntu changelog'
import sys,re, datetime
on_block=False
for line in sys.stdin.readlines():
line = line.strip()
if line.startswith('# ') or len(line) == 0:
continue
if line.startswith('## '):
if on_block:
print '\n -- dotCloud <ops@dotcloud.com> {0}\n'.format(date)
version, date = line[3:].split()
date = datetime.datetime.strptime(date, '(%Y-%m-%d)').strftime(
'%a, %d %b %Y 00:00:00 -0700')
on_block = True
print 'lxc-docker ({0}-1) precise; urgency=low'.format(version)
continue
if on_block:
print ' ' + line
print '\n -- dotCloud <ops@dotcloud.com> {0}'.format(date)

View File

@ -7,11 +7,12 @@ import (
"fmt" "fmt"
"github.com/dotcloud/docker/auth" "github.com/dotcloud/docker/auth"
"github.com/dotcloud/docker/utils" "github.com/dotcloud/docker/utils"
"github.com/shin-/cookiejar"
"io" "io"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"net/http/cookiejar"
"net/url" "net/url"
"strconv"
"strings" "strings"
) )
@ -106,40 +107,45 @@ func (r *Registry) getImagesInRepository(repository string, authConfig *auth.Aut
} }
// Retrieve an image from the Registry. // Retrieve an image from the Registry.
// Returns the Image object as well as the layer as an Archive (io.Reader) func (r *Registry) GetRemoteImageJSON(imgId, registry string, token []string) ([]byte, int, error) {
func (r *Registry) GetRemoteImageJSON(imgId, registry string, token []string) ([]byte, error) {
// Get the JSON // Get the JSON
req, err := http.NewRequest("GET", registry+"/images/"+imgId+"/json", nil) req, err := http.NewRequest("GET", registry+"/images/"+imgId+"/json", nil)
if err != nil { if err != nil {
return nil, fmt.Errorf("Failed to download json: %s", err) return nil, -1, fmt.Errorf("Failed to download json: %s", err)
} }
req.Header.Set("Authorization", "Token "+strings.Join(token, ", ")) req.Header.Set("Authorization", "Token "+strings.Join(token, ", "))
res, err := r.client.Do(req) res, err := r.client.Do(req)
if err != nil { if err != nil {
return nil, fmt.Errorf("Failed to download json: %s", err) return nil, -1, fmt.Errorf("Failed to download json: %s", err)
} }
defer res.Body.Close() defer res.Body.Close()
if res.StatusCode != 200 { if res.StatusCode != 200 {
return nil, fmt.Errorf("HTTP code %d", res.StatusCode) return nil, -1, fmt.Errorf("HTTP code %d", res.StatusCode)
} }
imageSize, err := strconv.Atoi(res.Header.Get("X-Docker-Size"))
if err != nil {
return nil, -1, err
}
jsonString, err := ioutil.ReadAll(res.Body) jsonString, err := ioutil.ReadAll(res.Body)
if err != nil { if err != nil {
return nil, fmt.Errorf("Failed to parse downloaded json: %s (%s)", err, jsonString) return nil, -1, fmt.Errorf("Failed to parse downloaded json: %s (%s)", err, jsonString)
} }
return jsonString, nil return jsonString, imageSize, nil
} }
func (r *Registry) GetRemoteImageLayer(imgId, registry string, token []string) (io.ReadCloser, int, error) { func (r *Registry) GetRemoteImageLayer(imgId, registry string, token []string) (io.ReadCloser, error) {
req, err := http.NewRequest("GET", registry+"/images/"+imgId+"/layer", nil) req, err := http.NewRequest("GET", registry+"/images/"+imgId+"/layer", nil)
if err != nil { if err != nil {
return nil, -1, fmt.Errorf("Error while getting from the server: %s\n", err) return nil, fmt.Errorf("Error while getting from the server: %s\n", err)
} }
req.Header.Set("Authorization", "Token "+strings.Join(token, ", ")) req.Header.Set("Authorization", "Token "+strings.Join(token, ", "))
res, err := r.client.Do(req) res, err := r.client.Do(req)
if err != nil { if err != nil {
return nil, -1, err return nil, err
} }
return res.Body, int(res.ContentLength), nil return res.Body, nil
} }
func (r *Registry) GetRemoteTags(registries []string, repository string, token []string) (map[string]string, error) { func (r *Registry) GetRemoteTags(registries []string, repository string, token []string) (map[string]string, error) {
@ -150,16 +156,16 @@ func (r *Registry) GetRemoteTags(registries []string, repository string, token [
} }
for _, host := range registries { for _, host := range registries {
endpoint := fmt.Sprintf("https://%s/v1/repositories/%s/tags", host, repository) endpoint := fmt.Sprintf("https://%s/v1/repositories/%s/tags", host, repository)
req, err := http.NewRequest("GET", endpoint, nil) req, err := r.opaqueRequest("GET", endpoint, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
req.Header.Set("Authorization", "Token "+strings.Join(token, ", ")) req.Header.Set("Authorization", "Token "+strings.Join(token, ", "))
res, err := r.client.Do(req) res, err := r.client.Do(req)
utils.Debugf("Got status code %d from %s", res.StatusCode, endpoint)
if err != nil { if err != nil {
return nil, err return nil, err
} }
utils.Debugf("Got status code %d from %s", res.StatusCode, endpoint)
defer res.Body.Close() defer res.Body.Close()
if res.StatusCode != 200 && res.StatusCode != 404 { if res.StatusCode != 200 && res.StatusCode != 404 {
@ -184,7 +190,7 @@ func (r *Registry) GetRemoteTags(registries []string, repository string, token [
func (r *Registry) GetRepositoryData(remote string) (*RepositoryData, error) { func (r *Registry) GetRepositoryData(remote string) (*RepositoryData, error) {
repositoryTarget := auth.IndexServerAddress() + "/repositories/" + remote + "/images" repositoryTarget := auth.IndexServerAddress() + "/repositories/" + remote + "/images"
req, err := http.NewRequest("GET", repositoryTarget, nil) req, err := r.opaqueRequest("GET", repositoryTarget, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -303,6 +309,15 @@ func (r *Registry) PushImageLayerRegistry(imgId string, layer io.Reader, registr
return nil return nil
} }
func (r *Registry) opaqueRequest(method, urlStr string, body io.Reader) (*http.Request, error) {
req, err := http.NewRequest(method, urlStr, body)
if err != nil {
return nil, err
}
req.URL.Opaque = strings.Replace(urlStr, req.URL.Scheme + ":", "", 1)
return req, err
}
// push a tag on the registry. // push a tag on the registry.
// Remote has the format '<user>/<repo> // Remote has the format '<user>/<repo>
func (r *Registry) PushRegistryTag(remote, revision, tag, registry string, token []string) error { func (r *Registry) PushRegistryTag(remote, revision, tag, registry string, token []string) error {
@ -310,7 +325,7 @@ func (r *Registry) PushRegistryTag(remote, revision, tag, registry string, token
revision = "\"" + revision + "\"" revision = "\"" + revision + "\""
registry = "https://" + registry + "/v1" registry = "https://" + registry + "/v1"
req, err := http.NewRequest("PUT", registry+"/repositories/"+remote+"/tags/"+tag, strings.NewReader(revision)) req, err := r.opaqueRequest("PUT", registry+"/repositories/"+remote+"/tags/"+tag, strings.NewReader(revision))
if err != nil { if err != nil {
return err return err
} }
@ -328,7 +343,7 @@ func (r *Registry) PushRegistryTag(remote, revision, tag, registry string, token
return nil return nil
} }
func (r *Registry) PushImageJSONIndex(remote string, imgList []*ImgData, validate bool) (*RepositoryData, error) { func (r *Registry) PushImageJSONIndex(remote string, imgList []*ImgData, validate bool, regs []string) (*RepositoryData, error) {
imgListJSON, err := json.Marshal(imgList) imgListJSON, err := json.Marshal(imgList)
if err != nil { if err != nil {
return nil, err return nil, err
@ -340,13 +355,16 @@ func (r *Registry) PushImageJSONIndex(remote string, imgList []*ImgData, validat
utils.Debugf("Image list pushed to index:\n%s\n", imgListJSON) utils.Debugf("Image list pushed to index:\n%s\n", imgListJSON)
req, err := http.NewRequest("PUT", auth.IndexServerAddress()+"/repositories/"+remote+"/"+suffix, bytes.NewReader(imgListJSON)) req, err := r.opaqueRequest("PUT", auth.IndexServerAddress()+"/repositories/"+remote+"/"+suffix, bytes.NewReader(imgListJSON))
if err != nil { if err != nil {
return nil, err return nil, err
} }
req.SetBasicAuth(r.authConfig.Username, r.authConfig.Password) req.SetBasicAuth(r.authConfig.Username, r.authConfig.Password)
req.ContentLength = int64(len(imgListJSON)) req.ContentLength = int64(len(imgListJSON))
req.Header.Set("X-Docker-Token", "true") req.Header.Set("X-Docker-Token", "true")
if validate {
req.Header["X-Docker-Endpoints"] = regs
}
res, err := r.client.Do(req) res, err := r.client.Do(req)
if err != nil { if err != nil {
@ -357,14 +375,16 @@ func (r *Registry) PushImageJSONIndex(remote string, imgList []*ImgData, validat
// Redirect if necessary // Redirect if necessary
for res.StatusCode >= 300 && res.StatusCode < 400 { for res.StatusCode >= 300 && res.StatusCode < 400 {
utils.Debugf("Redirected to %s\n", res.Header.Get("Location")) utils.Debugf("Redirected to %s\n", res.Header.Get("Location"))
req, err = http.NewRequest("PUT", res.Header.Get("Location"), bytes.NewReader(imgListJSON)) req, err = r.opaqueRequest("PUT", res.Header.Get("Location"), bytes.NewReader(imgListJSON))
if err != nil { if err != nil {
return nil, err return nil, err
} }
req.SetBasicAuth(r.authConfig.Username, r.authConfig.Password) req.SetBasicAuth(r.authConfig.Username, r.authConfig.Password)
req.ContentLength = int64(len(imgListJSON)) req.ContentLength = int64(len(imgListJSON))
req.Header.Set("X-Docker-Token", "true") req.Header.Set("X-Docker-Token", "true")
if validate {
req.Header["X-Docker-Endpoints"] = regs
}
res, err = r.client.Do(req) res, err = r.client.Do(req)
if err != nil { if err != nil {
return nil, err return nil, err
@ -433,11 +453,6 @@ func (r *Registry) SearchRepositories(term string) (*SearchResults, error) {
return result, err return result, err
} }
func (r *Registry) ResetClient(authConfig *auth.AuthConfig) {
r.authConfig = authConfig
r.client.Jar = cookiejar.NewCookieJar()
}
func (r *Registry) GetAuthConfig(withPasswd bool) *auth.AuthConfig { func (r *Registry) GetAuthConfig(withPasswd bool) *auth.AuthConfig {
password := "" password := ""
if withPasswd { if withPasswd {
@ -473,18 +488,18 @@ type Registry struct {
authConfig *auth.AuthConfig authConfig *auth.AuthConfig
} }
func NewRegistry(root string, authConfig *auth.AuthConfig) *Registry { func NewRegistry(root string, authConfig *auth.AuthConfig) (r *Registry, err error) {
httpTransport := &http.Transport{ httpTransport := &http.Transport{
DisableKeepAlives: true, DisableKeepAlives: true,
Proxy: http.ProxyFromEnvironment, Proxy: http.ProxyFromEnvironment,
} }
r := &Registry{ r = &Registry{
authConfig: authConfig, authConfig: authConfig,
client: &http.Client{ client: &http.Client{
Transport: httpTransport, Transport: httpTransport,
}, },
} }
r.client.Jar = cookiejar.NewCookieJar() r.client.Jar, err = cookiejar.New(nil)
return r return r, err
} }

View File

@ -32,6 +32,7 @@ type Runtime struct {
autoRestart bool autoRestart bool
volumes *Graph volumes *Graph
srv *Server srv *Server
Dns []string
} }
var sysInitPath string var sysInitPath string
@ -245,11 +246,12 @@ func (runtime *Runtime) UpdateCapabilities(quiet bool) {
} }
// FIXME: harmonize with NewGraph() // FIXME: harmonize with NewGraph()
func NewRuntime(autoRestart bool) (*Runtime, error) { func NewRuntime(autoRestart bool, dns []string) (*Runtime, error) {
runtime, err := NewRuntimeFromDirectory("/var/lib/docker", autoRestart) runtime, err := NewRuntimeFromDirectory("/var/lib/docker", autoRestart)
if err != nil { if err != nil {
return nil, err return nil, err
} }
runtime.Dns = dns
if k, err := utils.GetKernelVersion(); err != nil { if k, err := utils.GetKernelVersion(); err != nil {
log.Printf("WARNING: %s\n", err) log.Printf("WARNING: %s\n", err)

View File

@ -65,7 +65,11 @@ func init() {
// Create the "Server" // Create the "Server"
srv := &Server{ srv := &Server{
runtime: runtime, runtime: runtime,
enableCors: false,
lock: &sync.Mutex{},
pullingPool: make(map[string]struct{}),
pushingPool: make(map[string]struct{}),
} }
// Retrieve the Image // Retrieve the Image
if err := srv.ImagePull(unitTestImageName, "", "", os.Stdout, utils.NewStreamFormatter(false), nil); err != nil { if err := srv.ImagePull(unitTestImageName, "", "", os.Stdout, utils.NewStreamFormatter(false), nil); err != nil {

132
server.go
View File

@ -15,6 +15,7 @@ import (
"path" "path"
"runtime" "runtime"
"strings" "strings"
"sync"
) )
func (srv *Server) DockerVersion() APIVersion { func (srv *Server) DockerVersion() APIVersion {
@ -54,8 +55,11 @@ func (srv *Server) ContainerExport(name string, out io.Writer) error {
} }
func (srv *Server) ImagesSearch(term string) ([]APISearch, error) { func (srv *Server) ImagesSearch(term string) ([]APISearch, error) {
r, err := registry.NewRegistry(srv.runtime.root, nil)
results, err := registry.NewRegistry(srv.runtime.root, nil).SearchRepositories(term) if err != nil {
return nil, err
}
results, err := r.SearchRepositories(term)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -174,6 +178,8 @@ func (srv *Server) Images(all bool, filter string) ([]APIImages, error) {
out.Tag = tag out.Tag = tag
out.ID = image.ID out.ID = image.ID
out.Created = image.Created.Unix() out.Created = image.Created.Unix()
out.Size = image.Size
out.VirtualSize = image.getParentsSize(0) + image.Size
outs = append(outs, out) outs = append(outs, out)
} }
} }
@ -183,6 +189,8 @@ func (srv *Server) Images(all bool, filter string) ([]APIImages, error) {
var out APIImages var out APIImages
out.ID = image.ID out.ID = image.ID
out.Created = image.Created.Unix() out.Created = image.Created.Unix()
out.Size = image.Size
out.VirtualSize = image.getParentsSize(0) + image.Size
outs = append(outs, out) outs = append(outs, out)
} }
} }
@ -214,12 +222,24 @@ func (srv *Server) ImageHistory(name string) ([]APIHistory, error) {
return nil, err return nil, err
} }
lookupMap := make(map[string][]string)
for name, repository := range srv.runtime.repositories.Repositories {
for tag, id := range repository {
// If the ID already has a reverse lookup, do not update it unless for "latest"
if _, exists := lookupMap[id]; !exists {
lookupMap[id] = []string{}
}
lookupMap[id] = append(lookupMap[id], name+":"+tag)
}
}
outs := []APIHistory{} //produce [] when empty instead of 'null' outs := []APIHistory{} //produce [] when empty instead of 'null'
err = image.WalkHistory(func(img *Image) error { err = image.WalkHistory(func(img *Image) error {
var out APIHistory var out APIHistory
out.ID = srv.runtime.repositories.ImageName(img.ShortID()) out.ID = srv.runtime.repositories.ImageName(img.ShortID())
out.Created = img.Created.Unix() out.Created = img.Created.Unix()
out.CreatedBy = strings.Join(img.ContainerConfig.Cmd, " ") out.CreatedBy = strings.Join(img.ContainerConfig.Cmd, " ")
out.Tags = lookupMap[img.ID]
outs = append(outs, out) outs = append(outs, out)
return nil return nil
}) })
@ -268,6 +288,8 @@ func (srv *Server) Containers(all bool, n int, since, before string) []APIContai
c.Created = container.Created.Unix() c.Created = container.Created.Unix()
c.Status = container.State.String() c.Status = container.State.String()
c.Ports = container.NetworkSettings.PortMappingHuman() c.Ports = container.NetworkSettings.PortMappingHuman()
c.SizeRw, c.SizeRootFs = container.GetSize()
retContainers = append(retContainers, c) retContainers = append(retContainers, c)
} }
return retContainers return retContainers
@ -303,7 +325,7 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgId, endpoin
for _, id := range history { for _, id := range history {
if !srv.runtime.graph.Exists(id) { if !srv.runtime.graph.Exists(id) {
out.Write(sf.FormatStatus("Pulling %s metadata", id)) out.Write(sf.FormatStatus("Pulling %s metadata", id))
imgJSON, err := r.GetRemoteImageJSON(id, endpoint, token) imgJSON, imgSize, err := r.GetRemoteImageJSON(id, endpoint, token)
if err != nil { if err != nil {
// FIXME: Keep goging in case of error? // FIXME: Keep goging in case of error?
return err return err
@ -315,12 +337,12 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgId, endpoin
// Get the layer // Get the layer
out.Write(sf.FormatStatus("Pulling %s fs layer", id)) out.Write(sf.FormatStatus("Pulling %s fs layer", id))
layer, contentLength, err := r.GetRemoteImageLayer(img.ID, endpoint, token) layer, err := r.GetRemoteImageLayer(img.ID, endpoint, token)
if err != nil { if err != nil {
return err return err
} }
defer layer.Close() defer layer.Close()
if err := srv.runtime.graph.Register(utils.ProgressReader(layer, contentLength, out, sf.FormatProgress("Downloading", "%v/%v (%v)"), sf), false, img); err != nil { if err := srv.runtime.graph.Register(utils.ProgressReader(layer, imgSize, out, sf.FormatProgress("Downloading", "%v/%v (%v)"), sf), false, img); err != nil {
return err return err
} }
} }
@ -395,8 +417,51 @@ func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, local, re
return nil return nil
} }
func (srv *Server) poolAdd(kind, key string) error {
srv.lock.Lock()
defer srv.lock.Unlock()
if _, exists := srv.pullingPool[key]; exists {
return fmt.Errorf("%s %s is already in progress", key, kind)
}
switch kind {
case "pull":
srv.pullingPool[key] = struct{}{}
break
case "push":
srv.pushingPool[key] = struct{}{}
break
default:
return fmt.Errorf("Unkown pool type")
}
return nil
}
func (srv *Server) poolRemove(kind, key string) error {
switch kind {
case "pull":
delete(srv.pullingPool, key)
break
case "push":
delete(srv.pushingPool, key)
break
default:
return fmt.Errorf("Unkown pool type")
}
return nil
}
func (srv *Server) ImagePull(name, tag, endpoint string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig) error { func (srv *Server) ImagePull(name, tag, endpoint string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig) error {
r := registry.NewRegistry(srv.runtime.root, authConfig) r, err := registry.NewRegistry(srv.runtime.root, authConfig)
if err != nil {
return err
}
if err := srv.poolAdd("pull", name+":"+tag); err != nil {
return err
}
defer srv.poolRemove("pull", name+":"+tag)
out = utils.NewWriteFlusher(out) out = utils.NewWriteFlusher(out)
if endpoint != "" { if endpoint != "" {
if err := srv.pullImage(r, out, name, endpoint, nil, sf); err != nil { if err := srv.pullImage(r, out, name, endpoint, nil, sf); err != nil {
@ -412,7 +477,6 @@ func (srv *Server) ImagePull(name, tag, endpoint string, out io.Writer, sf *util
if err := srv.pullRepository(r, out, name, remote, tag, sf); err != nil { if err := srv.pullRepository(r, out, name, remote, tag, sf); err != nil {
return err return err
} }
return nil return nil
} }
@ -497,7 +561,7 @@ func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, name stri
srvName = fmt.Sprintf("src/%s", url.QueryEscape(strings.Join(parts, "/"))) srvName = fmt.Sprintf("src/%s", url.QueryEscape(strings.Join(parts, "/")))
} }
repoData, err := r.PushImageJSONIndex(srvName, imgList, false) repoData, err := r.PushImageJSONIndex(srvName, imgList, false, nil)
if err != nil { if err != nil {
return err return err
} }
@ -514,14 +578,14 @@ func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, name stri
// FIXME: Continue on error? // FIXME: Continue on error?
return err return err
} }
out.Write(sf.FormatStatus("Pushing tags for rev [%s] on {%s}", elem.ID, ep+"/users/"+srvName+"/"+elem.Tag)) out.Write(sf.FormatStatus("Pushing tags for rev [%s] on {%s}", elem.ID, ep+"/repositories/"+srvName+"/tags/"+elem.Tag))
if err := r.PushRegistryTag(srvName, elem.ID, elem.Tag, ep, repoData.Tokens); err != nil { if err := r.PushRegistryTag(srvName, elem.ID, elem.Tag, ep, repoData.Tokens); err != nil {
return err return err
} }
} }
} }
if _, err := r.PushImageJSONIndex(srvName, imgList, true); err != nil { if _, err := r.PushImageJSONIndex(srvName, imgList, true, repoData.Endpoints); err != nil {
return err return err
} }
return nil return nil
@ -587,11 +651,19 @@ func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgId,
return nil return nil
} }
// FIXME: Allow to interupt current push when new push of same image is done.
func (srv *Server) ImagePush(name, endpoint string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig) error { func (srv *Server) ImagePush(name, endpoint string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig) error {
if err := srv.poolAdd("push", name); err != nil {
return err
}
defer srv.poolRemove("push", name)
out = utils.NewWriteFlusher(out) out = utils.NewWriteFlusher(out)
img, err := srv.runtime.graph.Get(name) img, err := srv.runtime.graph.Get(name)
r := registry.NewRegistry(srv.runtime.root, authConfig) r, err2 := registry.NewRegistry(srv.runtime.root, authConfig)
if err2 != nil {
return err2
}
if err != nil { if err != nil {
out.Write(sf.FormatStatus("The push refers to a repository [%s] (len: %d)", name, len(srv.runtime.repositories.Repositories[name]))) out.Write(sf.FormatStatus("The push refers to a repository [%s] (len: %d)", name, len(srv.runtime.repositories.Repositories[name])))
// If it fails, try to get the repository // If it fails, try to get the repository
@ -652,6 +724,10 @@ func (srv *Server) ImageImport(src, repo, tag string, in io.Reader, out io.Write
func (srv *Server) ContainerCreate(config *Config) (string, error) { func (srv *Server) ContainerCreate(config *Config) (string, error) {
if config.Memory != 0 && config.Memory < 524288 {
return "", fmt.Errorf("Memory limit must be given in bytes (minimum 524288 bytes)")
}
if config.Memory > 0 && !srv.runtime.capabilities.MemoryLimit { if config.Memory > 0 && !srv.runtime.capabilities.MemoryLimit {
config.Memory = 0 config.Memory = 0
} }
@ -683,6 +759,9 @@ func (srv *Server) ContainerRestart(name string, t int) error {
func (srv *Server) ContainerDestroy(name string, removeVolume bool) error { func (srv *Server) ContainerDestroy(name string, removeVolume bool) error {
if container := srv.runtime.Get(name); container != nil { if container := srv.runtime.Get(name); container != nil {
if container.State.Running {
return fmt.Errorf("Impossible to remove a running container, please stop it first")
}
volumes := make(map[string]struct{}) volumes := make(map[string]struct{})
// Store all the deleted containers volumes // Store all the deleted containers volumes
for _, volumeId := range container.Volumes { for _, volumeId := range container.Volumes {
@ -900,17 +979,17 @@ func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, std
if stdout { if stdout {
cLog, err := container.ReadLog("stdout") cLog, err := container.ReadLog("stdout")
if err != nil { if err != nil {
utils.Debugf(err.Error()) utils.Debugf("Error reading logs (stdout): %s", err)
} else if _, err := io.Copy(out, cLog); err != nil { } else if _, err := io.Copy(out, cLog); err != nil {
utils.Debugf(err.Error()) utils.Debugf("Error streaming logs (stdout): %s", err)
} }
} }
if stderr { if stderr {
cLog, err := container.ReadLog("stderr") cLog, err := container.ReadLog("stderr")
if err != nil { if err != nil {
utils.Debugf(err.Error()) utils.Debugf("Error reading logs (stderr): %s", err)
} else if _, err := io.Copy(out, cLog); err != nil { } else if _, err := io.Copy(out, cLog); err != nil {
utils.Debugf(err.Error()) utils.Debugf("Error streaming logs (stderr): %s", err)
} }
} }
} }
@ -920,9 +999,6 @@ func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, std
if container.State.Ghost { if container.State.Ghost {
return fmt.Errorf("Impossible to attach to a ghost container") return fmt.Errorf("Impossible to attach to a ghost container")
} }
if !container.State.Running {
return fmt.Errorf("Impossible to attach to a stopped container, start it first")
}
var ( var (
cStdin io.ReadCloser cStdin io.ReadCloser
@ -972,23 +1048,29 @@ func (srv *Server) ImageInspect(name string) (*Image, error) {
return nil, fmt.Errorf("No such image: %s", name) return nil, fmt.Errorf("No such image: %s", name)
} }
func NewServer(autoRestart, enableCors bool) (*Server, error) { func NewServer(autoRestart, enableCors bool, dns ListOpts) (*Server, error) {
if runtime.GOARCH != "amd64" { if runtime.GOARCH != "amd64" {
log.Fatalf("The docker runtime currently only supports amd64 (not %s). This will change in the future. Aborting.", runtime.GOARCH) log.Fatalf("The docker runtime currently only supports amd64 (not %s). This will change in the future. Aborting.", runtime.GOARCH)
} }
runtime, err := NewRuntime(autoRestart) runtime, err := NewRuntime(autoRestart, dns)
if err != nil { if err != nil {
return nil, err return nil, err
} }
srv := &Server{ srv := &Server{
runtime: runtime, runtime: runtime,
enableCors: enableCors, enableCors: enableCors,
lock: &sync.Mutex{},
pullingPool: make(map[string]struct{}),
pushingPool: make(map[string]struct{}),
} }
runtime.srv = srv runtime.srv = srv
return srv, nil return srv, nil
} }
type Server struct { type Server struct {
runtime *Runtime runtime *Runtime
enableCors bool enableCors bool
lock *sync.Mutex
pullingPool map[string]struct{}
pushingPool map[string]struct{}
} }

View File

@ -147,3 +147,25 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) {
} }
} }
func TestRunWithTooLowMemoryLimit(t *testing.T) {
runtime, err := newTestRuntime()
srv := &Server{runtime: runtime}
if err != nil {
t.Fatal(err)
}
defer nuke(runtime)
// Try to create a container with a memory limit of 1 byte less than the minimum allowed limit.
_, err = srv.ContainerCreate(
&Config{
Image: GetTestImage(runtime).ID,
Memory: 524287,
CpuShares: 1000,
Cmd: []string{"/bin/cat"},
},
)
if err == nil {
t.Errorf("Memory limit is smaller than the allowed limit. Container creation should've failed!")
}
}

View File

@ -9,16 +9,16 @@ const (
getTermios = syscall.TIOCGETA getTermios = syscall.TIOCGETA
setTermios = syscall.TIOCSETA setTermios = syscall.TIOCSETA
ECHO = 0x00000008 ECHO = 0x00000008
ONLCR = 0x2 ONLCR = 0x2
ISTRIP = 0x20 ISTRIP = 0x20
INLCR = 0x40 INLCR = 0x40
ISIG = 0x80 ISIG = 0x80
IGNCR = 0x80 IGNCR = 0x80
ICANON = 0x100 ICANON = 0x100
ICRNL = 0x100 ICRNL = 0x100
IXOFF = 0x400 IXOFF = 0x400
IXON = 0x200 IXON = 0x200
) )
type Termios struct { type Termios struct {

2
testing/Vagrantfile vendored
View File

@ -30,7 +30,7 @@ Vagrant::Config.run do |config|
# Install docker dependencies # Install docker dependencies
pkg_cmd << "apt-get install -q -y python-software-properties; " \ pkg_cmd << "apt-get install -q -y python-software-properties; " \
"add-apt-repository -y ppa:gophers/go/ubuntu; apt-get update -qq; " \ "add-apt-repository -y ppa:gophers/go/ubuntu; apt-get update -qq; " \
"DEBIAN_FRONTEND=noninteractive apt-get install -q -y lxc bsdtar git golang-stable make; " "DEBIAN_FRONTEND=noninteractive apt-get install -q -y lxc git golang-stable aufs-tools make; "
# Activate new kernel # Activate new kernel
pkg_cmd << "shutdown -r +1; " pkg_cmd << "shutdown -r +1; "
config.vm.provision :shell, :inline => pkg_cmd config.vm.provision :shell, :inline => pkg_cmd

View File

@ -10,6 +10,7 @@ import (
"index/suffixarray" "index/suffixarray"
"io" "io"
"io/ioutil" "io/ioutil"
"log"
"net/http" "net/http"
"os" "os"
"os/exec" "os/exec"
@ -70,7 +71,7 @@ type progressReader struct {
readProgress int // How much has been read so far (bytes) readProgress int // How much has been read so far (bytes)
lastUpdate int // How many bytes read at least update lastUpdate int // How many bytes read at least update
template string // Template to print. Default "%v/%v (%v)" template string // Template to print. Default "%v/%v (%v)"
sf *StreamFormatter sf *StreamFormatter
} }
func (r *progressReader) Read(p []byte) (n int, err error) { func (r *progressReader) Read(p []byte) (n int, err error) {
@ -86,7 +87,7 @@ func (r *progressReader) Read(p []byte) (n int, err error) {
} }
if r.readProgress-r.lastUpdate > updateEvery || err != nil { if r.readProgress-r.lastUpdate > updateEvery || err != nil {
if r.readTotal > 0 { if r.readTotal > 0 {
fmt.Fprintf(r.output, r.template, r.readProgress, r.readTotal, fmt.Sprintf("%.0f%%", float64(r.readProgress)/float64(r.readTotal)*100)) fmt.Fprintf(r.output, r.template, HumanSize(int64(r.readProgress)), HumanSize(int64(r.readTotal)), fmt.Sprintf("%.0f%%", float64(r.readProgress)/float64(r.readTotal)*100))
} else { } else {
fmt.Fprintf(r.output, r.template, r.readProgress, "?", "n/a") fmt.Fprintf(r.output, r.template, r.readProgress, "?", "n/a")
} }
@ -103,7 +104,7 @@ func (r *progressReader) Close() error {
return io.ReadCloser(r.reader).Close() return io.ReadCloser(r.reader).Close()
} }
func ProgressReader(r io.ReadCloser, size int, output io.Writer, template []byte, sf *StreamFormatter) *progressReader { func ProgressReader(r io.ReadCloser, size int, output io.Writer, template []byte, sf *StreamFormatter) *progressReader {
tpl := string(template) tpl := string(template)
if tpl == "" { if tpl == "" {
tpl = string(sf.FormatProgress("", "%v/%v (%v)")) tpl = string(sf.FormatProgress("", "%v/%v (%v)"))
} }
@ -135,6 +136,20 @@ func HumanDuration(d time.Duration) string {
return fmt.Sprintf("%d years", d.Hours()/24/365) return fmt.Sprintf("%d years", d.Hours()/24/365)
} }
// HumanSize returns a human-readable approximation of a size
// using SI standard (eg. "44kB", "17MB")
func HumanSize(size int64) string {
i := 0
var sizef float64
sizef = float64(size)
units := []string{"B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"}
for sizef >= 1000.0 {
sizef = sizef / 1000.0
i++
}
return fmt.Sprintf("%.4g %s", sizef, units[i])
}
func Trunc(s string, maxlen int) string { func Trunc(s string, maxlen int) string {
if len(s) <= maxlen { if len(s) <= maxlen {
return s return s
@ -534,6 +549,7 @@ func GetKernelVersion() (*KernelVersionInfo, error) {
}, nil }, nil
} }
// FIXME: this is deprecated by CopyWithTar in archive.go
func CopyDirectory(source, dest string) error { func CopyDirectory(source, dest string) error {
if output, err := exec.Command("cp", "-ra", source, dest).CombinedOutput(); err != nil { if output, err := exec.Command("cp", "-ra", source, dest).CombinedOutput(); err != nil {
return fmt.Errorf("Error copy: %s (%s)", err, output) return fmt.Errorf("Error copy: %s (%s)", err, output)
@ -585,7 +601,7 @@ func (sf *StreamFormatter) FormatStatus(format string, a ...interface{}) []byte
sf.used = true sf.used = true
str := fmt.Sprintf(format, a...) str := fmt.Sprintf(format, a...)
if sf.json { if sf.json {
b, err := json.Marshal(&JSONMessage{Status:str}); b, err := json.Marshal(&JSONMessage{Status: str})
if err != nil { if err != nil {
return sf.FormatError(err) return sf.FormatError(err)
} }
@ -597,7 +613,7 @@ func (sf *StreamFormatter) FormatStatus(format string, a ...interface{}) []byte
func (sf *StreamFormatter) FormatError(err error) []byte { func (sf *StreamFormatter) FormatError(err error) []byte {
sf.used = true sf.used = true
if sf.json { if sf.json {
if b, err := json.Marshal(&JSONMessage{Error:err.Error()}); err == nil { if b, err := json.Marshal(&JSONMessage{Error: err.Error()}); err == nil {
return b return b
} }
return []byte("{\"error\":\"format error\"}") return []byte("{\"error\":\"format error\"}")
@ -608,10 +624,10 @@ func (sf *StreamFormatter) FormatError(err error) []byte {
func (sf *StreamFormatter) FormatProgress(action, str string) []byte { func (sf *StreamFormatter) FormatProgress(action, str string) []byte {
sf.used = true sf.used = true
if sf.json { if sf.json {
b, err := json.Marshal(&JSONMessage{Status: action, Progress:str}) b, err := json.Marshal(&JSONMessage{Status: action, Progress: str})
if err != nil { if err != nil {
return nil return nil
} }
return b return b
} }
return []byte(action + " " + str + "\r") return []byte(action + " " + str + "\r")
@ -620,3 +636,47 @@ func (sf *StreamFormatter) FormatProgress(action, str string) []byte {
func (sf *StreamFormatter) Used() bool { func (sf *StreamFormatter) Used() bool {
return sf.used return sf.used
} }
func CheckLocalDns() bool {
resolv, err := ioutil.ReadFile("/etc/resolv.conf")
if err != nil {
Debugf("Error openning resolv.conf: %s", err)
return false
}
for _, ip := range []string{
"127.0.0.1",
"127.0.1.1",
} {
if strings.Contains(string(resolv), ip) {
return true
}
}
return false
}
func ParseHost(host string, port int, addr string) string {
if strings.HasPrefix(addr, "unix://") {
return addr
}
if strings.HasPrefix(addr, "tcp://") {
addr = strings.TrimPrefix(addr, "tcp://")
}
if strings.Contains(addr, ":") {
hostParts := strings.Split(addr, ":")
if len(hostParts) != 2 {
log.Fatal("Invalid bind address format.")
os.Exit(-1)
}
if hostParts[0] != "" {
host = hostParts[0]
}
if p, err := strconv.Atoi(hostParts[1]); err == nil {
port = p
}
} else {
host = addr
}
return fmt.Sprintf("tcp://%s:%d", host, port)
}

View File

@ -261,3 +261,34 @@ func TestCompareKernelVersion(t *testing.T) {
&KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0, Flavor: "0"}, &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0, Flavor: "0"},
-1) -1)
} }
func TestHumanSize(t *testing.T) {
size1000 := HumanSize(1000)
if size1000 != "1 kB" {
t.Errorf("1000 -> expected 1 kB, got %s", size1000)
}
size1024 := HumanSize(1024)
if size1024 != "1.024 kB" {
t.Errorf("1024 -> expected 1.024 kB, got %s", size1024)
}
}
func TestParseHost(t *testing.T) {
if addr := ParseHost("127.0.0.1", 4243, "0.0.0.0"); addr != "tcp://0.0.0.0:4243" {
t.Errorf("0.0.0.0 -> expected tcp://0.0.0.0:4243, got %s", addr)
}
if addr := ParseHost("127.0.0.1", 4243, "0.0.0.1:5555"); addr != "tcp://0.0.0.1:5555" {
t.Errorf("0.0.0.1:5555 -> expected tcp://0.0.0.1:5555, got %s", addr)
}
if addr := ParseHost("127.0.0.1", 4243, ":6666"); addr != "tcp://127.0.0.1:6666" {
t.Errorf(":6666 -> expected tcp://127.0.0.1:6666, got %s", addr)
}
if addr := ParseHost("127.0.0.1", 4243, "tcp://:7777"); addr != "tcp://127.0.0.1:7777" {
t.Errorf("tcp://:7777 -> expected tcp://127.0.0.1:7777, got %s", addr)
}
if addr := ParseHost("127.0.0.1", 4243, "unix:///var/run/docker.sock"); addr != "unix:///var/run/docker.sock" {
t.Errorf("unix:///var/run/docker.sock -> expected unix:///var/run/docker.sock, got %s", addr)
}
}