mirror of https://github.com/docker/docs.git
Merge branch 'master' into login_signal
* master: (54 commits) Return JSONError for HTTPResponse error Fix TestEnv Revert "Bind daemon to 0.0.0.0 in Vagrant. Fixes #1304" Add unit tests for build no cache Add no cache for docker build Move note about officially supported kernel Solved the logo being squished in Safari Add hostname to the container environment. fix same issue in api.go improve tests Return registy status code in error update http://get.docker.io/latest Add check that the request is good fix tests about refactor checksums add ufw doc Fixed a couple of minor syntax errors. Updated the description of run -d Make sure the index also receives the checksums Switch json/payload order Remove unused parameter ...
This commit is contained in:
commit
58e0c68132
2
.mailmap
2
.mailmap
|
@ -23,3 +23,5 @@ Thatcher Peskens <thatcher@dotcloud.com>
|
||||||
Walter Stanish <walter@pratyeka.org>
|
Walter Stanish <walter@pratyeka.org>
|
||||||
<daniel@gasienica.ch> <dgasienica@zynga.com>
|
<daniel@gasienica.ch> <dgasienica@zynga.com>
|
||||||
Roberto Hashioka <roberto_hashioka@hotmail.com>
|
Roberto Hashioka <roberto_hashioka@hotmail.com>
|
||||||
|
Konstantin Pelykh <kpelykh@zettaset.com>
|
||||||
|
David Sissitka <me@dsissitka.com>
|
||||||
|
|
25
AUTHORS
25
AUTHORS
|
@ -4,12 +4,15 @@
|
||||||
# For a list of active project maintainers, see the MAINTAINERS file.
|
# For a list of active project maintainers, see the MAINTAINERS file.
|
||||||
#
|
#
|
||||||
Al Tobey <al@ooyala.com>
|
Al Tobey <al@ooyala.com>
|
||||||
|
Alex Gaynor <alex.gaynor@gmail.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>
|
Andreas Tiefenthaler <at@an-ti.eu>
|
||||||
Andrew Munsell <andrew@wizardapps.net>
|
Andrew Munsell <andrew@wizardapps.net>
|
||||||
|
Andrews Medina <andrewsmedina@gmail.com>
|
||||||
Andy Rothfusz <github@metaliveblog.com>
|
Andy Rothfusz <github@metaliveblog.com>
|
||||||
Andy Smith <github@anarkystic.com>
|
Andy Smith <github@anarkystic.com>
|
||||||
|
Anthony Bishopric <git@anthonybishopric.com>
|
||||||
Antony Messerli <amesserl@rackspace.com>
|
Antony Messerli <amesserl@rackspace.com>
|
||||||
Barry Allard <barry.allard@gmail.com>
|
Barry Allard <barry.allard@gmail.com>
|
||||||
Brandon Liu <bdon@bdon.org>
|
Brandon Liu <bdon@bdon.org>
|
||||||
|
@ -23,17 +26,23 @@ 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>
|
||||||
Daniel Von Fange <daniel@leancoder.com>
|
Daniel Von Fange <daniel@leancoder.com>
|
||||||
|
Daniel YC Lin <dlin.tw@gmail.com>
|
||||||
|
David Calavera <david.calavera@gmail.com>
|
||||||
|
David Sissitka <me@dsissitka.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>
|
Elias Probst <mail@eliasprobst.eu>
|
||||||
Eric Hanchrow <ehanchrow@ine.com>
|
Eric Hanchrow <ehanchrow@ine.com>
|
||||||
Evan Wies <evan@neomantra.net>
|
|
||||||
Eric Myhre <hash@exultant.us>
|
Eric Myhre <hash@exultant.us>
|
||||||
|
Erno Hopearuoho <erno.hopearuoho@gmail.com>
|
||||||
|
Evan Wies <evan@neomantra.net>
|
||||||
ezbercih <cem.ezberci@gmail.com>
|
ezbercih <cem.ezberci@gmail.com>
|
||||||
|
Fabrizio Regini <freegenie@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>
|
||||||
|
Gabriel Monroy <gabriel@opdemand.com>
|
||||||
Gareth Rushgrove <gareth@morethanseven.net>
|
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>
|
||||||
|
@ -41,6 +50,7 @@ Hunter Blanks <hunter@twilio.com>
|
||||||
Jeff Lindsay <progrium@gmail.com>
|
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>
|
||||||
|
Johan Euphrosine <proppy@google.com>
|
||||||
John Costa <john.costa@gmail.com>
|
John Costa <john.costa@gmail.com>
|
||||||
Jon Wedaman <jweede@gmail.com>
|
Jon Wedaman <jweede@gmail.com>
|
||||||
Jonas Pfenniger <jonas@pfenniger.name>
|
Jonas Pfenniger <jonas@pfenniger.name>
|
||||||
|
@ -48,28 +58,39 @@ Jonathan Rudenberg <jonathan@titanous.com>
|
||||||
Joseph Anthony Pasquale Holsten <joseph@josephholsten.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>
|
||||||
|
Karan Lyons <karan@karanlyons.com>
|
||||||
|
Keli Hu <dev@keli.hu>
|
||||||
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>
|
kim0 <email.ahmedkamal@googlemail.com>
|
||||||
|
Kimbro Staken <kstaken@kstaken.com>
|
||||||
Kiran Gangadharan <kiran.daredevil@gmail.com>
|
Kiran Gangadharan <kiran.daredevil@gmail.com>
|
||||||
|
Konstantin Pelykh <kpelykh@zettaset.com>
|
||||||
Louis Opter <kalessin@kalessin.fr>
|
Louis Opter <kalessin@kalessin.fr>
|
||||||
|
Marco Hennings <marco.hennings@freiheit.com>
|
||||||
Marcus Farkas <toothlessgear@finitebox.com>
|
Marcus Farkas <toothlessgear@finitebox.com>
|
||||||
Mark McGranaghan <mmcgrana@gmail.com>
|
Mark McGranaghan <mmcgrana@gmail.com>
|
||||||
Maxim Treskin <zerthurd@gmail.com>
|
Maxim Treskin <zerthurd@gmail.com>
|
||||||
meejah <meejah@meejah.ca>
|
meejah <meejah@meejah.ca>
|
||||||
Michael Crosby <crosby.michael@gmail.com>
|
Michael Crosby <crosby.michael@gmail.com>
|
||||||
|
Mike Gaffney <mike@uberu.com>
|
||||||
Mikhail Sobolev <mss@mawhrin.net>
|
Mikhail Sobolev <mss@mawhrin.net>
|
||||||
|
Nan Monnand Deng <monnand@gmail.com>
|
||||||
Nate Jones <nate@endot.org>
|
Nate Jones <nate@endot.org>
|
||||||
Nelson Chen <crazysim@gmail.com>
|
Nelson Chen <crazysim@gmail.com>
|
||||||
Niall O'Higgins <niallo@unworkable.org>
|
Niall O'Higgins <niallo@unworkable.org>
|
||||||
|
Nick Stenning <nick.stenning@digital.cabinet-office.gov.uk>
|
||||||
|
Nick Stinemates <nick@stinemates.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>
|
Phil Spitler <pspitler@gmail.com>
|
||||||
Piotr Bogdan <ppbogdan@gmail.com>
|
Piotr Bogdan <ppbogdan@gmail.com>
|
||||||
Renato Riccieri Santos Zannon <renato.riccieri@gmail.com>
|
Renato Riccieri Santos Zannon <renato.riccieri@gmail.com>
|
||||||
|
Rhys Hiltner <rhys@twitch.tv>
|
||||||
Robert Obryk <robryk@gmail.com>
|
Robert Obryk <robryk@gmail.com>
|
||||||
Roberto Hashioka <roberto_hashioka@hotmail.com>
|
Roberto Hashioka <roberto_hashioka@hotmail.com>
|
||||||
|
Ryan Fowler <rwfowler@gmail.com>
|
||||||
Sam Alba <sam.alba@gmail.com>
|
Sam Alba <sam.alba@gmail.com>
|
||||||
Sam J Sharpe <sam.sharpe@digital.cabinet-office.gov.uk>
|
Sam J Sharpe <sam.sharpe@digital.cabinet-office.gov.uk>
|
||||||
Shawn Siefkas <shawn.siefkas@meredith.com>
|
Shawn Siefkas <shawn.siefkas@meredith.com>
|
||||||
|
@ -83,6 +104,8 @@ 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>
|
||||||
Tobias Bieniek <Tobias.Bieniek@gmx.de>
|
Tobias Bieniek <Tobias.Bieniek@gmx.de>
|
||||||
|
Tobias Schwab <tobias.schwab@dynport.de>
|
||||||
|
Tom Hulihan <hulihan.tom159@gmail.com>
|
||||||
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>
|
||||||
|
|
2
Makefile
2
Makefile
|
@ -50,6 +50,7 @@ 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
|
s3cmd -P put docker-latest.tgz s3://get.docker.io/builds/`uname -s`/`uname -m`/docker-latest.tgz
|
||||||
s3cmd -P put $(SRCRELEASE)/bin/docker s3://get.docker.io/builds/`uname -s`/`uname -m`/docker
|
s3cmd -P put $(SRCRELEASE)/bin/docker s3://get.docker.io/builds/`uname -s`/`uname -m`/docker
|
||||||
|
echo $(RELEASE_VERSION) > latest ; s3cmd -P put latest s3://get.docker.io/latest ; rm latest
|
||||||
|
|
||||||
srcrelease: $(SRCRELEASE)
|
srcrelease: $(SRCRELEASE)
|
||||||
deps: $(DOCKER_DIR)
|
deps: $(DOCKER_DIR)
|
||||||
|
@ -65,7 +66,6 @@ $(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
|
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))
|
||||||
ifeq ($(GOPATH), $(BUILD_DIR))
|
ifeq ($(GOPATH), $(BUILD_DIR))
|
||||||
|
|
47
README.md
47
README.md
|
@ -167,7 +167,7 @@ Installing from source
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
1. Make sure you have a [Go language](http://golang.org/doc/install)
|
1. Make sure you have a [Go language](http://golang.org/doc/install)
|
||||||
compiler and [git](http://git-scm.com) installed.
|
compiler >= 1.1 and [git](http://git-scm.com) installed.
|
||||||
2. Checkout the source code
|
2. Checkout the source code
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
@ -444,51 +444,6 @@ With Standard Containers we can put an end to that embarrassment, by
|
||||||
making INDUSTRIAL-GRADE DELIVERY of software a reality.
|
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
|
|
||||||
|
|
||||||
### Legal
|
### Legal
|
||||||
|
|
||||||
Transfers of Docker shall be in accordance with applicable export
|
Transfers of Docker shall be in accordance with applicable export
|
||||||
|
|
25
api.go
25
api.go
|
@ -17,7 +17,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
const APIVERSION = 1.3
|
const APIVERSION = 1.4
|
||||||
const DEFAULTHTTPHOST string = "127.0.0.1"
|
const DEFAULTHTTPHOST string = "127.0.0.1"
|
||||||
const DEFAULTHTTPPORT int = 4243
|
const DEFAULTHTTPPORT int = 4243
|
||||||
|
|
||||||
|
@ -271,11 +271,18 @@ func getContainersChanges(srv *Server, version float64, w http.ResponseWriter, r
|
||||||
}
|
}
|
||||||
|
|
||||||
func getContainersTop(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
func getContainersTop(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
|
if version < 1.4 {
|
||||||
|
return fmt.Errorf("top was improved a lot since 1.3, Please upgrade your docker client.")
|
||||||
|
}
|
||||||
if vars == nil {
|
if vars == nil {
|
||||||
return fmt.Errorf("Missing parameter")
|
return fmt.Errorf("Missing parameter")
|
||||||
}
|
}
|
||||||
|
if err := parseForm(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
name := vars["name"]
|
name := vars["name"]
|
||||||
procsStr, err := srv.ContainerTop(name)
|
ps_args := r.Form.Get("ps_args")
|
||||||
|
procsStr, err := srv.ContainerTop(name, ps_args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -786,12 +793,8 @@ func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Requ
|
||||||
remoteURL := r.FormValue("remote")
|
remoteURL := r.FormValue("remote")
|
||||||
repoName := r.FormValue("t")
|
repoName := r.FormValue("t")
|
||||||
rawSuppressOutput := r.FormValue("q")
|
rawSuppressOutput := r.FormValue("q")
|
||||||
tag := ""
|
rawNoCache := r.FormValue("nocache")
|
||||||
if strings.Contains(repoName, ":") {
|
repoName, tag := utils.ParseRepositoryTag(repoName)
|
||||||
remoteParts := strings.Split(repoName, ":")
|
|
||||||
tag = remoteParts[1]
|
|
||||||
repoName = remoteParts[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
var context io.Reader
|
var context io.Reader
|
||||||
|
|
||||||
|
@ -837,8 +840,12 @@ func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Requ
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
noCache, err := getBoolParam(rawNoCache)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
b := NewBuildFile(srv, utils.NewWriteFlusher(w), !suppressOutput)
|
b := NewBuildFile(srv, utils.NewWriteFlusher(w), !suppressOutput, !noCache)
|
||||||
id, err := b.Build(context)
|
id, err := b.Build(context)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(w, "Error build: %s\n", err)
|
fmt.Fprintf(w, "Error build: %s\n", err)
|
||||||
|
|
|
@ -30,10 +30,8 @@ type APIInfo struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type APITop struct {
|
type APITop struct {
|
||||||
PID string
|
Titles []string
|
||||||
Tty string
|
Processes [][]string
|
||||||
Time string
|
|
||||||
Cmd string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type APIRmi struct {
|
type APIRmi struct {
|
||||||
|
|
27
api_test.go
27
api_test.go
|
@ -482,24 +482,33 @@ func TestGetContainersTop(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
r := httptest.NewRecorder()
|
r := httptest.NewRecorder()
|
||||||
if err := getContainersTop(srv, APIVERSION, r, nil, map[string]string{"name": container.ID}); err != nil {
|
req, err := http.NewRequest("GET", "/"+container.ID+"/top?ps_args=u", bytes.NewReader([]byte{}))
|
||||||
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
procs := []APITop{}
|
if err := getContainersTop(srv, APIVERSION, r, req, map[string]string{"name": container.ID}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
procs := APITop{}
|
||||||
if err := json.Unmarshal(r.Body.Bytes(), &procs); err != nil {
|
if err := json.Unmarshal(r.Body.Bytes(), &procs); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(procs) != 2 {
|
if len(procs.Titles) != 11 {
|
||||||
t.Fatalf("Expected 2 processes, found %d.", len(procs))
|
t.Fatalf("Expected 11 titles, found %d.", len(procs.Titles))
|
||||||
|
}
|
||||||
|
if procs.Titles[0] != "USER" || procs.Titles[10] != "COMMAND" {
|
||||||
|
t.Fatalf("Expected Titles[0] to be USER and Titles[10] to be COMMAND, found %s and %s.", procs.Titles[0], procs.Titles[10])
|
||||||
}
|
}
|
||||||
|
|
||||||
if procs[0].Cmd != "sh" && procs[0].Cmd != "busybox" {
|
if len(procs.Processes) != 2 {
|
||||||
t.Fatalf("Expected `busybox` or `sh`, found %s.", procs[0].Cmd)
|
t.Fatalf("Expected 2 processes, found %d.", len(procs.Processes))
|
||||||
}
|
}
|
||||||
|
if procs.Processes[0][10] != "/bin/sh" && procs.Processes[0][10] != "sleep" {
|
||||||
if procs[1].Cmd != "sh" && procs[1].Cmd != "busybox" {
|
t.Fatalf("Expected `sleep` or `/bin/sh`, found %s.", procs.Processes[0][10])
|
||||||
t.Fatalf("Expected `busybox` or `sh`, found %s.", procs[1].Cmd)
|
}
|
||||||
|
if procs.Processes[1][10] != "/bin/sh" && procs.Processes[1][10] != "sleep" {
|
||||||
|
t.Fatalf("Expected `sleep` or `/bin/sh`, found %s.", procs.Processes[1][10])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -173,7 +173,7 @@ func CopyWithTar(src, dst string) error {
|
||||||
}
|
}
|
||||||
// Create dst, copy src's content into it
|
// Create dst, copy src's content into it
|
||||||
utils.Debugf("Creating dest directory: %s", dst)
|
utils.Debugf("Creating dest directory: %s", dst)
|
||||||
if err := os.MkdirAll(dst, 0700); err != nil && !os.IsExist(err) {
|
if err := os.MkdirAll(dst, 0755); err != nil && !os.IsExist(err) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
utils.Debugf("Calling TarUntar(%s, %s)", src, dst)
|
utils.Debugf("Calling TarUntar(%s, %s)", src, dst)
|
||||||
|
|
|
@ -18,7 +18,7 @@ const CONFIGFILE = ".dockercfg"
|
||||||
// Only used for user auth + account creation
|
// Only used for user auth + account creation
|
||||||
const INDEXSERVER = "https://index.docker.io/v1/"
|
const INDEXSERVER = "https://index.docker.io/v1/"
|
||||||
|
|
||||||
//const INDEXSERVER = "http://indexstaging-docker.dotcloud.com/"
|
//const INDEXSERVER = "https://indexstaging-docker.dotcloud.com/v1/"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrConfigFileMissing = errors.New("The Auth config file is missing")
|
ErrConfigFileMissing = errors.New("The Auth config file is missing")
|
||||||
|
|
|
@ -124,6 +124,10 @@ func (builder *Builder) Create(config *Config) (*Container, error) {
|
||||||
func (builder *Builder) Commit(container *Container, repository, tag, comment, author string, config *Config) (*Image, error) {
|
func (builder *Builder) Commit(container *Container, repository, tag, comment, author string, config *Config) (*Image, error) {
|
||||||
// FIXME: freeze the container before copying it to avoid data corruption?
|
// FIXME: freeze the container before copying it to avoid data corruption?
|
||||||
// FIXME: this shouldn't be in commands.
|
// FIXME: this shouldn't be in commands.
|
||||||
|
if err := container.EnsureMounted(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
rwTar, err := container.ExportRw()
|
rwTar, err := container.ExportRw()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
78
buildfile.go
78
buildfile.go
|
@ -11,6 +11,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -30,6 +31,7 @@ type buildFile struct {
|
||||||
config *Config
|
config *Config
|
||||||
context string
|
context string
|
||||||
verbose bool
|
verbose bool
|
||||||
|
utilizeCache bool
|
||||||
|
|
||||||
tmpContainers map[string]struct{}
|
tmpContainers map[string]struct{}
|
||||||
tmpImages map[string]struct{}
|
tmpImages map[string]struct{}
|
||||||
|
@ -67,6 +69,9 @@ func (b *buildFile) CmdFrom(name string) error {
|
||||||
}
|
}
|
||||||
b.image = image.ID
|
b.image = image.ID
|
||||||
b.config = &Config{}
|
b.config = &Config{}
|
||||||
|
if b.config.Env == nil || len(b.config.Env) == 0 {
|
||||||
|
b.config.Env = append(b.config.Env, "HOME=/", "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin")
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,6 +95,7 @@ func (b *buildFile) CmdRun(args string) error {
|
||||||
|
|
||||||
utils.Debugf("Command to be executed: %v", b.config.Cmd)
|
utils.Debugf("Command to be executed: %v", b.config.Cmd)
|
||||||
|
|
||||||
|
if b.utilizeCache {
|
||||||
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 {
|
||||||
|
@ -100,6 +106,7 @@ func (b *buildFile) CmdRun(args string) error {
|
||||||
} else {
|
} else {
|
||||||
utils.Debugf("[BUILDER] Cache miss")
|
utils.Debugf("[BUILDER] Cache miss")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
cid, err := b.run()
|
cid, err := b.run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -112,6 +119,40 @@ func (b *buildFile) CmdRun(args string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *buildFile) FindEnvKey(key string) int {
|
||||||
|
for k, envVar := range b.config.Env {
|
||||||
|
envParts := strings.SplitN(envVar, "=", 2)
|
||||||
|
if key == envParts[0] {
|
||||||
|
return k
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *buildFile) ReplaceEnvMatches(value string) (string, error) {
|
||||||
|
exp, err := regexp.Compile("(\\\\\\\\+|[^\\\\]|\\b|\\A)\\$({?)([[:alnum:]_]+)(}?)")
|
||||||
|
if err != nil {
|
||||||
|
return value, err
|
||||||
|
}
|
||||||
|
matches := exp.FindAllString(value, -1)
|
||||||
|
for _, match := range matches {
|
||||||
|
match = match[strings.Index(match, "$"):]
|
||||||
|
matchKey := strings.Trim(match, "${}")
|
||||||
|
|
||||||
|
for _, envVar := range b.config.Env {
|
||||||
|
envParts := strings.SplitN(envVar, "=", 2)
|
||||||
|
envKey := envParts[0]
|
||||||
|
envValue := envParts[1]
|
||||||
|
|
||||||
|
if envKey == matchKey {
|
||||||
|
value = strings.Replace(value, match, envValue, -1)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (b *buildFile) CmdEnv(args string) error {
|
func (b *buildFile) CmdEnv(args string) error {
|
||||||
tmp := strings.SplitN(args, " ", 2)
|
tmp := strings.SplitN(args, " ", 2)
|
||||||
if len(tmp) != 2 {
|
if len(tmp) != 2 {
|
||||||
|
@ -120,14 +161,19 @@ func (b *buildFile) CmdEnv(args string) error {
|
||||||
key := strings.Trim(tmp[0], " \t")
|
key := strings.Trim(tmp[0], " \t")
|
||||||
value := strings.Trim(tmp[1], " \t")
|
value := strings.Trim(tmp[1], " \t")
|
||||||
|
|
||||||
for i, elem := range b.config.Env {
|
envKey := b.FindEnvKey(key)
|
||||||
if strings.HasPrefix(elem, key+"=") {
|
replacedValue, err := b.ReplaceEnvMatches(value)
|
||||||
b.config.Env[i] = key + "=" + value
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
replacedVar := fmt.Sprintf("%s=%s", key, replacedValue)
|
||||||
|
|
||||||
|
if envKey >= 0 {
|
||||||
|
b.config.Env[envKey] = replacedVar
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
b.config.Env = append(b.config.Env, replacedVar)
|
||||||
b.config.Env = append(b.config.Env, key+"="+value)
|
return b.commit("", b.config.Cmd, fmt.Sprintf("ENV %s", replacedVar))
|
||||||
return b.commit("", b.config.Cmd, fmt.Sprintf("ENV %s=%s", key, value))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *buildFile) CmdCmd(args string) error {
|
func (b *buildFile) CmdCmd(args string) error {
|
||||||
|
@ -242,7 +288,7 @@ func (b *buildFile) addContext(container *Container, orig, dest string) error {
|
||||||
} else if err := UntarPath(origPath, destPath); err != nil {
|
} else if err := UntarPath(origPath, destPath); err != nil {
|
||||||
utils.Debugf("Couldn't untar %s to %s: %s", origPath, destPath, err)
|
utils.Debugf("Couldn't untar %s to %s: %s", origPath, destPath, err)
|
||||||
// If that fails, just copy it as a regular file
|
// If that fails, just copy it as a regular file
|
||||||
if err := os.MkdirAll(path.Dir(destPath), 0700); err != nil {
|
if err := os.MkdirAll(path.Dir(destPath), 0755); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := CopyWithTar(origPath, destPath); err != nil {
|
if err := CopyWithTar(origPath, destPath); err != nil {
|
||||||
|
@ -260,8 +306,16 @@ func (b *buildFile) CmdAdd(args string) error {
|
||||||
if len(tmp) != 2 {
|
if len(tmp) != 2 {
|
||||||
return fmt.Errorf("Invalid ADD format")
|
return fmt.Errorf("Invalid ADD format")
|
||||||
}
|
}
|
||||||
orig := strings.Trim(tmp[0], " \t")
|
|
||||||
dest := strings.Trim(tmp[1], " \t")
|
orig, err := b.ReplaceEnvMatches(strings.Trim(tmp[0], " \t"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
dest, err := b.ReplaceEnvMatches(strings.Trim(tmp[1], " \t"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
cmd := b.config.Cmd
|
cmd := b.config.Cmd
|
||||||
b.config.Cmd = []string{"/bin/sh", "-c", fmt.Sprintf("#(nop) ADD %s in %s", orig, dest)}
|
b.config.Cmd = []string{"/bin/sh", "-c", fmt.Sprintf("#(nop) ADD %s in %s", orig, dest)}
|
||||||
|
@ -346,6 +400,7 @@ func (b *buildFile) commit(id string, autoCmd []string, comment string) error {
|
||||||
b.config.Cmd = []string{"/bin/sh", "-c", "#(nop) " + comment}
|
b.config.Cmd = []string{"/bin/sh", "-c", "#(nop) " + comment}
|
||||||
defer func(cmd []string) { b.config.Cmd = cmd }(cmd)
|
defer func(cmd []string) { b.config.Cmd = cmd }(cmd)
|
||||||
|
|
||||||
|
if b.utilizeCache {
|
||||||
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 {
|
||||||
|
@ -356,6 +411,8 @@ func (b *buildFile) commit(id string, autoCmd []string, comment string) error {
|
||||||
} else {
|
} else {
|
||||||
utils.Debugf("[BUILDER] Cache miss")
|
utils.Debugf("[BUILDER] Cache miss")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
container, err := b.builder.Create(b.config)
|
container, err := b.builder.Create(b.config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -449,7 +506,7 @@ func (b *buildFile) Build(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 NewBuildFile(srv *Server, out io.Writer, verbose bool) BuildFile {
|
func NewBuildFile(srv *Server, out io.Writer, verbose, utilizeCache bool) BuildFile {
|
||||||
return &buildFile{
|
return &buildFile{
|
||||||
builder: NewBuilder(srv.runtime),
|
builder: NewBuilder(srv.runtime),
|
||||||
runtime: srv.runtime,
|
runtime: srv.runtime,
|
||||||
|
@ -459,5 +516,6 @@ func NewBuildFile(srv *Server, out io.Writer, verbose bool) BuildFile {
|
||||||
tmpContainers: make(map[string]struct{}),
|
tmpContainers: make(map[string]struct{}),
|
||||||
tmpImages: make(map[string]struct{}),
|
tmpImages: make(map[string]struct{}),
|
||||||
verbose: verbose,
|
verbose: verbose,
|
||||||
|
utilizeCache: utilizeCache,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -129,6 +129,38 @@ CMD Hello world
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
`
|
||||||
|
from {IMAGE}
|
||||||
|
env FOO /foo/baz
|
||||||
|
env BAR /bar
|
||||||
|
env BAZ $BAR
|
||||||
|
env FOOPATH $PATH:$FOO
|
||||||
|
run [ "$BAR" = "$BAZ" ]
|
||||||
|
run [ "$FOOPATH" = "$PATH:/foo/baz" ]
|
||||||
|
`,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
`
|
||||||
|
from {IMAGE}
|
||||||
|
env FOO /bar
|
||||||
|
env TEST testdir
|
||||||
|
env BAZ /foobar
|
||||||
|
add testfile $BAZ/
|
||||||
|
add $TEST $FOO
|
||||||
|
run [ "$(cat /foobar/testfile)" = "test1" ]
|
||||||
|
run [ "$(cat /bar/withfile)" = "test2" ]
|
||||||
|
`,
|
||||||
|
[][2]string{
|
||||||
|
{"testfile", "test1"},
|
||||||
|
{"testdir/withfile", "test2"},
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: test building with 2 successive overlapping ADD commands
|
// FIXME: test building with 2 successive overlapping ADD commands
|
||||||
|
@ -163,22 +195,24 @@ func mkTestingFileServer(files [][2]string) (*httptest.Server, error) {
|
||||||
|
|
||||||
func TestBuild(t *testing.T) {
|
func TestBuild(t *testing.T) {
|
||||||
for _, ctx := range testContexts {
|
for _, ctx := range testContexts {
|
||||||
buildImage(ctx, t)
|
buildImage(ctx, t, nil, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildImage(context testContextTemplate, t *testing.T) *Image {
|
func buildImage(context testContextTemplate, t *testing.T, srv *Server, useCache bool) *Image {
|
||||||
|
if srv == nil {
|
||||||
runtime, err := newTestRuntime()
|
runtime, err := newTestRuntime()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer nuke(runtime)
|
defer nuke(runtime)
|
||||||
|
|
||||||
srv := &Server{
|
srv = &Server{
|
||||||
runtime: runtime,
|
runtime: runtime,
|
||||||
pullingPool: make(map[string]struct{}),
|
pullingPool: make(map[string]struct{}),
|
||||||
pushingPool: make(map[string]struct{}),
|
pushingPool: make(map[string]struct{}),
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
httpServer, err := mkTestingFileServer(context.remoteFiles)
|
httpServer, err := mkTestingFileServer(context.remoteFiles)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -192,10 +226,10 @@ func buildImage(context testContextTemplate, t *testing.T) *Image {
|
||||||
}
|
}
|
||||||
port := httpServer.URL[idx+1:]
|
port := httpServer.URL[idx+1:]
|
||||||
|
|
||||||
ip := runtime.networkManager.bridgeNetwork.IP
|
ip := srv.runtime.networkManager.bridgeNetwork.IP
|
||||||
dockerfile := constructDockerfile(context.dockerfile, ip, port)
|
dockerfile := constructDockerfile(context.dockerfile, ip, port)
|
||||||
|
|
||||||
buildfile := NewBuildFile(srv, ioutil.Discard, false)
|
buildfile := NewBuildFile(srv, ioutil.Discard, false, useCache)
|
||||||
id, err := buildfile.Build(mkTestContext(dockerfile, context.files, t))
|
id, err := buildfile.Build(mkTestContext(dockerfile, context.files, t))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
@ -213,7 +247,7 @@ func TestVolume(t *testing.T) {
|
||||||
from {IMAGE}
|
from {IMAGE}
|
||||||
volume /test
|
volume /test
|
||||||
cmd Hello world
|
cmd Hello world
|
||||||
`, nil, nil}, t)
|
`, nil, nil}, t, nil, true)
|
||||||
|
|
||||||
if len(img.Config.Volumes) == 0 {
|
if len(img.Config.Volumes) == 0 {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
|
@ -229,7 +263,7 @@ func TestBuildMaintainer(t *testing.T) {
|
||||||
img := buildImage(testContextTemplate{`
|
img := buildImage(testContextTemplate{`
|
||||||
from {IMAGE}
|
from {IMAGE}
|
||||||
maintainer dockerio
|
maintainer dockerio
|
||||||
`, nil, nil}, t)
|
`, nil, nil}, t, nil, true)
|
||||||
|
|
||||||
if img.Author != "dockerio" {
|
if img.Author != "dockerio" {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
|
@ -241,9 +275,15 @@ func TestBuildEnv(t *testing.T) {
|
||||||
from {IMAGE}
|
from {IMAGE}
|
||||||
env port 4243
|
env port 4243
|
||||||
`,
|
`,
|
||||||
nil, nil}, t)
|
nil, nil}, t, nil, true)
|
||||||
|
hasEnv := false
|
||||||
if img.Config.Env[0] != "port=4243" {
|
for _, envVar := range img.Config.Env {
|
||||||
|
if envVar == "port=4243" {
|
||||||
|
hasEnv = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !hasEnv {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -253,7 +293,7 @@ func TestBuildCmd(t *testing.T) {
|
||||||
from {IMAGE}
|
from {IMAGE}
|
||||||
cmd ["/bin/echo", "Hello World"]
|
cmd ["/bin/echo", "Hello World"]
|
||||||
`,
|
`,
|
||||||
nil, nil}, t)
|
nil, nil}, t, nil, true)
|
||||||
|
|
||||||
if img.Config.Cmd[0] != "/bin/echo" {
|
if img.Config.Cmd[0] != "/bin/echo" {
|
||||||
t.Log(img.Config.Cmd[0])
|
t.Log(img.Config.Cmd[0])
|
||||||
|
@ -270,7 +310,7 @@ func TestBuildExpose(t *testing.T) {
|
||||||
from {IMAGE}
|
from {IMAGE}
|
||||||
expose 4243
|
expose 4243
|
||||||
`,
|
`,
|
||||||
nil, nil}, t)
|
nil, nil}, t, nil, true)
|
||||||
|
|
||||||
if img.Config.PortSpecs[0] != "4243" {
|
if img.Config.PortSpecs[0] != "4243" {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
|
@ -282,8 +322,70 @@ func TestBuildEntrypoint(t *testing.T) {
|
||||||
from {IMAGE}
|
from {IMAGE}
|
||||||
entrypoint ["/bin/echo"]
|
entrypoint ["/bin/echo"]
|
||||||
`,
|
`,
|
||||||
nil, nil}, t)
|
nil, nil}, t, nil, true)
|
||||||
|
|
||||||
if img.Config.Entrypoint[0] != "/bin/echo" {
|
if img.Config.Entrypoint[0] != "/bin/echo" {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBuildImageWithCache(t *testing.T) {
|
||||||
|
runtime, err := newTestRuntime()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer nuke(runtime)
|
||||||
|
|
||||||
|
srv := &Server{
|
||||||
|
runtime: runtime,
|
||||||
|
pullingPool: make(map[string]struct{}),
|
||||||
|
pushingPool: make(map[string]struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
template := testContextTemplate{`
|
||||||
|
from {IMAGE}
|
||||||
|
maintainer dockerio
|
||||||
|
`,
|
||||||
|
nil, nil}
|
||||||
|
|
||||||
|
img := buildImage(template, t, srv, true)
|
||||||
|
imageId := img.ID
|
||||||
|
|
||||||
|
img = nil
|
||||||
|
img = buildImage(template, t, srv, true)
|
||||||
|
|
||||||
|
if imageId != img.ID {
|
||||||
|
t.Logf("Image ids should match: %s != %s", imageId, img.ID)
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildImageWithoutCache(t *testing.T) {
|
||||||
|
runtime, err := newTestRuntime()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer nuke(runtime)
|
||||||
|
|
||||||
|
srv := &Server{
|
||||||
|
runtime: runtime,
|
||||||
|
pullingPool: make(map[string]struct{}),
|
||||||
|
pushingPool: make(map[string]struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
template := testContextTemplate{`
|
||||||
|
from {IMAGE}
|
||||||
|
maintainer dockerio
|
||||||
|
`,
|
||||||
|
nil, nil}
|
||||||
|
|
||||||
|
img := buildImage(template, t, srv, true)
|
||||||
|
imageId := img.ID
|
||||||
|
|
||||||
|
img = nil
|
||||||
|
img = buildImage(template, t, srv, false)
|
||||||
|
|
||||||
|
if imageId == img.ID {
|
||||||
|
t.Logf("Image ids should not match: %s == %s", imageId, img.ID)
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
72
commands.go
72
commands.go
|
@ -31,6 +31,7 @@ const VERSION = "0.5.0-dev"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
GITCOMMIT string
|
GITCOMMIT string
|
||||||
|
AuthRequiredError = fmt.Errorf("Authentication is required.")
|
||||||
)
|
)
|
||||||
|
|
||||||
func (cli *DockerCli) getMethod(name string) (reflect.Method, bool) {
|
func (cli *DockerCli) getMethod(name string) (reflect.Method, bool) {
|
||||||
|
@ -160,6 +161,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
|
||||||
cmd := Subcmd("build", "[OPTIONS] PATH | URL | -", "Build a new container image from the source code at PATH")
|
cmd := Subcmd("build", "[OPTIONS] PATH | URL | -", "Build a new container image from the source code at PATH")
|
||||||
tag := cmd.String("t", "", "Tag to be applied to the resulting image in case of success")
|
tag := cmd.String("t", "", "Tag to be applied to the resulting image in case of success")
|
||||||
suppressOutput := cmd.Bool("q", false, "Suppress verbose build output")
|
suppressOutput := cmd.Bool("q", false, "Suppress verbose build output")
|
||||||
|
noCache := cmd.Bool("no-cache", false, "Do not use cache when building the image")
|
||||||
|
|
||||||
if err := cmd.Parse(args); err != nil {
|
if err := cmd.Parse(args); err != nil {
|
||||||
return nil
|
return nil
|
||||||
|
@ -208,6 +210,9 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
|
||||||
if isRemote {
|
if isRemote {
|
||||||
v.Set("remote", cmd.Arg(0))
|
v.Set("remote", cmd.Arg(0))
|
||||||
}
|
}
|
||||||
|
if *noCache {
|
||||||
|
v.Set("nocache", "1")
|
||||||
|
}
|
||||||
req, err := http.NewRequest("POST", fmt.Sprintf("/v%g/build?%s", APIVERSION, v.Encode()), body)
|
req, err := http.NewRequest("POST", fmt.Sprintf("/v%g/build?%s", APIVERSION, v.Encode()), body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -314,13 +319,21 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
|
||||||
email string
|
email string
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var promptDefault = func(prompt string, configDefault string) {
|
||||||
|
if configDefault == "" {
|
||||||
|
fmt.Fprintf(cli.out, "%s: ", prompt)
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(cli.out, "%s (%s): ", prompt, configDefault)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
authconfig, ok := cli.configFile.Configs[auth.IndexServerAddress()]
|
authconfig, ok := cli.configFile.Configs[auth.IndexServerAddress()]
|
||||||
if !ok {
|
if !ok {
|
||||||
authconfig = auth.AuthConfig{}
|
authconfig = auth.AuthConfig{}
|
||||||
}
|
}
|
||||||
|
|
||||||
if *flUsername == "" {
|
if *flUsername == "" {
|
||||||
fmt.Fprintf(cli.out, "Username (%s): ", authconfig.Username)
|
promptDefault("Username", authconfig.Username)
|
||||||
username = readAndEchoString(cli.in, cli.out)
|
username = readAndEchoString(cli.in, cli.out)
|
||||||
if username == "" {
|
if username == "" {
|
||||||
username = authconfig.Username
|
username = authconfig.Username
|
||||||
|
@ -340,7 +353,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if *flEmail == "" {
|
if *flEmail == "" {
|
||||||
fmt.Fprintf(cli.out, "Email (%s): ", authconfig.Email)
|
promptDefault("Email", authconfig.Email)
|
||||||
email = readAndEchoString(cli.in, cli.out)
|
email = readAndEchoString(cli.in, cli.out)
|
||||||
if email == "" {
|
if email == "" {
|
||||||
email = authconfig.Email
|
email = authconfig.Email
|
||||||
|
@ -440,6 +453,15 @@ func (cli *DockerCli) CmdVersion(args ...string) error {
|
||||||
if out.GoVersion != "" {
|
if out.GoVersion != "" {
|
||||||
fmt.Fprintf(cli.out, "Go version: %s\n", out.GoVersion)
|
fmt.Fprintf(cli.out, "Go version: %s\n", out.GoVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
release := utils.GetReleaseVersion()
|
||||||
|
if release != "" {
|
||||||
|
fmt.Fprintf(cli.out, "Last stable version: %s", release)
|
||||||
|
if strings.Trim(VERSION, "-dev") != release || strings.Trim(out.Version, "-dev") != release {
|
||||||
|
fmt.Fprintf(cli.out, ", please update docker")
|
||||||
|
}
|
||||||
|
fmt.Fprintf(cli.out, "\n")
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -596,23 +618,28 @@ func (cli *DockerCli) CmdTop(args ...string) error {
|
||||||
if err := cmd.Parse(args); err != nil {
|
if err := cmd.Parse(args); err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if cmd.NArg() != 1 {
|
if cmd.NArg() == 0 {
|
||||||
cmd.Usage()
|
cmd.Usage()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
body, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/top", nil)
|
val := url.Values{}
|
||||||
|
if cmd.NArg() > 1 {
|
||||||
|
val.Set("ps_args", strings.Join(cmd.Args()[1:], " "))
|
||||||
|
}
|
||||||
|
|
||||||
|
body, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/top?"+val.Encode(), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
var procs []APITop
|
procs := APITop{}
|
||||||
err = json.Unmarshal(body, &procs)
|
err = json.Unmarshal(body, &procs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
|
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
|
||||||
fmt.Fprintln(w, "PID\tTTY\tTIME\tCMD")
|
fmt.Fprintln(w, strings.Join(procs.Titles, "\t"))
|
||||||
for _, proc := range procs {
|
for _, proc := range procs.Processes {
|
||||||
fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", proc.PID, proc.Tty, proc.Time, proc.Cmd)
|
fmt.Fprintln(w, strings.Join(proc, "\t"))
|
||||||
}
|
}
|
||||||
w.Flush()
|
w.Flush()
|
||||||
return nil
|
return nil
|
||||||
|
@ -801,10 +828,6 @@ func (cli *DockerCli) CmdPush(args ...string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := cli.checkIfLogged("push"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we're not using a custom registry, we know the restrictions
|
// If we're not using a custom registry, we know the restrictions
|
||||||
// applied to repository names and can warn the user in advance.
|
// applied to repository names and can warn the user in advance.
|
||||||
// Custom repositories can have different rules, and we must also
|
// Custom repositories can have different rules, and we must also
|
||||||
|
@ -813,13 +836,22 @@ func (cli *DockerCli) CmdPush(args ...string) error {
|
||||||
return fmt.Errorf("Impossible to push a \"root\" repository. Please rename your repository in <user>/<repo> (ex: %s/%s)", cli.configFile.Configs[auth.IndexServerAddress()].Username, name)
|
return fmt.Errorf("Impossible to push a \"root\" repository. Please rename your repository in <user>/<repo> (ex: %s/%s)", cli.configFile.Configs[auth.IndexServerAddress()].Username, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
v := url.Values{}
|
||||||
|
push := func() error {
|
||||||
buf, err := json.Marshal(cli.configFile.Configs[auth.IndexServerAddress()])
|
buf, err := json.Marshal(cli.configFile.Configs[auth.IndexServerAddress()])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
v := url.Values{}
|
return cli.stream("POST", "/images/"+name+"/push?"+v.Encode(), bytes.NewBuffer(buf), cli.out)
|
||||||
if err := cli.stream("POST", "/images/"+name+"/push?"+v.Encode(), bytes.NewBuffer(buf), cli.out); err != nil {
|
}
|
||||||
|
|
||||||
|
if err := push(); err != nil {
|
||||||
|
if err == AuthRequiredError {
|
||||||
|
if err = cli.checkIfLogged("push"); err == nil {
|
||||||
|
return push()
|
||||||
|
}
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -1546,6 +1578,9 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if jm.Error != nil && jm.Error.Code == 401 {
|
||||||
|
return AuthRequiredError
|
||||||
|
}
|
||||||
jm.Display(out)
|
jm.Display(out)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -1658,14 +1693,13 @@ func (cli *DockerCli) monitorTtySize(id string) error {
|
||||||
}
|
}
|
||||||
cli.resizeTty(id)
|
cli.resizeTty(id)
|
||||||
|
|
||||||
c := make(chan os.Signal, 1)
|
sigchan := make(chan os.Signal, 1)
|
||||||
signal.Notify(c, syscall.SIGWINCH)
|
signal.Notify(sigchan, syscall.SIGWINCH)
|
||||||
go func() {
|
go func() {
|
||||||
for sig := range c {
|
for {
|
||||||
if sig == syscall.SIGWINCH {
|
<-sigchan
|
||||||
cli.resizeTty(id)
|
cli.resizeTty(id)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}()
|
}()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,7 +52,7 @@ type Container struct {
|
||||||
|
|
||||||
waitLock chan struct{}
|
waitLock chan struct{}
|
||||||
Volumes map[string]string
|
Volumes map[string]string
|
||||||
// Store rw/ro in a separate structure to preserve reserve-compatibility on-disk.
|
// Store rw/ro in a separate structure to preserve reverse-compatibility on-disk.
|
||||||
// Easier than migrating older container configs :)
|
// Easier than migrating older container configs :)
|
||||||
VolumesRW map[string]bool
|
VolumesRW map[string]bool
|
||||||
}
|
}
|
||||||
|
@ -100,7 +100,7 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
|
||||||
|
|
||||||
flHostname := cmd.String("h", "", "Container host name")
|
flHostname := cmd.String("h", "", "Container host name")
|
||||||
flUser := cmd.String("u", "", "Username or UID")
|
flUser := cmd.String("u", "", "Username or UID")
|
||||||
flDetach := cmd.Bool("d", false, "Detached mode: leave the container running in the background")
|
flDetach := cmd.Bool("d", false, "Detached mode: Run container in the background, print new container id")
|
||||||
flAttach := NewAttachOpts()
|
flAttach := NewAttachOpts()
|
||||||
cmd.Var(flAttach, "a", "Attach to stdin, stdout or stderr.")
|
cmd.Var(flAttach, "a", "Attach to stdin, stdout or stderr.")
|
||||||
flStdin := cmd.Bool("i", false, "Keep stdin open even if not attached")
|
flStdin := cmd.Bool("i", false, "Keep stdin open even if not attached")
|
||||||
|
@ -266,7 +266,8 @@ func (container *Container) FromDisk() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// Load container settings
|
// Load container settings
|
||||||
if err := json.Unmarshal(data, container); err != nil {
|
// udp broke compat of docker.PortMapping, but it's not used when loading a container, we can skip it
|
||||||
|
if err := json.Unmarshal(data, container); err != nil && !strings.Contains(err.Error(), "docker.PortMapping") {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -652,6 +653,7 @@ func (container *Container) Start(hostConfig *HostConfig) error {
|
||||||
"-e", "HOME=/",
|
"-e", "HOME=/",
|
||||||
"-e", "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
"-e", "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
||||||
"-e", "container=lxc",
|
"-e", "container=lxc",
|
||||||
|
"-e", "HOSTNAME="+container.Config.Hostname,
|
||||||
)
|
)
|
||||||
|
|
||||||
for _, elem := range container.Config.Env {
|
for _, elem := range container.Config.Env {
|
||||||
|
|
|
@ -960,6 +960,7 @@ func TestEnv(t *testing.T) {
|
||||||
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
||||||
"HOME=/",
|
"HOME=/",
|
||||||
"container=lxc",
|
"container=lxc",
|
||||||
|
"HOSTNAME=" + container.ShortID(),
|
||||||
}
|
}
|
||||||
sort.Strings(goodEnv)
|
sort.Strings(goodEnv)
|
||||||
if len(goodEnv) != len(actualEnv) {
|
if len(goodEnv) != len(actualEnv) {
|
||||||
|
|
|
@ -26,15 +26,15 @@ Docker Remote API
|
||||||
2. Versions
|
2. Versions
|
||||||
===========
|
===========
|
||||||
|
|
||||||
The current verson of the API is 1.3
|
The current verson of the API is 1.4
|
||||||
|
|
||||||
Calling /images/<name>/insert is the same as calling
|
Calling /images/<name>/insert is the same as calling
|
||||||
/v1.3/images/<name>/insert
|
/v1.4/images/<name>/insert
|
||||||
|
|
||||||
You can still call an old version of the api using
|
You can still call an old version of the api using
|
||||||
/v1.0/images/<name>/insert
|
/v1.0/images/<name>/insert
|
||||||
|
|
||||||
:doc:`docker_remote_api_v1.3`
|
:doc:`docker_remote_api_v1.4`
|
||||||
*****************************
|
*****************************
|
||||||
|
|
||||||
What's new
|
What's new
|
||||||
|
@ -42,7 +42,19 @@ What's new
|
||||||
|
|
||||||
.. http:get:: /containers/(id)/top
|
.. http:get:: /containers/(id)/top
|
||||||
|
|
||||||
**New!** List the processes running inside a container.
|
**New!** You can now use ps args with docker top, like `docker top <container_id> aux`
|
||||||
|
|
||||||
|
:doc:`docker_remote_api_v1.3`
|
||||||
|
*****************************
|
||||||
|
|
||||||
|
docker v0.5.0 51f6c4a_
|
||||||
|
|
||||||
|
What's new
|
||||||
|
----------
|
||||||
|
|
||||||
|
.. http:get:: /containers/(id)/top
|
||||||
|
|
||||||
|
List the processes running inside a container.
|
||||||
|
|
||||||
.. http:get:: /events:
|
.. http:get:: /events:
|
||||||
|
|
||||||
|
@ -138,6 +150,7 @@ Initial version
|
||||||
.. _a8ae398: https://github.com/dotcloud/docker/commit/a8ae398bf52e97148ee7bd0d5868de2e15bd297f
|
.. _a8ae398: https://github.com/dotcloud/docker/commit/a8ae398bf52e97148ee7bd0d5868de2e15bd297f
|
||||||
.. _8d73740: https://github.com/dotcloud/docker/commit/8d73740343778651c09160cde9661f5f387b36f4
|
.. _8d73740: https://github.com/dotcloud/docker/commit/8d73740343778651c09160cde9661f5f387b36f4
|
||||||
.. _2e7649b: https://github.com/dotcloud/docker/commit/2e7649beda7c820793bd46766cbc2cfeace7b168
|
.. _2e7649b: https://github.com/dotcloud/docker/commit/2e7649beda7c820793bd46766cbc2cfeace7b168
|
||||||
|
.. _51f6c4a: https://github.com/dotcloud/docker/commit/51f6c4a7372450d164c61e0054daf0223ddbd909
|
||||||
|
|
||||||
==================================
|
==================================
|
||||||
Docker Remote API Client Libraries
|
Docker Remote API Client Libraries
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -12,6 +12,7 @@
|
||||||
Build a new container image from the source code at PATH
|
Build a new container image from the source code at PATH
|
||||||
-t="": Tag to be applied to the resulting image in case of success.
|
-t="": Tag to be applied to the resulting image in case of success.
|
||||||
-q=false: Suppress verbose build output.
|
-q=false: Suppress verbose build output.
|
||||||
|
-no-cache: Do not use the cache when building the image.
|
||||||
When a single Dockerfile is given as URL, then no context is set. When a git repository is set as URL, the repository is used as context
|
When a single Dockerfile is given as URL, then no context is set. When a git repository is set as URL, the repository is used as context
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
-a=map[]: Attach to stdin, stdout or stderr.
|
-a=map[]: Attach to stdin, stdout or stderr.
|
||||||
-c=0: CPU shares (relative weight)
|
-c=0: CPU shares (relative weight)
|
||||||
-cidfile="": Write the container ID to the file
|
-cidfile="": Write the container ID to the file
|
||||||
-d=false: Detached mode: leave the container running in the background
|
-d=false: Detached mode: Run container in the background, print new container id
|
||||||
-e=[]: Set environment variables
|
-e=[]: Set environment variables
|
||||||
-h="": Container host name
|
-h="": Container host name
|
||||||
-i=false: Keep stdin open even if not attached
|
-i=false: Keep stdin open even if not attached
|
||||||
|
|
|
@ -4,10 +4,6 @@
|
||||||
|
|
||||||
.. _dockermanifesto:
|
.. _dockermanifesto:
|
||||||
|
|
||||||
*(This was our original Welcome page, but it is a bit forward-looking
|
|
||||||
for docs, and maybe not enough vision for a true manifesto. We'll
|
|
||||||
reveal more vision in the future to make it more Manifesto-y.)*
|
|
||||||
|
|
||||||
Docker Manifesto
|
Docker Manifesto
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
|
@ -131,60 +127,3 @@ sitting 10 miles away.
|
||||||
|
|
||||||
With Standard Containers we can put an end to that embarrassment, by
|
With Standard Containers we can put an end to that embarrassment, by
|
||||||
making INDUSTRIAL-GRADE DELIVERY of software a reality.
|
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
|
|
||||||
^^^^^^^^
|
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,7 @@ This time, we're requesting shared access to $COUCH1's volumes.
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
COUCH2=$(docker run -d -volumes-from $COUCH1) shykes/couchdb:2013-05-03)
|
COUCH2=$(docker run -d -volumes-from $COUCH1 shykes/couchdb:2013-05-03)
|
||||||
|
|
||||||
Browse data on the second database
|
Browse data on the second database
|
||||||
----------------------------------
|
----------------------------------
|
||||||
|
@ -48,6 +48,6 @@ Browse data on the second database
|
||||||
|
|
||||||
HOST=localhost
|
HOST=localhost
|
||||||
URL="http://$HOST:$(docker port $COUCH2 5984)/_utils/"
|
URL="http://$HOST:$(docker port $COUCH2 5984)/_utils/"
|
||||||
echo "Navigate to $URL in your browser. You should see the same data as in the first database!"
|
echo "Navigate to $URL in your browser. You should see the same data as in the first database"'!'
|
||||||
|
|
||||||
Congratulations, you are running 2 Couchdb containers, completely isolated from each other *except* for their data.
|
Congratulations, you are running 2 Couchdb containers, completely isolated from each other *except* for their data.
|
||||||
|
|
|
@ -15,12 +15,11 @@ In short, Docker has the following kernel requirements:
|
||||||
|
|
||||||
- Cgroups and namespaces must be enabled.
|
- Cgroups and namespaces must be enabled.
|
||||||
|
|
||||||
|
The officially supported kernel is the one recommended by the
|
||||||
The officially supported kernel is the one recommended by the
|
:ref:`ubuntu_linux` installation path. It is the one that most developers
|
||||||
:ref:`ubuntu_linux` installation path. It is the one that most developers
|
will use, and the one that receives the most attention from the core
|
||||||
will use, and the one that receives the most attention from the core
|
contributors. If you decide to go with a different kernel and hit a bug,
|
||||||
contributors. If you decide to go with a different kernel and hit a bug,
|
please try to reproduce it with the official kernels first.
|
||||||
please try to reproduce it with the official kernels first.
|
|
||||||
|
|
||||||
If you cannot or do not want to use the "official" kernels,
|
If you cannot or do not want to use the "official" kernels,
|
||||||
here is some technical background about the features (both optional and
|
here is some technical background about the features (both optional and
|
||||||
|
|
|
@ -19,6 +19,8 @@ Docker has the following dependencies
|
||||||
* Linux kernel 3.8 (read more about :ref:`kernel`)
|
* Linux kernel 3.8 (read more about :ref:`kernel`)
|
||||||
* AUFS file system support (we are working on BTRFS support as an alternative)
|
* AUFS file system support (we are working on BTRFS support as an alternative)
|
||||||
|
|
||||||
|
Please read :ref:`ufw`, if you plan to use `UFW (Uncomplicated Firewall) <https://help.ubuntu.com/community/UFW>`_
|
||||||
|
|
||||||
.. _ubuntu_precise:
|
.. _ubuntu_precise:
|
||||||
|
|
||||||
Ubuntu Precise 12.04 (LTS) (64-bit)
|
Ubuntu Precise 12.04 (LTS) (64-bit)
|
||||||
|
@ -135,3 +137,35 @@ Verify it worked
|
||||||
|
|
||||||
|
|
||||||
**Done!**, now continue with the :ref:`hello_world` example.
|
**Done!**, now continue with the :ref:`hello_world` example.
|
||||||
|
|
||||||
|
|
||||||
|
.. _ufw:
|
||||||
|
|
||||||
|
Docker and UFW
|
||||||
|
^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Docker uses a bridge to manage containers networking, by default UFW drop all `forwarding`, a first step is to enable forwarding:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
sudo nano /etc/default/ufw
|
||||||
|
----
|
||||||
|
# Change:
|
||||||
|
# DEFAULT_FORWARD_POLICY="DROP"
|
||||||
|
# to
|
||||||
|
DEFAULT_FORWARD_POLICY="ACCEPT"
|
||||||
|
|
||||||
|
Then reload UFW:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
sudo ufw reload
|
||||||
|
|
||||||
|
|
||||||
|
UFW's default set of rules denied all `incoming`, so if you want to be able to reach your containers from another host,
|
||||||
|
you should allow incoming connexions on the docker port (default 4243):
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
sudo ufw allow 4243/tcp
|
||||||
|
|
||||||
|
|
|
@ -182,7 +182,7 @@ The copy obeys the following rules:
|
||||||
written at ``<dst>``.
|
written at ``<dst>``.
|
||||||
* If ``<dest>`` doesn't exist, it is created along with all missing
|
* If ``<dest>`` doesn't exist, it is created along with all missing
|
||||||
directories in its path. All new files and directories are created
|
directories in its path. All new files and directories are created
|
||||||
with mode 0700, uid and gid 0.
|
with mode 0755, uid and gid 0.
|
||||||
|
|
||||||
3.8 ENTRYPOINT
|
3.8 ENTRYPOINT
|
||||||
--------------
|
--------------
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Thatcher Penskens <thatcher@dotcloud.com>
|
Thatcher Peskens <thatcher@dotcloud.com>
|
||||||
|
|
|
@ -68,18 +68,18 @@
|
||||||
|
|
||||||
<div style="float: right" class="pull-right">
|
<div style="float: right" class="pull-right">
|
||||||
<ul class="nav">
|
<ul class="nav">
|
||||||
<li id="nav-introduction"><a href="http://www.docker.io/">Home</a></li>
|
<li id="nav-introduction"><a href="http://www.docker.io/" title="Docker Homepage">Home</a></li>
|
||||||
<li id="nav-about"><a href="http://www.docker.io/">About</a></li>
|
<li id="nav-about"><a href="http://www.docker.io/about/" title="About">About</a></li>
|
||||||
<li id="nav-community"><a href="http://www.docker.io/">Community</a></li>
|
<li id="nav-community"><a href="http://www.docker.io/community/" title="Community">Community</a></li>
|
||||||
<li id="nav-gettingstarted"><a href="http://www.docker.io/gettingstarted/">Getting started</a></li>
|
<li id="nav-gettingstarted"><a href="http://www.docker.io/gettingstarted/">Getting started</a></li>
|
||||||
<li id="nav-documentation" class="active"><a href="http://docs.docker.io/en/latest/">Documentation</a></li>
|
<li id="nav-documentation" class="active"><a href="http://docs.docker.io/en/latest/">Documentation</a></li>
|
||||||
<li id="nav-blog"><a href="http://blog.docker.io/">Blog</a></li>
|
<li id="nav-blog"><a href="http://blog.docker.io/" title="Docker Blog">Blog</a></li>
|
||||||
<li id="nav-index"><a href="http://index.docker.io/" title="Docker Image Index, find images here">INDEX <img class="inline-icon" src="{{ pathto('_static/img/external-link-icon.png', 1) }}" title="external link"> </a></li>
|
<li id="nav-index"><a href="http://index.docker.io/" title="Docker Image Index, find images here">INDEX <img class="inline-icon" src="{{ pathto('_static/img/external-link-icon.png', 1) }}" title="external link"> </a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin-left: -12px; float: left;">
|
<div style="margin-left: -12px; float: left;">
|
||||||
<a href="http://www.docker.io" title="Docker Homepage"><img style="margin-top: 0px; height: 60px; margin-left: 10px;" src="{{ pathto('_static/img/docker-top-logo.png', 1) }}"></a>
|
<a href="http://www.docker.io" title="Docker Homepage"><img style="margin-top: 0px; height: 60px; width: 160px; margin-left: 10px;" src="{{ pathto('_static/img/docker-top-logo.png', 1) }}"></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
Thatcher Penskens <thatcher@dotcloud.com>
|
|
|
@ -1,2 +0,0 @@
|
||||||
www:
|
|
||||||
type: static
|
|
|
@ -1,220 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
|
|
||||||
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
|
|
||||||
<!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
|
|
||||||
<!--[if gt IE 8]><!-->
|
|
||||||
<html class="no-js" xmlns="http://www.w3.org/1999/html" xmlns="http://www.w3.org/1999/html"> <!--<![endif]-->
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
|
||||||
<title>Docker - the Linux container runtime</title>
|
|
||||||
|
|
||||||
<meta name="description" content="Docker encapsulates heterogeneous payloads in standard containers">
|
|
||||||
<meta name="viewport" content="width=device-width">
|
|
||||||
|
|
||||||
<!-- twitter bootstrap -->
|
|
||||||
<link rel="stylesheet" href="../static/css/bootstrap.min.css">
|
|
||||||
<link rel="stylesheet" href="../static/css/bootstrap-responsive.min.css">
|
|
||||||
|
|
||||||
<!-- main style file -->
|
|
||||||
<link rel="stylesheet" href="../static/css/main.css">
|
|
||||||
|
|
||||||
<!-- vendor scripts -->
|
|
||||||
<script src="../static/js/vendor/jquery-1.9.1.min.js" type="text/javascript" ></script>
|
|
||||||
<script src="../static/js/vendor/modernizr-2.6.2-respond-1.1.0.min.js" type="text/javascript" ></script>
|
|
||||||
|
|
||||||
</head>
|
|
||||||
|
|
||||||
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<div class="navbar navbar-fixed-top">
|
|
||||||
<div class="navbar-dotcloud">
|
|
||||||
<div class="container" style="text-align: center;">
|
|
||||||
|
|
||||||
<div style="float: right" class="pull-right">
|
|
||||||
<ul class="nav">
|
|
||||||
<li id="nav-introduction"><a href="../">Introduction</a></li>
|
|
||||||
<li id="nav-gettingstarted" class="active"><a href="">Getting started</a></li>
|
|
||||||
<li id="nav-documentation" class=""><a href="http://docs.docker.io/en/latest/">Documentation</a></li>
|
|
||||||
<li id="nav-blog"><a href="http://blog.docker.io/">Blog</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="margin-left: -12px; float: left;">
|
|
||||||
<a href="../index.html"><img style="margin-top: 12px; height: 38px" src="../static/img/docker-letters-logo.gif"></a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="container">
|
|
||||||
<div class="row">
|
|
||||||
|
|
||||||
<div class="span12 titlebar">
|
|
||||||
|
|
||||||
<div class="pull-right" id="fork-us" style="margin-top: 16px; margin-right: 16px;">
|
|
||||||
<a href="http://github.com/dotcloud/docker/"><img src="../static/img/fork-us.png"> Fork us on Github</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h1 class="pageheader"> GETTING STARTED</h1>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="container">
|
|
||||||
<div class="alert alert-info" style="margin-bottom: 0;">
|
|
||||||
<strong>Docker is still under heavy development.</strong> It should not yet be used in production. Check <a href="http://github.com/dotcloud/docker">the repo</a> for recent progress.
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="span6">
|
|
||||||
<section class="contentblock">
|
|
||||||
<h2>
|
|
||||||
<a name="installing-on-ubuntu-1204-and-1210" class="anchor" href="#installing-on-ubuntu-1204-and-1210"><span class="mini-icon mini-icon-link"></span>
|
|
||||||
</a>Installing on Ubuntu</h2>
|
|
||||||
|
|
||||||
<p><strong>Requirements</strong></p>
|
|
||||||
<ul>
|
|
||||||
<li>Ubuntu 12.04 (LTS) (64-bit)</li>
|
|
||||||
<li> or Ubuntu 12.10 (quantal) (64-bit)</li>
|
|
||||||
<li>The 3.8 Linux Kernel</li>
|
|
||||||
</ul>
|
|
||||||
<ol>
|
|
||||||
<li>
|
|
||||||
<p><strong>Install dependencies</strong></p>
|
|
||||||
The linux-image-extra package is only needed on standard Ubuntu EC2 AMIs in order to install the aufs kernel module.
|
|
||||||
<pre>sudo apt-get install linux-image-extra-`uname -r`</pre>
|
|
||||||
|
|
||||||
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p><strong>Install Docker</strong></p>
|
|
||||||
<p>Add the Ubuntu PPA (Personal Package Archive) sources to your apt sources list, update and install.</p>
|
|
||||||
<p>This may import a new GPG key (key 63561DC6: public key "Launchpad PPA for dotcloud team" imported).</p>
|
|
||||||
<div class="highlight">
|
|
||||||
<pre>sudo apt-get install software-properties-common</pre>
|
|
||||||
<pre>sudo add-apt-repository ppa:dotcloud/lxc-docker</pre>
|
|
||||||
<pre>sudo apt-get update</pre>
|
|
||||||
<pre>sudo apt-get install lxc-docker</pre>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li>
|
|
||||||
<p><strong>Run!</strong></p>
|
|
||||||
|
|
||||||
<div class="highlight">
|
|
||||||
<pre>docker run -i -t ubuntu /bin/bash</pre>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
Continue with the <a href="http://docs.docker.io/en/latest/examples/hello_world/">Hello world</a> example.<br>
|
|
||||||
Or check <a href="http://docs.docker.io/en/latest/installation/ubuntulinux/">more detailed installation instructions</a>
|
|
||||||
</ol>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="contentblock">
|
|
||||||
<h2>Contributing to Docker</h2>
|
|
||||||
|
|
||||||
<p>Want to hack on Docker? Awesome! We have some <a href="http://docs.docker.io/en/latest/contributing/contributing/">instructions to get you started</a>. They are probably not perfect, please let us know if anything feels wrong or incomplete.</p>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="span6">
|
|
||||||
<section class="contentblock">
|
|
||||||
<h2>Quick install on other operating systems</h2>
|
|
||||||
<p><strong>For other operating systems we recommend and provide a streamlined install with virtualbox,
|
|
||||||
vagrant and an Ubuntu virtual machine.</strong></p>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li><a href="http://docs.docker.io/en/latest/installation/vagrant/">Mac OS X and other linuxes</a></li>
|
|
||||||
<li><a href="http://docs.docker.io/en/latest/installation/windows/">Windows</a></li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="contentblock">
|
|
||||||
<h2>Questions? Want to get in touch?</h2>
|
|
||||||
<p>There are several ways to get in touch:</p>
|
|
||||||
<p><strong>Join the discussion on IRC.</strong> We can be found in the <a href="irc://chat.freenode.net#docker">#docker</a> channel on chat.freenode.net</p>
|
|
||||||
<p><strong>Discussions</strong> happen on our google group: <a href="https://groups.google.com/d/forum/docker-club">docker-club at googlegroups.com</a></p>
|
|
||||||
<p>All our <strong>development and decisions</strong> are made out in the open on Github <a href="http://www.github.com/dotcloud/docker">github.com/dotcloud/docker</a></p>
|
|
||||||
<p><strong>Get help on using Docker</strong> by asking on <a href="http://stackoverflow.com/tags/docker/">Stackoverflow</a></p>
|
|
||||||
<p>And of course, <strong>tweet</strong> your tweets to <a href="http://twitter.com/getdocker/">twitter.com/getdocker</a></p>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
|
|
||||||
<section class="contentblock">
|
|
||||||
<div id="wufoo-z7x3p3">
|
|
||||||
Fill out my <a href="http://dotclouddocker.wufoo.com/forms/z7x3p3">online form</a>.
|
|
||||||
</div>
|
|
||||||
<script type="text/javascript">var z7x3p3;(function(d, t) {
|
|
||||||
var s = d.createElement(t), options = {
|
|
||||||
'userName':'dotclouddocker',
|
|
||||||
'formHash':'z7x3p3',
|
|
||||||
'autoResize':true,
|
|
||||||
'height':'577',
|
|
||||||
'async':true,
|
|
||||||
'header':'show'};
|
|
||||||
s.src = ('https:' == d.location.protocol ? 'https://' : 'http://') + 'wufoo.com/scripts/embed/form.js';
|
|
||||||
s.onload = s.onreadystatechange = function() {
|
|
||||||
var rs = this.readyState; if (rs) if (rs != 'complete') if (rs != 'loaded') return;
|
|
||||||
try { z7x3p3 = new WufooForm();z7x3p3.initialize(options);z7x3p3.display(); } catch (e) {}};
|
|
||||||
var scr = d.getElementsByTagName(t)[0], par = scr.parentNode; par.insertBefore(s, scr);
|
|
||||||
})(document, 'script');</script>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="container">
|
|
||||||
<footer id="footer" class="footer">
|
|
||||||
<div class="row">
|
|
||||||
<div class="span12 social">
|
|
||||||
<div class="tbox textright forceleftmargin social links pull-right">
|
|
||||||
<a class="twitter" href="http://twitter.com/getdocker">Twitter</a>
|
|
||||||
<a class="github" href="https://github.com/dotcloud/docker/">GitHub</a>
|
|
||||||
</div>
|
|
||||||
Docker is a project by <a href="http://www.dotcloud.com">dotCloud</a>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="emptyspace" style="height: 40px">
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<!-- bootstrap javascipts -->
|
|
||||||
<script src="../static/js/vendor/bootstrap.min.js" type="text/javascript"></script>
|
|
||||||
|
|
||||||
<!-- Google analytics -->
|
|
||||||
<script type="text/javascript">
|
|
||||||
|
|
||||||
var _gaq = _gaq || [];
|
|
||||||
_gaq.push(['_setAccount', 'UA-6096819-11']);
|
|
||||||
_gaq.push(['_setDomainName', 'docker.io']);
|
|
||||||
_gaq.push(['_setAllowLinker', true]);
|
|
||||||
_gaq.push(['_trackPageview']);
|
|
||||||
|
|
||||||
(function() {
|
|
||||||
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
|
|
||||||
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
|
|
||||||
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
|
|
||||||
})();
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,359 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
|
|
||||||
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
|
|
||||||
<!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
|
|
||||||
<!--[if gt IE 8]><!-->
|
|
||||||
<html class="no-js" xmlns="http://www.w3.org/1999/html"> <!--<![endif]-->
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
|
||||||
<meta name="google-site-verification" content="UxV66EKuPe87dgnH1sbrldrx6VsoWMrx5NjwkgUFxXI" />
|
|
||||||
<title>Docker - the Linux container engine</title>
|
|
||||||
|
|
||||||
<meta name="description" content="Docker encapsulates heterogeneous payloads in standard containers">
|
|
||||||
<meta name="viewport" content="width=device-width">
|
|
||||||
|
|
||||||
<!-- twitter bootstrap -->
|
|
||||||
<link rel="stylesheet" href="static/css/bootstrap.min.css">
|
|
||||||
<link rel="stylesheet" href="static/css/bootstrap-responsive.min.css">
|
|
||||||
|
|
||||||
<!-- main style file -->
|
|
||||||
<link rel="stylesheet" href="static/css/main.css">
|
|
||||||
|
|
||||||
<!-- vendor scripts -->
|
|
||||||
<script src="static/js/vendor/jquery-1.9.1.min.js" type="text/javascript" ></script>
|
|
||||||
<script src="static/js/vendor/modernizr-2.6.2-respond-1.1.0.min.js" type="text/javascript" ></script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.indexlabel {
|
|
||||||
float: left;
|
|
||||||
width: 150px;
|
|
||||||
display: block;
|
|
||||||
padding: 10px 20px 10px;
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight: 200;
|
|
||||||
background-color: #a30000;
|
|
||||||
color: white;
|
|
||||||
height: 22px;
|
|
||||||
}
|
|
||||||
.searchbutton {
|
|
||||||
font-size: 20px;
|
|
||||||
height: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.debug {
|
|
||||||
border: 1px red dotted;
|
|
||||||
}
|
|
||||||
.twitterblock {
|
|
||||||
min-height: 75px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.twitterblock img {
|
|
||||||
float: left;
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
|
|
||||||
|
|
||||||
</head>
|
|
||||||
|
|
||||||
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<div class="navbar navbar-fixed-top">
|
|
||||||
<div class="navbar-dotcloud">
|
|
||||||
<div class="container" style="text-align: center;">
|
|
||||||
|
|
||||||
|
|
||||||
<div class="pull-left" id="fork-us" style="margin-top: 16px;">
|
|
||||||
<a href="http://github.com/dotcloud/docker/"><img src="static/img/fork-us.png" alt="fork-icon"> Fork us on Github</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="pull-right" >
|
|
||||||
<ul class="nav">
|
|
||||||
<li id="nav-introduction" class="active"><a href="/">Introduction</a></li>
|
|
||||||
<li id="nav-gettingstarted"><a href="gettingstarted">Getting started</a></li>
|
|
||||||
<li id="nav-documentation" class=""><a href="http://docs.docker.io/en/latest/">Documentation</a></li>
|
|
||||||
<li id="nav-blog"><a href="http://blog.docker.io/">Blog</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="container" style="margin-top: 30px;">
|
|
||||||
<div class="row">
|
|
||||||
|
|
||||||
<div class="span12">
|
|
||||||
<section class="contentblock header">
|
|
||||||
|
|
||||||
<div class="span5" style="margin-bottom: 15px;">
|
|
||||||
<div style="text-align: center;" >
|
|
||||||
<img src="static/img/docker_letters_500px.png" alt="docker letters">
|
|
||||||
|
|
||||||
<h2>The Linux container engine</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="display: block; text-align: center; margin-top: 20px;">
|
|
||||||
|
|
||||||
<h5>
|
|
||||||
Docker is an open-source engine which automates the deployment of applications as highly portable, self-sufficient containers which are independent of hardware, language, framework, packaging system and hosting provider.
|
|
||||||
</h5>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div style="display: block; text-align: center; margin-top: 30px;">
|
|
||||||
<a class="btn btn-custom btn-large" href="gettingstarted/">Let's get started</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="span6" >
|
|
||||||
<div class="js-video" >
|
|
||||||
<iframe width="600" height="360" src="http://www.youtube.com/embed/wW9CAH9nSLs?feature=player_detailpage&rel=0&modestbranding=1&start=11" frameborder="0" allowfullscreen></iframe>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<br style="clear: both"/>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="container">
|
|
||||||
<div class="row">
|
|
||||||
|
|
||||||
<div class="span6">
|
|
||||||
<section class="contentblock">
|
|
||||||
<h4>Heterogeneous payloads</h4>
|
|
||||||
<p>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.</p>
|
|
||||||
<h4>Any server</h4>
|
|
||||||
<p>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.</p>
|
|
||||||
<h4>Isolation</h4>
|
|
||||||
<p>Docker isolates processes from each other and from the underlying host, using lightweight containers.</p>
|
|
||||||
<h4>Repeatability</h4>
|
|
||||||
<p>Because each container is isolated in its own filesystem, they behave the same regardless of where, when, and alongside what they run.</p>
|
|
||||||
</section>
|
|
||||||
<section class="contentblock">
|
|
||||||
<div class="container">
|
|
||||||
<div class="span2" style="margin-left: 0" >
|
|
||||||
<a href="http://dotcloud.theresumator.com/apply/mWjkD4/Software-Engineer.html" title="Job description"><img src="static/img/hiring_graphic.png" alt="we're hiring" width="140" style="margin-top: 25px"></a>
|
|
||||||
</div>
|
|
||||||
<div class="span4" style="margin-left: 0">
|
|
||||||
<h4>Do you think it is cool to hack on docker? Join us!</h4>
|
|
||||||
<ul>
|
|
||||||
<li>Work on open source</li>
|
|
||||||
<li>Program in Go</li>
|
|
||||||
</ul>
|
|
||||||
<a href="http://dotcloud.theresumator.com/apply/mWjkD4/Software-Engineer.html" title="Job description">read more</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
<div class="span6">
|
|
||||||
<section class="contentblock">
|
|
||||||
<h1>New! Docker Index</h1>
|
|
||||||
On the Docker Index you can find and explore pre-made container images. It allows you to share your images and download them.
|
|
||||||
|
|
||||||
<br><br>
|
|
||||||
<a href="https://index.docker.io" target="_blank">
|
|
||||||
<div class="indexlabel">
|
|
||||||
DOCKER index
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<input type="button" class="searchbutton" value="Search images"
|
|
||||||
onClick="window.open('https://index.docker.io')" />
|
|
||||||
|
|
||||||
</section>
|
|
||||||
<section class="contentblock">
|
|
||||||
<div id="wufoo-z7x3p3">
|
|
||||||
Fill out my <a href="http://dotclouddocker.wufoo.com/forms/z7x3p3">online form</a>.
|
|
||||||
</div>
|
|
||||||
<script type="text/javascript">var z7x3p3;(function(d, t) {
|
|
||||||
var s = d.createElement(t), options = {
|
|
||||||
'userName':'dotclouddocker',
|
|
||||||
'formHash':'z7x3p3',
|
|
||||||
'autoResize':true,
|
|
||||||
'height':'577',
|
|
||||||
'async':true,
|
|
||||||
'header':'show'};
|
|
||||||
s.src = ('https:' == d.location.protocol ? 'https://' : 'http://') + 'wufoo.com/scripts/embed/form.js';
|
|
||||||
s.onload = s.onreadystatechange = function() {
|
|
||||||
var rs = this.readyState; if (rs) if (rs != 'complete') if (rs != 'loaded') return;
|
|
||||||
try { z7x3p3 = new WufooForm();z7x3p3.initialize(options);z7x3p3.display(); } catch (e) {}};
|
|
||||||
var scr = d.getElementsByTagName(t)[0], par = scr.parentNode; par.insertBefore(s, scr);
|
|
||||||
})(document, 'script');</script>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="container">
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="span6">
|
|
||||||
<section class="contentblock twitterblock">
|
|
||||||
<img src="https://si0.twimg.com/profile_images/2707460527/252a64411a339184ff375a96fb68dcb0_bigger.png">
|
|
||||||
<em>Mitchell Hashimoto @mitchellh:</em> Docker launched today. It is incredible. They’re also working RIGHT NOW on a Vagrant provider. LXC is COMING!!
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
<div class="span6">
|
|
||||||
<section class="contentblock twitterblock">
|
|
||||||
<img src="https://si0.twimg.com/profile_images/1108290260/Adam_Jacob-114x150_original_bigger.jpg">
|
|
||||||
<em>Adam Jacob @adamhjk:</em> Docker is clearly the right idea. @solomonstre absolutely killed it. Containerized app deployment is the future, I think.
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="span6">
|
|
||||||
<section class="contentblock twitterblock">
|
|
||||||
<img src="https://si0.twimg.com/profile_images/14872832/twitter_pic_bigger.jpg">
|
|
||||||
<em>Matt Townsend @mtownsend:</em> I have a serious code crush on docker.io - it's Lego for PaaS. Motherfucking awesome Lego.
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
<div class="span6">
|
|
||||||
<section class="contentblock twitterblock">
|
|
||||||
<img src="https://si0.twimg.com/profile_images/1312352395/rupert-259x300_bigger.jpg">
|
|
||||||
<em>Rob Harrop @robertharrop:</em> Impressed by @getdocker - it's all kinds of magic. Serious rethink of AWS architecture happening @skillsmatter.
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="span6">
|
|
||||||
<section class="contentblock twitterblock">
|
|
||||||
<img src="https://twimg0-a.akamaihd.net/profile_images/2491994496/rbevyyq6ykp6bnoby2je_bigger.jpeg">
|
|
||||||
<em>John Willis @botchagalupe:</em> IMHO docker is to paas what chef was to Iaas 4 years ago
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
<div class="span6">
|
|
||||||
<section class="contentblock twitterblock">
|
|
||||||
<img src="https://twimg0-a.akamaihd.net/profile_images/3348427561/9d7f08f1e103a16c8debd169301b9944_bigger.jpeg">
|
|
||||||
<em>John Feminella @superninjarobot:</em> So, @getdocker is pure excellence. If you've ever wished for arbitrary, PaaS-agnostic, lxc/aufs Linux containers, this is your jam!
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="span6">
|
|
||||||
<section class="contentblock twitterblock">
|
|
||||||
<img src="https://si0.twimg.com/profile_images/3408403010/4496ccdd14e9b7285eca04c31a740207_bigger.jpeg">
|
|
||||||
<em>David Romulan @destructuring:</em> I haven't had this much fun since AWS
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
<div class="span6">
|
|
||||||
<section class="contentblock twitterblock">
|
|
||||||
<img src="https://si0.twimg.com/profile_images/780893320/My_Avatar_bigger.jpg">
|
|
||||||
<em>Ricardo Gladwell @rgladwell:</em> wow @getdocker is either amazing or totally stupid
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="container">
|
|
||||||
<div class="row">
|
|
||||||
<div class="span6">
|
|
||||||
|
|
||||||
<section class="contentblock">
|
|
||||||
|
|
||||||
<h2>Notable features</h2>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>Filesystem isolation: each process container runs in a completely separate root filesystem.</li>
|
|
||||||
<li>Resource isolation: system resources like cpu and memory can be allocated differently to each process container, using cgroups.</li>
|
|
||||||
<li>Network isolation: each process container runs in its own network namespace, with a virtual interface and IP address of its own.</li>
|
|
||||||
<li>Copy-on-write: root filesystems are created using copy-on-write, which makes deployment extremely fast, memory-cheap and disk-cheap.</li>
|
|
||||||
<li>Logging: the standard streams (stdout/stderr/stdin) of each process container is collected and logged for real-time or batch retrieval.</li>
|
|
||||||
<li>Change management: changes to a container's filesystem can be committed into a new image and re-used to create more containers. No templating or manual configuration required.</li>
|
|
||||||
<li>Interactive shell: docker can allocate a pseudo-tty and attach to the standard input of any container, for example to run a throwaway interactive shell.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h2>Under the hood</h2>
|
|
||||||
|
|
||||||
<p>Under the hood, Docker is built on the following components:</p>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>The <a href="http://blog.dotcloud.com/kernel-secrets-from-the-paas-garage-part-24-c">cgroup</a> and <a href="http://blog.dotcloud.com/under-the-hood-linux-kernels-on-dotcloud-part">namespacing</a> capabilities of the Linux kernel;</li>
|
|
||||||
<li><a href="http://aufs.sourceforge.net/aufs.html">AUFS</a>, a powerful union filesystem with copy-on-write capabilities;</li>
|
|
||||||
<li>The <a href="http://golang.org">Go</a> programming language;</li>
|
|
||||||
<li><a href="http://lxc.sourceforge.net/">lxc</a>, a set of convenience scripts to simplify the creation of linux containers.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h2>Who started it</h2>
|
|
||||||
<p>
|
|
||||||
Docker is an open-source implementation of the deployment engine which powers <a href="http://dotcloud.com">dotCloud</a>, a popular Platform-as-a-Service.</p>
|
|
||||||
|
|
||||||
<p>It benefits directly from the experience accumulated over several years of large-scale operation and support of hundreds of thousands
|
|
||||||
of applications and databases.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="span6">
|
|
||||||
|
|
||||||
|
|
||||||
<section class="contentblock">
|
|
||||||
<h3 id="twitter">Twitter</h3>
|
|
||||||
<a class="twitter-timeline" href="https://twitter.com/getdocker" data-widget-id="312730839718957056">Tweets by @getdocker</a>
|
|
||||||
<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src="//platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");</script>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div> <!-- end container -->
|
|
||||||
|
|
||||||
|
|
||||||
<div class="container">
|
|
||||||
<footer id="footer" class="footer">
|
|
||||||
<div class="row">
|
|
||||||
<div class="span12">
|
|
||||||
<div class="tbox textright forceleftmargin social links pull-right">
|
|
||||||
<a class="twitter" href="http://twitter.com/getdocker">Twitter</a>
|
|
||||||
<a class="github" href="https://github.com/dotcloud/docker/">GitHub</a>
|
|
||||||
</div>
|
|
||||||
Docker is a project by <a href="http://www.dotcloud.com">dotCloud</a>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="emptyspace" style="height: 40px">
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- bootstrap javascipts -->
|
|
||||||
<script src="static/js/vendor/bootstrap.min.js" type="text/javascript"></script>
|
|
||||||
|
|
||||||
<!-- Google analytics -->
|
|
||||||
<script type="text/javascript">
|
|
||||||
|
|
||||||
var _gaq = _gaq || [];
|
|
||||||
_gaq.push(['_setAccount', 'UA-6096819-11']);
|
|
||||||
_gaq.push(['_setDomainName', 'docker.io']);
|
|
||||||
_gaq.push(['_setAllowLinker', true]);
|
|
||||||
_gaq.push(['_trackPageview']);
|
|
||||||
|
|
||||||
(function() {
|
|
||||||
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
|
|
||||||
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
|
|
||||||
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
|
|
||||||
})();
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,6 +0,0 @@
|
||||||
|
|
||||||
# rule to redirect original links created when hosted on github pages
|
|
||||||
rewrite ^/documentation/(.*).html http://docs.docker.io/en/latest/$1/ permanent;
|
|
||||||
|
|
||||||
# rewrite the stuff which was on the current page
|
|
||||||
rewrite ^/gettingstarted.html$ /gettingstarted/ permanent;
|
|
|
@ -1 +0,0 @@
|
||||||
../theme/docker/static
|
|
59
graph.go
59
graph.go
|
@ -1,9 +1,7 @@
|
||||||
package docker
|
package docker
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/dotcloud/docker/registry"
|
|
||||||
"github.com/dotcloud/docker/utils"
|
"github.com/dotcloud/docker/utils"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
@ -11,7 +9,6 @@ import (
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -19,9 +16,6 @@ import (
|
||||||
type Graph struct {
|
type Graph struct {
|
||||||
Root string
|
Root string
|
||||||
idIndex *utils.TruncIndex
|
idIndex *utils.TruncIndex
|
||||||
checksumLock map[string]*sync.Mutex
|
|
||||||
lockSumFile *sync.Mutex
|
|
||||||
lockSumMap *sync.Mutex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGraph instantiates a new graph at the given root path in the filesystem.
|
// NewGraph instantiates a new graph at the given root path in the filesystem.
|
||||||
|
@ -38,9 +32,6 @@ func NewGraph(root string) (*Graph, error) {
|
||||||
graph := &Graph{
|
graph := &Graph{
|
||||||
Root: abspath,
|
Root: abspath,
|
||||||
idIndex: utils.NewTruncIndex(),
|
idIndex: utils.NewTruncIndex(),
|
||||||
checksumLock: make(map[string]*sync.Mutex),
|
|
||||||
lockSumFile: &sync.Mutex{},
|
|
||||||
lockSumMap: &sync.Mutex{},
|
|
||||||
}
|
}
|
||||||
if err := graph.restore(); err != nil {
|
if err := graph.restore(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -99,11 +90,6 @@ func (graph *Graph) Get(name string) (*Image, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
graph.lockSumMap.Lock()
|
|
||||||
defer graph.lockSumMap.Unlock()
|
|
||||||
if _, exists := graph.checksumLock[img.ID]; !exists {
|
|
||||||
graph.checksumLock[img.ID] = &sync.Mutex{}
|
|
||||||
}
|
|
||||||
return img, nil
|
return img, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,16 +109,15 @@ func (graph *Graph) Create(layerData Archive, container *Container, comment, aut
|
||||||
img.Container = container.ID
|
img.Container = container.ID
|
||||||
img.ContainerConfig = *container.Config
|
img.ContainerConfig = *container.Config
|
||||||
}
|
}
|
||||||
if err := graph.Register(layerData, layerData != nil, img); err != nil {
|
if err := graph.Register(nil, layerData, img); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
go img.Checksum()
|
|
||||||
return img, nil
|
return img, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register imports a pre-existing image into the graph.
|
// Register imports a pre-existing image into the graph.
|
||||||
// FIXME: pass img as first argument
|
// FIXME: pass img as first argument
|
||||||
func (graph *Graph) Register(layerData Archive, store bool, img *Image) error {
|
func (graph *Graph) Register(jsonData []byte, layerData Archive, img *Image) error {
|
||||||
if err := ValidateID(img.ID); err != nil {
|
if err := ValidateID(img.ID); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -145,7 +130,7 @@ func (graph *Graph) Register(layerData Archive, store bool, img *Image) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Mktemp failed: %s", err)
|
return fmt.Errorf("Mktemp failed: %s", err)
|
||||||
}
|
}
|
||||||
if err := StoreImage(img, layerData, tmp, store); err != nil {
|
if err := StoreImage(img, jsonData, layerData, tmp); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// Commit
|
// Commit
|
||||||
|
@ -154,7 +139,6 @@ func (graph *Graph) Register(layerData Archive, store bool, img *Image) error {
|
||||||
}
|
}
|
||||||
img.graph = graph
|
img.graph = graph
|
||||||
graph.idIndex.Add(img.ID)
|
graph.idIndex.Add(img.ID)
|
||||||
graph.checksumLock[img.ID] = &sync.Mutex{}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -311,40 +295,3 @@ func (graph *Graph) Heads() (map[string]*Image, error) {
|
||||||
func (graph *Graph) imageRoot(id string) string {
|
func (graph *Graph) imageRoot(id string) string {
|
||||||
return path.Join(graph.Root, id)
|
return path.Join(graph.Root, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (graph *Graph) getStoredChecksums() (map[string]string, error) {
|
|
||||||
checksums := make(map[string]string)
|
|
||||||
// FIXME: Store the checksum in memory
|
|
||||||
|
|
||||||
if checksumDict, err := ioutil.ReadFile(path.Join(graph.Root, "checksums")); err == nil {
|
|
||||||
if err := json.Unmarshal(checksumDict, &checksums); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return checksums, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (graph *Graph) storeChecksums(checksums map[string]string) error {
|
|
||||||
checksumJSON, err := json.Marshal(checksums)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := ioutil.WriteFile(path.Join(graph.Root, "checksums"), checksumJSON, 0600); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (graph *Graph) UpdateChecksums(newChecksums map[string]*registry.ImgData) error {
|
|
||||||
graph.lockSumFile.Lock()
|
|
||||||
defer graph.lockSumFile.Unlock()
|
|
||||||
|
|
||||||
localChecksums, err := graph.getStoredChecksums()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for id, elem := range newChecksums {
|
|
||||||
localChecksums[id] = elem.Checksum
|
|
||||||
}
|
|
||||||
return graph.storeChecksums(localChecksums)
|
|
||||||
}
|
|
||||||
|
|
|
@ -38,7 +38,7 @@ func TestInterruptedRegister(t *testing.T) {
|
||||||
Comment: "testing",
|
Comment: "testing",
|
||||||
Created: time.Now(),
|
Created: time.Now(),
|
||||||
}
|
}
|
||||||
go graph.Register(badArchive, false, image)
|
go graph.Register(nil, badArchive, image)
|
||||||
time.Sleep(200 * time.Millisecond)
|
time.Sleep(200 * time.Millisecond)
|
||||||
w.CloseWithError(errors.New("But I'm not a tarball!")) // (Nobody's perfect, darling)
|
w.CloseWithError(errors.New("But I'm not a tarball!")) // (Nobody's perfect, darling)
|
||||||
if _, err := graph.Get(image.ID); err == nil {
|
if _, err := graph.Get(image.ID); err == nil {
|
||||||
|
@ -49,7 +49,7 @@ func TestInterruptedRegister(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if err := graph.Register(goodArchive, false, image); err != nil {
|
if err := graph.Register(nil, goodArchive, image); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -95,7 +95,7 @@ func TestRegister(t *testing.T) {
|
||||||
Comment: "testing",
|
Comment: "testing",
|
||||||
Created: time.Now(),
|
Created: time.Now(),
|
||||||
}
|
}
|
||||||
err = graph.Register(archive, false, image)
|
err = graph.Register(nil, archive, image)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -225,7 +225,7 @@ func TestDelete(t *testing.T) {
|
||||||
t.Fatal(err)
|
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(nil, archive, img1); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if err := graph.Delete(img1.ID); err != nil {
|
if err := graph.Delete(img1.ID); err != nil {
|
||||||
|
|
150
image.go
150
image.go
|
@ -2,7 +2,6 @@ package docker
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/sha256"
|
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -14,6 +13,7 @@ import (
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
@ -47,6 +47,19 @@ func LoadImage(root string) (*Image, error) {
|
||||||
if err := ValidateID(img.ID); err != nil {
|
if err := ValidateID(img.ID); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if buf, err := ioutil.ReadFile(path.Join(root, "layersize")); err != nil {
|
||||||
|
if !os.IsNotExist(err) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if size, err := strconv.Atoi(string(buf)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
img.Size = int64(size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check that the filesystem layer exists
|
// Check that the filesystem layer exists
|
||||||
if stat, err := os.Stat(layerPath(root)); err != nil {
|
if stat, err := os.Stat(layerPath(root)); err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
|
@ -59,7 +72,7 @@ func LoadImage(root string) (*Image, error) {
|
||||||
return img, nil
|
return img, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func StoreImage(img *Image, layerData Archive, root string, store bool) error {
|
func StoreImage(img *Image, jsonData []byte, layerData Archive, root string) error {
|
||||||
// Check that root doesn't already exist
|
// Check that root doesn't already exist
|
||||||
if _, err := os.Stat(root); err == nil {
|
if _, err := os.Stat(root); err == nil {
|
||||||
return fmt.Errorf("Image %s already exists", img.ID)
|
return fmt.Errorf("Image %s already exists", img.ID)
|
||||||
|
@ -72,26 +85,6 @@ func StoreImage(img *Image, layerData Archive, root string, store bool) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if store {
|
|
||||||
layerArchive := layerArchivePath(root)
|
|
||||||
file, err := os.OpenFile(layerArchive, os.O_WRONLY|os.O_CREATE, 0600)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// FIXME: Retrieve the image layer size from here?
|
|
||||||
if _, err := io.Copy(file, layerData); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// FIXME: Don't close/open, read/write instead of Copy
|
|
||||||
file.Close()
|
|
||||||
|
|
||||||
file, err = os.Open(layerArchive)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
layerData = file
|
|
||||||
}
|
|
||||||
// If layerData is not nil, unpack it into the new layer
|
// If layerData is not nil, unpack it into the new layer
|
||||||
if layerData != nil {
|
if layerData != nil {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
@ -102,18 +95,10 @@ func StoreImage(img *Image, layerData Archive, root string, store bool) error {
|
||||||
utils.Debugf("Untar time: %vs\n", time.Now().Sub(start).Seconds())
|
utils.Debugf("Untar time: %vs\n", time.Now().Sub(start).Seconds())
|
||||||
}
|
}
|
||||||
|
|
||||||
return StoreSize(img, root)
|
// If raw json is provided, then use it
|
||||||
}
|
if jsonData != nil {
|
||||||
|
return ioutil.WriteFile(jsonPath(root), jsonData, 0600)
|
||||||
func StoreSize(img *Image, root string) error {
|
} else { // Otherwise, unmarshal the image
|
||||||
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
|
|
||||||
jsonData, err := json.Marshal(img)
|
jsonData, err := json.Marshal(img)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -121,6 +106,25 @@ func StoreSize(img *Image, root string) error {
|
||||||
if err := ioutil.WriteFile(jsonPath(root), jsonData, 0600); err != nil {
|
if err := ioutil.WriteFile(jsonPath(root), jsonData, 0600); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return StoreSize(img, root)
|
||||||
|
}
|
||||||
|
|
||||||
|
func StoreSize(img *Image, root string) error {
|
||||||
|
layer := layerPath(root)
|
||||||
|
|
||||||
|
var totalSize int64 = 0
|
||||||
|
filepath.Walk(layer, func(path string, fileInfo os.FileInfo, err error) error {
|
||||||
|
totalSize += fileInfo.Size()
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
img.Size = totalSize
|
||||||
|
|
||||||
|
if err := ioutil.WriteFile(path.Join(root, "layersize"), []byte(strconv.Itoa(int(totalSize))), 0600); err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,10 +132,6 @@ func layerPath(root string) string {
|
||||||
return path.Join(root, "layer")
|
return path.Join(root, "layer")
|
||||||
}
|
}
|
||||||
|
|
||||||
func layerArchivePath(root string) string {
|
|
||||||
return path.Join(root, "layer.tar.xz")
|
|
||||||
}
|
|
||||||
|
|
||||||
func jsonPath(root string) string {
|
func jsonPath(root string) string {
|
||||||
return path.Join(root, "json")
|
return path.Join(root, "json")
|
||||||
}
|
}
|
||||||
|
@ -308,80 +308,6 @@ func (img *Image) layer() (string, error) {
|
||||||
return layerPath(root), nil
|
return layerPath(root), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (img *Image) Checksum() (string, error) {
|
|
||||||
img.graph.checksumLock[img.ID].Lock()
|
|
||||||
defer img.graph.checksumLock[img.ID].Unlock()
|
|
||||||
|
|
||||||
root, err := img.root()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
checksums, err := img.graph.getStoredChecksums()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if checksum, ok := checksums[img.ID]; ok {
|
|
||||||
return checksum, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
layer, err := img.layer()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
jsonData, err := ioutil.ReadFile(jsonPath(root))
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
var layerData io.Reader
|
|
||||||
|
|
||||||
if file, err := os.Open(layerArchivePath(root)); err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
layerData, err = Tar(layer, Xz)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
defer file.Close()
|
|
||||||
layerData = file
|
|
||||||
}
|
|
||||||
|
|
||||||
h := sha256.New()
|
|
||||||
if _, err := h.Write(jsonData); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if _, err := h.Write([]byte("\n")); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := io.Copy(h, layerData); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
hash := "sha256:" + hex.EncodeToString(h.Sum(nil))
|
|
||||||
|
|
||||||
// Reload the json file to make sure not to overwrite faster sums
|
|
||||||
img.graph.lockSumFile.Lock()
|
|
||||||
defer img.graph.lockSumFile.Unlock()
|
|
||||||
|
|
||||||
checksums, err = img.graph.getStoredChecksums()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
checksums[img.ID] = hash
|
|
||||||
|
|
||||||
// Dump the checksums to disc
|
|
||||||
if err := img.graph.storeChecksums(checksums); err != nil {
|
|
||||||
return hash, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return hash, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (img *Image) getParentsSize(size int64) int64 {
|
func (img *Image) getParentsSize(size int64) int64 {
|
||||||
parentImage, err := img.GetParent()
|
parentImage, err := img.GetParent()
|
||||||
if err != nil || parentImage == nil {
|
if err != nil || parentImage == nil {
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
description "Run docker"
|
description "Run docker"
|
||||||
|
|
||||||
start on runlevel [2345]
|
start on filesystem or runlevel [2345]
|
||||||
stop on starting rc RUNLEVEL=[016]
|
stop on runlevel [!2345]
|
||||||
|
|
||||||
respawn
|
respawn
|
||||||
|
|
||||||
script
|
exec /usr/bin/docker -d
|
||||||
/usr/bin/docker -d
|
|
||||||
end script
|
|
||||||
|
|
|
@ -17,8 +17,10 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrAlreadyExists = errors.New("Image already exists")
|
var (
|
||||||
var ErrInvalidRepositoryName = errors.New("Invalid repository name (ex: \"registry.domain.tld/myrepos\")")
|
ErrAlreadyExists = errors.New("Image already exists")
|
||||||
|
ErrInvalidRepositoryName = errors.New("Invalid repository name (ex: \"registry.domain.tld/myrepos\")")
|
||||||
|
)
|
||||||
|
|
||||||
func pingRegistryEndpoint(endpoint string) error {
|
func pingRegistryEndpoint(endpoint string) error {
|
||||||
if endpoint == auth.IndexServerAddress() {
|
if endpoint == auth.IndexServerAddress() {
|
||||||
|
@ -109,7 +111,14 @@ func doWithCookies(c *http.Client, req *http.Request) (*http.Response, error) {
|
||||||
for _, cookie := range c.Jar.Cookies(req.URL) {
|
for _, cookie := range c.Jar.Cookies(req.URL) {
|
||||||
req.AddCookie(cookie)
|
req.AddCookie(cookie)
|
||||||
}
|
}
|
||||||
return c.Do(req)
|
res, err := c.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(res.Cookies()) > 0 {
|
||||||
|
c.Jar.SetCookies(req.URL, res.Cookies())
|
||||||
|
}
|
||||||
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the user agent field in the header based on the versions provided
|
// Set the user agent field in the header based on the versions provided
|
||||||
|
@ -135,10 +144,10 @@ func (r *Registry) GetRemoteHistory(imgID, registry string, token []string) ([]s
|
||||||
}
|
}
|
||||||
req.Header.Set("Authorization", "Token "+strings.Join(token, ", "))
|
req.Header.Set("Authorization", "Token "+strings.Join(token, ", "))
|
||||||
r.setUserAgent(req)
|
r.setUserAgent(req)
|
||||||
res, err := r.client.Do(req)
|
res, err := doWithCookies(r.client, req)
|
||||||
if err != nil || res.StatusCode != 200 {
|
if err != nil || res.StatusCode != 200 {
|
||||||
if res != nil {
|
if res != nil {
|
||||||
return nil, fmt.Errorf("Internal server error: %d trying to fetch remote history for %s", res.StatusCode, imgID)
|
return nil, utils.NewHTTPRequestError(fmt.Sprintf("Internal server error: %d trying to fetch remote history for %s", res.StatusCode, imgID), res)
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -182,13 +191,13 @@ func (r *Registry) GetRemoteImageJSON(imgID, registry string, token []string) ([
|
||||||
}
|
}
|
||||||
req.Header.Set("Authorization", "Token "+strings.Join(token, ", "))
|
req.Header.Set("Authorization", "Token "+strings.Join(token, ", "))
|
||||||
r.setUserAgent(req)
|
r.setUserAgent(req)
|
||||||
res, err := r.client.Do(req)
|
res, err := doWithCookies(r.client, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, -1, 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, -1, fmt.Errorf("HTTP code %d", res.StatusCode)
|
return nil, -1, utils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d", res.StatusCode), res)
|
||||||
}
|
}
|
||||||
|
|
||||||
imageSize, err := strconv.Atoi(res.Header.Get("X-Docker-Size"))
|
imageSize, err := strconv.Atoi(res.Header.Get("X-Docker-Size"))
|
||||||
|
@ -210,7 +219,7 @@ func (r *Registry) GetRemoteImageLayer(imgID, registry string, token []string) (
|
||||||
}
|
}
|
||||||
req.Header.Set("Authorization", "Token "+strings.Join(token, ", "))
|
req.Header.Set("Authorization", "Token "+strings.Join(token, ", "))
|
||||||
r.setUserAgent(req)
|
r.setUserAgent(req)
|
||||||
res, err := r.client.Do(req)
|
res, err := doWithCookies(r.client, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -231,7 +240,7 @@ func (r *Registry) GetRemoteTags(registries []string, repository string, token [
|
||||||
}
|
}
|
||||||
req.Header.Set("Authorization", "Token "+strings.Join(token, ", "))
|
req.Header.Set("Authorization", "Token "+strings.Join(token, ", "))
|
||||||
r.setUserAgent(req)
|
r.setUserAgent(req)
|
||||||
res, err := r.client.Do(req)
|
res, err := doWithCookies(r.client, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -259,8 +268,11 @@ func (r *Registry) GetRemoteTags(registries []string, repository string, token [
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Registry) GetRepositoryData(indexEp, remote string) (*RepositoryData, error) {
|
func (r *Registry) GetRepositoryData(indexEp, remote string) (*RepositoryData, error) {
|
||||||
|
|
||||||
repositoryTarget := fmt.Sprintf("%srepositories/%s/images", indexEp, remote)
|
repositoryTarget := fmt.Sprintf("%srepositories/%s/images", indexEp, remote)
|
||||||
|
|
||||||
|
utils.Debugf("[registry] Calling GET %s", repositoryTarget)
|
||||||
|
|
||||||
req, err := r.opaqueRequest("GET", repositoryTarget, nil)
|
req, err := r.opaqueRequest("GET", repositoryTarget, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -277,12 +289,12 @@ func (r *Registry) GetRepositoryData(indexEp, remote string) (*RepositoryData, e
|
||||||
}
|
}
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
if res.StatusCode == 401 {
|
if res.StatusCode == 401 {
|
||||||
return nil, fmt.Errorf("Please login first (HTTP code %d)", res.StatusCode)
|
return nil, utils.NewHTTPRequestError(fmt.Sprintf("Please login first (HTTP code %d)", res.StatusCode), res)
|
||||||
}
|
}
|
||||||
// TODO: Right now we're ignoring checksums in the response body.
|
// TODO: Right now we're ignoring checksums in the response body.
|
||||||
// In the future, we need to use them to check image validity.
|
// In the future, we need to use them to check image validity.
|
||||||
if res.StatusCode != 200 {
|
if res.StatusCode != 200 {
|
||||||
return nil, fmt.Errorf("HTTP code: %d", res.StatusCode)
|
return nil, utils.NewHTTPRequestError(fmt.Sprintf("HTTP code: %d", res.StatusCode), res)
|
||||||
}
|
}
|
||||||
|
|
||||||
var tokens []string
|
var tokens []string
|
||||||
|
@ -323,19 +335,17 @@ func (r *Registry) GetRepositoryData(indexEp, remote string) (*RepositoryData, e
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Push a local image to the registry
|
func (r *Registry) PushImageChecksumRegistry(imgData *ImgData, registry string, token []string) error {
|
||||||
func (r *Registry) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, registry string, token []string) error {
|
|
||||||
// FIXME: try json with UTF8
|
utils.Debugf("[registry] Calling PUT %s", registry+"images/"+imgData.ID+"/checksum")
|
||||||
req, err := http.NewRequest("PUT", registry+"images/"+imgData.ID+"/json", strings.NewReader(string(jsonRaw)))
|
|
||||||
|
req, err := http.NewRequest("PUT", registry+"images/"+imgData.ID+"/checksum", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
req.Header.Add("Content-type", "application/json")
|
|
||||||
req.Header.Set("Authorization", "Token "+strings.Join(token, ","))
|
req.Header.Set("Authorization", "Token "+strings.Join(token, ","))
|
||||||
req.Header.Set("X-Docker-Checksum", imgData.Checksum)
|
req.Header.Set("X-Docker-Checksum", imgData.Checksum)
|
||||||
r.setUserAgent(req)
|
|
||||||
|
|
||||||
utils.Debugf("Setting checksum for %s: %s", imgData.ID, imgData.Checksum)
|
|
||||||
res, err := doWithCookies(r.client, req)
|
res, err := doWithCookies(r.client, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Failed to upload metadata: %s", err)
|
return fmt.Errorf("Failed to upload metadata: %s", err)
|
||||||
|
@ -360,29 +370,68 @@ func (r *Registry) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, regis
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Registry) PushImageLayerRegistry(imgID string, layer io.Reader, registry string, token []string) error {
|
// Push a local image to the registry
|
||||||
req, err := http.NewRequest("PUT", registry+"images/"+imgID+"/layer", layer)
|
func (r *Registry) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, registry string, token []string) error {
|
||||||
|
|
||||||
|
utils.Debugf("[registry] Calling PUT %s", registry+"images/"+imgData.ID+"/json")
|
||||||
|
|
||||||
|
req, err := http.NewRequest("PUT", registry+"images/"+imgData.ID+"/json", bytes.NewReader(jsonRaw))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
req.Header.Add("Content-type", "application/json")
|
||||||
|
req.Header.Set("Authorization", "Token "+strings.Join(token, ","))
|
||||||
|
r.setUserAgent(req)
|
||||||
|
|
||||||
|
res, err := doWithCookies(r.client, req)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed to upload metadata: %s", err)
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
if res.StatusCode != 200 {
|
||||||
|
errBody, err := ioutil.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
return utils.NewHTTPRequestError(fmt.Sprint("HTTP code %d while uploading metadata and error when trying to parse response body: %s", res.StatusCode, err), res)
|
||||||
|
}
|
||||||
|
var jsonBody map[string]string
|
||||||
|
if err := json.Unmarshal(errBody, &jsonBody); err != nil {
|
||||||
|
errBody = []byte(err.Error())
|
||||||
|
} else if jsonBody["error"] == "Image already exists" {
|
||||||
|
return ErrAlreadyExists
|
||||||
|
}
|
||||||
|
return utils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d while uploading metadata: %s", res.StatusCode, errBody), res)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Registry) PushImageLayerRegistry(imgID string, layer io.Reader, registry string, token []string, jsonRaw []byte) (checksum string, err error) {
|
||||||
|
|
||||||
|
utils.Debugf("[registry] Calling PUT %s", registry+"images/"+imgID+"/layer")
|
||||||
|
|
||||||
|
tarsumLayer := &utils.TarSum{Reader: layer}
|
||||||
|
|
||||||
|
req, err := http.NewRequest("PUT", registry+"images/"+imgID+"/layer", tarsumLayer)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
req.ContentLength = -1
|
req.ContentLength = -1
|
||||||
req.TransferEncoding = []string{"chunked"}
|
req.TransferEncoding = []string{"chunked"}
|
||||||
req.Header.Set("Authorization", "Token "+strings.Join(token, ","))
|
req.Header.Set("Authorization", "Token "+strings.Join(token, ","))
|
||||||
r.setUserAgent(req)
|
r.setUserAgent(req)
|
||||||
res, err := doWithCookies(r.client, req)
|
res, err := doWithCookies(r.client, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Failed to upload layer: %s", err)
|
return "", fmt.Errorf("Failed to upload layer: %s", err)
|
||||||
}
|
}
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
|
|
||||||
if res.StatusCode != 200 {
|
if res.StatusCode != 200 {
|
||||||
errBody, err := ioutil.ReadAll(res.Body)
|
errBody, err := ioutil.ReadAll(res.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("HTTP code %d while uploading metadata and error when trying to parse response body: %s", res.StatusCode, err)
|
return "", utils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d while uploading metadata and error when trying to parse response body: %s", res.StatusCode, err), res)
|
||||||
}
|
}
|
||||||
return fmt.Errorf("Received HTTP code %d while uploading layer: %s", res.StatusCode, errBody)
|
return "", utils.NewHTTPRequestError(fmt.Sprintf("Received HTTP code %d while uploading layer: %s", res.StatusCode, errBody), res)
|
||||||
}
|
}
|
||||||
return nil
|
return tarsumLayer.Sum(jsonRaw), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Registry) opaqueRequest(method, urlStr string, body io.Reader) (*http.Request, error) {
|
func (r *Registry) opaqueRequest(method, urlStr string, body io.Reader) (*http.Request, error) {
|
||||||
|
@ -414,13 +463,25 @@ func (r *Registry) PushRegistryTag(remote, revision, tag, registry string, token
|
||||||
}
|
}
|
||||||
res.Body.Close()
|
res.Body.Close()
|
||||||
if res.StatusCode != 200 && res.StatusCode != 201 {
|
if res.StatusCode != 200 && res.StatusCode != 201 {
|
||||||
return fmt.Errorf("Internal server error: %d trying to push tag %s on %s", res.StatusCode, tag, remote)
|
return utils.NewHTTPRequestError(fmt.Sprintf("Internal server error: %d trying to push tag %s on %s", res.StatusCode, tag, remote), res)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Registry) PushImageJSONIndex(indexEp, remote string, imgList []*ImgData, validate bool, regs []string) (*RepositoryData, error) {
|
func (r *Registry) PushImageJSONIndex(indexEp, remote string, imgList []*ImgData, validate bool, regs []string) (*RepositoryData, error) {
|
||||||
imgListJSON, err := json.Marshal(imgList)
|
cleanImgList := []*ImgData{}
|
||||||
|
|
||||||
|
if validate {
|
||||||
|
for _, elem := range imgList {
|
||||||
|
if elem.Checksum != "" {
|
||||||
|
cleanImgList = append(cleanImgList, elem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cleanImgList = imgList
|
||||||
|
}
|
||||||
|
|
||||||
|
imgListJSON, err := json.Marshal(cleanImgList)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -430,7 +491,7 @@ func (r *Registry) PushImageJSONIndex(indexEp, remote string, imgList []*ImgData
|
||||||
}
|
}
|
||||||
|
|
||||||
u := fmt.Sprintf("%srepositories/%s/%s", indexEp, remote, suffix)
|
u := fmt.Sprintf("%srepositories/%s/%s", indexEp, remote, suffix)
|
||||||
utils.Debugf("PUT %s", u)
|
utils.Debugf("[registry] PUT %s", u)
|
||||||
utils.Debugf("Image list pushed to index:\n%s\n", imgListJSON)
|
utils.Debugf("Image list pushed to index:\n%s\n", imgListJSON)
|
||||||
req, err := r.opaqueRequest("PUT", u, bytes.NewReader(imgListJSON))
|
req, err := r.opaqueRequest("PUT", u, bytes.NewReader(imgListJSON))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -479,7 +540,7 @@ func (r *Registry) PushImageJSONIndex(indexEp, remote string, imgList []*ImgData
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("Error: Status %d trying to push repository %s: %s", res.StatusCode, remote, errBody)
|
return nil, utils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to push repository %s: %s", res.StatusCode, remote, errBody), res)
|
||||||
}
|
}
|
||||||
if res.Header.Get("X-Docker-Token") != "" {
|
if res.Header.Get("X-Docker-Token") != "" {
|
||||||
tokens = res.Header["X-Docker-Token"]
|
tokens = res.Header["X-Docker-Token"]
|
||||||
|
@ -503,7 +564,7 @@ func (r *Registry) PushImageJSONIndex(indexEp, remote string, imgList []*ImgData
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("Error: Status %d trying to push checksums %s: %s", res.StatusCode, remote, errBody)
|
return nil, utils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to push checksums %s: %s", res.StatusCode, remote, errBody), res)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -525,7 +586,7 @@ func (r *Registry) SearchRepositories(term string) (*SearchResults, error) {
|
||||||
}
|
}
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
if res.StatusCode != 200 {
|
if res.StatusCode != 200 {
|
||||||
return nil, fmt.Errorf("Unexepected status code %d", res.StatusCode)
|
return nil, utils.NewHTTPRequestError(fmt.Sprintf("Unexepected status code %d", res.StatusCode), res)
|
||||||
}
|
}
|
||||||
rawData, err := ioutil.ReadAll(res.Body)
|
rawData, err := ioutil.ReadAll(res.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
150
server.go
150
server.go
|
@ -309,35 +309,34 @@ func (srv *Server) ImageHistory(name string) ([]APIHistory, error) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (srv *Server) ContainerTop(name string) ([]APITop, error) {
|
func (srv *Server) ContainerTop(name, ps_args string) (*APITop, error) {
|
||||||
if container := srv.runtime.Get(name); container != nil {
|
if container := srv.runtime.Get(name); container != nil {
|
||||||
output, err := exec.Command("lxc-ps", "--name", container.ID).CombinedOutput()
|
output, err := exec.Command("lxc-ps", "--name", container.ID, "--", ps_args).CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Error trying to use lxc-ps: %s (%s)", err, output)
|
return nil, fmt.Errorf("Error trying to use lxc-ps: %s (%s)", err, output)
|
||||||
}
|
}
|
||||||
var procs []APITop
|
procs := APITop{}
|
||||||
for i, line := range strings.Split(string(output), "\n") {
|
for i, line := range strings.Split(string(output), "\n") {
|
||||||
if i == 0 || len(line) == 0 {
|
if len(line) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
proc := APITop{}
|
words := []string{}
|
||||||
scanner := bufio.NewScanner(strings.NewReader(line))
|
scanner := bufio.NewScanner(strings.NewReader(line))
|
||||||
scanner.Split(bufio.ScanWords)
|
scanner.Split(bufio.ScanWords)
|
||||||
if !scanner.Scan() {
|
if !scanner.Scan() {
|
||||||
return nil, fmt.Errorf("Error trying to use lxc-ps")
|
return nil, fmt.Errorf("Error trying to use lxc-ps")
|
||||||
}
|
}
|
||||||
// no scanner.Text because we skip container id
|
// no scanner.Text because we skip container id
|
||||||
scanner.Scan()
|
for scanner.Scan() {
|
||||||
proc.PID = scanner.Text()
|
words = append(words, scanner.Text())
|
||||||
scanner.Scan()
|
|
||||||
proc.Tty = scanner.Text()
|
|
||||||
scanner.Scan()
|
|
||||||
proc.Time = scanner.Text()
|
|
||||||
scanner.Scan()
|
|
||||||
proc.Cmd = scanner.Text()
|
|
||||||
procs = append(procs, proc)
|
|
||||||
}
|
}
|
||||||
return procs, nil
|
if i == 0 {
|
||||||
|
procs.Titles = words
|
||||||
|
} else {
|
||||||
|
procs.Processes = append(procs.Processes, words)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &procs, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("No such container: %s", name)
|
return nil, fmt.Errorf("No such container: %s", name)
|
||||||
|
@ -439,7 +438,7 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgID, endpoin
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer layer.Close()
|
defer layer.Close()
|
||||||
if err := srv.runtime.graph.Register(utils.ProgressReader(layer, imgSize, out, sf.FormatProgress("Downloading", "%8v/%v (%v)"), sf), false, img); err != nil {
|
if err := srv.runtime.graph.Register(imgJSON, utils.ProgressReader(layer, imgSize, out, sf.FormatProgress("Downloading", "%8v/%v (%v)"), sf), img); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -455,12 +454,6 @@ func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, localName
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
utils.Debugf("Updating checksums")
|
|
||||||
// Reload the json file to make sure not to overwrite faster sums
|
|
||||||
if err := srv.runtime.graph.UpdateChecksums(repoData.ImgList); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
utils.Debugf("Retrieving the tag list")
|
utils.Debugf("Retrieving the tag list")
|
||||||
tagsList, err := r.GetRemoteTags(repoData.Endpoints, remoteName, repoData.Tokens)
|
tagsList, err := r.GetRemoteTags(repoData.Endpoints, remoteName, repoData.Tokens)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -598,41 +591,6 @@ func (srv *Server) ImagePull(localName string, tag string, out io.Writer, sf *ut
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieve the checksum of an image
|
|
||||||
// Priority:
|
|
||||||
// - Check on the stored checksums
|
|
||||||
// - Check if the archive exists, if it does not, ask the registry
|
|
||||||
// - If the archive does exists, process the checksum from it
|
|
||||||
// - If the archive does not exists and not found on registry, process checksum from layer
|
|
||||||
func (srv *Server) getChecksum(imageID string) (string, error) {
|
|
||||||
// FIXME: Use in-memory map instead of reading the file each time
|
|
||||||
if sums, err := srv.runtime.graph.getStoredChecksums(); err != nil {
|
|
||||||
return "", err
|
|
||||||
} else if checksum, exists := sums[imageID]; exists {
|
|
||||||
return checksum, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
img, err := srv.runtime.graph.Get(imageID)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := os.Stat(layerArchivePath(srv.runtime.graph.imageRoot(imageID))); err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
// TODO: Ask the registry for the checksum
|
|
||||||
// As the archive is not there, it is supposed to come from a pull.
|
|
||||||
} else {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
checksum, err := img.Checksum()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return checksum, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retrieve the all the images to be uploaded in the correct order
|
// Retrieve the all the images to be uploaded in the correct order
|
||||||
// Note: we can't use a map as it is not ordered
|
// Note: we can't use a map as it is not ordered
|
||||||
func (srv *Server) getImageList(localRepo map[string]string) ([]*registry.ImgData, error) {
|
func (srv *Server) getImageList(localRepo map[string]string) ([]*registry.ImgData, error) {
|
||||||
|
@ -649,13 +607,9 @@ func (srv *Server) getImageList(localRepo map[string]string) ([]*registry.ImgDat
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
imageSet[img.ID] = struct{}{}
|
imageSet[img.ID] = struct{}{}
|
||||||
checksum, err := srv.getChecksum(img.ID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
imgList = append([]*registry.ImgData{{
|
imgList = append([]*registry.ImgData{{
|
||||||
ID: img.ID,
|
ID: img.ID,
|
||||||
Checksum: checksum,
|
|
||||||
Tag: tag,
|
Tag: tag,
|
||||||
}}, imgList...)
|
}}, imgList...)
|
||||||
return nil
|
return nil
|
||||||
|
@ -666,7 +620,7 @@ func (srv *Server) getImageList(localRepo map[string]string) ([]*registry.ImgDat
|
||||||
|
|
||||||
func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, localName, remoteName string, localRepo map[string]string, indexEp string, sf *utils.StreamFormatter) error {
|
func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, localName, remoteName string, localRepo map[string]string, indexEp string, sf *utils.StreamFormatter) error {
|
||||||
out = utils.NewWriteFlusher(out)
|
out = utils.NewWriteFlusher(out)
|
||||||
out.Write(sf.FormatStatus("Processing checksums"))
|
|
||||||
imgList, err := srv.getImageList(localRepo)
|
imgList, err := srv.getImageList(localRepo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -690,9 +644,11 @@ func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, localName
|
||||||
out.Write(sf.FormatStatus("Image %s already pushed, skipping", elem.ID))
|
out.Write(sf.FormatStatus("Image %s already pushed, skipping", elem.ID))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err := srv.pushImage(r, out, remoteName, elem.ID, ep, repoData.Tokens, sf); err != nil {
|
if checksum, err := srv.pushImage(r, out, remoteName, elem.ID, ep, repoData.Tokens, sf); err != nil {
|
||||||
// FIXME: Continue on error?
|
// FIXME: Continue on error?
|
||||||
return err
|
return err
|
||||||
|
} else {
|
||||||
|
elem.Checksum = checksum
|
||||||
}
|
}
|
||||||
out.Write(sf.FormatStatus("Pushing tags for rev [%s] on {%s}", elem.ID, ep+"repositories/"+remoteName+"/tags/"+elem.Tag))
|
out.Write(sf.FormatStatus("Pushing tags for rev [%s] on {%s}", elem.ID, ep+"repositories/"+remoteName+"/tags/"+elem.Tag))
|
||||||
if err := r.PushRegistryTag(remoteName, elem.ID, elem.Tag, ep, repoData.Tokens); err != nil {
|
if err := r.PushRegistryTag(remoteName, elem.ID, elem.Tag, ep, repoData.Tokens); err != nil {
|
||||||
|
@ -708,64 +664,45 @@ func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, localName
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgID, ep string, token []string, sf *utils.StreamFormatter) error {
|
func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgID, ep string, token []string, sf *utils.StreamFormatter) (checksum string, err error) {
|
||||||
out = utils.NewWriteFlusher(out)
|
out = utils.NewWriteFlusher(out)
|
||||||
jsonRaw, err := ioutil.ReadFile(path.Join(srv.runtime.graph.Root, imgID, "json"))
|
jsonRaw, err := ioutil.ReadFile(path.Join(srv.runtime.graph.Root, imgID, "json"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Error while retreiving the path for {%s}: %s", imgID, err)
|
return "", fmt.Errorf("Error while retreiving the path for {%s}: %s", imgID, err)
|
||||||
}
|
}
|
||||||
out.Write(sf.FormatStatus("Pushing %s", imgID))
|
out.Write(sf.FormatStatus("Pushing %s", imgID))
|
||||||
|
|
||||||
// Make sure we have the image's checksum
|
|
||||||
checksum, err := srv.getChecksum(imgID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
imgData := ®istry.ImgData{
|
imgData := ®istry.ImgData{
|
||||||
ID: imgID,
|
ID: imgID,
|
||||||
Checksum: checksum,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send the json
|
// Send the json
|
||||||
if err := r.PushImageJSONRegistry(imgData, jsonRaw, ep, token); err != nil {
|
if err := r.PushImageJSONRegistry(imgData, jsonRaw, ep, token); err != nil {
|
||||||
if err == registry.ErrAlreadyExists {
|
if err == registry.ErrAlreadyExists {
|
||||||
out.Write(sf.FormatStatus("Image %s already pushed, skipping", imgData.ID))
|
out.Write(sf.FormatStatus("Image %s already pushed, skipping", imgData.ID))
|
||||||
return nil
|
return "", nil
|
||||||
}
|
}
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieve the tarball to be sent
|
layerData, err := srv.runtime.graph.TempLayerArchive(imgID, Uncompressed, sf, out)
|
||||||
var layerData *TempArchive
|
|
||||||
// If the archive exists, use it
|
|
||||||
file, err := os.Open(layerArchivePath(srv.runtime.graph.imageRoot(imgID)))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(err) {
|
return "", fmt.Errorf("Failed to generate layer archive: %s", err)
|
||||||
// If the archive does not exist, create one from the layer
|
|
||||||
layerData, err = srv.runtime.graph.TempLayerArchive(imgID, Xz, sf, out)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Failed to generate layer archive: %s", err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
defer file.Close()
|
|
||||||
st, err := file.Stat()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
layerData = &TempArchive{
|
|
||||||
File: file,
|
|
||||||
Size: st.Size(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send the layer
|
// Send the layer
|
||||||
if err := r.PushImageLayerRegistry(imgData.ID, utils.ProgressReader(layerData, int(layerData.Size), out, sf.FormatProgress("Pushing", "%8v/%v (%v)"), sf), ep, token); err != nil {
|
if checksum, err := r.PushImageLayerRegistry(imgData.ID, utils.ProgressReader(layerData, int(layerData.Size), out, sf.FormatProgress("Pushing", "%8v/%v (%v)"), sf), ep, token, jsonRaw); err != nil {
|
||||||
return err
|
return "", err
|
||||||
|
} else {
|
||||||
|
imgData.Checksum = checksum
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
|
// Send the checksum
|
||||||
|
if err := r.PushImageChecksumRegistry(imgData, ep, token); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return imgData.Checksum, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: Allow to interupt current push when new push of same image is done.
|
// FIXME: Allow to interupt current push when new push of same image is done.
|
||||||
|
@ -803,7 +740,7 @@ func (srv *Server) ImagePush(localName string, out io.Writer, sf *utils.StreamFo
|
||||||
|
|
||||||
var token []string
|
var token []string
|
||||||
out.Write(sf.FormatStatus("The push refers to an image: [%s]", localName))
|
out.Write(sf.FormatStatus("The push refers to an image: [%s]", localName))
|
||||||
if err := srv.pushImage(r, out, remoteName, img.ID, endpoint, token, sf); err != nil {
|
if _, err := srv.pushImage(r, out, remoteName, img.ID, endpoint, token, sf); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -995,6 +932,9 @@ func (srv *Server) deleteImage(img *Image, repoName, tag string) ([]APIRmi, erro
|
||||||
parsedRepo := strings.Split(repoAndTag, ":")[0]
|
parsedRepo := strings.Split(repoAndTag, ":")[0]
|
||||||
if strings.Contains(img.ID, repoName) {
|
if strings.Contains(img.ID, repoName) {
|
||||||
repoName = parsedRepo
|
repoName = parsedRepo
|
||||||
|
if len(srv.runtime.repositories.ByID()[img.ID]) == 1 && len(strings.Split(repoAndTag, ":")) > 1 {
|
||||||
|
tag = strings.Split(repoAndTag, ":")[1]
|
||||||
|
}
|
||||||
} else if repoName != parsedRepo {
|
} else if repoName != parsedRepo {
|
||||||
// the id belongs to multiple repos, like base:latest and user:test,
|
// the id belongs to multiple repos, like base:latest and user:test,
|
||||||
// in that case return conflict
|
// in that case return conflict
|
||||||
|
@ -1037,13 +977,7 @@ func (srv *Server) ImageDelete(name string, autoPrune bool) ([]APIRmi, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var tag string
|
name, tag := utils.ParseRepositoryTag(name)
|
||||||
if strings.Contains(name, ":") {
|
|
||||||
nameParts := strings.Split(name, ":")
|
|
||||||
name = nameParts[0]
|
|
||||||
tag = nameParts[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
return srv.deleteImage(img, name, tag)
|
return srv.deleteImage(img, name, tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
126
server_test.go
126
server_test.go
|
@ -2,6 +2,7 @@ package docker
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/dotcloud/docker/utils"
|
"github.com/dotcloud/docker/utils"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
@ -20,16 +21,20 @@ func TestContainerTagImageDelete(t *testing.T) {
|
||||||
if err := srv.runtime.repositories.Set("utest", "tag1", unitTestImageName, false); err != nil {
|
if err := srv.runtime.repositories.Set("utest", "tag1", unitTestImageName, false); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := srv.runtime.repositories.Set("utest/docker", "tag2", unitTestImageName, false); err != nil {
|
if err := srv.runtime.repositories.Set("utest/docker", "tag2", unitTestImageName, false); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
if err := srv.runtime.repositories.Set("utest:5000/docker", "tag3", unitTestImageName, false); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
images, err := srv.Images(false, "")
|
images, err := srv.Images(false, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(images) != len(initialImages)+2 {
|
if len(images) != len(initialImages)+3 {
|
||||||
t.Errorf("Expected %d images, %d found", len(initialImages)+2, len(images))
|
t.Errorf("Expected %d images, %d found", len(initialImages)+2, len(images))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,6 +47,19 @@ func TestContainerTagImageDelete(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(images) != len(initialImages)+2 {
|
||||||
|
t.Errorf("Expected %d images, %d found", len(initialImages)+2, len(images))
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := srv.ImageDelete("utest:5000/docker:tag3", true); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
images, err = srv.Images(false, "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
if len(images) != len(initialImages)+1 {
|
if len(images) != len(initialImages)+1 {
|
||||||
t.Errorf("Expected %d images, %d found", len(initialImages)+1, len(images))
|
t.Errorf("Expected %d images, %d found", len(initialImages)+1, len(images))
|
||||||
}
|
}
|
||||||
|
@ -90,6 +108,27 @@ func TestCreateRm(t *testing.T) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCommit(t *testing.T) {
|
||||||
|
runtime := mkRuntime(t)
|
||||||
|
defer nuke(runtime)
|
||||||
|
|
||||||
|
srv := &Server{runtime: runtime}
|
||||||
|
|
||||||
|
config, _, _, err := ParseRun([]string{GetTestImage(runtime).ID, "/bin/cat"}, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := srv.ContainerCreate(config)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := srv.ContainerCommit(id, "testrepo", "testtag", "", "", config); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestCreateStartRestartStopStartKillRm(t *testing.T) {
|
func TestCreateStartRestartStopStartKillRm(t *testing.T) {
|
||||||
runtime := mkRuntime(t)
|
runtime := mkRuntime(t)
|
||||||
defer nuke(runtime)
|
defer nuke(runtime)
|
||||||
|
@ -203,3 +242,88 @@ func TestLogEvent(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRmi(t *testing.T) {
|
||||||
|
runtime := mkRuntime(t)
|
||||||
|
defer nuke(runtime)
|
||||||
|
srv := &Server{runtime: runtime}
|
||||||
|
|
||||||
|
initialImages, err := srv.Images(false, "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
config, hostConfig, _, err := ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
containerID, err := srv.ContainerCreate(config)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
//To remove
|
||||||
|
err = srv.ContainerStart(containerID, hostConfig)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
imageID, err := srv.ContainerCommit(containerID, "test", "", "", "", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = srv.ContainerTag(imageID, "test", "0.1", false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
containerID, err = srv.ContainerCreate(config)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
//To remove
|
||||||
|
err = srv.ContainerStart(containerID, hostConfig)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = srv.ContainerCommit(containerID, "test", "", "", "", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
images, err := srv.Images(false, "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(images)-len(initialImages) != 2 {
|
||||||
|
t.Fatalf("Expected 2 new images, found %d.", len(images)-len(initialImages))
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = srv.ImageDelete(imageID, true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
images, err = srv.Images(false, "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(images)-len(initialImages) != 1 {
|
||||||
|
t.Fatalf("Expected 1 new image, found %d.", len(images)-len(initialImages))
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, image := range images {
|
||||||
|
if strings.Contains(unitTestImageID, image.ID) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if image.Repository == "" {
|
||||||
|
t.Fatalf("Expected tagged image, got untagged one.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -40,6 +40,10 @@ Deployment
|
||||||
export SMTP_USER=xxxxxxxxxxxx
|
export SMTP_USER=xxxxxxxxxxxx
|
||||||
export SMTP_PWD=xxxxxxxxxxxx
|
export SMTP_PWD=xxxxxxxxxxxx
|
||||||
|
|
||||||
|
# Define docker registry functional test credentials
|
||||||
|
export REGISTRY_USER=xxxxxxxxxxxx
|
||||||
|
export REGISTRY_PWD=xxxxxxxxxxxx
|
||||||
|
|
||||||
# Checkout docker
|
# Checkout docker
|
||||||
git clone git://github.com/dotcloud/docker.git
|
git clone git://github.com/dotcloud/docker.git
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,9 @@ Vagrant::Config.run do |config|
|
||||||
"chown #{USER}.#{USER} /data; cd /data; " \
|
"chown #{USER}.#{USER} /data; cd /data; " \
|
||||||
"#{CFG_PATH}/setup.sh #{USER} #{CFG_PATH} #{ENV['BUILDBOT_PWD']} " \
|
"#{CFG_PATH}/setup.sh #{USER} #{CFG_PATH} #{ENV['BUILDBOT_PWD']} " \
|
||||||
"#{ENV['IRC_PWD']} #{ENV['IRC_CHANNEL']} #{ENV['SMTP_USER']} " \
|
"#{ENV['IRC_PWD']} #{ENV['IRC_CHANNEL']} #{ENV['SMTP_USER']} " \
|
||||||
"#{ENV['SMTP_PWD']} #{ENV['EMAIL_RCP']}; "
|
"#{ENV['SMTP_PWD']} #{ENV['EMAIL_RCP']}; " \
|
||||||
|
"#{CFG_PATH}/setup_credentials.sh #{USER} " \
|
||||||
|
"#{ENV['REGISTRY_USER']} #{ENV['REGISTRY_PWD']}; "
|
||||||
# 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:dotcloud/docker-golang/ubuntu; apt-get update -qq; " \
|
"add-apt-repository -y ppa:dotcloud/docker-golang/ubuntu; apt-get update -qq; " \
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
# Credentials for tests. Buildbot source this file on tests
|
||||||
|
# when needed.
|
||||||
|
|
||||||
|
# Docker registry credentials. Format: 'username:password'
|
||||||
|
export DOCKER_CREDS=''
|
|
@ -19,6 +19,7 @@ TEST_USER = 'buildbot' # Credential to authenticate build triggers
|
||||||
TEST_PWD = 'docker' # Credential to authenticate build triggers
|
TEST_PWD = 'docker' # Credential to authenticate build triggers
|
||||||
BUILDER_NAME = 'docker'
|
BUILDER_NAME = 'docker'
|
||||||
GITHUB_DOCKER = 'github.com/dotcloud/docker'
|
GITHUB_DOCKER = 'github.com/dotcloud/docker'
|
||||||
|
BUILDBOT_PATH = '/data/buildbot'
|
||||||
DOCKER_PATH = '/data/docker'
|
DOCKER_PATH = '/data/docker'
|
||||||
BUILDER_PATH = '/data/buildbot/slave/{0}/build'.format(BUILDER_NAME)
|
BUILDER_PATH = '/data/buildbot/slave/{0}/build'.format(BUILDER_NAME)
|
||||||
DOCKER_BUILD_PATH = BUILDER_PATH + '/src/github.com/dotcloud/docker'
|
DOCKER_BUILD_PATH = BUILDER_PATH + '/src/github.com/dotcloud/docker'
|
||||||
|
@ -41,16 +42,19 @@ c['db'] = {'db_url':"sqlite:///state.sqlite"}
|
||||||
c['slaves'] = [BuildSlave('buildworker', BUILDBOT_PWD)]
|
c['slaves'] = [BuildSlave('buildworker', BUILDBOT_PWD)]
|
||||||
c['slavePortnum'] = PORT_MASTER
|
c['slavePortnum'] = PORT_MASTER
|
||||||
|
|
||||||
|
|
||||||
# Schedulers
|
# Schedulers
|
||||||
c['schedulers'] = [ForceScheduler(name='trigger', builderNames=[BUILDER_NAME,
|
c['schedulers'] = [ForceScheduler(name='trigger', builderNames=[BUILDER_NAME,
|
||||||
'coverage'])]
|
'registry','coverage'])]
|
||||||
c['schedulers'] += [SingleBranchScheduler(name="all",
|
c['schedulers'] += [SingleBranchScheduler(name="all",
|
||||||
change_filter=filter.ChangeFilter(branch='master'), treeStableTimer=None,
|
change_filter=filter.ChangeFilter(branch='master'), treeStableTimer=None,
|
||||||
builderNames=[BUILDER_NAME])]
|
builderNames=[BUILDER_NAME])]
|
||||||
c['schedulers'] += [Nightly(name='daily', branch=None, builderNames=['coverage'],
|
c['schedulers'] += [Nightly(name='daily', branch=None, builderNames=['coverage','registry'],
|
||||||
hour=0, minute=30)]
|
hour=0, minute=30)]
|
||||||
|
|
||||||
|
|
||||||
# Builders
|
# Builders
|
||||||
|
# Docker commit test
|
||||||
factory = BuildFactory()
|
factory = BuildFactory()
|
||||||
factory.addStep(ShellCommand(description='Docker',logEnviron=False,usePTY=True,
|
factory.addStep(ShellCommand(description='Docker',logEnviron=False,usePTY=True,
|
||||||
command=["sh", "-c", Interpolate("cd ..; rm -rf build; export GOPATH={0}; "
|
command=["sh", "-c", Interpolate("cd ..; rm -rf build; export GOPATH={0}; "
|
||||||
|
@ -58,6 +62,7 @@ factory.addStep(ShellCommand(description='Docker',logEnviron=False,usePTY=True,
|
||||||
"go test -v".format(BUILDER_PATH,GITHUB_DOCKER,DOCKER_BUILD_PATH))]))
|
"go test -v".format(BUILDER_PATH,GITHUB_DOCKER,DOCKER_BUILD_PATH))]))
|
||||||
c['builders'] = [BuilderConfig(name=BUILDER_NAME,slavenames=['buildworker'],
|
c['builders'] = [BuilderConfig(name=BUILDER_NAME,slavenames=['buildworker'],
|
||||||
factory=factory)]
|
factory=factory)]
|
||||||
|
|
||||||
# Docker coverage test
|
# Docker coverage test
|
||||||
coverage_cmd = ('GOPATH=`pwd` go get -d github.com/dotcloud/docker\n'
|
coverage_cmd = ('GOPATH=`pwd` go get -d github.com/dotcloud/docker\n'
|
||||||
'GOPATH=`pwd` go get github.com/axw/gocov/gocov\n'
|
'GOPATH=`pwd` go get github.com/axw/gocov/gocov\n'
|
||||||
|
@ -69,6 +74,17 @@ factory.addStep(ShellCommand(description='Coverage',logEnviron=False,usePTY=True
|
||||||
c['builders'] += [BuilderConfig(name='coverage',slavenames=['buildworker'],
|
c['builders'] += [BuilderConfig(name='coverage',slavenames=['buildworker'],
|
||||||
factory=factory)]
|
factory=factory)]
|
||||||
|
|
||||||
|
# Registry Functionaltest builder
|
||||||
|
factory = BuildFactory()
|
||||||
|
factory.addStep(ShellCommand(description='registry', logEnviron=False,
|
||||||
|
command='. {0}/master/credentials.cfg; '
|
||||||
|
'{1}/testing/functionaltests/test_registry.sh'.format(BUILDBOT_PATH,
|
||||||
|
DOCKER_PATH), usePTY=True))
|
||||||
|
|
||||||
|
c['builders'] += [BuilderConfig(name='registry',slavenames=['buildworker'],
|
||||||
|
factory=factory)]
|
||||||
|
|
||||||
|
|
||||||
# Status
|
# Status
|
||||||
authz_cfg = authz.Authz(auth=auth.BasicAuth([(TEST_USER, TEST_PWD)]),
|
authz_cfg = authz.Authz(auth=auth.BasicAuth([(TEST_USER, TEST_PWD)]),
|
||||||
forceBuild='auth')
|
forceBuild='auth')
|
||||||
|
|
|
@ -4,3 +4,4 @@ buildbot==0.8.7p1
|
||||||
buildbot_slave==0.8.7p1
|
buildbot_slave==0.8.7p1
|
||||||
nose==1.2.1
|
nose==1.2.1
|
||||||
requests==1.1.0
|
requests==1.1.0
|
||||||
|
flask==0.10.1
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Setup of test credentials. Called by Vagrantfile
|
||||||
|
export PATH="/bin:sbin:/usr/bin:/usr/sbin:/usr/local/bin"
|
||||||
|
|
||||||
|
USER=$1
|
||||||
|
REGISTRY_USER=$2
|
||||||
|
REGISTRY_PWD=$3
|
||||||
|
|
||||||
|
BUILDBOT_PATH="/data/buildbot"
|
||||||
|
DOCKER_PATH="/data/docker"
|
||||||
|
|
||||||
|
function run { su $USER -c "$1"; }
|
||||||
|
|
||||||
|
run "cp $DOCKER_PATH/testing/buildbot/credentials.cfg $BUILDBOT_PATH/master"
|
||||||
|
cd $BUILDBOT_PATH/master
|
||||||
|
run "sed -i -E 's#(export DOCKER_CREDS=).+#\1\"$REGISTRY_USER:$REGISTRY_PWD\"#' credentials.cfg"
|
|
@ -0,0 +1,11 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
rm -rf docker-registry
|
||||||
|
|
||||||
|
# Get latest docker registry
|
||||||
|
git clone https://github.com/dotcloud/docker-registry.git
|
||||||
|
|
||||||
|
# Configure and run registry tests
|
||||||
|
cd docker-registry; cp config_sample.yml config.yml
|
||||||
|
cd test; python -m unittest workflow
|
|
@ -0,0 +1,158 @@
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/tar"
|
||||||
|
"bytes"
|
||||||
|
"compress/gzip"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
|
"hash"
|
||||||
|
"io"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type verboseHash struct {
|
||||||
|
hash.Hash
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h verboseHash) Write(buf []byte) (int, error) {
|
||||||
|
Debugf("--->%s<---", buf)
|
||||||
|
return h.Hash.Write(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
type TarSum struct {
|
||||||
|
io.Reader
|
||||||
|
tarR *tar.Reader
|
||||||
|
tarW *tar.Writer
|
||||||
|
gz *gzip.Writer
|
||||||
|
bufTar *bytes.Buffer
|
||||||
|
bufGz *bytes.Buffer
|
||||||
|
h hash.Hash
|
||||||
|
h2 verboseHash
|
||||||
|
sums []string
|
||||||
|
finished bool
|
||||||
|
first bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *TarSum) encodeHeader(h *tar.Header) error {
|
||||||
|
for _, elem := range [][2]string{
|
||||||
|
{"name", h.Name},
|
||||||
|
{"mode", strconv.Itoa(int(h.Mode))},
|
||||||
|
{"uid", strconv.Itoa(h.Uid)},
|
||||||
|
{"gid", strconv.Itoa(h.Gid)},
|
||||||
|
{"size", strconv.Itoa(int(h.Size))},
|
||||||
|
{"mtime", strconv.Itoa(int(h.ModTime.UTC().Unix()))},
|
||||||
|
{"typeflag", string([]byte{h.Typeflag})},
|
||||||
|
{"linkname", h.Linkname},
|
||||||
|
{"uname", h.Uname},
|
||||||
|
{"gname", h.Gname},
|
||||||
|
{"devmajor", strconv.Itoa(int(h.Devmajor))},
|
||||||
|
{"devminor", strconv.Itoa(int(h.Devminor))},
|
||||||
|
// {"atime", strconv.Itoa(int(h.AccessTime.UTC().Unix()))},
|
||||||
|
// {"ctime", strconv.Itoa(int(h.ChangeTime.UTC().Unix()))},
|
||||||
|
} {
|
||||||
|
// Debugf("-->%s<-- -->%s<--", elem[0], elem[1])
|
||||||
|
if _, err := ts.h.Write([]byte(elem[0] + elem[1])); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *TarSum) Read(buf []byte) (int, error) {
|
||||||
|
if ts.gz == nil {
|
||||||
|
ts.bufTar = bytes.NewBuffer([]byte{})
|
||||||
|
ts.bufGz = bytes.NewBuffer([]byte{})
|
||||||
|
ts.tarR = tar.NewReader(ts.Reader)
|
||||||
|
ts.tarW = tar.NewWriter(ts.bufTar)
|
||||||
|
ts.gz = gzip.NewWriter(ts.bufGz)
|
||||||
|
ts.h = sha256.New()
|
||||||
|
// ts.h = verboseHash{sha256.New()}
|
||||||
|
ts.h.Reset()
|
||||||
|
ts.first = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if ts.finished {
|
||||||
|
return ts.bufGz.Read(buf)
|
||||||
|
}
|
||||||
|
buf2 := make([]byte, len(buf), cap(buf))
|
||||||
|
|
||||||
|
n, err := ts.tarR.Read(buf2)
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
if _, err := ts.h.Write(buf2[:n]); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if !ts.first {
|
||||||
|
ts.sums = append(ts.sums, hex.EncodeToString(ts.h.Sum(nil)))
|
||||||
|
ts.h.Reset()
|
||||||
|
} else {
|
||||||
|
ts.first = false
|
||||||
|
}
|
||||||
|
|
||||||
|
currentHeader, err := ts.tarR.Next()
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
if err := ts.gz.Close(); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
ts.finished = true
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
if err := ts.encodeHeader(currentHeader); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if err := ts.tarW.WriteHeader(currentHeader); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if _, err := ts.tarW.Write(buf2[:n]); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
ts.tarW.Flush()
|
||||||
|
if _, err := io.Copy(ts.gz, ts.bufTar); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
ts.gz.Flush()
|
||||||
|
|
||||||
|
return ts.bufGz.Read(buf)
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filling the hash buffer
|
||||||
|
if _, err = ts.h.Write(buf2[:n]); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filling the tar writter
|
||||||
|
if _, err = ts.tarW.Write(buf2[:n]); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
ts.tarW.Flush()
|
||||||
|
|
||||||
|
// Filling the gz writter
|
||||||
|
if _, err = io.Copy(ts.gz, ts.bufTar); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
ts.gz.Flush()
|
||||||
|
|
||||||
|
return ts.bufGz.Read(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *TarSum) Sum(extra []byte) string {
|
||||||
|
sort.Strings(ts.sums)
|
||||||
|
h := sha256.New()
|
||||||
|
if extra != nil {
|
||||||
|
h.Write(extra)
|
||||||
|
}
|
||||||
|
for _, sum := range ts.sums {
|
||||||
|
Debugf("-->%s<--", sum)
|
||||||
|
h.Write([]byte(sum))
|
||||||
|
}
|
||||||
|
checksum := "tarsum+sha256:" + hex.EncodeToString(h.Sum(nil))
|
||||||
|
Debugf("checksum processed: %s", checksum)
|
||||||
|
return checksum
|
||||||
|
}
|
|
@ -607,22 +607,39 @@ func NewWriteFlusher(w io.Writer) *WriteFlusher {
|
||||||
return &WriteFlusher{w: w, flusher: flusher}
|
return &WriteFlusher{w: w, flusher: flusher}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type JSONError struct {
|
||||||
|
Code int `json:"code,omitempty"`
|
||||||
|
Message string `json:"message,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
type JSONMessage struct {
|
type JSONMessage struct {
|
||||||
Status string `json:"status,omitempty"`
|
Status string `json:"status,omitempty"`
|
||||||
Progress string `json:"progress,omitempty"`
|
Progress string `json:"progress,omitempty"`
|
||||||
Error string `json:"error,omitempty"`
|
ErrorMessage string `json:"error,omitempty"` //deprecated
|
||||||
ID string `json:"id,omitempty"`
|
ID string `json:"id,omitempty"`
|
||||||
Time int64 `json:"time,omitempty"`
|
Time int64 `json:"time,omitempty"`
|
||||||
|
Error *JSONError `json:"errorDetail,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (jm *JSONMessage) Display(out io.Writer) (error) {
|
func (e *JSONError) Error() string {
|
||||||
|
return e.Message
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHTTPRequestError(msg string, res *http.Response) error {
|
||||||
|
return &JSONError{
|
||||||
|
Message: msg,
|
||||||
|
Code: res.StatusCode,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (jm *JSONMessage) Display(out io.Writer) error {
|
||||||
if jm.Time != 0 {
|
if jm.Time != 0 {
|
||||||
fmt.Fprintf(out, "[%s] ", time.Unix(jm.Time, 0))
|
fmt.Fprintf(out, "[%s] ", time.Unix(jm.Time, 0))
|
||||||
}
|
}
|
||||||
if jm.Progress != "" {
|
if jm.Progress != "" {
|
||||||
fmt.Fprintf(out, "%s %s\r", jm.Status, jm.Progress)
|
fmt.Fprintf(out, "%s %s\r", jm.Status, jm.Progress)
|
||||||
} else if jm.Error != "" {
|
} else if jm.Error != nil {
|
||||||
return fmt.Errorf(jm.Error)
|
return jm.Error
|
||||||
} else if jm.ID != "" {
|
} else if jm.ID != "" {
|
||||||
fmt.Fprintf(out, "%s: %s\n", jm.ID, jm.Status)
|
fmt.Fprintf(out, "%s: %s\n", jm.ID, jm.Status)
|
||||||
} else {
|
} else {
|
||||||
|
@ -631,7 +648,6 @@ func (jm *JSONMessage) Display(out io.Writer) (error) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
type StreamFormatter struct {
|
type StreamFormatter struct {
|
||||||
json bool
|
json bool
|
||||||
used bool
|
used bool
|
||||||
|
@ -657,7 +673,11 @@ 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 {
|
jsonError, ok := err.(*JSONError)
|
||||||
|
if !ok {
|
||||||
|
jsonError = &JSONError{Message: err.Error()}
|
||||||
|
}
|
||||||
|
if b, err := json.Marshal(&JSONMessage{Error: jsonError, ErrorMessage: err.Error()}); err == nil {
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
return []byte("{\"error\":\"format error\"}")
|
return []byte("{\"error\":\"format error\"}")
|
||||||
|
@ -731,6 +751,22 @@ func ParseHost(host string, port int, addr string) string {
|
||||||
return fmt.Sprintf("tcp://%s:%d", host, port)
|
return fmt.Sprintf("tcp://%s:%d", host, port)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetReleaseVersion() string {
|
||||||
|
resp, err := http.Get("http://get.docker.io/latest")
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if resp.ContentLength > 24 || resp.StatusCode != 200 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(string(body))
|
||||||
|
}
|
||||||
|
|
||||||
// Get a repos name and returns the right reposName + tag
|
// Get a repos name and returns the right reposName + tag
|
||||||
// The tag can be confusing because of a port in a repository name.
|
// The tag can be confusing because of a port in a repository name.
|
||||||
// Ex: localhost.localdomain:5000/samalba/hipache:latest
|
// Ex: localhost.localdomain:5000/samalba/hipache:latest
|
||||||
|
|
|
@ -282,3 +282,24 @@ func TestParseHost(t *testing.T) {
|
||||||
t.Errorf("unix:///var/run/docker.sock -> expected unix:///var/run/docker.sock, got %s", addr)
|
t.Errorf("unix:///var/run/docker.sock -> expected unix:///var/run/docker.sock, got %s", addr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseRepositoryTag(t *testing.T) {
|
||||||
|
if repo, tag := ParseRepositoryTag("root"); repo != "root" || tag != "" {
|
||||||
|
t.Errorf("Expected repo: '%s' and tag: '%s', got '%s' and '%s'", "root", "", repo, tag)
|
||||||
|
}
|
||||||
|
if repo, tag := ParseRepositoryTag("root:tag"); repo != "root" || tag != "tag" {
|
||||||
|
t.Errorf("Expected repo: '%s' and tag: '%s', got '%s' and '%s'", "root", "tag", repo, tag)
|
||||||
|
}
|
||||||
|
if repo, tag := ParseRepositoryTag("user/repo"); repo != "user/repo" || tag != "" {
|
||||||
|
t.Errorf("Expected repo: '%s' and tag: '%s', got '%s' and '%s'", "user/repo", "", repo, tag)
|
||||||
|
}
|
||||||
|
if repo, tag := ParseRepositoryTag("user/repo:tag"); repo != "user/repo" || tag != "tag" {
|
||||||
|
t.Errorf("Expected repo: '%s' and tag: '%s', got '%s' and '%s'", "user/repo", "tag", repo, tag)
|
||||||
|
}
|
||||||
|
if repo, tag := ParseRepositoryTag("url:5000/repo"); repo != "url:5000/repo" || tag != "" {
|
||||||
|
t.Errorf("Expected repo: '%s' and tag: '%s', got '%s' and '%s'", "url:5000/repo", "", repo, tag)
|
||||||
|
}
|
||||||
|
if repo, tag := ParseRepositoryTag("url:5000/repo:tag"); repo != "url:5000/repo" || tag != "tag" {
|
||||||
|
t.Errorf("Expected repo: '%s' and tag: '%s', got '%s' and '%s'", "url:5000/repo", "tag", repo, tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -191,7 +191,7 @@ func TestMergeConfig(t *testing.T) {
|
||||||
if len(configUser.Volumes) != 3 {
|
if len(configUser.Volumes) != 3 {
|
||||||
t.Fatalf("Expected 3 volumes, /test1, /test2 and /test3, found %d", len(configUser.Volumes))
|
t.Fatalf("Expected 3 volumes, /test1, /test2 and /test3, found %d", len(configUser.Volumes))
|
||||||
}
|
}
|
||||||
for v, _ := range configUser.Volumes {
|
for v := range configUser.Volumes {
|
||||||
if v != "/test1" && v != "/test2" && v != "/test3" {
|
if v != "/test1" && v != "/test2" && v != "/test3" {
|
||||||
t.Fatalf("Expected /test1 or /test2 or /test3, found %s", v)
|
t.Fatalf("Expected /test1 or /test2 or /test3, found %s", v)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue