merge master and add doc

This commit is contained in:
Victor Vieux 2013-06-22 01:08:20 +02:00
commit 4d1692726b
36 changed files with 1713 additions and 788 deletions

View file

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

22
AUTHORS
View file

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

View file

@ -1,5 +1,28 @@
# Changelog # Changelog
## 0.4.4 (2013-06-19)
- Builder: fix a regression introduced in 0.4.3 which caused builds to fail on new clients.
## 0.4.3 (2013-06-19)
+ Builder: ADD of a local file will detect tar archives and unpack them
* Runtime: Remove bsdtar dependency
* Runtime: Add unix socket and multiple -H support
* Runtime: Prevent rm of running containers
* Runtime: Use go1.1 cookiejar
* Builder: ADD improvements: use tar for copy + automatically unpack local archives
* Builder: ADD uses tar/untar for copies instead of calling 'cp -ar'
* Builder: nicer output for 'docker build'
* Builder: fixed the behavior of ADD to be (mostly) reverse-compatible, predictable and well-documented.
* Client: HumanReadable ProgressBar sizes in pull
* Client: Fix docker version's git commit output
* API: Send all tags on History API call
* API: Add tag lookup to history command. Fixes #882
- Runtime: Fix issue detaching from running TTY container
- Runtime: Forbid parralel push/pull for a single image/repo. Fixes #311
- Runtime: Fix race condition within Run command when attaching.
- Builder: fix a bug which caused builds to fail if ADD was the first command
- Documentation: fix missing command in irc bouncer example
## 0.4.2 (2013-06-17) ## 0.4.2 (2013-06-17)
- Packaging: Bumped version to work around an Ubuntu bug - Packaging: Bumped version to work around an Ubuntu bug

1
FIXME
View file

@ -33,3 +33,4 @@ to put them - so we put them here :)
* Caching after an ADD * Caching after an ADD
* entry point config * entry point config
* bring back git revision info, looks like it was lost * bring back git revision info, looks like it was lost
* Clean up the ProgressReader api, it's a PITA to use

View file

@ -17,7 +17,7 @@ endif
GIT_COMMIT = $(shell git rev-parse --short HEAD) GIT_COMMIT = $(shell git rev-parse --short HEAD)
GIT_STATUS = $(shell test -n "`git status --porcelain`" && echo "+CHANGES") GIT_STATUS = $(shell test -n "`git status --porcelain`" && echo "+CHANGES")
BUILD_OPTIONS = -ldflags "-X main.GITCOMMIT $(GIT_COMMIT)$(GIT_STATUS)" BUILD_OPTIONS = -a -ldflags "-X main.GITCOMMIT $(GIT_COMMIT)$(GIT_STATUS) -d -w"
SRC_DIR := $(GOPATH)/src SRC_DIR := $(GOPATH)/src
@ -33,7 +33,7 @@ all: $(DOCKER_BIN)
$(DOCKER_BIN): $(DOCKER_DIR) $(DOCKER_BIN): $(DOCKER_DIR)
@mkdir -p $(dir $@) @mkdir -p $(dir $@)
@(cd $(DOCKER_MAIN); go build $(GO_OPTIONS) $(BUILD_OPTIONS) -o $@) @(cd $(DOCKER_MAIN); CGO_ENABLED=0 go build $(GO_OPTIONS) $(BUILD_OPTIONS) -o $@)
@echo $(DOCKER_BIN_RELATIVE) is created. @echo $(DOCKER_BIN_RELATIVE) is created.
$(DOCKER_DIR): $(DOCKER_DIR):
@ -74,6 +74,9 @@ endif
test: all test: all
@(cd $(DOCKER_DIR); sudo -E go test $(GO_OPTIONS)) @(cd $(DOCKER_DIR); sudo -E go test $(GO_OPTIONS))
testall: all
@(cd $(DOCKER_DIR); sudo -E go test ./... $(GO_OPTIONS))
fmt: fmt:
@gofmt -s -l -w . @gofmt -s -l -w .

View file

@ -97,7 +97,7 @@ Quick install on Ubuntu 12.04 and 12.10
--------------------------------------- ---------------------------------------
```bash ```bash
curl get.docker.io | sh -x curl get.docker.io | sudo sh -x
``` ```
Binary installs Binary installs

94
api.go
View file

@ -7,13 +7,19 @@ import (
"github.com/dotcloud/docker/utils" "github.com/dotcloud/docker/utils"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"io" "io"
"io/ioutil"
"log" "log"
"net"
"net/http" "net/http"
"os"
"os/exec"
"strconv" "strconv"
"strings" "strings"
) )
const APIVERSION = 1.2 const APIVERSION = 1.3
const DEFAULTHTTPHOST string = "127.0.0.1"
const DEFAULTHTTPPORT int = 4243
func hijackServer(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) { func hijackServer(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) {
conn, _, err := w.(http.Hijacker).Hijack() conn, _, err := w.(http.Hijacker).Hijack()
@ -49,6 +55,10 @@ func httpError(w http.ResponseWriter, err error) {
http.Error(w, err.Error(), http.StatusConflict) http.Error(w, err.Error(), http.StatusConflict)
} else if strings.HasPrefix(err.Error(), "Impossible") { } else if strings.HasPrefix(err.Error(), "Impossible") {
http.Error(w, err.Error(), http.StatusNotAcceptable) http.Error(w, err.Error(), http.StatusNotAcceptable)
} else if strings.HasPrefix(err.Error(), "Wrong login/password") {
http.Error(w, err.Error(), http.StatusUnauthorized)
} else if strings.Contains(err.Error(), "hasn't been activated") {
http.Error(w, err.Error(), http.StatusForbidden)
} else { } else {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
} }
@ -719,34 +729,65 @@ func postImagesGetCache(srv *Server, version float64, w http.ResponseWriter, r *
} }
func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if err := r.ParseMultipartForm(4096); err != nil { if version < 1.3 {
return err return fmt.Errorf("Multipart upload for build is no longer supported. Please upgrade your docker client.")
} }
remote := r.FormValue("t") remoteURL := r.FormValue("remote")
repoName := r.FormValue("t")
tag := "" tag := ""
if strings.Contains(remote, ":") { if strings.Contains(repoName, ":") {
remoteParts := strings.Split(remote, ":") remoteParts := strings.Split(repoName, ":")
tag = remoteParts[1] tag = remoteParts[1]
remote = remoteParts[0] repoName = remoteParts[0]
} }
dockerfile, _, err := r.FormFile("Dockerfile") var context io.Reader
if err != nil {
return err
}
context, _, err := r.FormFile("Context") if remoteURL == "" {
if err != nil { context = r.Body
if err != http.ErrMissingFile { } else if utils.IsGIT(remoteURL) {
if !strings.HasPrefix(remoteURL, "git://") {
remoteURL = "https://" + remoteURL
}
root, err := ioutil.TempDir("", "docker-build-git")
if err != nil {
return err return err
} }
} defer os.RemoveAll(root)
if output, err := exec.Command("git", "clone", remoteURL, root).CombinedOutput(); err != nil {
return fmt.Errorf("Error trying to use git: %s (%s)", err, output)
}
c, err := Tar(root, Bzip2)
if err != nil {
return err
}
context = c
} else if utils.IsURL(remoteURL) {
f, err := utils.Download(remoteURL, ioutil.Discard)
if err != nil {
return err
}
defer f.Body.Close()
dockerFile, err := ioutil.ReadAll(f.Body)
if err != nil {
return err
}
c, err := mkBuildContext(string(dockerFile), nil)
if err != nil {
return err
}
context = c
}
b := NewBuildFile(srv, utils.NewWriteFlusher(w)) b := NewBuildFile(srv, utils.NewWriteFlusher(w))
if id, err := b.Build(dockerfile, context); err != nil { id, err := b.Build(context)
if err != nil {
fmt.Fprintf(w, "Error build: %s\n", err) fmt.Fprintf(w, "Error build: %s\n", err)
} else if remote != "" { return err
srv.runtime.repositories.Set(remote, tag, id, false) }
if repoName != "" {
srv.runtime.repositories.Set(repoName, tag, id, false)
} }
return nil return nil
} }
@ -816,6 +857,7 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
localFct := fct localFct := fct
f := func(w http.ResponseWriter, r *http.Request) { f := func(w http.ResponseWriter, r *http.Request) {
utils.Debugf("Calling %s %s", localMethod, localRoute) utils.Debugf("Calling %s %s", localMethod, localRoute)
if logging { if logging {
log.Println(r.Method, r.RequestURI) log.Println(r.Method, r.RequestURI)
} }
@ -836,6 +878,7 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
w.WriteHeader(http.StatusNotFound) w.WriteHeader(http.StatusNotFound)
return return
} }
if err := localFct(srv, version, w, r, mux.Vars(r)); err != nil { if err := localFct(srv, version, w, r, mux.Vars(r)); err != nil {
httpError(w, err) httpError(w, err)
} }
@ -852,12 +895,21 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
return r, nil return r, nil
} }
func ListenAndServe(addr string, srv *Server, logging bool) error { func ListenAndServe(proto, addr string, srv *Server, logging bool) error {
log.Printf("Listening for HTTP on %s\n", addr) log.Printf("Listening for HTTP on %s (%s)\n", addr, proto)
r, err := createRouter(srv, logging) r, err := createRouter(srv, logging)
if err != nil { if err != nil {
return err return err
} }
return http.ListenAndServe(addr, r) l, e := net.Listen(proto, addr)
if e != nil {
return e
}
//as the daemon is launched as root, change to permission of the socket to allow non-root to connect
if proto == "unix" {
os.Chmod(addr, 0777)
}
httpSrv := http.Server{Addr: addr, Handler: r}
return httpSrv.Serve(l)
} }

View file

@ -1,7 +1,9 @@
package docker package docker
import ( import (
"archive/tar"
"bufio" "bufio"
"bytes"
"errors" "errors"
"fmt" "fmt"
"github.com/dotcloud/docker/utils" "github.com/dotcloud/docker/utils"
@ -10,6 +12,7 @@ import (
"os" "os"
"os/exec" "os/exec"
"path" "path"
"path/filepath"
) )
type Archive io.Reader type Archive io.Reader
@ -160,51 +163,60 @@ func CopyWithTar(src, dst string) error {
if err != nil { if err != nil {
return err return err
} }
var dstExists bool
dstSt, err := os.Stat(dst)
if err != nil {
if !os.IsNotExist(err) {
return err
}
} else {
dstExists = true
}
// Things that can go wrong if the source is a directory
if srcSt.IsDir() {
// The destination exists and is a regular file
if dstExists && !dstSt.IsDir() {
return fmt.Errorf("Can't copy a directory over a regular file")
}
// Things that can go wrong if the source is a regular file
} else {
utils.Debugf("The destination exists, it's a directory, and doesn't end in /")
// The destination exists, it's a directory, and doesn't end in /
if dstExists && dstSt.IsDir() && dst[len(dst)-1] != '/' {
return fmt.Errorf("Can't copy a regular file over a directory %s |%s|", dst, dst[len(dst)-1])
}
}
// Create the destination
var dstDir string
if srcSt.IsDir() || dst[len(dst)-1] == '/' {
// The destination ends in /, or the source is a directory
// --> dst is the holding directory and needs to be created for -C
dstDir = dst
} else {
// The destination doesn't end in /
// --> dst is the file
dstDir = path.Dir(dst)
}
if !dstExists {
// Create the holding directory if necessary
utils.Debugf("Creating the holding directory %s", dstDir)
if err := os.MkdirAll(dstDir, 0700); err != nil && !os.IsExist(err) {
return err
}
}
if !srcSt.IsDir() { if !srcSt.IsDir() {
return TarUntar(path.Dir(src), []string{path.Base(src)}, dstDir) return CopyFileWithTar(src, dst)
} }
return TarUntar(src, nil, dstDir) // Create dst, copy src's content into it
utils.Debugf("Creating dest directory: %s", dst)
if err := os.MkdirAll(dst, 0700); err != nil && !os.IsExist(err) {
return err
}
utils.Debugf("Calling TarUntar(%s, %s)", src, dst)
return TarUntar(src, nil, dst)
}
// CopyFileWithTar emulates the behavior of the 'cp' command-line
// for a single file. It copies a regular file from path `src` to
// path `dst`, and preserves all its metadata.
//
// If `dst` ends with a trailing slash '/', the final destination path
// will be `dst/base(src)`.
func CopyFileWithTar(src, dst string) error {
utils.Debugf("CopyFileWithTar(%s, %s)", src, dst)
srcSt, err := os.Stat(src)
if err != nil {
return err
}
if srcSt.IsDir() {
return fmt.Errorf("Can't copy a directory")
}
// Clean up the trailing /
if dst[len(dst)-1] == '/' {
dst = path.Join(dst, filepath.Base(src))
}
// Create the holding directory if necessary
if err := os.MkdirAll(filepath.Dir(dst), 0700); err != nil && !os.IsExist(err) {
return err
}
buf := new(bytes.Buffer)
tw := tar.NewWriter(buf)
hdr, err := tar.FileInfoHeader(srcSt, "")
if err != nil {
return err
}
hdr.Name = filepath.Base(dst)
if err := tw.WriteHeader(hdr); err != nil {
return err
}
srcF, err := os.Open(src)
if err != nil {
return err
}
if _, err := io.Copy(tw, srcF); err != nil {
return err
}
tw.Close()
return Untar(buf, filepath.Dir(dst))
} }
// CmdStream executes a command, and returns its stdout as a stream. // CmdStream executes a command, and returns its stdout as a stream.

View file

@ -82,7 +82,7 @@ func decodeAuth(authStr string) (*AuthConfig, error) {
func LoadConfig(rootPath string) (*AuthConfig, error) { func LoadConfig(rootPath string) (*AuthConfig, error) {
confFile := path.Join(rootPath, CONFIGFILE) confFile := path.Join(rootPath, CONFIGFILE)
if _, err := os.Stat(confFile); err != nil { if _, err := os.Stat(confFile); err != nil {
return &AuthConfig{rootPath:rootPath}, ErrConfigFileMissing return &AuthConfig{rootPath: rootPath}, ErrConfigFileMissing
} }
b, err := ioutil.ReadFile(confFile) b, err := ioutil.ReadFile(confFile)
if err != nil { if err != nil {
@ -146,7 +146,7 @@ func Login(authConfig *AuthConfig, store bool) (string, error) {
if reqStatusCode == 201 { if reqStatusCode == 201 {
status = "Account created. Please use the confirmation link we sent" + status = "Account created. Please use the confirmation link we sent" +
" to your e-mail to activate it.\n" " to your e-mail to activate it."
storeConfig = true storeConfig = true
} else if reqStatusCode == 403 { } else if reqStatusCode == 403 {
return "", fmt.Errorf("Login: Your account hasn't been activated. " + return "", fmt.Errorf("Login: Your account hasn't been activated. " +
@ -165,10 +165,11 @@ func Login(authConfig *AuthConfig, store bool) (string, error) {
return "", err return "", err
} }
if resp.StatusCode == 200 { if resp.StatusCode == 200 {
status = "Login Succeeded\n" status = "Login Succeeded"
storeConfig = true storeConfig = true
} else if resp.StatusCode == 401 { } else if resp.StatusCode == 401 {
if store { if store {
authConfig.Email = ""
if err := SaveConfig(authConfig); err != nil { if err := SaveConfig(authConfig); err != nil {
return "", err return "", err
} }

View file

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

View file

@ -1,314 +0,0 @@
package docker
import (
"bufio"
"encoding/json"
"fmt"
"github.com/dotcloud/docker/utils"
"io"
"net/url"
"os"
"reflect"
"strings"
)
type builderClient struct {
cli *DockerCli
image string
maintainer string
config *Config
tmpContainers map[string]struct{}
tmpImages map[string]struct{}
needCommit bool
}
func (b *builderClient) clearTmp(containers, images map[string]struct{}) {
for i := range images {
if _, _, err := b.cli.call("DELETE", "/images/"+i, nil); err != nil {
utils.Debugf("%s", err)
}
utils.Debugf("Removing image %s", i)
}
}
func (b *builderClient) CmdFrom(name string) error {
obj, statusCode, err := b.cli.call("GET", "/images/"+name+"/json", nil)
if statusCode == 404 {
remote := name
var tag string
if strings.Contains(remote, ":") {
remoteParts := strings.Split(remote, ":")
tag = remoteParts[1]
remote = remoteParts[0]
}
var out io.Writer
if os.Getenv("DEBUG") != "" {
out = os.Stdout
} else {
out = &utils.NopWriter{}
}
if err := b.cli.stream("POST", "/images/create?fromImage="+remote+"&tag="+tag, nil, out); err != nil {
return err
}
obj, _, err = b.cli.call("GET", "/images/"+name+"/json", nil)
if err != nil {
return err
}
}
if err != nil {
return err
}
img := &APIID{}
if err := json.Unmarshal(obj, img); err != nil {
return err
}
b.image = img.ID
utils.Debugf("Using image %s", b.image)
return nil
}
func (b *builderClient) CmdMaintainer(name string) error {
b.needCommit = true
b.maintainer = name
return nil
}
func (b *builderClient) CmdRun(args string) error {
if b.image == "" {
return fmt.Errorf("Please provide a source image with `from` prior to run")
}
config, _, err := ParseRun([]string{b.image, "/bin/sh", "-c", args}, nil)
if err != nil {
return err
}
cmd, env := b.config.Cmd, b.config.Env
b.config.Cmd = nil
MergeConfig(b.config, config)
body, statusCode, err := b.cli.call("POST", "/images/getCache", &APIImageConfig{ID: b.image, Config: b.config})
if err != nil {
if statusCode != 404 {
return err
}
}
if statusCode != 404 {
apiID := &APIID{}
if err := json.Unmarshal(body, apiID); err != nil {
return err
}
utils.Debugf("Use cached version")
b.image = apiID.ID
return nil
}
cid, err := b.run()
if err != nil {
return err
}
b.config.Cmd, b.config.Env = cmd, env
return b.commit(cid)
}
func (b *builderClient) CmdEnv(args string) error {
b.needCommit = true
tmp := strings.SplitN(args, " ", 2)
if len(tmp) != 2 {
return fmt.Errorf("Invalid ENV format")
}
key := strings.Trim(tmp[0], " ")
value := strings.Trim(tmp[1], " ")
for i, elem := range b.config.Env {
if strings.HasPrefix(elem, key+"=") {
b.config.Env[i] = key + "=" + value
return nil
}
}
b.config.Env = append(b.config.Env, key+"="+value)
return nil
}
func (b *builderClient) CmdCmd(args string) error {
b.needCommit = true
var cmd []string
if err := json.Unmarshal([]byte(args), &cmd); err != nil {
utils.Debugf("Error unmarshalling: %s, using /bin/sh -c", err)
b.config.Cmd = []string{"/bin/sh", "-c", args}
} else {
b.config.Cmd = cmd
}
return nil
}
func (b *builderClient) CmdExpose(args string) error {
ports := strings.Split(args, " ")
b.config.PortSpecs = append(ports, b.config.PortSpecs...)
return nil
}
func (b *builderClient) CmdInsert(args string) error {
// tmp := strings.SplitN(args, "\t ", 2)
// sourceUrl, destPath := tmp[0], tmp[1]
// v := url.Values{}
// v.Set("url", sourceUrl)
// v.Set("path", destPath)
// body, _, err := b.cli.call("POST", "/images/insert?"+v.Encode(), nil)
// if err != nil {
// return err
// }
// apiId := &APIId{}
// if err := json.Unmarshal(body, apiId); err != nil {
// return err
// }
// FIXME: Reimplement this, we need to retrieve the resulting Id
return fmt.Errorf("INSERT not implemented")
}
func (b *builderClient) run() (string, error) {
if b.image == "" {
return "", fmt.Errorf("Please provide a source image with `from` prior to run")
}
b.config.Image = b.image
body, _, err := b.cli.call("POST", "/containers/create", b.config)
if err != nil {
return "", err
}
apiRun := &APIRun{}
if err := json.Unmarshal(body, apiRun); err != nil {
return "", err
}
for _, warning := range apiRun.Warnings {
fmt.Fprintln(os.Stderr, "WARNING: ", warning)
}
//start the container
_, _, err = b.cli.call("POST", "/containers/"+apiRun.ID+"/start", nil)
if err != nil {
return "", err
}
b.tmpContainers[apiRun.ID] = struct{}{}
// Wait for it to finish
body, _, err = b.cli.call("POST", "/containers/"+apiRun.ID+"/wait", nil)
if err != nil {
return "", err
}
apiWait := &APIWait{}
if err := json.Unmarshal(body, apiWait); err != nil {
return "", err
}
if apiWait.StatusCode != 0 {
return "", fmt.Errorf("The command %v returned a non-zero code: %d", b.config.Cmd, apiWait.StatusCode)
}
return apiRun.ID, nil
}
func (b *builderClient) commit(id string) error {
if b.image == "" {
return fmt.Errorf("Please provide a source image with `from` prior to run")
}
b.config.Image = b.image
if id == "" {
cmd := b.config.Cmd
b.config.Cmd = []string{"true"}
cid, err := b.run()
if err != nil {
return err
}
id = cid
b.config.Cmd = cmd
}
// Commit the container
v := url.Values{}
v.Set("container", id)
v.Set("author", b.maintainer)
body, _, err := b.cli.call("POST", "/commit?"+v.Encode(), b.config)
if err != nil {
return err
}
apiID := &APIID{}
if err := json.Unmarshal(body, apiID); err != nil {
return err
}
b.tmpImages[apiID.ID] = struct{}{}
b.image = apiID.ID
b.needCommit = false
return nil
}
func (b *builderClient) Build(dockerfile, context io.Reader) (string, error) {
defer b.clearTmp(b.tmpContainers, b.tmpImages)
file := bufio.NewReader(dockerfile)
for {
line, err := file.ReadString('\n')
if err != nil {
if err == io.EOF {
break
}
return "", err
}
line = strings.Replace(strings.TrimSpace(line), " ", " ", 1)
// Skip comments and empty line
if len(line) == 0 || line[0] == '#' {
continue
}
tmp := strings.SplitN(line, " ", 2)
if len(tmp) != 2 {
return "", fmt.Errorf("Invalid Dockerfile format")
}
instruction := strings.ToLower(strings.Trim(tmp[0], " "))
arguments := strings.Trim(tmp[1], " ")
fmt.Fprintf(os.Stderr, "%s %s (%s)\n", strings.ToUpper(instruction), arguments, b.image)
method, exists := reflect.TypeOf(b).MethodByName("Cmd" + strings.ToUpper(instruction[:1]) + strings.ToLower(instruction[1:]))
if !exists {
fmt.Fprintf(os.Stderr, "Skipping unknown instruction %s\n", strings.ToUpper(instruction))
}
ret := method.Func.Call([]reflect.Value{reflect.ValueOf(b), reflect.ValueOf(arguments)})[0].Interface()
if ret != nil {
return "", ret.(error)
}
fmt.Fprintf(os.Stderr, "===> %v\n", b.image)
}
if b.needCommit {
if err := b.commit(""); err != nil {
return "", err
}
}
if b.image != "" {
// The build is successful, keep the temporary containers and images
for i := range b.tmpImages {
delete(b.tmpImages, i)
}
for i := range b.tmpContainers {
delete(b.tmpContainers, i)
}
fmt.Fprintf(os.Stderr, "Build finished. image id: %s\n", b.image)
return b.image, nil
}
return "", fmt.Errorf("An error occured during the build\n")
}
func NewBuilderClient(addr string, port int) BuildFile {
return &builderClient{
cli: NewDockerCli(addr, port),
config: &Config{},
tmpContainers: make(map[string]struct{}),
tmpImages: make(map[string]struct{}),
}
}

View file

@ -14,7 +14,7 @@ import (
) )
type BuildFile interface { type BuildFile interface {
Build(io.Reader, io.Reader) (string, error) Build(io.Reader) (string, error)
CmdFrom(string) error CmdFrom(string) error
CmdRun(string) error CmdRun(string) error
} }
@ -125,8 +125,8 @@ func (b *buildFile) CmdEnv(args string) error {
if len(tmp) != 2 { if len(tmp) != 2 {
return fmt.Errorf("Invalid ENV format") return fmt.Errorf("Invalid ENV format")
} }
key := strings.Trim(tmp[0], " ") key := strings.Trim(tmp[0], " \t")
value := strings.Trim(tmp[1], " ") value := strings.Trim(tmp[1], " \t")
for i, elem := range b.config.Env { for i, elem := range b.config.Env {
if strings.HasPrefix(elem, key+"=") { if strings.HasPrefix(elem, key+"=") {
@ -165,34 +165,17 @@ func (b *buildFile) CmdCopy(args string) error {
return fmt.Errorf("COPY has been deprecated. Please use ADD instead") return fmt.Errorf("COPY has been deprecated. Please use ADD instead")
} }
func (b *buildFile) CmdAdd(args string) error { func (b *buildFile) addRemote(container *Container, orig, dest string) error {
if b.context == "" { file, err := utils.Download(orig, ioutil.Discard)
return fmt.Errorf("No context given. Impossible to use ADD")
}
tmp := strings.SplitN(args, " ", 2)
if len(tmp) != 2 {
return fmt.Errorf("Invalid ADD format")
}
orig := strings.Trim(tmp[0], " ")
dest := strings.Trim(tmp[1], " ")
cmd := b.config.Cmd
// Create the container and start it
b.config.Cmd = []string{"/bin/sh", "-c", fmt.Sprintf("#(nop) ADD %s in %s", orig, dest)}
b.config.Image = b.image
container, err := b.builder.Create(b.config)
if err != nil { if err != nil {
return err return err
} }
b.tmpContainers[container.ID] = struct{}{} defer file.Body.Close()
fmt.Fprintf(b.out, " ---> Running in %s\n", utils.TruncateID(container.ID))
if err := container.EnsureMounted(); err != nil { return container.Inject(file.Body, dest)
return err }
}
defer container.Unmount()
func (b *buildFile) addContext(container *Container, orig, dest string) error {
origPath := path.Join(b.context, orig) origPath := path.Join(b.context, orig)
destPath := path.Join(container.RootfsPath(), dest) destPath := path.Join(container.RootfsPath(), dest)
// Preserve the trailing '/' // Preserve the trailing '/'
@ -218,6 +201,46 @@ func (b *buildFile) CmdAdd(args string) error {
return err return err
} }
} }
return nil
}
func (b *buildFile) CmdAdd(args string) error {
if b.context == "" {
return fmt.Errorf("No context given. Impossible to use ADD")
}
tmp := strings.SplitN(args, " ", 2)
if len(tmp) != 2 {
return fmt.Errorf("Invalid ADD format")
}
orig := strings.Trim(tmp[0], " \t")
dest := strings.Trim(tmp[1], " \t")
cmd := b.config.Cmd
b.config.Cmd = []string{"/bin/sh", "-c", fmt.Sprintf("#(nop) ADD %s in %s", orig, dest)}
b.config.Image = b.image
// Create the container and start it
container, err := b.builder.Create(b.config)
if err != nil {
return err
}
b.tmpContainers[container.ID] = struct{}{}
if err := container.EnsureMounted(); err != nil {
return err
}
defer container.Unmount()
if utils.IsURL(orig) {
if err := b.addRemote(container, orig, dest); err != nil {
return err
}
} else {
if err := b.addContext(container, orig, dest); err != nil {
return err
}
}
if err := b.commit(container.ID, cmd, fmt.Sprintf("ADD %s in %s", orig, dest)); err != nil { if err := b.commit(container.ID, cmd, fmt.Sprintf("ADD %s in %s", orig, dest)); err != nil {
return err return err
} }
@ -259,7 +282,9 @@ func (b *buildFile) commit(id string, autoCmd []string, comment string) error {
} }
b.config.Image = b.image b.config.Image = b.image
if id == "" { if id == "" {
cmd := b.config.Cmd
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)
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
@ -271,21 +296,17 @@ func (b *buildFile) commit(id string, autoCmd []string, comment string) error {
} else { } else {
utils.Debugf("[BUILDER] Cache miss") utils.Debugf("[BUILDER] Cache miss")
} }
// Create the container and start it
container, err := b.builder.Create(b.config) container, err := b.builder.Create(b.config)
if err != nil { if err != nil {
return err return err
} }
b.tmpContainers[container.ID] = struct{}{} b.tmpContainers[container.ID] = struct{}{}
fmt.Fprintf(b.out, " ---> Running in %s\n", utils.TruncateID(container.ID)) fmt.Fprintf(b.out, " ---> Running in %s\n", utils.TruncateID(container.ID))
id = container.ID
if err := container.EnsureMounted(); err != nil { if err := container.EnsureMounted(); err != nil {
return err return err
} }
defer container.Unmount() defer container.Unmount()
id = container.ID
} }
container := b.runtime.Get(id) container := b.runtime.Get(id)
@ -306,18 +327,23 @@ func (b *buildFile) commit(id string, autoCmd []string, comment string) error {
return nil return nil
} }
func (b *buildFile) Build(dockerfile, context io.Reader) (string, error) { func (b *buildFile) Build(context io.Reader) (string, error) {
if context != nil { // FIXME: @creack any reason for using /tmp instead of ""?
name, err := ioutil.TempDir("/tmp", "docker-build") // FIXME: @creack "name" is a terrible variable name
if err != nil { name, err := ioutil.TempDir("/tmp", "docker-build")
return "", err if err != nil {
} return "", err
if err := Untar(context, name); err != nil {
return "", err
}
defer os.RemoveAll(name)
b.context = name
} }
if err := Untar(context, name); err != nil {
return "", err
}
defer os.RemoveAll(name)
b.context = name
dockerfile, err := os.Open(path.Join(name, "Dockerfile"))
if err != nil {
return "", fmt.Errorf("Can't build a directory with no Dockerfile")
}
// FIXME: "file" is also a terrible variable name ;)
file := bufio.NewReader(dockerfile) file := bufio.NewReader(dockerfile)
stepN := 0 stepN := 0
for { for {
@ -329,7 +355,7 @@ func (b *buildFile) Build(dockerfile, context io.Reader) (string, error) {
return "", err return "", err
} }
} }
line = strings.Replace(strings.TrimSpace(line), " ", " ", 1) line = strings.Trim(strings.Replace(line, "\t", " ", -1), " \t\r\n")
// Skip comments and empty line // Skip comments and empty line
if len(line) == 0 || line[0] == '#' { if len(line) == 0 || line[0] == '#' {
continue continue

View file

@ -1,37 +1,91 @@
package docker package docker
import ( import (
"github.com/dotcloud/docker/utils" "io/ioutil"
"strings"
"testing" "testing"
) )
const Dockerfile = ` // mkTestContext generates a build context from the contents of the provided dockerfile.
# VERSION 0.1 // This context is suitable for use as an argument to BuildFile.Build()
# DOCKER-VERSION 0.2 func mkTestContext(dockerfile string, files [][2]string, t *testing.T) Archive {
context, err := mkBuildContext(dockerfile, files)
if err != nil {
t.Fatal(err)
}
return context
}
from ` + unitTestImageName + ` // A testContextTemplate describes a build context and how to test it
type testContextTemplate struct {
// Contents of the Dockerfile
dockerfile string
// Additional files in the context, eg [][2]string{"./passwd", "gordon"}
files [][2]string
}
// A table of all the contexts to build and test.
// A new docker runtime will be created and torn down for each context.
var testContexts []testContextTemplate = []testContextTemplate{
{
`
from docker-ut
run sh -c 'echo root:testpass > /tmp/passwd' run sh -c 'echo root:testpass > /tmp/passwd'
run mkdir -p /var/run/sshd run mkdir -p /var/run/sshd
` run [ "$(cat /tmp/passwd)" = "root:testpass" ]
run [ "$(ls -d /var/run/sshd)" = "/var/run/sshd" ]
`,
nil,
},
const DockerfileNoNewLine = ` {
# VERSION 0.1 `
# DOCKER-VERSION 0.2 from docker-ut
add foo /usr/lib/bla/bar
run [ "$(cat /usr/lib/bla/bar)" = 'hello world!' ]
`,
[][2]string{{"foo", "hello world!"}},
},
from ` + unitTestImageName + ` {
run sh -c 'echo root:testpass > /tmp/passwd' `
run mkdir -p /var/run/sshd` from docker-ut
add f /
run [ "$(cat /f)" = "hello" ]
add f /abc
run [ "$(cat /abc)" = "hello" ]
add f /x/y/z
run [ "$(cat /x/y/z)" = "hello" ]
add f /x/y/d/
run [ "$(cat /x/y/d/f)" = "hello" ]
add d /
run [ "$(cat /ga)" = "bu" ]
add d /somewhere
run [ "$(cat /somewhere/ga)" = "bu" ]
add d /anotherplace/
run [ "$(cat /anotherplace/ga)" = "bu" ]
add d /somewheeeere/over/the/rainbooow
run [ "$(cat /somewheeeere/over/the/rainbooow/ga)" = "bu" ]
`,
[][2]string{
{"f", "hello"},
{"d/ga", "bu"},
},
},
// FIXME: test building with a context {
`
// FIXME: test building with a local ADD as first command from docker-ut
env FOO BAR
run [ "$FOO" = "BAR" ]
`,
nil,
},
}
// FIXME: test building with 2 successive overlapping ADD commands // FIXME: test building with 2 successive overlapping ADD commands
func TestBuild(t *testing.T) { func TestBuild(t *testing.T) {
dockerfiles := []string{Dockerfile, DockerfileNoNewLine} for _, ctx := range testContexts {
for _, Dockerfile := range dockerfiles {
runtime, err := newTestRuntime() runtime, err := newTestRuntime()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -40,50 +94,9 @@ func TestBuild(t *testing.T) {
srv := &Server{runtime: runtime} srv := &Server{runtime: runtime}
buildfile := NewBuildFile(srv, &utils.NopWriter{}) buildfile := NewBuildFile(srv, ioutil.Discard)
if _, err := buildfile.Build(mkTestContext(ctx.dockerfile, ctx.files, t)); err != nil {
imgID, err := buildfile.Build(strings.NewReader(Dockerfile), nil)
if err != nil {
t.Fatal(err) t.Fatal(err)
} }
builder := NewBuilder(runtime)
container, err := builder.Create(
&Config{
Image: imgID,
Cmd: []string{"cat", "/tmp/passwd"},
},
)
if err != nil {
t.Fatal(err)
}
defer runtime.Destroy(container)
output, err := container.Output()
if err != nil {
t.Fatal(err)
}
if string(output) != "root:testpass\n" {
t.Fatalf("Unexpected output. Read '%s', expected '%s'", output, "root:testpass\n")
}
container2, err := builder.Create(
&Config{
Image: imgID,
Cmd: []string{"ls", "-d", "/var/run/sshd"},
},
)
if err != nil {
t.Fatal(err)
}
defer runtime.Destroy(container2)
output, err = container2.Output()
if err != nil {
t.Fatal(err)
}
if string(output) != "/var/run/sshd\n" {
t.Fatal("/var/run/sshd has not been created")
}
} }
} }

View file

@ -1,6 +1,7 @@
package docker package docker
import ( import (
"archive/tar"
"bytes" "bytes"
"encoding/json" "encoding/json"
"flag" "flag"
@ -10,14 +11,12 @@ import (
"github.com/dotcloud/docker/utils" "github.com/dotcloud/docker/utils"
"io" "io"
"io/ioutil" "io/ioutil"
"mime/multipart"
"net" "net"
"net/http" "net/http"
"net/http/httputil" "net/http/httputil"
"net/url" "net/url"
"os" "os"
"os/signal" "os/signal"
"path"
"path/filepath" "path/filepath"
"reflect" "reflect"
"regexp" "regexp"
@ -29,7 +28,7 @@ import (
"unicode" "unicode"
) )
const VERSION = "0.4.2" const VERSION = "0.4.4"
var ( var (
GITCOMMIT string GITCOMMIT string
@ -40,8 +39,8 @@ func (cli *DockerCli) getMethod(name string) (reflect.Method, bool) {
return reflect.TypeOf(cli).MethodByName(methodName) return reflect.TypeOf(cli).MethodByName(methodName)
} }
func ParseCommands(addr string, port int, args ...string) error { func ParseCommands(proto, addr string, args ...string) error {
cli := NewDockerCli(addr, port) cli := NewDockerCli(proto, addr)
if len(args) > 0 { if len(args) > 0 {
method, exists := cli.getMethod(args[0]) method, exists := cli.getMethod(args[0])
@ -74,7 +73,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
return nil return nil
} }
} }
help := fmt.Sprintf("Usage: docker [OPTIONS] COMMAND [arg...]\n -H=\"%s:%d\": Host:port to bind/connect to\n\nA self-sufficient runtime for linux containers.\n\nCommands:\n", cli.host, cli.port) help := fmt.Sprintf("Usage: docker [OPTIONS] COMMAND [arg...]\n -H=[tcp://%s:%d]: tcp://host:port to bind/connect to or unix://path/to/socker to use\n\nA self-sufficient runtime for linux containers.\n\nCommands:\n", DEFAULTHTTPHOST, DEFAULTHTTPPORT)
for _, command := range [][2]string{ for _, command := range [][2]string{
{"attach", "Attach to a running container"}, {"attach", "Attach to a running container"},
{"build", "Build a container from a Dockerfile"}, {"build", "Build a container from a Dockerfile"},
@ -131,8 +130,33 @@ func (cli *DockerCli) CmdInsert(args ...string) error {
return nil return nil
} }
// mkBuildContext returns an archive of an empty context with the contents
// of `dockerfile` at the path ./Dockerfile
func mkBuildContext(dockerfile string, files [][2]string) (Archive, error) {
buf := new(bytes.Buffer)
tw := tar.NewWriter(buf)
files = append(files, [2]string{"Dockerfile", dockerfile})
for _, file := range files {
name, content := file[0], file[1]
hdr := &tar.Header{
Name: name,
Size: int64(len(content)),
}
if err := tw.WriteHeader(hdr); err != nil {
return nil, err
}
if _, err := tw.Write([]byte(content)); err != nil {
return nil, err
}
}
if err := tw.Close(); err != nil {
return nil, err
}
return buf, nil
}
func (cli *DockerCli) CmdBuild(args ...string) error { func (cli *DockerCli) CmdBuild(args ...string) error {
cmd := Subcmd("build", "[OPTIONS] PATH | -", "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")
if err := cmd.Parse(args); err != nil { if err := cmd.Parse(args); err != nil {
return nil return nil
@ -143,76 +167,55 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
} }
var ( var (
multipartBody io.Reader context Archive
file io.ReadCloser isRemote bool
contextPath string err error
) )
// Init the needed component for the Multipart
buff := bytes.NewBuffer([]byte{})
multipartBody = buff
w := multipart.NewWriter(buff)
boundary := strings.NewReader("\r\n--" + w.Boundary() + "--\r\n")
compression := Bzip2
if cmd.Arg(0) == "-" { if cmd.Arg(0) == "-" {
file = os.Stdin // As a special case, 'docker build -' will build from an empty context with the
// contents of stdin as a Dockerfile
dockerfile, err := ioutil.ReadAll(os.Stdin)
if err != nil {
return err
}
context, err = mkBuildContext(string(dockerfile), nil)
} else if utils.IsURL(cmd.Arg(0)) || utils.IsGIT(cmd.Arg(0)) {
isRemote = true
} else { } else {
// Send Dockerfile from arg/Dockerfile (deprecate later) context, err = Tar(cmd.Arg(0), Uncompressed)
f, err := os.Open(path.Join(cmd.Arg(0), "Dockerfile")) }
if err != nil { var body io.Reader
return err // Setup an upload progress bar
} // FIXME: ProgressReader shouldn't be this annoyning to use
file = f if context != nil {
// Send context from arg
// Create a FormFile multipart for the context if needed
// FIXME: Use NewTempArchive in order to have the size and avoid too much memory usage?
context, err := Tar(cmd.Arg(0), compression)
if err != nil {
return err
}
// NOTE: Do this in case '.' or '..' is input
absPath, err := filepath.Abs(cmd.Arg(0))
if err != nil {
return err
}
wField, err := w.CreateFormFile("Context", filepath.Base(absPath)+"."+compression.Extension())
if err != nil {
return err
}
// FIXME: Find a way to have a progressbar for the upload too
sf := utils.NewStreamFormatter(false) sf := utils.NewStreamFormatter(false)
io.Copy(wField, utils.ProgressReader(ioutil.NopCloser(context), -1, os.Stdout, sf.FormatProgress("Caching Context", "%v/%v (%v)"), sf)) body = utils.ProgressReader(ioutil.NopCloser(context), 0, os.Stderr, sf.FormatProgress("Uploading context", "%v bytes%0.0s%0.0s"), sf)
multipartBody = io.MultiReader(multipartBody, boundary)
} }
// Create a FormFile multipart for the Dockerfile // Upload the build context
wField, err := w.CreateFormFile("Dockerfile", "Dockerfile")
if err != nil {
return err
}
io.Copy(wField, file)
multipartBody = io.MultiReader(multipartBody, boundary)
v := &url.Values{} v := &url.Values{}
v.Set("t", *tag) v.Set("t", *tag)
// Send the multipart request with correct content-type if isRemote {
req, err := http.NewRequest("POST", fmt.Sprintf("http://%s:%d%s?%s", cli.host, cli.port, "/build", v.Encode()), multipartBody) v.Set("remote", cmd.Arg(0))
}
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
} }
req.Header.Set("Content-Type", w.FormDataContentType()) if context != nil {
if contextPath != "" { req.Header.Set("Content-Type", "application/tar")
req.Header.Set("X-Docker-Context-Compression", compression.Flag())
fmt.Println("Uploading Context...")
} }
dial, err := net.Dial(cli.proto, cli.addr)
resp, err := http.DefaultClient.Do(req) if err != nil {
return err
}
clientconn := httputil.NewClientConn(dial, nil)
resp, err := clientconn.Do(req)
defer clientconn.Close()
if err != nil { if err != nil {
return err return err
} }
defer resp.Body.Close() defer resp.Body.Close()
// Check for errors // Check for errors
if resp.StatusCode < 200 || resp.StatusCode >= 400 { if resp.StatusCode < 200 || resp.StatusCode >= 400 {
body, err := ioutil.ReadAll(resp.Body) body, err := ioutil.ReadAll(resp.Body)
@ -311,6 +314,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
email = cli.authConfig.Email email = cli.authConfig.Email
} }
} else { } else {
password = cli.authConfig.Password
email = cli.authConfig.Email email = cli.authConfig.Email
} }
term.RestoreTerminal(oldState) term.RestoreTerminal(oldState)
@ -319,7 +323,14 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
cli.authConfig.Password = password cli.authConfig.Password = password
cli.authConfig.Email = email cli.authConfig.Email = email
body, _, err := cli.call("POST", "/auth", cli.authConfig) body, statusCode, err := cli.call("POST", "/auth", cli.authConfig)
if statusCode == 401 {
cli.authConfig.Username = ""
cli.authConfig.Password = ""
cli.authConfig.Email = ""
auth.SaveConfig(cli.authConfig)
return err
}
if err != nil { if err != nil {
return err return err
} }
@ -332,7 +343,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
} }
auth.SaveConfig(cli.authConfig) auth.SaveConfig(cli.authConfig)
if out2.Status != "" { if out2.Status != "" {
fmt.Print(out2.Status) fmt.Println(out2.Status)
} }
return nil return nil
} }
@ -1044,10 +1055,10 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
return nil return nil
} }
if err := cli.stream("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stdout=1", nil, os.Stdout); err != nil { if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stdout=1", false, nil, os.Stdout); err != nil {
return err return err
} }
if err := cli.stream("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stderr=1", nil, os.Stderr); err != nil { if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stderr=1", false, nil, os.Stderr); err != nil {
return err return err
} }
return nil return nil
@ -1078,37 +1089,18 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
return fmt.Errorf("Impossible to attach to a stopped container, start it first") return fmt.Errorf("Impossible to attach to a stopped container, start it first")
} }
splitStderr := container.Config.Tty
connections := 1
if splitStderr {
connections += 1
}
chErrors := make(chan error, connections)
if container.Config.Tty { if container.Config.Tty {
cli.monitorTtySize(cmd.Arg(0)) cli.monitorTtySize(cmd.Arg(0))
} }
if splitStderr {
go func() {
chErrors <- cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?stream=1&stderr=1", false, nil, os.Stderr)
}()
}
v := url.Values{} v := url.Values{}
v.Set("stream", "1") v.Set("stream", "1")
v.Set("stdin", "1") v.Set("stdin", "1")
v.Set("stdout", "1") v.Set("stdout", "1")
if !splitStderr { v.Set("stderr", "1")
v.Set("stderr", "1")
} if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), container.Config.Tty, os.Stdin, os.Stdout); err != nil {
go func() { return err
chErrors <- cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), container.Config.Tty, os.Stdin, os.Stdout)
}()
for connections > 0 {
err := <-chErrors
if err != nil {
return err
}
connections -= 1
} }
return nil return nil
} }
@ -1334,7 +1326,7 @@ func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int,
params = bytes.NewBuffer(buf) params = bytes.NewBuffer(buf)
} }
req, err := http.NewRequest(method, fmt.Sprintf("http://%s:%d/v%g%s", cli.host, cli.port, APIVERSION, path), params) req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), params)
if err != nil { if err != nil {
return nil, -1, err return nil, -1, err
} }
@ -1344,7 +1336,13 @@ func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int,
} else if method == "POST" { } else if method == "POST" {
req.Header.Set("Content-Type", "plain/text") req.Header.Set("Content-Type", "plain/text")
} }
resp, err := http.DefaultClient.Do(req) dial, err := net.Dial(cli.proto, cli.addr)
if err != nil {
return nil, -1, err
}
clientconn := httputil.NewClientConn(dial, nil)
resp, err := clientconn.Do(req)
defer clientconn.Close()
if err != nil { if err != nil {
if strings.Contains(err.Error(), "connection refused") { if strings.Contains(err.Error(), "connection refused") {
return nil, -1, fmt.Errorf("Can't connect to docker daemon. Is 'docker -d' running on this host?") return nil, -1, fmt.Errorf("Can't connect to docker daemon. Is 'docker -d' running on this host?")
@ -1369,7 +1367,7 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e
if (method == "POST" || method == "PUT") && in == nil { if (method == "POST" || method == "PUT") && in == nil {
in = bytes.NewReader([]byte{}) in = bytes.NewReader([]byte{})
} }
req, err := http.NewRequest(method, fmt.Sprintf("http://%s:%d/v%g%s", cli.host, cli.port, APIVERSION, path), in) req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), in)
if err != nil { if err != nil {
return err return err
} }
@ -1377,7 +1375,13 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e
if method == "POST" { if method == "POST" {
req.Header.Set("Content-Type", "plain/text") req.Header.Set("Content-Type", "plain/text")
} }
resp, err := http.DefaultClient.Do(req) dial, err := net.Dial(cli.proto, cli.addr)
if err != nil {
return err
}
clientconn := httputil.NewClientConn(dial, nil)
resp, err := clientconn.Do(req)
defer clientconn.Close()
if err != nil { if err != nil {
if strings.Contains(err.Error(), "connection refused") { if strings.Contains(err.Error(), "connection refused") {
return fmt.Errorf("Can't connect to docker daemon. Is 'docker -d' running on this host?") return fmt.Errorf("Can't connect to docker daemon. Is 'docker -d' running on this host?")
@ -1385,6 +1389,7 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e
return err return err
} }
defer resp.Body.Close() defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 400 { if resp.StatusCode < 200 || resp.StatusCode >= 400 {
body, err := ioutil.ReadAll(resp.Body) body, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
@ -1422,19 +1427,24 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e
} }
func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in *os.File, out io.Writer) error { func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in *os.File, out io.Writer) error {
req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), nil) req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), nil)
if err != nil { if err != nil {
return err return err
} }
req.Header.Set("User-Agent", "Docker-Client/"+VERSION)
req.Header.Set("Content-Type", "plain/text") req.Header.Set("Content-Type", "plain/text")
dial, err := net.Dial("tcp", fmt.Sprintf("%s:%d", cli.host, cli.port))
dial, err := net.Dial(cli.proto, cli.addr)
if err != nil { if err != nil {
return err return err
} }
clientconn := httputil.NewClientConn(dial, nil) clientconn := httputil.NewClientConn(dial, nil)
clientconn.Do(req)
defer clientconn.Close() defer clientconn.Close()
// Server hijacks the connection, error 'connection closed' expected
clientconn.Do(req)
rwc, br := clientconn.Hijack() rwc, br := clientconn.Hijack()
defer rwc.Close() defer rwc.Close()
@ -1510,13 +1520,13 @@ func Subcmd(name, signature, description string) *flag.FlagSet {
return flags return flags
} }
func NewDockerCli(addr string, port int) *DockerCli { func NewDockerCli(proto, addr string) *DockerCli {
authConfig, _ := auth.LoadConfig(os.Getenv("HOME")) authConfig, _ := auth.LoadConfig(os.Getenv("HOME"))
return &DockerCli{addr, port, authConfig} return &DockerCli{proto, addr, authConfig}
} }
type DockerCli struct { type DockerCli struct {
host string proto string
port int addr string
authConfig *auth.AuthConfig authConfig *auth.AuthConfig
} }

View file

@ -76,7 +76,7 @@ type Config struct {
} }
func ParseRun(args []string, capabilities *Capabilities) (*Config, *flag.FlagSet, error) { func ParseRun(args []string, capabilities *Capabilities) (*Config, *flag.FlagSet, error) {
cmd := Subcmd("run", "[OPTIONS] IMAGE COMMAND [ARG...]", "Run a command in a new container") cmd := Subcmd("run", "[OPTIONS] IMAGE [COMMAND] [ARG...]", "Run a command in a new container")
if len(args) > 0 && args[0] != "--help" { if len(args) > 0 && args[0] != "--help" {
cmd.SetOutput(ioutil.Discard) cmd.SetOutput(ioutil.Discard)
} }
@ -632,7 +632,6 @@ func (container *Container) waitLxc() error {
} }
time.Sleep(500 * time.Millisecond) time.Sleep(500 * time.Millisecond)
} }
panic("Unreachable")
} }
func (container *Container) monitor() { func (container *Container) monitor() {
@ -821,8 +820,6 @@ func (container *Container) WaitTimeout(timeout time.Duration) error {
case <-done: case <-done:
return nil return nil
} }
panic("Unreachable")
} }
func (container *Container) EnsureMounted() error { func (container *Container) EnsureMounted() error {

View file

@ -116,7 +116,6 @@ func crashTest() error {
return err return err
} }
} }
return nil
} }
func main() { func main() {

View file

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

View file

@ -19,13 +19,35 @@ Docker Remote API
2. Versions 2. Versions
=========== ===========
The current verson of the API is 1.2 The current verson of the API is 1.3
Calling /images/<name>/insert is the same as calling /v1.2/images/<name>/insert Calling /images/<name>/insert is the same as calling /v1.3/images/<name>/insert
You can still call an old version of the api using /v1.0/images/<name>/insert You can still call an old version of the api using /v1.0/images/<name>/insert
:doc:`docker_remote_api_v1.3`
*****************************
What's new
----------
Builder (/build):
- Simplify the upload of the build context
- Simply stream a tarball instead of multipart upload with 4 intermediary buffers
- Simpler, less memory usage, less disk usage and faster
.. Note::
The /build improvements are not reverse-compatible. Pre 1.3 clients will break on /build.
List containers (/containers/json):
- You can use size=1 to get the size of the containers
:doc:`docker_remote_api_v1.2` :doc:`docker_remote_api_v1.2`
***************************** *****************************
docker v0.4.2 2e7649b_
What's new What's new
---------- ----------
@ -36,6 +58,7 @@ The client should send it's authConfig as POST on each call of /images/(name)/pu
.. http:post:: /auth only checks the configuration but doesn't store it on the server .. http:post:: /auth only checks the configuration but doesn't store it on the server
Deleting an image is now improved, will only untag the image if it has chidrens and remove all the untagged parents if has any. Deleting an image is now improved, will only untag the image if it has chidrens and remove all the untagged parents if has any.
.. http:post:: /images/<name>/delete now returns a JSON with the list of images deleted/untagged .. http:post:: /images/<name>/delete now returns a JSON with the list of images deleted/untagged
@ -64,6 +87,9 @@ Uses json stream instead of HTML hijack, it looks like this:
... ...
:doc:`docker_remote_api_v1.0`
*****************************
docker v0.3.4 8d73740_ docker v0.3.4 8d73740_
What's new What's new
@ -74,6 +100,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
================================== ==================================
Docker Remote API Client Libraries Docker Remote API Client Libraries

View file

@ -847,7 +847,7 @@ Build an image from Dockerfile via stdin
.. http:post:: /build .. http:post:: /build
Build an image from Dockerfile via stdin Build an image from Dockerfile
**Example request**: **Example request**:
@ -866,9 +866,12 @@ Build an image from Dockerfile via stdin
{{ STREAM }} {{ STREAM }}
:query t: tag to be applied to the resulting image in case of success :query t: tag to be applied to the resulting image in case of success
:query remote: resource to fetch, as URI
:statuscode 200: no error :statuscode 200: no error
:statuscode 500: server error :statuscode 500: server error
{{ STREAM }} is the raw text output of the build command. It uses the HTTP Hijack method in order to stream.
Check auth configuration Check auth configuration
************************ ************************
@ -895,9 +898,16 @@ Check auth configuration
.. sourcecode:: http .. sourcecode:: http
HTTP/1.1 200 OK HTTP/1.1 200 OK
Content-Type: application/json
{
"Status": "Login Succeeded"
}
:statuscode 200: no error :statuscode 200: no error
:statuscode 204: no error :statuscode 204: no error
:statuscode 401: unauthorized
:statuscode 403: forbidden
:statuscode 500: server error :statuscode 500: server error
@ -1027,5 +1037,5 @@ In this version of the API, /attach, uses hijacking to transport stdin, stdout a
To enable cross origin requests to the remote api add the flag "-api-enable-cors" when running docker in daemon mode. To enable cross origin requests to the remote api add the flag "-api-enable-cors" when running docker in daemon mode.
docker -d -H="192.168.1.9:4243" -api-enable-cors docker -d -H="tcp://192.168.1.9:4243" -api-enable-cors

File diff suppressed because it is too large Load diff

View file

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

View file

@ -8,9 +8,11 @@
:: ::
Usage: docker build [OPTIONS] PATH | - Usage: docker build [OPTIONS] PATH | URL | -
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.
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
Examples Examples
-------- --------
@ -27,7 +29,15 @@ Examples
.. code-block:: bash .. code-block:: bash
docker build - docker build - < Dockerfile
| This will read a Dockerfile from Stdin without context. Due to the lack of a context, no contents of any local directory will be sent to the docker daemon. | This will read a Dockerfile from Stdin without context. Due to the lack of a context, no contents of any local directory will be sent to the docker daemon.
| ADD doesn't work when running in this mode due to the absence of the context, thus having no source files to copy to the container. | ADD doesn't work when running in this mode due to the absence of the context, thus having no source files to copy to the container.
.. code-block:: bash
docker build github.com/creack/docker-firefox
| This will clone the github repository and use it as context. The Dockerfile at the root of the repository is used as Dockerfile.
| Note that you can specify an arbitrary git repository by using the 'git://' schema.

View file

@ -8,7 +8,7 @@
:: ::
Usage: docker run [OPTIONS] IMAGE COMMAND [ARG...] Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
Run a command in a new container Run a command in a new container

View file

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

View file

@ -33,11 +33,20 @@ Running an interactive shell
# allocate a tty, attach stdin and stdout # allocate a tty, attach stdin and stdout
docker run -i -t base /bin/bash docker run -i -t base /bin/bash
Bind Docker to another host/port Bind Docker to another host/port or a unix socket
-------------------------------- -------------------------------------------------
If you want Docker to listen to another port and bind to another ip With -H it is possible to make the Docker daemon to listen on a specific ip and port. By default, it will listen on 127.0.0.1:4243 to allow only local connections but you can set it to 0.0.0.0:4243 or a specific host ip to give access to everybody.
use -host and -port on both deamon and client
Similarly, the Docker client can use -H to connect to a custom port.
-H accepts host and port assignment in the following format: tcp://[host][:port] or unix://path
For example:
* tcp://host -> tcp connection on host:4243
* tcp://host:port -> tcp connection on host:port
* tcp://:port -> tcp connection on 127.0.0.1:port
* unix://path/to/socket -> unix socket located at path/to/socket
.. code-block:: bash .. code-block:: bash
@ -46,6 +55,17 @@ use -host and -port on both deamon and client
# Download a base image # Download a base image
docker -H :5555 pull base docker -H :5555 pull base
You can use multiple -H, for example, if you want to listen
on both tcp and a unix socket
.. code-block:: bash
# Run docker in daemon mode
sudo <path to>/docker -H tcp://127.0.0.1:4243 -H unix:///var/run/docker.sock
# Download a base image
docker pull base
# OR
docker -H unix:///var/run/docker.sock pull base
Starting a long-running worker process Starting a long-running worker process
-------------------------------------- --------------------------------------

View file

@ -121,19 +121,7 @@ functionally equivalent to prefixing the command with `<key>=<value>`
.. note:: .. note::
The environment variables will persist when a container is run from the resulting image. The environment variables will persist when a container is run from the resulting image.
2.7 INSERT 2.7 ADD
----------
``INSERT <file url> <path>``
The `INSERT` instruction will download the file from the given url to the given
path within the image. It is similar to `RUN curl -o <path> <url>`, assuming
curl was installed within the image.
.. note::
The path must include the file name.
2.8 ADD
------- -------
``ADD <src> <dest>`` ``ADD <src> <dest>``
@ -141,7 +129,7 @@ curl was installed within the image.
The `ADD` instruction will copy new files from <src> and add them to the container's filesystem at path `<dest>`. The `ADD` instruction will copy new files from <src> and add them to the container's filesystem at path `<dest>`.
`<src>` must be the path to a file or directory relative to the source directory being built (also called the `<src>` must be the path to a file or directory relative to the source directory being built (also called the
context of the build). context of the build) or a remote file URL.
`<dest>` is the path at which the source will be copied in the destination container. `<dest>` is the path at which the source will be copied in the destination container.
@ -182,7 +170,6 @@ files and directories are created with mode 0700, uid and gid 0.
RUN apt-get update RUN apt-get update
RUN apt-get install -y inotify-tools nginx apache2 openssh-server RUN apt-get install -y inotify-tools nginx apache2 openssh-server
INSERT https://raw.github.com/creack/docker-vps/master/nginx-wrapper.sh /usr/sbin/nginx-wrapper
.. code-block:: bash .. code-block:: bash

View file

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

View file

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

View file

@ -19,19 +19,14 @@ run add-apt-repository "deb http://archive.ubuntu.com/ubuntu $(lsb_release -sc)
run add-apt-repository -y ppa:dotcloud/docker-golang/ubuntu run add-apt-repository -y ppa:dotcloud/docker-golang/ubuntu
run apt-get update run apt-get update
# Packages required to checkout, build and upload docker # Packages required to checkout, build and upload docker
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q s3cmd run DEBIAN_FRONTEND=noninteractive apt-get install -y -q s3cmd curl
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q curl
run curl -s -o /go.tar.gz https://go.googlecode.com/files/go1.1.1.linux-amd64.tar.gz run curl -s -o /go.tar.gz https://go.googlecode.com/files/go1.1.1.linux-amd64.tar.gz
run tar -C /usr/local -xzf /go.tar.gz run tar -C /usr/local -xzf /go.tar.gz
run echo "export PATH=/usr/local/go/bin:$PATH" > /.bashrc run echo "export PATH=/usr/local/go/bin:$PATH" > /.bashrc
run echo "export PATH=/usr/local/go/bin:$PATH" > /.bash_profile run echo "export PATH=/usr/local/go/bin:$PATH" > /.bash_profile
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q git run DEBIAN_FRONTEND=noninteractive apt-get install -y -q git build-essential
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q build-essential
# Packages required to build an ubuntu package # Packages required to build an ubuntu package
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q golang-stable run DEBIAN_FRONTEND=noninteractive apt-get install -y -q golang-stable debhelper autotools-dev devscripts
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q debhelper
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q autotools-dev
run apt-get install -y -q devscripts
# Copy dockerbuilder files into the container # Copy dockerbuilder files into the container
add . /src add . /src
run cp /src/dockerbuilder /usr/local/bin/ && chmod +x /usr/local/bin/dockerbuilder run cp /src/dockerbuilder /usr/local/bin/ && chmod +x /usr/local/bin/dockerbuilder

View file

@ -257,7 +257,6 @@ func proxy(listener net.Listener, proto, address string) error {
utils.Debugf("Connected to backend, splicing") utils.Debugf("Connected to backend, splicing")
splice(src, dst) splice(src, dst)
} }
panic("Unreachable")
} }
func halfSplice(dst, src net.Conn) error { func halfSplice(dst, src net.Conn) error {

View file

@ -2,11 +2,11 @@
# #
# Dependencies: debhelper autotools-dev devscripts golang-stable # Dependencies: debhelper autotools-dev devscripts golang-stable
# Notes: # Notes:
# Use 'make ubuntu' to create the ubuntu package # Use 'make ubuntu' to create the ubuntu package and push it to stating PPA by
# GPG_KEY environment variable needs to contain a GPG private key for package to be signed # default. To push to production, set PUBLISH_PPA=1 before doing 'make ubuntu'
# and uploaded to docker PPA. # GPG_KEY environment variable needs to contain a GPG private key for package
# If GPG_KEY is not defined, make ubuntu will create docker package and exit with # to be signed and uploaded to docker PPA. If GPG_KEY is not defined,
# status code 2 # make ubuntu will create docker package and exit with status code 2
PKG_NAME=lxc-docker PKG_NAME=lxc-docker
GITHUB_PATH=github.com/dotcloud/docker GITHUB_PATH=github.com/dotcloud/docker
@ -15,7 +15,7 @@ VERSION=$(shell sed -En '0,/^\#\# /{s/^\#\# ([^ ]+).+/\1/p}' ../../CHANGELOG.md)
all: all:
# Compile docker. Used by dpkg-buildpackage. # Compile docker. Used by dpkg-buildpackage.
cd src/${GITHUB_PATH}/docker; GOPATH=${CURDIR} go build cd src/${GITHUB_PATH}/docker; GOPATH=${CURDIR} CGO_ENABLED=0 go build -a -ldflags '-d -w'
install: install:
# Used by dpkg-buildpackage # Used by dpkg-buildpackage
@ -52,9 +52,11 @@ ubuntu:
if /usr/bin/test "$${GPG_KEY}" == ""; then exit 2; fi if /usr/bin/test "$${GPG_KEY}" == ""; then exit 2; fi
mkdir ${BUILD_SRC} mkdir ${BUILD_SRC}
# Import gpg signing key # Import gpg signing key
echo "$${GPG_KEY}" | gpg --allow-secret-key-import --import echo "$${GPG_KEY}" | gpg --allow-secret-key-import --import || true
# Sign the package # Sign the package
cd ${BUILD_SRC}; dpkg-source -x ${BUILD_SRC}/../${PKG_NAME}_${VERSION}-1.dsc cd ${BUILD_SRC}; dpkg-source -x ${BUILD_SRC}/../${PKG_NAME}_${VERSION}-1.dsc
cd ${BUILD_SRC}/${PKG_NAME}-${VERSION}; debuild -S -sa cd ${BUILD_SRC}/${PKG_NAME}-${VERSION}; debuild -S -sa
cd ${BUILD_SRC};dput ppa:dotcloud/lxc-docker ${PKG_NAME}_${VERSION}-1_source.changes # Upload to PPA
if [ "${PUBLISH_PPA}" = "1" ]; then cd ${BUILD_SRC};dput ppa:dotcloud/lxc-docker ${PKG_NAME}_${VERSION}-1_source.changes; fi
if [ "${PUBLISH_PPA}" != "1" ]; then cd ${BUILD_SRC};dput ppa:dotcloud/docker-staging ${PKG_NAME}_${VERSION}-1_source.changes; fi
rm -rf ${BUILD_SRC} rm -rf ${BUILD_SRC}

View file

@ -7,10 +7,10 @@ import (
"fmt" "fmt"
"github.com/dotcloud/docker/auth" "github.com/dotcloud/docker/auth"
"github.com/dotcloud/docker/utils" "github.com/dotcloud/docker/utils"
"github.com/shin-/cookiejar"
"io" "io"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"net/http/cookiejar"
"net/url" "net/url"
"strconv" "strconv"
"strings" "strings"
@ -314,7 +314,7 @@ func (r *Registry) opaqueRequest(method, urlStr string, body io.Reader) (*http.R
if err != nil { if err != nil {
return nil, err return nil, err
} }
req.URL.Opaque = strings.Replace(urlStr, req.URL.Scheme + ":", "", 1) req.URL.Opaque = strings.Replace(urlStr, req.URL.Scheme+":", "", 1)
return req, err return req, err
} }
@ -453,11 +453,6 @@ func (r *Registry) SearchRepositories(term string) (*SearchResults, error) {
return result, err return result, err
} }
func (r *Registry) ResetClient(authConfig *auth.AuthConfig) {
r.authConfig = authConfig
r.client.Jar = cookiejar.NewCookieJar()
}
func (r *Registry) GetAuthConfig(withPasswd bool) *auth.AuthConfig { func (r *Registry) GetAuthConfig(withPasswd bool) *auth.AuthConfig {
password := "" password := ""
if withPasswd { if withPasswd {
@ -493,18 +488,18 @@ type Registry struct {
authConfig *auth.AuthConfig authConfig *auth.AuthConfig
} }
func NewRegistry(root string, authConfig *auth.AuthConfig) *Registry { func NewRegistry(root string, authConfig *auth.AuthConfig) (r *Registry, err error) {
httpTransport := &http.Transport{ httpTransport := &http.Transport{
DisableKeepAlives: true, DisableKeepAlives: true,
Proxy: http.ProxyFromEnvironment, Proxy: http.ProxyFromEnvironment,
} }
r := &Registry{ r = &Registry{
authConfig: authConfig, authConfig: authConfig,
client: &http.Client{ client: &http.Client{
Transport: httpTransport, Transport: httpTransport,
}, },
} }
r.client.Jar = cookiejar.NewCookieJar() r.client.Jar, err = cookiejar.New(nil)
return r return r, err
} }

View file

@ -55,8 +55,11 @@ func (srv *Server) ContainerExport(name string, out io.Writer) error {
} }
func (srv *Server) ImagesSearch(term string) ([]APISearch, error) { func (srv *Server) ImagesSearch(term string) ([]APISearch, error) {
r, err := registry.NewRegistry(srv.runtime.root, nil)
results, err := registry.NewRegistry(srv.runtime.root, nil).SearchRepositories(term) if err != nil {
return nil, err
}
results, err := r.SearchRepositories(term)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -451,12 +454,15 @@ func (srv *Server) poolRemove(kind, key string) error {
} }
func (srv *Server) ImagePull(name, tag, endpoint string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig) error { func (srv *Server) ImagePull(name, tag, endpoint string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig) error {
r, err := registry.NewRegistry(srv.runtime.root, authConfig)
if err != nil {
return err
}
if err := srv.poolAdd("pull", name+":"+tag); err != nil { if err := srv.poolAdd("pull", name+":"+tag); err != nil {
return err return err
} }
defer srv.poolRemove("pull", name+":"+tag) defer srv.poolRemove("pull", name+":"+tag)
r := registry.NewRegistry(srv.runtime.root, authConfig)
out = utils.NewWriteFlusher(out) out = utils.NewWriteFlusher(out)
if endpoint != "" { if endpoint != "" {
if err := srv.pullImage(r, out, name, endpoint, nil, sf); err != nil { if err := srv.pullImage(r, out, name, endpoint, nil, sf); err != nil {
@ -655,8 +661,10 @@ func (srv *Server) ImagePush(name, endpoint string, out io.Writer, sf *utils.Str
out = utils.NewWriteFlusher(out) out = utils.NewWriteFlusher(out)
img, err := srv.runtime.graph.Get(name) img, err := srv.runtime.graph.Get(name)
r := registry.NewRegistry(srv.runtime.root, authConfig) r, err2 := registry.NewRegistry(srv.runtime.root, authConfig)
if err2 != nil {
return err2
}
if err != nil { if err != nil {
out.Write(sf.FormatStatus("The push refers to a repository [%s] (len: %d)", name, len(srv.runtime.repositories.Repositories[name]))) out.Write(sf.FormatStatus("The push refers to a repository [%s] (len: %d)", name, len(srv.runtime.repositories.Repositories[name])))
// If it fails, try to get the repository // If it fails, try to get the repository
@ -752,6 +760,9 @@ func (srv *Server) ContainerRestart(name string, t int) error {
func (srv *Server) ContainerDestroy(name string, removeVolume bool) error { func (srv *Server) ContainerDestroy(name string, removeVolume bool) error {
if container := srv.runtime.Get(name); container != nil { if container := srv.runtime.Get(name); container != nil {
if container.State.Running {
return fmt.Errorf("Impossible to remove a running container, please stop it first")
}
volumes := make(map[string]struct{}) volumes := make(map[string]struct{})
// Store all the deleted containers volumes // Store all the deleted containers volumes
for _, volumeId := range container.Volumes { for _, volumeId := range container.Volumes {
@ -969,17 +980,17 @@ func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, std
if stdout { if stdout {
cLog, err := container.ReadLog("stdout") cLog, err := container.ReadLog("stdout")
if err != nil { if err != nil {
utils.Debugf(err.Error()) utils.Debugf("Error reading logs (stdout): %s", err)
} else if _, err := io.Copy(out, cLog); err != nil { } else if _, err := io.Copy(out, cLog); err != nil {
utils.Debugf(err.Error()) utils.Debugf("Error streaming logs (stdout): %s", err)
} }
} }
if stderr { if stderr {
cLog, err := container.ReadLog("stderr") cLog, err := container.ReadLog("stderr")
if err != nil { if err != nil {
utils.Debugf(err.Error()) utils.Debugf("Error reading logs (stderr): %s", err)
} else if _, err := io.Copy(out, cLog); err != nil { } else if _, err := io.Copy(out, cLog); err != nil {
utils.Debugf(err.Error()) utils.Debugf("Error streaming logs (stderr): %s", err)
} }
} }
} }

View file

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

View file

@ -10,6 +10,7 @@ import (
"index/suffixarray" "index/suffixarray"
"io" "io"
"io/ioutil" "io/ioutil"
"log"
"net/http" "net/http"
"os" "os"
"os/exec" "os/exec"
@ -86,7 +87,7 @@ func (r *progressReader) Read(p []byte) (n int, err error) {
} }
if r.readProgress-r.lastUpdate > updateEvery || err != nil { if r.readProgress-r.lastUpdate > updateEvery || err != nil {
if r.readTotal > 0 { if r.readTotal > 0 {
fmt.Fprintf(r.output, r.template, HumanSize(int64(r.readProgress)), HumanSize(int64(r.readTotal)), fmt.Sprintf("%.0f%%", float64(r.readProgress)/float64(r.readTotal)*100)) fmt.Fprintf(r.output, r.template, HumanSize(int64(r.readProgress)), HumanSize(int64(r.readTotal)), fmt.Sprintf("%2.0f%%",float64(r.readProgress)/float64(r.readTotal)*100))
} else { } else {
fmt.Fprintf(r.output, r.template, r.readProgress, "?", "n/a") fmt.Fprintf(r.output, r.template, r.readProgress, "?", "n/a")
} }
@ -146,7 +147,7 @@ func HumanSize(size int64) string {
sizef = sizef / 1000.0 sizef = sizef / 1000.0
i++ i++
} }
return fmt.Sprintf("%.4g %s", sizef, units[i]) return fmt.Sprintf("%5.4g %s", sizef, units[i])
} }
func Trunc(s string, maxlen int) string { func Trunc(s string, maxlen int) string {
@ -235,7 +236,6 @@ func (r *bufReader) Read(p []byte) (n int, err error) {
} }
r.wait.Wait() r.wait.Wait()
} }
panic("unreachable")
} }
func (r *bufReader) Close() error { func (r *bufReader) Close() error {
@ -636,6 +636,14 @@ func (sf *StreamFormatter) Used() bool {
return sf.used return sf.used
} }
func IsURL(str string) bool {
return strings.HasPrefix(str, "http://") || strings.HasPrefix(str, "https://")
}
func IsGIT(str string) bool {
return strings.HasPrefix(str, "git://") || strings.HasPrefix(str, "github.com/")
}
func CheckLocalDns() bool { func CheckLocalDns() bool {
resolv, err := ioutil.ReadFile("/etc/resolv.conf") resolv, err := ioutil.ReadFile("/etc/resolv.conf")
if err != nil { if err != nil {
@ -652,3 +660,28 @@ func CheckLocalDns() bool {
} }
return false return false
} }
func ParseHost(host string, port int, addr string) string {
if strings.HasPrefix(addr, "unix://") {
return addr
}
if strings.HasPrefix(addr, "tcp://") {
addr = strings.TrimPrefix(addr, "tcp://")
}
if strings.Contains(addr, ":") {
hostParts := strings.Split(addr, ":")
if len(hostParts) != 2 {
log.Fatal("Invalid bind address format.")
os.Exit(-1)
}
if hostParts[0] != "" {
host = hostParts[0]
}
if p, err := strconv.Atoi(hostParts[1]); err == nil {
port = p
}
} else {
host = addr
}
return fmt.Sprintf("tcp://%s:%d", host, port)
}

View file

@ -274,3 +274,21 @@ func TestHumanSize(t *testing.T) {
t.Errorf("1024 -> expected 1.024 kB, got %s", size1024) t.Errorf("1024 -> expected 1.024 kB, got %s", size1024)
} }
} }
func TestParseHost(t *testing.T) {
if addr := ParseHost("127.0.0.1", 4243, "0.0.0.0"); addr != "tcp://0.0.0.0:4243" {
t.Errorf("0.0.0.0 -> expected tcp://0.0.0.0:4243, got %s", addr)
}
if addr := ParseHost("127.0.0.1", 4243, "0.0.0.1:5555"); addr != "tcp://0.0.0.1:5555" {
t.Errorf("0.0.0.1:5555 -> expected tcp://0.0.0.1:5555, got %s", addr)
}
if addr := ParseHost("127.0.0.1", 4243, ":6666"); addr != "tcp://127.0.0.1:6666" {
t.Errorf(":6666 -> expected tcp://127.0.0.1:6666, got %s", addr)
}
if addr := ParseHost("127.0.0.1", 4243, "tcp://:7777"); addr != "tcp://127.0.0.1:7777" {
t.Errorf("tcp://:7777 -> expected tcp://127.0.0.1:7777, got %s", addr)
}
if addr := ParseHost("127.0.0.1", 4243, "unix:///var/run/docker.sock"); addr != "unix:///var/run/docker.sock" {
t.Errorf("unix:///var/run/docker.sock -> expected unix:///var/run/docker.sock, got %s", addr)
}
}