merge master and add doc
This commit is contained in:
commit
4d1692726b
4
.mailmap
4
.mailmap
|
@ -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
22
AUTHORS
|
@ -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>
|
||||||
|
|
23
CHANGELOG.md
23
CHANGELOG.md
|
@ -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
1
FIXME
|
@ -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
|
||||||
|
|
7
Makefile
7
Makefile
|
@ -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 .
|
||||||
|
|
||||||
|
|
|
@ -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
94
api.go
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
98
archive.go
98
archive.go
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
|
@ -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{}),
|
|
||||||
}
|
|
||||||
}
|
|
112
buildfile.go
112
buildfile.go
|
@ -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
|
||||||
|
|
|
@ -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")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
208
commands.go
208
commands.go
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -116,7 +116,6 @@ func crashTest() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
--------------------------------------
|
--------------------------------------
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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")
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
29
server.go
29
server.go
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue