mirror of
https://github.com/clearlinux/docker.git
synced 2026-08-20 20:56:42 +00:00
Compare commits
161 Commits
v0.7.0-rc4
...
v0.6.5
| Author | SHA1 | Date | |
|---|---|---|---|
| 3ff845904d | |||
| ec488fa123 | |||
| 1edde0e8a3 | |||
| 596facd124 | |||
| 4fd389eee5 | |||
| 6a9fb81c57 | |||
| 5fb6d6e47c | |||
| 79bf2c2b0e | |||
| 4f6ce1ba9e | |||
| da054148dc | |||
| 0174951195 | |||
| caa7cf14c0 | |||
| 700a71e6b6 | |||
| 02e0fdee57 | |||
| baf23d3c3e | |||
| 5b75aea704 | |||
| d91e145332 | |||
| 1384db5e7f | |||
| ff147707b2 | |||
| 05430c7170 | |||
| eb79e8c5dd | |||
| b48fc96579 | |||
| e0b59ab52b | |||
| a6a274f312 | |||
| 4f5790d104 | |||
| 70c976de03 | |||
| 4947f61c20 | |||
| 1de23f1b81 | |||
| b283900764 | |||
| e47f9984f8 | |||
| b038b0cd44 | |||
| 7e8b2c3836 | |||
| a52e23c5e4 | |||
| 0d2924408b | |||
| 76afb6e73d | |||
| c0662488c7 | |||
| 971cf56d89 | |||
| 50bb4a1636 | |||
| 3c0c75bbf9 | |||
| 7f95c7aeec | |||
| 395a24ffd5 | |||
| 45d94944f3 | |||
| 54d3b4dd28 | |||
| 8f39f0b57d | |||
| d5c7e92a9a | |||
| 110b944f45 | |||
| 13c521526d | |||
| 516cf54843 | |||
| 356af1540f | |||
| 2e6b241dc7 | |||
| 94e5081bac | |||
| e179a24ad5 | |||
| da864a845b | |||
| 640682d56c | |||
| 66a159342b | |||
| 7c882a8003 | |||
| 7d440f70fd | |||
| 0c9b319dd0 | |||
| 7c08aeeba4 | |||
| 7072d2aaca | |||
| 21161dbd51 | |||
| cf86e2bb22 | |||
| d789750851 | |||
| fb8d488896 | |||
| dbe7d67c18 | |||
| 6c863b49ef | |||
| 89fb51f606 | |||
| 256b7537e3 | |||
| c4923757f1 | |||
| 387aeb78fb | |||
| 99a9783abc | |||
| 0e24db3a68 | |||
| db99966724 | |||
| d063c8d941 | |||
| 249f5a65a5 | |||
| 494cd07f72 | |||
| 607c1a520e | |||
| bf61d41d6c | |||
| ce965b8c43 | |||
| f7a2f0b937 | |||
| 4576d0f802 | |||
| 515070d513 | |||
| 1cbdaebaa1 | |||
| ff567f8729 | |||
| 279791bc74 | |||
| 1e704a4ddc | |||
| 31111f61e0 | |||
| 217b6070f0 | |||
| 36c26a0f0f | |||
| a675e249b1 | |||
| acc2aec988 | |||
| 0e021ed470 | |||
| b9eb5e04ae | |||
| 0e44864b66 | |||
| e661473bcd | |||
| 98c94dd4db | |||
| 656b66e51b | |||
| c9375d14fc | |||
| 489c370fcc | |||
| aa6c99aff6 | |||
| 92384649cf | |||
| c2413889bb | |||
| e81da876df | |||
| f0dbdba5c0 | |||
| 0324e567ad | |||
| ef5cf6c1ec | |||
| afaddd04f7 | |||
| 8e43db95f2 | |||
| 7a116220e7 | |||
| 894a68d2ee | |||
| dc57d4ca26 | |||
| bc8a1081b0 | |||
| 33ccd29ae7 | |||
| 2aaaf90320 | |||
| 99f1675566 | |||
| 7e4d008403 | |||
| b0515a7016 | |||
| 8ff7b70c91 | |||
| 01284cbf73 | |||
| 10e10c9573 | |||
| b73065ab33 | |||
| d93023daa9 | |||
| 81866087fe | |||
| e70030a0c3 | |||
| 3bd45824da | |||
| 13c913d883 | |||
| 92beaed066 | |||
| 6b04913e73 | |||
| 93810b289c | |||
| ea59ce60c5 | |||
| a1fc8e17a7 | |||
| f903feee03 | |||
| 26adda6c31 | |||
| 7e09c6332d | |||
| 3b0cbc59ea | |||
| bba5fd4555 | |||
| 9bceae2440 | |||
| e7269b9841 | |||
| 8cf0f2dbe5 | |||
| 3e014aa662 | |||
| 333bc23f21 | |||
| 7cf1877098 | |||
| 4918769b1a | |||
| 4b7c071e9c | |||
| 4870265a9f | |||
| 43fd85eb8f | |||
| ac5bc86771 | |||
| 85956c70b5 | |||
| 8c38c30985 | |||
| 255b6aadfa | |||
| 8bce284496 | |||
| 83a2e92d47 | |||
| f248a8e13c | |||
| c405d53b6e | |||
| bc39ac71e6 | |||
| 2110b3cca8 | |||
| 095aab9d97 | |||
| 4967f9f19c | |||
| 1043055b10 | |||
| aa3697520a | |||
| 181b9baddd |
@@ -17,3 +17,4 @@ docs/_templates
|
||||
bundles/
|
||||
.hg/
|
||||
.git/
|
||||
vendor/pkg/
|
||||
|
||||
@@ -151,6 +151,7 @@ Roberto Hashioka <roberto_hashioka@hotmail.com>
|
||||
Ryan Fowler <rwfowler@gmail.com>
|
||||
Sam Alba <sam.alba@gmail.com>
|
||||
Sam J Sharpe <sam.sharpe@digital.cabinet-office.gov.uk>
|
||||
Scott Bessler <scottbessler@gmail.com>
|
||||
Sean P. Kane <skane@newrelic.com>
|
||||
Shawn Siefkas <shawn.siefkas@meredith.com>
|
||||
Shih-Yuan Lee <fourdollars@gmail.com>
|
||||
@@ -185,4 +186,5 @@ Vladimir Kirillov <proger@wilab.org.ua>
|
||||
Walter Stanish <walter@pratyeka.org>
|
||||
Wes Morgan <cap10morgan@gmail.com>
|
||||
Will Dietz <w@wdtz.org>
|
||||
Yang Bai <hamo.by@gmail.com>
|
||||
Zaiste! <oh@zaiste.net>
|
||||
|
||||
@@ -1,5 +1,34 @@
|
||||
# Changelog
|
||||
|
||||
## 0.6.5 (2013-10-29)
|
||||
+ Runtime: Containers can now be named
|
||||
+ Runtime: Containers can now be linked together for service discovery
|
||||
+ Runtime: 'run -a', 'start -a' and 'attach' can forward signals to the container for better integration with process supervisors
|
||||
+ Runtime: Automatically start crashed containers after a reboot
|
||||
+ Runtime: Expose IP, port, and proto as separate environment vars for container links
|
||||
* Runtime: Allow ports to be published to specific ips
|
||||
* Runtime: Prohibit inter-container communication by default
|
||||
* Documentation: Fix the flags for nc in example
|
||||
- Client: Only pass stdin to hijack when needed to avoid closed pipe errors
|
||||
* Testing: Remove warnings and prevent mount issues
|
||||
* Client: Use less reflection in command-line method invocation
|
||||
- Runtime: Ignore ErrClosedPipe for stdin in Container.Attach
|
||||
- Testing: Change logic for tty resize to avoid warning in tests
|
||||
- Client: Monitor the tty size after starting the container, not prior
|
||||
- Hack: Update install.sh with $sh_c to get sudo/su for modprobe
|
||||
- Client: Remove useless os.Exit() calls after log.Fatal
|
||||
* Hack: Update all the mkimage scripts to use --numeric-owner as a tar argument
|
||||
* Hack: Update hack/release.sh process to automatically invoke hack/make.sh and bail on build and test issues
|
||||
+ Hack: Add initial init scripts library and a safer Ubuntu packaging script that works for Debian
|
||||
- Runtime: Fix untag during removal of images
|
||||
- Runtime: Remove unused field kernelVersion
|
||||
* Hack: Add -p option to invoke debootstrap with http_proxy
|
||||
- Builder: Fix race condition in docker build with verbose output
|
||||
- Registry: Fix content-type for PushImageJSONIndex method
|
||||
* Runtime: Fix issue when mounting subdirectories of /mnt in container
|
||||
* Runtime: Check return value of syscall.Chdir when changing working directory inside dockerinit
|
||||
* Contrib: Improve helper tools to generate debian and Arch linux server images
|
||||
|
||||
## 0.6.4 (2013-10-16)
|
||||
- Runtime: Add cleanup of container when Start() fails
|
||||
- Testing: Catch errClosing error when TCP and UDP proxies are terminated
|
||||
|
||||
+5
-7
@@ -33,15 +33,13 @@ run apt-get update
|
||||
run apt-get install -y -q curl
|
||||
run apt-get install -y -q git
|
||||
run apt-get install -y -q mercurial
|
||||
run apt-get install -y -q build-essential
|
||||
run apt-get install -y -q build-essential libsqlite3-dev
|
||||
|
||||
# Install Go from source (for eventual cross-compiling)
|
||||
env CGO_ENABLED 0
|
||||
run curl -s https://go.googlecode.com/files/go1.1.2.src.tar.gz | tar -v -C / -xz && mv /go /goroot
|
||||
run cd /goroot/src && ./make.bash
|
||||
env GOROOT /goroot
|
||||
env PATH $PATH:/goroot/bin
|
||||
# Install Go
|
||||
run curl -s https://go.googlecode.com/files/go1.2rc2.src.tar.gz | tar -v -C /usr/local -xz
|
||||
env PATH /usr/local/go/bin:/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin
|
||||
env GOPATH /go:/go/src/github.com/dotcloud/docker/vendor
|
||||
run cd /usr/local/go/src && ./make.bash && go install -ldflags '-w -linkmode external -extldflags "-static -Wl,--unresolved-symbols=ignore-in-shared-libs"' -tags netgo -a std
|
||||
|
||||
# Ubuntu stuff
|
||||
run apt-get install -y -q ruby1.9.3 rubygems libffi-dev
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
Docker
|
||||
Copyright 2012-2013 dotCloud, inc.
|
||||
Copyright 2012-2013 Docker, Inc.
|
||||
|
||||
This product includes software developed at dotCloud,
|
||||
inc. (http://www.dotcloud.com).
|
||||
This product includes software developed at Docker, Inc. (http://www.docker.com).
|
||||
|
||||
This product contains software (https://github.com/kr/pty) developed
|
||||
by Keith Rarick, licensed under the MIT License.
|
||||
|
||||
@@ -193,10 +193,10 @@ wrong or incomplete.
|
||||
*Brought to you courtesy of our legal counsel. For more context,
|
||||
please see the Notice document.*
|
||||
|
||||
Transfers of Docker shall be in accordance with applicable export
|
||||
controls of any country and all other applicable legal requirements.
|
||||
Docker shall not be distributed or downloaded to or in Cuba, Iran,
|
||||
North Korea, Sudan or Syria and shall not be distributed or downloaded
|
||||
to any person on the Denied Persons List administered by the U.S.
|
||||
Department of Commerce.
|
||||
Transfers of Docker shall be in accordance with applicable export controls
|
||||
of any country and all other applicable legal requirements. Without limiting the
|
||||
foregoing, Docker shall not be distributed or downloaded to any individual or
|
||||
location if such distribution or download would violate the applicable US
|
||||
government export regulations.
|
||||
|
||||
For more information, please see http://www.bis.doc.gov
|
||||
|
||||
@@ -42,6 +42,9 @@ func hijackServer(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) {
|
||||
|
||||
//If we don't do this, POST method without Content-type (even with empty body) will fail
|
||||
func parseForm(r *http.Request) error {
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
if err := r.ParseForm(); err != nil && !strings.HasPrefix(err.Error(), "mime:") {
|
||||
return err
|
||||
}
|
||||
@@ -69,12 +72,12 @@ func httpError(w http.ResponseWriter, err error) {
|
||||
statusCode = http.StatusUnauthorized
|
||||
} else if strings.Contains(err.Error(), "hasn't been activated") {
|
||||
statusCode = http.StatusForbidden
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
utils.Errorf("HTTP Error: statusCode=%d %s", statusCode, err.Error())
|
||||
http.Error(w, err.Error(), statusCode)
|
||||
}
|
||||
http.Error(w, err.Error(), statusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func writeJSON(w http.ResponseWriter, code int, v interface{}) error {
|
||||
@@ -135,8 +138,23 @@ func postContainersKill(srv *Server, version float64, w http.ResponseWriter, r *
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
if err := parseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
name := vars["name"]
|
||||
if err := srv.ContainerKill(name); err != nil {
|
||||
|
||||
signal := 0
|
||||
if r != nil {
|
||||
s := r.Form.Get("signal")
|
||||
if s != "" {
|
||||
if s, err := strconv.Atoi(s); err != nil {
|
||||
return err
|
||||
} else {
|
||||
signal = s
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := srv.ContainerKill(name, signal); err != nil {
|
||||
return err
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
@@ -503,8 +521,12 @@ func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http
|
||||
}
|
||||
|
||||
func postContainersCreate(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := parseForm(r); err != nil {
|
||||
return nil
|
||||
}
|
||||
config := &Config{}
|
||||
out := &APIRun{}
|
||||
name := r.Form.Get("name")
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(config); err != nil {
|
||||
return err
|
||||
@@ -515,16 +537,19 @@ func postContainersCreate(srv *Server, version float64, w http.ResponseWriter, r
|
||||
return err
|
||||
}
|
||||
|
||||
if !config.NetworkDisabled && len(config.Dns) == 0 && len(srv.runtime.Dns) == 0 && utils.CheckLocalDns(resolvConf) {
|
||||
if !config.NetworkDisabled && len(config.Dns) == 0 && len(srv.runtime.config.Dns) == 0 && utils.CheckLocalDns(resolvConf) {
|
||||
out.Warnings = append(out.Warnings, fmt.Sprintf("Docker detected local DNS server on resolv.conf. Using default external servers: %v", defaultDns))
|
||||
config.Dns = defaultDns
|
||||
}
|
||||
|
||||
id, err := srv.ContainerCreate(config)
|
||||
id, warnings, err := srv.ContainerCreate(config, name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
out.ID = id
|
||||
for _, warning := range warnings {
|
||||
out.Warnings = append(out.Warnings, warning)
|
||||
}
|
||||
|
||||
if config.Memory > 0 && !srv.runtime.capabilities.MemoryLimit {
|
||||
log.Println("WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.")
|
||||
@@ -570,12 +595,17 @@ func deleteContainers(srv *Server, version float64, w http.ResponseWriter, r *ht
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
name := vars["name"]
|
||||
|
||||
removeVolume, err := getBoolParam(r.Form.Get("v"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
removeLink, err := getBoolParam(r.Form.Get("link"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := srv.ContainerDestroy(name, removeVolume); err != nil {
|
||||
if err := srv.ContainerDestroy(name, removeVolume, removeLink); err != nil {
|
||||
return err
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
@@ -622,6 +652,10 @@ func postContainersStart(srv *Server, version float64, w http.ResponseWriter, r
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
name := vars["name"]
|
||||
// Register any links from the host config before starting the container
|
||||
if err := srv.RegisterLinks(name, hostConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := srv.ContainerStart(name, hostConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -655,6 +689,7 @@ func postContainersWait(srv *Server, version float64, w http.ResponseWriter, r *
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
name := vars["name"]
|
||||
|
||||
status, err := srv.ContainerWait(name)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -975,7 +1010,7 @@ func makeHttpHandler(srv *Server, logging bool, localMethod string, localRoute s
|
||||
if err != nil {
|
||||
version = APIVERSION
|
||||
}
|
||||
if srv.enableCors {
|
||||
if srv.runtime.config.EnableCors {
|
||||
writeCorsHeaders(w, r)
|
||||
}
|
||||
|
||||
|
||||
+9
-17
@@ -1,7 +1,5 @@
|
||||
package docker
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
type APIHistory struct {
|
||||
ID string `json:"Id"`
|
||||
Tags []string `json:",omitempty"`
|
||||
@@ -52,17 +50,18 @@ type APIContainers struct {
|
||||
Ports []APIPort
|
||||
SizeRw int64
|
||||
SizeRootFs int64
|
||||
Names []string
|
||||
}
|
||||
|
||||
func (self *APIContainers) ToLegacy() APIContainersOld {
|
||||
return APIContainersOld{
|
||||
ID: self.ID,
|
||||
Image: self.Image,
|
||||
Command: self.Command,
|
||||
Created: self.Created,
|
||||
Status: self.Status,
|
||||
Ports: displayablePorts(self.Ports),
|
||||
SizeRw: self.SizeRw,
|
||||
ID: self.ID,
|
||||
Image: self.Image,
|
||||
Command: self.Command,
|
||||
Created: self.Created,
|
||||
Status: self.Status,
|
||||
Ports: displayablePorts(self.Ports),
|
||||
SizeRw: self.SizeRw,
|
||||
SizeRootFs: self.SizeRootFs,
|
||||
}
|
||||
}
|
||||
@@ -96,14 +95,7 @@ type APIPort struct {
|
||||
PrivatePort int64
|
||||
PublicPort int64
|
||||
Type string
|
||||
}
|
||||
|
||||
func (port *APIPort) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(map[string]interface{}{
|
||||
"PrivatePort": port.PrivatePort,
|
||||
"PublicPort": port.PublicPort,
|
||||
"Type": port.Type,
|
||||
})
|
||||
IP string
|
||||
}
|
||||
|
||||
type APIVersion struct {
|
||||
|
||||
+40
-23
@@ -347,10 +347,12 @@ func TestGetContainersJSON(t *testing.T) {
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
container, err := runtime.Create(&Config{
|
||||
beginLen := runtime.containers.Len()
|
||||
|
||||
container, _, err := runtime.Create(&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"echo", "test"},
|
||||
})
|
||||
}, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -369,8 +371,8 @@ func TestGetContainersJSON(t *testing.T) {
|
||||
if err := json.Unmarshal(r.Body.Bytes(), &containers); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(containers) != 1 {
|
||||
t.Fatalf("Expected %d container, %d found", 1, len(containers))
|
||||
if len(containers) != beginLen+1 {
|
||||
t.Fatalf("Expected %d container, %d found (started with: %d)", beginLen+1, len(containers), beginLen)
|
||||
}
|
||||
if containers[0].ID != container.ID {
|
||||
t.Fatalf("Container ID mismatch. Expected: %s, received: %s\n", container.ID, containers[0].ID)
|
||||
@@ -384,11 +386,12 @@ func TestGetContainersExport(t *testing.T) {
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
// Create a container and remove a file
|
||||
container, err := runtime.Create(
|
||||
container, _, err := runtime.Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"touch", "/test"},
|
||||
},
|
||||
"",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -434,11 +437,12 @@ func TestGetContainersChanges(t *testing.T) {
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
// Create a container and remove a file
|
||||
container, err := runtime.Create(
|
||||
container, _, err := runtime.Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"/bin/rm", "/etc/passwd"},
|
||||
},
|
||||
"",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -477,12 +481,13 @@ func TestGetContainersTop(t *testing.T) {
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
container, err := runtime.Create(
|
||||
container, _, err := runtime.Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"/bin/sh", "-c", "cat"},
|
||||
OpenStdin: true,
|
||||
},
|
||||
"",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -559,11 +564,12 @@ func TestGetContainersByName(t *testing.T) {
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
// Create a container and remove a file
|
||||
container, err := runtime.Create(
|
||||
container, _, err := runtime.Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"echo", "test"},
|
||||
},
|
||||
"",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -590,11 +596,12 @@ func TestPostCommit(t *testing.T) {
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
// Create a container and remove a file
|
||||
container, err := runtime.Create(
|
||||
container, _, err := runtime.Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"touch", "/test"},
|
||||
},
|
||||
"",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -684,12 +691,13 @@ func TestPostContainersKill(t *testing.T) {
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
container, err := runtime.Create(
|
||||
container, _, err := runtime.Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"/bin/cat"},
|
||||
OpenStdin: true,
|
||||
},
|
||||
"",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -726,12 +734,13 @@ func TestPostContainersRestart(t *testing.T) {
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
container, err := runtime.Create(
|
||||
container, _, err := runtime.Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"/bin/cat"},
|
||||
Cmd: []string{"/bin/top"},
|
||||
OpenStdin: true,
|
||||
},
|
||||
"",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -780,12 +789,13 @@ func TestPostContainersStart(t *testing.T) {
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
container, err := runtime.Create(
|
||||
container, _, err := runtime.Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"/bin/cat"},
|
||||
OpenStdin: true,
|
||||
},
|
||||
"",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -832,12 +842,13 @@ func TestPostContainersStop(t *testing.T) {
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
container, err := runtime.Create(
|
||||
container, _, err := runtime.Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"/bin/cat"},
|
||||
Cmd: []string{"/bin/top"},
|
||||
OpenStdin: true,
|
||||
},
|
||||
"",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -879,12 +890,13 @@ func TestPostContainersWait(t *testing.T) {
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
container, err := runtime.Create(
|
||||
container, _, err := runtime.Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"/bin/sleep", "1"},
|
||||
OpenStdin: true,
|
||||
},
|
||||
"",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -921,12 +933,13 @@ func TestPostContainersAttach(t *testing.T) {
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
container, err := runtime.Create(
|
||||
container, _, err := runtime.Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"/bin/cat"},
|
||||
OpenStdin: true,
|
||||
},
|
||||
"",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -1010,12 +1023,13 @@ func TestPostContainersAttachStderr(t *testing.T) {
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
container, err := runtime.Create(
|
||||
container, _, err := runtime.Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"/bin/sh", "-c", "/bin/cat >&2"},
|
||||
OpenStdin: true,
|
||||
},
|
||||
"",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -1102,10 +1116,10 @@ func TestDeleteContainers(t *testing.T) {
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
container, err := runtime.Create(&Config{
|
||||
container, _, err := runtime.Create(&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"touch", "/test"},
|
||||
})
|
||||
}, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -1140,7 +1154,8 @@ func TestOptionsRoute(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{runtime: runtime, enableCors: true}
|
||||
runtime.config.EnableCors = true
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
r := httptest.NewRecorder()
|
||||
router, err := createRouter(srv, false)
|
||||
@@ -1163,7 +1178,8 @@ func TestGetEnabledCors(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{runtime: runtime, enableCors: true}
|
||||
runtime.config.EnableCors = true
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
r := httptest.NewRecorder()
|
||||
|
||||
@@ -1290,11 +1306,12 @@ func TestPostContainersCopy(t *testing.T) {
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
// Create a container and remove a file
|
||||
container, err := runtime.Create(
|
||||
container, _, err := runtime.Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"touch", "/test.txt"},
|
||||
},
|
||||
"",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
+16
-6
@@ -332,7 +332,7 @@ func (b *buildFile) CmdAdd(args string) error {
|
||||
|
||||
b.config.Image = b.image
|
||||
// Create the container and start it
|
||||
container, err := b.runtime.Create(b.config)
|
||||
container, _, err := b.runtime.Create(b.config, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -367,7 +367,7 @@ func (b *buildFile) run() (string, error) {
|
||||
b.config.Image = b.image
|
||||
|
||||
// Create the container and start it
|
||||
c, err := b.runtime.Create(b.config)
|
||||
c, _, err := b.runtime.Create(b.config, "")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -378,15 +378,22 @@ func (b *buildFile) run() (string, error) {
|
||||
c.Path = b.config.Cmd[0]
|
||||
c.Args = b.config.Cmd[1:]
|
||||
|
||||
var errCh chan error
|
||||
|
||||
if b.verbose {
|
||||
errCh = utils.Go(func() error {
|
||||
return <-c.Attach(nil, nil, b.out, b.out)
|
||||
})
|
||||
}
|
||||
|
||||
//start the container
|
||||
hostConfig := &HostConfig{}
|
||||
if err := c.Start(hostConfig); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if b.verbose {
|
||||
err = <-c.Attach(nil, nil, b.out, b.out)
|
||||
if err != nil {
|
||||
if errCh != nil {
|
||||
if err := <-errCh; err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
@@ -423,10 +430,13 @@ func (b *buildFile) commit(id string, autoCmd []string, comment string) error {
|
||||
}
|
||||
}
|
||||
|
||||
container, err := b.runtime.Create(b.config)
|
||||
container, warnings, err := b.runtime.Create(b.config, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, warning := range warnings {
|
||||
fmt.Fprintf(b.out, " ---> [Warning] %s\n", warning)
|
||||
}
|
||||
b.tmpContainers[container.ID] = struct{}{}
|
||||
fmt.Fprintf(b.out, " ---> Running in %s\n", utils.TruncateID(container.ID))
|
||||
id = container.ID
|
||||
|
||||
+253
-113
@@ -23,6 +23,7 @@ import (
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
@@ -41,9 +42,13 @@ var (
|
||||
ErrConnectionRefused = errors.New("Can't connect to docker daemon. Is 'docker -d' running on this host?")
|
||||
)
|
||||
|
||||
func (cli *DockerCli) getMethod(name string) (reflect.Method, bool) {
|
||||
func (cli *DockerCli) getMethod(name string) (func(...string) error, bool) {
|
||||
methodName := "Cmd" + strings.ToUpper(name[:1]) + strings.ToLower(name[1:])
|
||||
return reflect.TypeOf(cli).MethodByName(methodName)
|
||||
method := reflect.ValueOf(cli).MethodByName(methodName)
|
||||
if !method.IsValid() {
|
||||
return nil, false
|
||||
}
|
||||
return method.Interface().(func(...string) error), true
|
||||
}
|
||||
|
||||
func ParseCommands(proto, addr string, args ...string) error {
|
||||
@@ -55,14 +60,7 @@ func ParseCommands(proto, addr string, args ...string) error {
|
||||
fmt.Println("Error: Command not found:", args[0])
|
||||
return cli.CmdHelp(args[1:]...)
|
||||
}
|
||||
ret := method.Func.CallSlice([]reflect.Value{
|
||||
reflect.ValueOf(cli),
|
||||
reflect.ValueOf(args[1:]),
|
||||
})[0].Interface()
|
||||
if ret == nil {
|
||||
return nil
|
||||
}
|
||||
return ret.(error)
|
||||
return method(args[1:]...)
|
||||
}
|
||||
return cli.CmdHelp(args...)
|
||||
}
|
||||
@@ -73,10 +71,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
|
||||
if !exists {
|
||||
fmt.Fprintf(cli.err, "Error: Command not found: %s\n", args[0])
|
||||
} else {
|
||||
method.Func.CallSlice([]reflect.Value{
|
||||
reflect.ValueOf(cli),
|
||||
reflect.ValueOf([]string{"--help"}),
|
||||
})[0].Interface()
|
||||
method("--help")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -99,7 +94,6 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
|
||||
{"login", "Register or Login to the docker registry server"},
|
||||
{"logs", "Fetch the logs of a container"},
|
||||
{"port", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT"},
|
||||
{"top", "Lookup the running processes of a container"},
|
||||
{"ps", "List containers"},
|
||||
{"pull", "Pull an image or a repository from the docker registry server"},
|
||||
{"push", "Push an image or a repository to the docker registry server"},
|
||||
@@ -111,6 +105,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
|
||||
{"start", "Start a stopped container"},
|
||||
{"stop", "Stop a running container"},
|
||||
{"tag", "Tag an image into a repository"},
|
||||
{"top", "Lookup the running processes of a container"},
|
||||
{"version", "Show the docker version information"},
|
||||
{"wait", "Block until a container stops, then print its exit code"},
|
||||
} {
|
||||
@@ -545,8 +540,23 @@ func (cli *DockerCli) CmdRestart(args ...string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cli *DockerCli) forwardAllSignals(cid string) chan os.Signal {
|
||||
sigc := make(chan os.Signal, 1)
|
||||
utils.CatchAll(sigc)
|
||||
go func() {
|
||||
for s := range sigc {
|
||||
if _, _, err := cli.call("POST", fmt.Sprintf("/containers/%s/kill?signal=%d", cid, s), nil); err != nil {
|
||||
utils.Debugf("Error sending signal: %s", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
return sigc
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdStart(args ...string) error {
|
||||
cmd := Subcmd("start", "CONTAINER [CONTAINER...]", "Restart a stopped container")
|
||||
attach := cmd.Bool("a", false, "Attach container's stdout/stderr and forward all signals to the process")
|
||||
openStdin := cmd.Bool("i", false, "Attach container's stdin")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
@@ -555,17 +565,75 @@ func (cli *DockerCli) CmdStart(args ...string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var cErr chan error
|
||||
if *attach || *openStdin {
|
||||
if cmd.NArg() > 1 {
|
||||
return fmt.Errorf("Impossible to start and attach multiple containers at once.")
|
||||
}
|
||||
|
||||
body, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
container := &Container{}
|
||||
err = json.Unmarshal(body, container)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !container.Config.Tty {
|
||||
sigc := cli.forwardAllSignals(cmd.Arg(0))
|
||||
defer utils.StopCatch(sigc)
|
||||
}
|
||||
|
||||
if container.Config.Tty && cli.isTerminal {
|
||||
if err := cli.monitorTtySize(cmd.Arg(0)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
var in io.ReadCloser
|
||||
|
||||
v := url.Values{}
|
||||
v.Set("stream", "1")
|
||||
if *openStdin && container.Config.OpenStdin {
|
||||
v.Set("stdin", "1")
|
||||
in = cli.in
|
||||
}
|
||||
v.Set("stdout", "1")
|
||||
v.Set("stderr", "1")
|
||||
|
||||
cErr = utils.Go(func() error {
|
||||
return cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), container.Config.Tty, in, cli.out, cli.err, nil)
|
||||
})
|
||||
}
|
||||
|
||||
var encounteredError error
|
||||
for _, name := range args {
|
||||
for _, name := range cmd.Args() {
|
||||
_, _, err := cli.call("POST", "/containers/"+name+"/start", nil)
|
||||
if err != nil {
|
||||
fmt.Fprintf(cli.err, "%s\n", err)
|
||||
encounteredError = fmt.Errorf("Error: failed to start one or more containers")
|
||||
if !*attach || !*openStdin {
|
||||
fmt.Fprintf(cli.err, "%s\n", err)
|
||||
encounteredError = fmt.Errorf("Error: failed to start one or more containers")
|
||||
}
|
||||
} else {
|
||||
fmt.Fprintf(cli.out, "%s\n", name)
|
||||
if !*attach || !*openStdin {
|
||||
fmt.Fprintf(cli.out, "%s\n", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
return encounteredError
|
||||
if encounteredError != nil {
|
||||
if *openStdin || *attach {
|
||||
cli.in.Close()
|
||||
<-cErr
|
||||
}
|
||||
return encounteredError
|
||||
}
|
||||
if *openStdin || *attach {
|
||||
return <-cErr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdInspect(args ...string) error {
|
||||
@@ -577,30 +645,39 @@ func (cli *DockerCli) CmdInspect(args ...string) error {
|
||||
cmd.Usage()
|
||||
return nil
|
||||
}
|
||||
fmt.Fprintf(cli.out, "[")
|
||||
for i, name := range args {
|
||||
if i > 0 {
|
||||
fmt.Fprintf(cli.out, ",")
|
||||
}
|
||||
|
||||
indented := new(bytes.Buffer)
|
||||
status := 0
|
||||
|
||||
for _, name := range args {
|
||||
obj, _, err := cli.call("GET", "/containers/"+name+"/json", nil)
|
||||
if err != nil {
|
||||
obj, _, err = cli.call("GET", "/images/"+name+"/json", nil)
|
||||
if err != nil {
|
||||
fmt.Fprintf(cli.err, "No such image or container: %s\n", name)
|
||||
status = 1
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
indented := new(bytes.Buffer)
|
||||
if err = json.Indent(indented, obj, "", " "); err != nil {
|
||||
fmt.Fprintf(cli.err, "%s\n", err)
|
||||
status = 1
|
||||
continue
|
||||
}
|
||||
if _, err := io.Copy(cli.out, indented); err != nil {
|
||||
fmt.Fprintf(cli.err, "%s\n", err)
|
||||
}
|
||||
indented.WriteString(",")
|
||||
}
|
||||
// Remove trailling ','
|
||||
indented.Truncate(indented.Len() - 1)
|
||||
|
||||
fmt.Fprintf(cli.out, "[")
|
||||
if _, err := io.Copy(cli.out, indented); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(cli.out, "]")
|
||||
if status != 0 {
|
||||
return &utils.StatusError{Status: status}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -647,11 +724,11 @@ func (cli *DockerCli) CmdPort(args ...string) error {
|
||||
}
|
||||
|
||||
port := cmd.Arg(1)
|
||||
proto := "Tcp"
|
||||
proto := "tcp"
|
||||
parts := strings.SplitN(port, "/", 2)
|
||||
if len(parts) == 2 && len(parts[1]) != 0 {
|
||||
port = parts[0]
|
||||
proto = strings.ToUpper(parts[1][:1]) + strings.ToLower(parts[1][1:])
|
||||
proto = parts[1]
|
||||
}
|
||||
body, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil)
|
||||
if err != nil {
|
||||
@@ -663,8 +740,14 @@ func (cli *DockerCli) CmdPort(args ...string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if frontend, exists := out.NetworkSettings.PortMapping[proto][port]; exists {
|
||||
fmt.Fprintf(cli.out, "%s\n", frontend)
|
||||
if frontends, exists := out.NetworkSettings.Ports[Port(port+"/"+proto)]; exists {
|
||||
if frontends == nil {
|
||||
fmt.Fprintf(cli.out, "%s\n", port)
|
||||
} else {
|
||||
for _, frontend := range frontends {
|
||||
fmt.Fprintf(cli.out, "%s:%s\n", frontend.HostIp, frontend.HostPort)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("Error: No private port '%s' allocated on %s", cmd.Arg(1), cmd.Arg(0))
|
||||
}
|
||||
@@ -740,6 +823,8 @@ func (cli *DockerCli) CmdHistory(args ...string) error {
|
||||
func (cli *DockerCli) CmdRm(args ...string) error {
|
||||
cmd := Subcmd("rm", "[OPTIONS] CONTAINER [CONTAINER...]", "Remove one or more containers")
|
||||
v := cmd.Bool("v", false, "Remove the volumes associated to the container")
|
||||
link := cmd.Bool("link", false, "Remove the specified link and not the underlying container")
|
||||
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
@@ -751,6 +836,9 @@ func (cli *DockerCli) CmdRm(args ...string) error {
|
||||
if *v {
|
||||
val.Set("v", "1")
|
||||
}
|
||||
if *link {
|
||||
val.Set("link", "1")
|
||||
}
|
||||
for _, name := range cmd.Args() {
|
||||
_, _, err := cli.call("DELETE", "/containers/"+name+"?"+val.Encode(), nil)
|
||||
if err != nil {
|
||||
@@ -985,25 +1073,19 @@ func (cli *DockerCli) CmdImages(args ...string) error {
|
||||
out.Tag = "<none>"
|
||||
}
|
||||
|
||||
if !*noTrunc {
|
||||
out.ID = utils.TruncateID(out.ID)
|
||||
}
|
||||
|
||||
if !*quiet {
|
||||
fmt.Fprintf(w, "%s\t%s\t", out.Repository, out.Tag)
|
||||
if *noTrunc {
|
||||
fmt.Fprintf(w, "%s\t", out.ID)
|
||||
} else {
|
||||
fmt.Fprintf(w, "%s\t", utils.TruncateID(out.ID))
|
||||
}
|
||||
fmt.Fprintf(w, "%s ago\t", utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))))
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t", out.Repository, out.Tag, out.ID, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))))
|
||||
if out.VirtualSize > 0 {
|
||||
fmt.Fprintf(w, "%s (virtual %s)\n", utils.HumanSize(out.Size), utils.HumanSize(out.VirtualSize))
|
||||
} else {
|
||||
fmt.Fprintf(w, "%s\n", utils.HumanSize(out.Size))
|
||||
}
|
||||
} else {
|
||||
if *noTrunc {
|
||||
fmt.Fprintln(w, out.ID)
|
||||
} else {
|
||||
fmt.Fprintln(w, utils.TruncateID(out.ID))
|
||||
}
|
||||
fmt.Fprintln(w, out.ID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1017,10 +1099,10 @@ func (cli *DockerCli) CmdImages(args ...string) error {
|
||||
func displayablePorts(ports []APIPort) string {
|
||||
result := []string{}
|
||||
for _, port := range ports {
|
||||
if port.Type == "tcp" {
|
||||
result = append(result, fmt.Sprintf("%d->%d", port.PublicPort, port.PrivatePort))
|
||||
if port.IP == "" {
|
||||
result = append(result, fmt.Sprintf("%d/%s", port.PublicPort, port.Type))
|
||||
} else {
|
||||
result = append(result, fmt.Sprintf("%d->%d/%s", port.PublicPort, port.PrivatePort, port.Type))
|
||||
result = append(result, fmt.Sprintf("%s:%d->%d/%s", port.IP, port.PublicPort, port.PrivatePort, port.Type))
|
||||
}
|
||||
}
|
||||
sort.Strings(result)
|
||||
@@ -1073,7 +1155,7 @@ func (cli *DockerCli) CmdPs(args ...string) error {
|
||||
}
|
||||
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
|
||||
if !*quiet {
|
||||
fmt.Fprint(w, "ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS")
|
||||
fmt.Fprint(w, "ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS\tNAMES")
|
||||
if *size {
|
||||
fmt.Fprintln(w, "\tSIZE")
|
||||
} else {
|
||||
@@ -1082,12 +1164,20 @@ func (cli *DockerCli) CmdPs(args ...string) error {
|
||||
}
|
||||
|
||||
for _, out := range outs {
|
||||
if !*noTrunc {
|
||||
out.ID = utils.TruncateID(out.ID)
|
||||
}
|
||||
|
||||
// Remove the leading / from the names
|
||||
for i := 0; i < len(out.Names); i++ {
|
||||
out.Names[i] = out.Names[i][1:]
|
||||
}
|
||||
|
||||
if !*quiet {
|
||||
if *noTrunc {
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t", out.ID, out.Image, out.Command, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, displayablePorts(out.Ports))
|
||||
} else {
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t", utils.TruncateID(out.ID), out.Image, utils.Trunc(out.Command, 20), utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, displayablePorts(out.Ports))
|
||||
if !*noTrunc {
|
||||
out.Command = utils.Trunc(out.Command, 20)
|
||||
}
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t%s\t", out.ID, out.Image, out.Command, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, displayablePorts(out.Ports), strings.Join(out.Names, ","))
|
||||
if *size {
|
||||
if out.SizeRootFs > 0 {
|
||||
fmt.Fprintf(w, "%s (virtual %s)\n", utils.HumanSize(out.SizeRw), utils.HumanSize(out.SizeRootFs))
|
||||
@@ -1098,11 +1188,7 @@ func (cli *DockerCli) CmdPs(args ...string) error {
|
||||
fmt.Fprint(w, "\n")
|
||||
}
|
||||
} else {
|
||||
if *noTrunc {
|
||||
fmt.Fprintln(w, out.ID)
|
||||
} else {
|
||||
fmt.Fprintln(w, utils.TruncateID(out.ID))
|
||||
}
|
||||
fmt.Fprintln(w, out.ID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1229,15 +1315,18 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
|
||||
cmd.Usage()
|
||||
return nil
|
||||
}
|
||||
name := cmd.Arg(0)
|
||||
|
||||
if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stdout=1&stderr=1", false, nil, cli.out, cli.err); err != nil {
|
||||
if err := cli.hijack("POST", "/containers/"+name+"/attach?logs=1&stdout=1&stderr=1", false, nil, cli.out, cli.err, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdAttach(args ...string) error {
|
||||
cmd := Subcmd("attach", "CONTAINER", "Attach to a running container")
|
||||
cmd := Subcmd("attach", "[OPTIONS] CONTAINER", "Attach to a running container")
|
||||
noStdin := cmd.Bool("nostdin", false, "Do not attach stdin")
|
||||
proxy := cmd.Bool("sig-proxy", true, "Proxify all received signal to the process (even in non-tty mode)")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
@@ -1245,8 +1334,8 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
|
||||
cmd.Usage()
|
||||
return nil
|
||||
}
|
||||
|
||||
body, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil)
|
||||
name := cmd.Arg(0)
|
||||
body, _, err := cli.call("GET", "/containers/"+name+"/json", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1261,19 +1350,29 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
|
||||
return fmt.Errorf("Impossible to attach to a stopped container, start it first")
|
||||
}
|
||||
|
||||
if container.Config.Tty {
|
||||
if container.Config.Tty && cli.isTerminal {
|
||||
if err := cli.monitorTtySize(cmd.Arg(0)); err != nil {
|
||||
utils.Debugf("Error monitoring tty size: %s", err)
|
||||
utils.Debugf("Error monitoring TTY size: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
var in io.ReadCloser
|
||||
|
||||
v := url.Values{}
|
||||
v.Set("stream", "1")
|
||||
v.Set("stdin", "1")
|
||||
if !*noStdin && container.Config.OpenStdin {
|
||||
v.Set("stdin", "1")
|
||||
in = cli.in
|
||||
}
|
||||
v.Set("stdout", "1")
|
||||
v.Set("stderr", "1")
|
||||
|
||||
if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), container.Config.Tty, cli.in, cli.out, cli.err); err != nil {
|
||||
if *proxy && !container.Config.Tty {
|
||||
sigc := cli.forwardAllSignals(cmd.Arg(0))
|
||||
defer utils.StopCatch(sigc)
|
||||
}
|
||||
|
||||
if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), container.Config.Tty, in, cli.out, cli.err, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@@ -1326,18 +1425,6 @@ func (cli *DockerCli) CmdSearch(args ...string) error {
|
||||
// Ports type - Used to parse multiple -p flags
|
||||
type ports []int
|
||||
|
||||
// ListOpts type
|
||||
type ListOpts []string
|
||||
|
||||
func (opts *ListOpts) String() string {
|
||||
return fmt.Sprint(*opts)
|
||||
}
|
||||
|
||||
func (opts *ListOpts) Set(value string) error {
|
||||
*opts = append(*opts, value)
|
||||
return nil
|
||||
}
|
||||
|
||||
// AttachOpts stores arguments to 'docker run -a', eg. which streams to attach to
|
||||
type AttachOpts map[string]bool
|
||||
|
||||
@@ -1436,6 +1523,13 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
||||
flRm := cmd.Lookup("rm")
|
||||
autoRemove, _ := strconv.ParseBool(flRm.Value.String())
|
||||
|
||||
flSigProxy := cmd.Lookup("sig-proxy")
|
||||
sigProxy, _ := strconv.ParseBool(flSigProxy.Value.String())
|
||||
flName := cmd.Lookup("name")
|
||||
if config.Tty {
|
||||
sigProxy = false
|
||||
}
|
||||
|
||||
var containerIDFile *os.File
|
||||
if len(hostConfig.ContainerIDFile) > 0 {
|
||||
if _, err := ioutil.ReadFile(hostConfig.ContainerIDFile); err == nil {
|
||||
@@ -1447,9 +1541,14 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
||||
}
|
||||
defer containerIDFile.Close()
|
||||
}
|
||||
containerValues := url.Values{}
|
||||
name := flName.Value.String()
|
||||
if name != "" {
|
||||
containerValues.Set("name", name)
|
||||
}
|
||||
|
||||
//create the container
|
||||
body, statusCode, err := cli.call("POST", "/containers/create", config)
|
||||
body, statusCode, err := cli.call("POST", "/containers/create?"+containerValues.Encode(), config)
|
||||
//if image not found try to pull it
|
||||
if statusCode == 404 {
|
||||
_, tag := utils.ParseRepositoryTag(config.Image)
|
||||
@@ -1490,7 +1589,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
body, _, err = cli.call("POST", "/containers/create", config)
|
||||
body, _, err = cli.call("POST", "/containers/create?"+containerValues.Encode(), config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1514,12 +1613,15 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
||||
}
|
||||
}
|
||||
|
||||
//start the container
|
||||
if _, _, err = cli.call("POST", "/containers/"+runResult.ID+"/start", hostConfig); err != nil {
|
||||
return err
|
||||
if sigProxy {
|
||||
sigc := cli.forwardAllSignals(runResult.ID)
|
||||
defer utils.StopCatch(sigc)
|
||||
}
|
||||
|
||||
var wait chan struct{}
|
||||
var (
|
||||
wait chan struct{}
|
||||
errCh chan error
|
||||
)
|
||||
|
||||
if !config.AttachStdout && !config.AttachStderr {
|
||||
// Make this asynchrone in order to let the client write to stdin before having to read the ID
|
||||
@@ -1530,20 +1632,18 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
||||
}()
|
||||
}
|
||||
|
||||
hijacked := make(chan bool)
|
||||
|
||||
if config.AttachStdin || config.AttachStdout || config.AttachStderr {
|
||||
if config.Tty {
|
||||
if err := cli.monitorTtySize(runResult.ID); err != nil {
|
||||
utils.Errorf("Error monitoring TTY size: %s\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
v := url.Values{}
|
||||
v.Set("logs", "1")
|
||||
v.Set("stream", "1")
|
||||
var out, stderr io.Writer
|
||||
var in io.ReadCloser
|
||||
|
||||
if config.AttachStdin {
|
||||
v.Set("stdin", "1")
|
||||
in = cli.in
|
||||
}
|
||||
if config.AttachStdout {
|
||||
v.Set("stdout", "1")
|
||||
@@ -1558,18 +1658,36 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
||||
}
|
||||
}
|
||||
|
||||
signals := make(chan os.Signal, 1)
|
||||
signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM)
|
||||
go func() {
|
||||
for sig := range signals {
|
||||
fmt.Printf("\nReceived signal: %s; cleaning up\n", sig)
|
||||
if err := cli.CmdStop("-t", "4", runResult.ID); err != nil {
|
||||
fmt.Printf("failed to stop container: %v", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
errCh = utils.Go(func() error {
|
||||
return cli.hijack("POST", "/containers/"+runResult.ID+"/attach?"+v.Encode(), config.Tty, in, out, stderr, hijacked)
|
||||
})
|
||||
} else {
|
||||
close(hijacked)
|
||||
}
|
||||
|
||||
if err := cli.hijack("POST", "/containers/"+runResult.ID+"/attach?"+v.Encode(), config.Tty, cli.in, out, stderr); err != nil {
|
||||
// Acknowledge the hijack before starting
|
||||
select {
|
||||
case <-hijacked:
|
||||
case err := <-errCh:
|
||||
if err != nil {
|
||||
utils.Debugf("Error hijack: %s", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
//start the container
|
||||
if _, _, err = cli.call("POST", "/containers/"+runResult.ID+"/start", hostConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && cli.isTerminal {
|
||||
if err := cli.monitorTtySize(runResult.ID); err != nil {
|
||||
utils.Errorf("Error monitoring TTY size: %s\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
if errCh != nil {
|
||||
if err := <-errCh; err != nil {
|
||||
utils.Debugf("Error hijack: %s", err)
|
||||
return err
|
||||
}
|
||||
@@ -1579,13 +1697,19 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
||||
// Detached mode
|
||||
<-wait
|
||||
} else {
|
||||
status, err := getExitCode(cli, runResult.ID)
|
||||
running, status, err := getExitCode(cli, runResult.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if autoRemove {
|
||||
_, _, err = cli.call("DELETE", "/containers/"+runResult.ID, nil)
|
||||
if err != nil {
|
||||
if running {
|
||||
return fmt.Errorf("Impossible to auto-remove a detached container")
|
||||
}
|
||||
// Wait for the process to
|
||||
if _, _, err := cli.call("POST", "/containers/"+runResult.ID+"/wait", nil); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, _, err := cli.call("DELETE", "/containers/"+runResult.ID, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -1642,6 +1766,10 @@ func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int,
|
||||
params = bytes.NewBuffer(buf)
|
||||
}
|
||||
|
||||
// fixme: refactor client to support redirect
|
||||
re := regexp.MustCompile("/+")
|
||||
path = re.ReplaceAllString(path, "/")
|
||||
|
||||
req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), params)
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
@@ -1670,6 +1798,7 @@ func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int,
|
||||
return nil, -1, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
@@ -1687,6 +1816,11 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer, h
|
||||
if (method == "POST" || method == "PUT") && in == nil {
|
||||
in = bytes.NewReader([]byte{})
|
||||
}
|
||||
|
||||
// fixme: refactor client to support redirect
|
||||
re := regexp.MustCompile("/+")
|
||||
path = re.ReplaceAllString(path, "/")
|
||||
|
||||
req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), in)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -1742,7 +1876,10 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer, h
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.ReadCloser, stdout, stderr io.Writer) error {
|
||||
func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.ReadCloser, stdout, stderr io.Writer, started chan bool) error {
|
||||
// fixme: refactor client to support redirect
|
||||
re := regexp.MustCompile("/+")
|
||||
path = re.ReplaceAllString(path, "/")
|
||||
|
||||
req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), nil)
|
||||
if err != nil {
|
||||
@@ -1768,6 +1905,10 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea
|
||||
rwc, br := clientconn.Hijack()
|
||||
defer rwc.Close()
|
||||
|
||||
if started != nil {
|
||||
started <- true
|
||||
}
|
||||
|
||||
var receiveStdout chan error
|
||||
|
||||
if stdout != nil {
|
||||
@@ -1854,9 +1995,6 @@ func (cli *DockerCli) resizeTty(id string) {
|
||||
}
|
||||
|
||||
func (cli *DockerCli) monitorTtySize(id string) error {
|
||||
if !cli.isTerminal {
|
||||
return fmt.Errorf("Impossible to monitor size on non-tty")
|
||||
}
|
||||
cli.resizeTty(id)
|
||||
|
||||
sigchan := make(chan os.Signal, 1)
|
||||
@@ -1904,20 +2042,22 @@ func waitForExit(cli *DockerCli, containerId string) (int, error) {
|
||||
return out.StatusCode, nil
|
||||
}
|
||||
|
||||
func getExitCode(cli *DockerCli, containerId string) (int, error) {
|
||||
// getExitCode perform an inspect on the container. It returns
|
||||
// the running state and the exit code.
|
||||
func getExitCode(cli *DockerCli, containerId string) (bool, int, error) {
|
||||
body, _, err := cli.call("GET", "/containers/"+containerId+"/json", nil)
|
||||
if err != nil {
|
||||
// If we can't connect, then the daemon probably died.
|
||||
if err != ErrConnectionRefused {
|
||||
return -1, err
|
||||
return false, -1, err
|
||||
}
|
||||
return -1, nil
|
||||
return false, -1, nil
|
||||
}
|
||||
c := &Container{}
|
||||
if err := json.Unmarshal(body, c); err != nil {
|
||||
return -1, err
|
||||
return false, -1, err
|
||||
}
|
||||
return c.State.ExitCode, nil
|
||||
return c.State.Running, c.State.ExitCode, nil
|
||||
}
|
||||
|
||||
func NewDockerCli(in io.ReadCloser, out, err io.Writer, proto, addr string) *DockerCli {
|
||||
|
||||
+81
-16
@@ -84,10 +84,24 @@ func TestRunHostname(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
setTimeout(t, "CmdRun timed out", 5*time.Second, func() {
|
||||
container := globalRuntime.List()[0]
|
||||
|
||||
setTimeout(t, "CmdRun timed out", 10*time.Second, func() {
|
||||
<-c
|
||||
|
||||
go func() {
|
||||
cli.CmdWait(container.ID)
|
||||
}()
|
||||
|
||||
if _, err := bufio.NewReader(stdout).ReadString('\n'); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
// Cleanup pipes
|
||||
if err := closeWrap(stdout, stdoutPipe); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRunWorkdir checks that 'docker run -w' correctly sets a custom working directory
|
||||
@@ -115,10 +129,24 @@ func TestRunWorkdir(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
setTimeout(t, "CmdRun timed out", 5*time.Second, func() {
|
||||
container := globalRuntime.List()[0]
|
||||
|
||||
setTimeout(t, "CmdRun timed out", 10*time.Second, func() {
|
||||
<-c
|
||||
|
||||
go func() {
|
||||
cli.CmdWait(container.ID)
|
||||
}()
|
||||
|
||||
if _, err := bufio.NewReader(stdout).ReadString('\n'); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
// Cleanup pipes
|
||||
if err := closeWrap(stdout, stdoutPipe); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRunWorkdirExists checks that 'docker run -w' correctly sets a custom working directory, even if it exists
|
||||
@@ -146,10 +174,24 @@ func TestRunWorkdirExists(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
container := globalRuntime.List()[0]
|
||||
|
||||
setTimeout(t, "CmdRun timed out", 5*time.Second, func() {
|
||||
<-c
|
||||
|
||||
go func() {
|
||||
cli.CmdWait(container.ID)
|
||||
}()
|
||||
|
||||
if _, err := bufio.NewReader(stdout).ReadString('\n'); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
// Cleanup pipes
|
||||
if err := closeWrap(stdout, stdoutPipe); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunExit(t *testing.T) {
|
||||
@@ -262,7 +304,7 @@ func TestRunDisconnectTty(t *testing.T) {
|
||||
// We're simulating a disconnect so the return value doesn't matter. What matters is the
|
||||
// fact that CmdRun returns.
|
||||
if err := cli.CmdRun("-i", "-t", unitTestImageID, "/bin/cat"); err != nil {
|
||||
utils.Debugf("Error CmdRun: %s\n", err)
|
||||
utils.Debugf("Error CmdRun: %s", err)
|
||||
}
|
||||
|
||||
close(c1)
|
||||
@@ -393,12 +435,14 @@ func TestRunDetach(t *testing.T) {
|
||||
container := globalRuntime.List()[0]
|
||||
|
||||
setTimeout(t, "Escape sequence timeout", 5*time.Second, func() {
|
||||
stdinPipe.Write([]byte{'', ''})
|
||||
stdinPipe.Write([]byte{16, 17})
|
||||
if err := stdinPipe.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
closeWrap(stdin, stdinPipe, stdout, stdoutPipe)
|
||||
|
||||
// wait for CmdRun to return
|
||||
setTimeout(t, "Waiting for CmdRun timed out", 15*time.Second, func() {
|
||||
<-ch
|
||||
@@ -411,7 +455,6 @@ func TestRunDetach(t *testing.T) {
|
||||
|
||||
setTimeout(t, "Waiting for container to die timed out", 20*time.Second, func() {
|
||||
container.Kill()
|
||||
container.Wait()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -423,39 +466,62 @@ func TestAttachDetach(t *testing.T) {
|
||||
cli := NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
defer cleanup(globalRuntime)
|
||||
|
||||
go stdout.Read(make([]byte, 1024))
|
||||
setTimeout(t, "Starting container timed out", 2*time.Second, func() {
|
||||
ch := make(chan struct{})
|
||||
go func() {
|
||||
defer close(ch)
|
||||
if err := cli.CmdRun("-i", "-t", "-d", unitTestImageID, "cat"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
}()
|
||||
|
||||
container := globalRuntime.List()[0]
|
||||
var container *Container
|
||||
|
||||
setTimeout(t, "Reading container's id timed out", 10*time.Second, func() {
|
||||
buf := make([]byte, 1024)
|
||||
n, err := stdout.Read(buf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
container = globalRuntime.List()[0]
|
||||
|
||||
if strings.Trim(string(buf[:n]), " \r\n") != container.ShortID() {
|
||||
t.Fatalf("Wrong ID received. Expect %s, received %s", container.ShortID(), buf[:n])
|
||||
}
|
||||
})
|
||||
setTimeout(t, "Starting container timed out", 10*time.Second, func() {
|
||||
<-ch
|
||||
})
|
||||
|
||||
stdin, stdinPipe = io.Pipe()
|
||||
stdout, stdoutPipe = io.Pipe()
|
||||
cli = NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
|
||||
|
||||
ch := make(chan struct{})
|
||||
ch = make(chan struct{})
|
||||
go func() {
|
||||
defer close(ch)
|
||||
if err := cli.CmdAttach(container.ShortID()); err != nil {
|
||||
t.Fatal(err)
|
||||
if err != io.ErrClosedPipe {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
setTimeout(t, "First read/write assertion timed out", 2*time.Second, func() {
|
||||
if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
|
||||
t.Fatal(err)
|
||||
if err != io.ErrClosedPipe {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
setTimeout(t, "Escape sequence timeout", 5*time.Second, func() {
|
||||
stdinPipe.Write([]byte{'', ''})
|
||||
stdinPipe.Write([]byte{16, 17})
|
||||
if err := stdinPipe.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
closeWrap(stdin, stdinPipe, stdout, stdoutPipe)
|
||||
|
||||
// wait for CmdRun to return
|
||||
setTimeout(t, "Waiting for CmdAttach timed out", 15*time.Second, func() {
|
||||
@@ -469,7 +535,6 @@ func TestAttachDetach(t *testing.T) {
|
||||
|
||||
setTimeout(t, "Waiting for container to die timedout", 5*time.Second, func() {
|
||||
container.Kill()
|
||||
container.Wait()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -484,7 +549,7 @@ func TestAttachDisconnect(t *testing.T) {
|
||||
go func() {
|
||||
// Start a process in daemon mode
|
||||
if err := cli.CmdRun("-d", "-i", unitTestImageID, "/bin/cat"); err != nil {
|
||||
utils.Debugf("Error CmdRun: %s\n", err)
|
||||
utils.Debugf("Error CmdRun: %s", err)
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -570,7 +635,7 @@ func TestRunAutoRemove(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
setTimeout(t, "CmdRun timed out", 5*time.Second, func() {
|
||||
setTimeout(t, "CmdRun timed out", 10*time.Second, func() {
|
||||
<-c
|
||||
})
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"net"
|
||||
)
|
||||
|
||||
type DaemonConfig struct {
|
||||
Pidfile string
|
||||
GraphPath string
|
||||
ProtoAddresses []string
|
||||
AutoRestart bool
|
||||
EnableCors bool
|
||||
Dns []string
|
||||
EnableIptables bool
|
||||
BridgeIface string
|
||||
DefaultIp net.IP
|
||||
InterContainerCommunication bool
|
||||
}
|
||||
+279
-86
@@ -1,6 +1,7 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
@@ -43,6 +44,7 @@ type Container struct {
|
||||
ResolvConfPath string
|
||||
HostnamePath string
|
||||
HostsPath string
|
||||
Name string
|
||||
|
||||
cmd *exec.Cmd
|
||||
stdout *utils.WriteBroadcaster
|
||||
@@ -58,6 +60,8 @@ type Container struct {
|
||||
// Store rw/ro in a separate structure to preserve reverse-compatibility on-disk.
|
||||
// Easier than migrating older container configs :)
|
||||
VolumesRW map[string]bool
|
||||
|
||||
activeLinks map[string]*Link
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
@@ -70,7 +74,8 @@ type Config struct {
|
||||
AttachStdin bool
|
||||
AttachStdout bool
|
||||
AttachStderr bool
|
||||
PortSpecs []string
|
||||
PortSpecs []string // Deprecated - Can be in the format of 8080/tcp
|
||||
ExposedPorts map[Port]struct{}
|
||||
Tty bool // Attach standard streams to a tty, including stdin if it is not closed.
|
||||
OpenStdin bool // Open stdin
|
||||
StdinOnce bool // If true, close stdin after the 1 attached client disconnects.
|
||||
@@ -90,6 +95,8 @@ type HostConfig struct {
|
||||
Binds []string
|
||||
ContainerIDFile string
|
||||
LxcConf []KeyValuePair
|
||||
PortBindings map[Port][]PortBinding
|
||||
Links []string
|
||||
}
|
||||
|
||||
type BindMap struct {
|
||||
@@ -99,7 +106,11 @@ type BindMap struct {
|
||||
}
|
||||
|
||||
var (
|
||||
ErrInvaidWorikingDirectory = errors.New("The working directory is invalid. It needs to be an absolute path.")
|
||||
ErrContainerStart = errors.New("The container failed to start. Unkown error")
|
||||
ErrContainerStartTimeout = errors.New("The container failed to start due to timed out.")
|
||||
ErrInvalidWorikingDirectory = errors.New("The working directory is invalid. It needs to be an absolute path.")
|
||||
ErrConflictAttachDetach = errors.New("Conflicting options: -a and -d")
|
||||
ErrConflictDetachAutoRemove = errors.New("Conflicting options: -rm and -d")
|
||||
)
|
||||
|
||||
type KeyValuePair struct {
|
||||
@@ -107,6 +118,34 @@ type KeyValuePair struct {
|
||||
Value string
|
||||
}
|
||||
|
||||
type PortBinding struct {
|
||||
HostIp string
|
||||
HostPort string
|
||||
}
|
||||
|
||||
// 80/tcp
|
||||
type Port string
|
||||
|
||||
func (p Port) Proto() string {
|
||||
return strings.Split(string(p), "/")[1]
|
||||
}
|
||||
|
||||
func (p Port) Port() string {
|
||||
return strings.Split(string(p), "/")[0]
|
||||
}
|
||||
|
||||
func (p Port) Int() int {
|
||||
i, err := parsePort(p.Port())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
func NewPort(proto, port string) Port {
|
||||
return Port(fmt.Sprintf("%s/%s", port, proto))
|
||||
}
|
||||
|
||||
func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, *flag.FlagSet, error) {
|
||||
cmd := Subcmd("run", "[OPTIONS] IMAGE [COMMAND] [ARG...]", "Run a command in a new container")
|
||||
if os.Getenv("TEST") != "" {
|
||||
@@ -127,6 +166,8 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
|
||||
flNetwork := cmd.Bool("n", true, "Enable networking for this container")
|
||||
flPrivileged := cmd.Bool("privileged", false, "Give extended privileges to this container")
|
||||
flAutoRemove := cmd.Bool("rm", false, "Automatically remove the container when it exits (incompatible with -d)")
|
||||
cmd.Bool("sig-proxy", true, "Proxify all received signal to the process (even in non-tty mode)")
|
||||
cmd.String("name", "", "Assign a name to the container")
|
||||
|
||||
if capabilities != nil && *flMemory > 0 && !capabilities.MemoryLimit {
|
||||
//fmt.Fprintf(stdout, "WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n")
|
||||
@@ -135,35 +176,45 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
|
||||
|
||||
flCpuShares := cmd.Int64("c", 0, "CPU shares (relative weight)")
|
||||
|
||||
var flPorts ListOpts
|
||||
cmd.Var(&flPorts, "p", "Expose a container's port to the host (use 'docker port' to see the actual mapping)")
|
||||
var flPublish utils.ListOpts
|
||||
cmd.Var(&flPublish, "p", "Publish a container's port to the host (use 'docker port' to see the actual mapping)")
|
||||
|
||||
var flEnv ListOpts
|
||||
var flExpose utils.ListOpts
|
||||
cmd.Var(&flExpose, "expose", "Expose a port from the container without publishing it to your host")
|
||||
|
||||
var flEnv utils.ListOpts
|
||||
cmd.Var(&flEnv, "e", "Set environment variables")
|
||||
|
||||
var flDns ListOpts
|
||||
var flDns utils.ListOpts
|
||||
cmd.Var(&flDns, "dns", "Set custom dns servers")
|
||||
|
||||
flVolumes := NewPathOpts()
|
||||
cmd.Var(flVolumes, "v", "Bind mount a volume (e.g. from the host: -v /host:/container, from docker: -v /container)")
|
||||
|
||||
var flVolumesFrom ListOpts
|
||||
var flVolumesFrom utils.ListOpts
|
||||
cmd.Var(&flVolumesFrom, "volumes-from", "Mount volumes from the specified container")
|
||||
|
||||
flEntrypoint := cmd.String("entrypoint", "", "Overwrite the default entrypoint of the image")
|
||||
|
||||
var flLxcOpts ListOpts
|
||||
var flLxcOpts utils.ListOpts
|
||||
cmd.Var(&flLxcOpts, "lxc-conf", "Add custom lxc options -lxc-conf=\"lxc.cgroup.cpuset.cpus = 0,1\"")
|
||||
|
||||
var flLinks utils.ListOpts
|
||||
cmd.Var(&flLinks, "link", "Add link to another container (name:alias)")
|
||||
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil, nil, cmd, err
|
||||
}
|
||||
if *flDetach && len(flAttach) > 0 {
|
||||
return nil, nil, cmd, fmt.Errorf("Conflicting options: -a and -d")
|
||||
return nil, nil, cmd, ErrConflictAttachDetach
|
||||
}
|
||||
if *flWorkingDir != "" && !path.IsAbs(*flWorkingDir) {
|
||||
return nil, nil, cmd, ErrInvaidWorikingDirectory
|
||||
return nil, nil, cmd, ErrInvalidWorikingDirectory
|
||||
}
|
||||
if *flDetach && *flAutoRemove {
|
||||
return nil, nil, cmd, ErrConflictDetachAutoRemove
|
||||
}
|
||||
|
||||
// If neither -d or -a are set, attach to everything by default
|
||||
if len(flAttach) == 0 && !*flDetach {
|
||||
if !*flDetach {
|
||||
@@ -175,10 +226,6 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
|
||||
}
|
||||
}
|
||||
|
||||
if *flDetach && *flAutoRemove {
|
||||
return nil, nil, cmd, fmt.Errorf("Conflicting options: -rm and -d")
|
||||
}
|
||||
|
||||
var binds []string
|
||||
|
||||
// add any bind targets to the list of container volumes
|
||||
@@ -220,10 +267,28 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
|
||||
hostname = parts[0]
|
||||
domainname = parts[1]
|
||||
}
|
||||
|
||||
ports, portBindings, err := parsePortSpecs(flPublish)
|
||||
if err != nil {
|
||||
return nil, nil, cmd, err
|
||||
}
|
||||
|
||||
// Merge in exposed ports to the map of published ports
|
||||
for _, e := range flExpose {
|
||||
if strings.Contains(e, ":") {
|
||||
return nil, nil, cmd, fmt.Errorf("Invalid port format for -expose: %s", e)
|
||||
}
|
||||
p := NewPort(splitProtoPort(e))
|
||||
if _, exists := ports[p]; !exists {
|
||||
ports[p] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
config := &Config{
|
||||
Hostname: hostname,
|
||||
Hostname: *flHostname,
|
||||
Domainname: domainname,
|
||||
PortSpecs: flPorts,
|
||||
PortSpecs: nil, // Deprecated
|
||||
ExposedPorts: ports,
|
||||
User: *flUser,
|
||||
Tty: *flTty,
|
||||
NetworkDisabled: !*flNetwork,
|
||||
@@ -243,10 +308,13 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
|
||||
Privileged: *flPrivileged,
|
||||
WorkingDir: *flWorkingDir,
|
||||
}
|
||||
|
||||
hostConfig := &HostConfig{
|
||||
Binds: binds,
|
||||
ContainerIDFile: *flContainerIDFile,
|
||||
LxcConf: lxcConf,
|
||||
PortBindings: portBindings,
|
||||
Links: flLinks,
|
||||
}
|
||||
|
||||
if capabilities != nil && *flMemory > 0 && !capabilities.SwapLimit {
|
||||
@@ -261,36 +329,38 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
|
||||
return config, hostConfig, cmd, nil
|
||||
}
|
||||
|
||||
type PortMapping map[string]string
|
||||
type PortMapping map[string]string // Deprecated
|
||||
|
||||
type NetworkSettings struct {
|
||||
IPAddress string
|
||||
IPPrefixLen int
|
||||
Gateway string
|
||||
Bridge string
|
||||
PortMapping map[string]PortMapping
|
||||
PortMapping map[string]PortMapping // Deprecated
|
||||
Ports map[Port][]PortBinding
|
||||
}
|
||||
|
||||
// returns a more easy to process description of the port mapping defined in the settings
|
||||
func (settings *NetworkSettings) PortMappingAPI() []APIPort {
|
||||
var mapping []APIPort
|
||||
for private, public := range settings.PortMapping["Tcp"] {
|
||||
pubint, _ := strconv.ParseInt(public, 0, 0)
|
||||
privint, _ := strconv.ParseInt(private, 0, 0)
|
||||
mapping = append(mapping, APIPort{
|
||||
PrivatePort: privint,
|
||||
PublicPort: pubint,
|
||||
Type: "tcp",
|
||||
})
|
||||
}
|
||||
for private, public := range settings.PortMapping["Udp"] {
|
||||
pubint, _ := strconv.ParseInt(public, 0, 0)
|
||||
privint, _ := strconv.ParseInt(private, 0, 0)
|
||||
mapping = append(mapping, APIPort{
|
||||
PrivatePort: privint,
|
||||
PublicPort: pubint,
|
||||
Type: "udp",
|
||||
})
|
||||
for port, bindings := range settings.Ports {
|
||||
p, _ := parsePort(port.Port())
|
||||
if len(bindings) == 0 {
|
||||
mapping = append(mapping, APIPort{
|
||||
PublicPort: int64(p),
|
||||
Type: port.Proto(),
|
||||
})
|
||||
continue
|
||||
}
|
||||
for _, binding := range bindings {
|
||||
p, _ := parsePort(port.Port())
|
||||
h, _ := parsePort(binding.HostPort)
|
||||
mapping = append(mapping, APIPort{
|
||||
PrivatePort: int64(p),
|
||||
PublicPort: int64(h),
|
||||
Type: port.Proto(),
|
||||
IP: binding.HostIp,
|
||||
})
|
||||
}
|
||||
}
|
||||
return mapping
|
||||
}
|
||||
@@ -460,11 +530,13 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s
|
||||
} else {
|
||||
_, err = io.Copy(cStdin, stdin)
|
||||
}
|
||||
if err == io.ErrClosedPipe {
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
utils.Errorf("attach: stdin: %s", err)
|
||||
}
|
||||
// Discard error, expecting pipe error
|
||||
errors <- nil
|
||||
errors <- err
|
||||
}()
|
||||
}
|
||||
}
|
||||
@@ -590,7 +662,7 @@ func (container *Container) Start(hostConfig *HostConfig) (err error) {
|
||||
if container.runtime.networkManager.disabled {
|
||||
container.Config.NetworkDisabled = true
|
||||
} else {
|
||||
if err := container.allocateNetwork(); err != nil {
|
||||
if err := container.allocateNetwork(hostConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -780,6 +852,46 @@ func (container *Container) Start(hostConfig *HostConfig) (err error) {
|
||||
"-e", "container=lxc",
|
||||
"-e", "HOSTNAME="+container.Config.Hostname,
|
||||
)
|
||||
|
||||
// Init any links between the parent and children
|
||||
runtime := container.runtime
|
||||
|
||||
children, err := runtime.Children(container.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(children) > 0 {
|
||||
container.activeLinks = make(map[string]*Link, len(children))
|
||||
|
||||
// If we encounter an error make sure that we rollback any network
|
||||
// config and ip table changes
|
||||
rollback := func() {
|
||||
for _, link := range container.activeLinks {
|
||||
link.Disable()
|
||||
}
|
||||
container.activeLinks = nil
|
||||
}
|
||||
|
||||
for p, child := range children {
|
||||
link, err := NewLink(container, child, p, runtime.networkManager.bridgeIface)
|
||||
if err != nil {
|
||||
rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
container.activeLinks[link.Alias()] = link
|
||||
if err := link.Enable(); err != nil {
|
||||
rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
for _, envVar := range link.ToEnv() {
|
||||
params = append(params, "-e", envVar)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if container.Config.WorkingDir != "" {
|
||||
workingDir := path.Clean(container.Config.WorkingDir)
|
||||
utils.Debugf("[working dir] working dir is %s", workingDir)
|
||||
@@ -831,12 +943,43 @@ func (container *Container) Start(hostConfig *HostConfig) (err error) {
|
||||
container.ToDisk()
|
||||
container.SaveHostConfig(hostConfig)
|
||||
go container.monitor(hostConfig)
|
||||
return nil
|
||||
|
||||
defer utils.Debugf("Container running: %v", container.State.Running)
|
||||
// We wait for the container to be fully running.
|
||||
// Timeout after 5 seconds. In case of broken pipe, just retry.
|
||||
// Note: The container can run and finish correctly before
|
||||
// the end of this loop
|
||||
for now := time.Now(); time.Since(now) < 5*time.Second; {
|
||||
// If the container dies while waiting for it, just return
|
||||
if !container.State.Running {
|
||||
return nil
|
||||
}
|
||||
output, err := exec.Command("lxc-info", "-s", "-n", container.ID).CombinedOutput()
|
||||
if err != nil {
|
||||
utils.Debugf("Error with lxc-info: %s (%s)", err, output)
|
||||
|
||||
output, err = exec.Command("lxc-info", "-s", "-n", container.ID).CombinedOutput()
|
||||
if err != nil {
|
||||
utils.Debugf("Second Error with lxc-info: %s (%s)", err, output)
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
if strings.Contains(string(output), "RUNNING") {
|
||||
return nil
|
||||
}
|
||||
utils.Debugf("Waiting for the container to start (running: %v): %s", container.State.Running, bytes.TrimSpace(output))
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
|
||||
if container.State.Running {
|
||||
return ErrContainerStartTimeout
|
||||
}
|
||||
return ErrContainerStart
|
||||
}
|
||||
|
||||
func (container *Container) Run() error {
|
||||
hostConfig := &HostConfig{}
|
||||
if err := container.Start(hostConfig); err != nil {
|
||||
if err := container.Start(&HostConfig{}); err != nil {
|
||||
return err
|
||||
}
|
||||
container.Wait()
|
||||
@@ -882,7 +1025,7 @@ func (container *Container) StderrPipe() (io.ReadCloser, error) {
|
||||
return utils.NewBufReader(reader), nil
|
||||
}
|
||||
|
||||
func (container *Container) allocateNetwork() error {
|
||||
func (container *Container) allocateNetwork(hostConfig *HostConfig) error {
|
||||
if container.Config.NetworkDisabled {
|
||||
return nil
|
||||
}
|
||||
@@ -909,36 +1052,62 @@ func (container *Container) allocateNetwork() error {
|
||||
}
|
||||
}
|
||||
|
||||
var portSpecs []string
|
||||
if !container.State.Ghost {
|
||||
portSpecs = container.Config.PortSpecs
|
||||
} else {
|
||||
for backend, frontend := range container.NetworkSettings.PortMapping["Tcp"] {
|
||||
portSpecs = append(portSpecs, fmt.Sprintf("%s:%s/tcp", frontend, backend))
|
||||
if container.Config.PortSpecs != nil {
|
||||
utils.Debugf("Migrating port mappings for container: %s", strings.Join(container.Config.PortSpecs, ", "))
|
||||
if err := migratePortMappings(container.Config, hostConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
for backend, frontend := range container.NetworkSettings.PortMapping["Udp"] {
|
||||
portSpecs = append(portSpecs, fmt.Sprintf("%s:%s/udp", frontend, backend))
|
||||
container.Config.PortSpecs = nil
|
||||
if err := container.SaveHostConfig(hostConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
container.NetworkSettings.PortMapping = make(map[string]PortMapping)
|
||||
container.NetworkSettings.PortMapping["Tcp"] = make(PortMapping)
|
||||
container.NetworkSettings.PortMapping["Udp"] = make(PortMapping)
|
||||
for _, spec := range portSpecs {
|
||||
nat, err := iface.AllocatePort(spec)
|
||||
if err != nil {
|
||||
iface.Release()
|
||||
return err
|
||||
portSpecs := make(map[Port]struct{})
|
||||
bindings := make(map[Port][]PortBinding)
|
||||
|
||||
if !container.State.Ghost {
|
||||
if container.Config.ExposedPorts != nil {
|
||||
portSpecs = container.Config.ExposedPorts
|
||||
}
|
||||
if hostConfig.PortBindings != nil {
|
||||
bindings = hostConfig.PortBindings
|
||||
}
|
||||
} else {
|
||||
if container.NetworkSettings.Ports != nil {
|
||||
for port, binding := range container.NetworkSettings.Ports {
|
||||
portSpecs[port] = struct{}{}
|
||||
bindings[port] = binding
|
||||
}
|
||||
}
|
||||
proto := strings.Title(nat.Proto)
|
||||
backend, frontend := strconv.Itoa(nat.Backend), strconv.Itoa(nat.Frontend)
|
||||
container.NetworkSettings.PortMapping[proto][backend] = frontend
|
||||
}
|
||||
|
||||
container.NetworkSettings.PortMapping = nil
|
||||
|
||||
for port := range portSpecs {
|
||||
binding := bindings[port]
|
||||
for i := 0; i < len(binding); i++ {
|
||||
b := binding[i]
|
||||
nat, err := iface.AllocatePort(port, b)
|
||||
if err != nil {
|
||||
iface.Release()
|
||||
return err
|
||||
}
|
||||
utils.Debugf("Allocate port: %s:%s->%s", nat.Binding.HostIp, port, nat.Binding.HostPort)
|
||||
binding[i] = nat.Binding
|
||||
}
|
||||
bindings[port] = binding
|
||||
}
|
||||
container.SaveHostConfig(hostConfig)
|
||||
|
||||
container.NetworkSettings.Ports = bindings
|
||||
container.network = iface
|
||||
|
||||
container.NetworkSettings.Bridge = container.runtime.networkManager.bridgeIface
|
||||
container.NetworkSettings.IPAddress = iface.IPNet.IP.String()
|
||||
container.NetworkSettings.IPPrefixLen, _ = iface.IPNet.Mask.Size()
|
||||
container.NetworkSettings.Gateway = iface.Gateway.String()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -951,7 +1120,7 @@ func (container *Container) releaseNetwork() {
|
||||
container.NetworkSettings = &NetworkSettings{}
|
||||
}
|
||||
|
||||
// FIXME: replace this with a control socket within docker-init
|
||||
// FIXME: replace this with a control socket within dockerinit
|
||||
func (container *Container) waitLxc() error {
|
||||
for {
|
||||
output, err := exec.Command("lxc-info", "-n", container.ID).CombinedOutput()
|
||||
@@ -1021,6 +1190,14 @@ func (container *Container) monitor(hostConfig *HostConfig) {
|
||||
|
||||
func (container *Container) cleanup() {
|
||||
container.releaseNetwork()
|
||||
|
||||
// Disable all active links
|
||||
if container.activeLinks != nil {
|
||||
for _, link := range container.activeLinks {
|
||||
link.Disable()
|
||||
}
|
||||
}
|
||||
|
||||
if container.Config.OpenStdin {
|
||||
if err := container.stdin.Close(); err != nil {
|
||||
utils.Errorf("%s: Error close stdin: %s", container.ID, err)
|
||||
@@ -1044,54 +1221,57 @@ func (container *Container) cleanup() {
|
||||
}
|
||||
}
|
||||
|
||||
func (container *Container) kill() error {
|
||||
func (container *Container) kill(sig int) error {
|
||||
container.State.Lock()
|
||||
defer container.State.Unlock()
|
||||
|
||||
if !container.State.Running {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Sending SIGKILL to the process via lxc
|
||||
output, err := exec.Command("lxc-kill", "-n", container.ID, "9").CombinedOutput()
|
||||
if err != nil {
|
||||
log.Printf("error killing container %s (%s, %s)", container.ID, output, err)
|
||||
if output, err := exec.Command("lxc-kill", "-n", container.ID, strconv.Itoa(sig)).CombinedOutput(); err != nil {
|
||||
log.Printf("error killing container %s (%s, %s)", container.ShortID(), output, err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (container *Container) Kill() error {
|
||||
if !container.State.Running {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 1. Send SIGKILL
|
||||
if err := container.kill(9); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 2. Wait for the process to die, in last resort, try to kill the process directly
|
||||
if err := container.WaitTimeout(10 * time.Second); err != nil {
|
||||
if container.cmd == nil {
|
||||
return fmt.Errorf("lxc-kill failed, impossible to kill the container %s", container.ID)
|
||||
return fmt.Errorf("lxc-kill failed, impossible to kill the container %s", container.ShortID())
|
||||
}
|
||||
log.Printf("Container %s failed to exit within 10 seconds of lxc SIGKILL - trying direct SIGKILL", container.ID)
|
||||
log.Printf("Container %s failed to exit within 10 seconds of lxc-kill %s - trying direct SIGKILL", "SIGKILL", container.ShortID())
|
||||
if err := container.cmd.Process.Kill(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for the container to be actually stopped
|
||||
container.Wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (container *Container) Kill() error {
|
||||
container.State.Lock()
|
||||
defer container.State.Unlock()
|
||||
if !container.State.Running {
|
||||
return nil
|
||||
}
|
||||
return container.kill()
|
||||
}
|
||||
|
||||
func (container *Container) Stop(seconds int) error {
|
||||
container.State.Lock()
|
||||
defer container.State.Unlock()
|
||||
if !container.State.Running {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 1. Send a SIGTERM
|
||||
if output, err := exec.Command("lxc-kill", "-n", container.ID, "15").CombinedOutput(); err != nil {
|
||||
log.Print(string(output))
|
||||
if err := container.kill(15); err != nil {
|
||||
utils.Debugf("Error sending kill SIGTERM: %s", err)
|
||||
log.Print("Failed to send SIGTERM to the process, force killing")
|
||||
if err := container.kill(); err != nil {
|
||||
if err := container.kill(9); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -1099,7 +1279,8 @@ func (container *Container) Stop(seconds int) error {
|
||||
// 2. Wait for the process to exit on its own
|
||||
if err := container.WaitTimeout(time.Duration(seconds) * time.Second); err != nil {
|
||||
log.Printf("Container %v failed to exit within %d seconds of SIGTERM - using the force", container.ID, seconds)
|
||||
if err := container.kill(); err != nil {
|
||||
// 3. If it doesn't, then send SIGKILL
|
||||
if err := container.Kill(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -1202,6 +1383,12 @@ func (container *Container) Mounted() (bool, error) {
|
||||
}
|
||||
|
||||
func (container *Container) Unmount() error {
|
||||
if _, err := os.Stat(container.RootfsPath()); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
return Unmount(container.RootfsPath())
|
||||
}
|
||||
|
||||
@@ -1292,3 +1479,9 @@ func (container *Container) Copy(resource string) (Archive, error) {
|
||||
}
|
||||
return TarFilter(basePath, Uncompressed, filter)
|
||||
}
|
||||
|
||||
// Returns true if the container exposes a certain port
|
||||
func (container *Container) Exposes(p Port) bool {
|
||||
_, exists := container.Config.ExposedPorts[p]
|
||||
return exists
|
||||
}
|
||||
|
||||
+90
-39
@@ -18,11 +18,12 @@ import (
|
||||
func TestIDFormat(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
container1, err := runtime.Create(
|
||||
container1, _, err := runtime.Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"/bin/sh", "-c", "echo hello world"},
|
||||
},
|
||||
"",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -388,11 +389,12 @@ func TestRun(t *testing.T) {
|
||||
func TestOutput(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
container, err := runtime.Create(
|
||||
container, _, err := runtime.Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"echo", "-n", "foobar"},
|
||||
},
|
||||
"",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -407,16 +409,39 @@ func TestOutput(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestContainerNetwork(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
container, _, err := runtime.Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"ping", "-c", "1", "127.0.0.1"},
|
||||
},
|
||||
"",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer runtime.Destroy(container)
|
||||
if err := container.Run(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if container.State.ExitCode != 0 {
|
||||
t.Errorf("Unexpected ping 127.0.0.1 exit code %d (expected 0)", container.State.ExitCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKillDifferentUser(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
container, err := runtime.Create(&Config{
|
||||
container, _, err := runtime.Create(&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"cat"},
|
||||
OpenStdin: true,
|
||||
User: "daemon",
|
||||
},
|
||||
"",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -471,7 +496,7 @@ func TestCreateVolume(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
c, err := runtime.Create(config)
|
||||
c, _, err := runtime.Create(config, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -486,10 +511,11 @@ func TestCreateVolume(t *testing.T) {
|
||||
func TestKill(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
container, err := runtime.Create(&Config{
|
||||
container, _, err := runtime.Create(&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"sleep", "2"},
|
||||
},
|
||||
"",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -530,10 +556,10 @@ func TestExitCode(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
trueContainer, err := runtime.Create(&Config{
|
||||
trueContainer, _, err := runtime.Create(&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"/bin/true", ""},
|
||||
})
|
||||
}, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -545,10 +571,10 @@ func TestExitCode(t *testing.T) {
|
||||
t.Errorf("Unexpected exit code %d (expected 0)", trueContainer.State.ExitCode)
|
||||
}
|
||||
|
||||
falseContainer, err := runtime.Create(&Config{
|
||||
falseContainer, _, err := runtime.Create(&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"/bin/false", ""},
|
||||
})
|
||||
}, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -564,10 +590,11 @@ func TestExitCode(t *testing.T) {
|
||||
func TestRestart(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
container, err := runtime.Create(&Config{
|
||||
container, _, err := runtime.Create(&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"echo", "-n", "foobar"},
|
||||
},
|
||||
"",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -594,12 +621,13 @@ func TestRestart(t *testing.T) {
|
||||
func TestRestartStdin(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
container, err := runtime.Create(&Config{
|
||||
container, _, err := runtime.Create(&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"cat"},
|
||||
|
||||
OpenStdin: true,
|
||||
},
|
||||
"",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -672,10 +700,11 @@ func TestUser(t *testing.T) {
|
||||
defer nuke(runtime)
|
||||
|
||||
// Default user must be root
|
||||
container, err := runtime.Create(&Config{
|
||||
container, _, err := runtime.Create(&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"id"},
|
||||
},
|
||||
"",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -690,12 +719,13 @@ func TestUser(t *testing.T) {
|
||||
}
|
||||
|
||||
// Set a username
|
||||
container, err = runtime.Create(&Config{
|
||||
container, _, err = runtime.Create(&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"id"},
|
||||
|
||||
User: "root",
|
||||
},
|
||||
"",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -710,12 +740,13 @@ func TestUser(t *testing.T) {
|
||||
}
|
||||
|
||||
// Set a UID
|
||||
container, err = runtime.Create(&Config{
|
||||
container, _, err = runtime.Create(&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"id"},
|
||||
|
||||
User: "0",
|
||||
},
|
||||
"",
|
||||
)
|
||||
if err != nil || container.State.ExitCode != 0 {
|
||||
t.Fatal(err)
|
||||
@@ -730,12 +761,13 @@ func TestUser(t *testing.T) {
|
||||
}
|
||||
|
||||
// Set a different user by uid
|
||||
container, err = runtime.Create(&Config{
|
||||
container, _, err = runtime.Create(&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"id"},
|
||||
|
||||
User: "1",
|
||||
},
|
||||
"",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -752,12 +784,13 @@ func TestUser(t *testing.T) {
|
||||
}
|
||||
|
||||
// Set a different user by username
|
||||
container, err = runtime.Create(&Config{
|
||||
container, _, err = runtime.Create(&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"id"},
|
||||
|
||||
User: "daemon",
|
||||
},
|
||||
"",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -772,12 +805,13 @@ func TestUser(t *testing.T) {
|
||||
}
|
||||
|
||||
// Test an wrong username
|
||||
container, err = runtime.Create(&Config{
|
||||
container, _, err = runtime.Create(&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"id"},
|
||||
|
||||
User: "unknownuser",
|
||||
},
|
||||
"",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -793,20 +827,22 @@ func TestMultipleContainers(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
container1, err := runtime.Create(&Config{
|
||||
container1, _, err := runtime.Create(&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"sleep", "2"},
|
||||
},
|
||||
"",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer runtime.Destroy(container1)
|
||||
|
||||
container2, err := runtime.Create(&Config{
|
||||
container2, _, err := runtime.Create(&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"sleep", "2"},
|
||||
},
|
||||
"",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -847,12 +883,13 @@ func TestMultipleContainers(t *testing.T) {
|
||||
func TestStdin(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
container, err := runtime.Create(&Config{
|
||||
container, _, err := runtime.Create(&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"cat"},
|
||||
|
||||
OpenStdin: true,
|
||||
},
|
||||
"",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -892,12 +929,13 @@ func TestStdin(t *testing.T) {
|
||||
func TestTty(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
container, err := runtime.Create(&Config{
|
||||
container, _, err := runtime.Create(&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"cat"},
|
||||
|
||||
OpenStdin: true,
|
||||
},
|
||||
"",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -937,10 +975,11 @@ func TestTty(t *testing.T) {
|
||||
func TestEnv(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
container, err := runtime.Create(&Config{
|
||||
container, _, err := runtime.Create(&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"env"},
|
||||
},
|
||||
"",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -986,12 +1025,13 @@ func TestEnv(t *testing.T) {
|
||||
func TestEntrypoint(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
container, err := runtime.Create(
|
||||
container, _, err := runtime.Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Entrypoint: []string{"/bin/echo"},
|
||||
Cmd: []string{"-n", "foobar"},
|
||||
},
|
||||
"",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -1009,11 +1049,12 @@ func TestEntrypoint(t *testing.T) {
|
||||
func TestEntrypointNoCmd(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
container, err := runtime.Create(
|
||||
container, _, err := runtime.Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Entrypoint: []string{"/bin/echo", "foobar"},
|
||||
},
|
||||
"",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -1060,7 +1101,7 @@ func TestLXCConfig(t *testing.T) {
|
||||
cpuMin := 100
|
||||
cpuMax := 10000
|
||||
cpu := cpuMin + rand.Intn(cpuMax-cpuMin)
|
||||
container, err := runtime.Create(&Config{
|
||||
container, _, err := runtime.Create(&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"/bin/true"},
|
||||
|
||||
@@ -1068,6 +1109,7 @@ func TestLXCConfig(t *testing.T) {
|
||||
Memory: int64(mem),
|
||||
CpuShares: int64(cpu),
|
||||
},
|
||||
"",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -1084,12 +1126,13 @@ func TestLXCConfig(t *testing.T) {
|
||||
func TestCustomLxcConfig(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
container, err := runtime.Create(&Config{
|
||||
container, _, err := runtime.Create(&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"/bin/true"},
|
||||
|
||||
Hostname: "foobar",
|
||||
},
|
||||
"",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -1115,10 +1158,11 @@ func BenchmarkRunSequencial(b *testing.B) {
|
||||
runtime := mkRuntime(b)
|
||||
defer nuke(runtime)
|
||||
for i := 0; i < b.N; i++ {
|
||||
container, err := runtime.Create(&Config{
|
||||
container, _, err := runtime.Create(&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"echo", "-n", "foo"},
|
||||
},
|
||||
"",
|
||||
)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
@@ -1147,10 +1191,11 @@ func BenchmarkRunParallel(b *testing.B) {
|
||||
complete := make(chan error)
|
||||
tasks = append(tasks, complete)
|
||||
go func(i int, complete chan error) {
|
||||
container, err := runtime.Create(&Config{
|
||||
container, _, err := runtime.Create(&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"echo", "-n", "foo"},
|
||||
},
|
||||
"",
|
||||
)
|
||||
if err != nil {
|
||||
complete <- err
|
||||
@@ -1297,12 +1342,13 @@ func TestBindMounts(t *testing.T) {
|
||||
func TestVolumesFromReadonlyMount(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
container, err := runtime.Create(
|
||||
container, _, err := runtime.Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"/bin/echo", "-n", "foobar"},
|
||||
Volumes: map[string]struct{}{"/test": {}},
|
||||
},
|
||||
"",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -1316,12 +1362,13 @@ func TestVolumesFromReadonlyMount(t *testing.T) {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
container2, err := runtime.Create(
|
||||
container2, _, err := runtime.Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"/bin/echo", "-n", "foobar"},
|
||||
VolumesFrom: container.ID,
|
||||
},
|
||||
"",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -1352,11 +1399,12 @@ func TestRestartWithVolumes(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
container, err := runtime.Create(&Config{
|
||||
container, _, err := runtime.Create(&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"echo", "-n", "foobar"},
|
||||
Volumes: map[string]struct{}{"/test": {}},
|
||||
},
|
||||
"",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -1395,11 +1443,12 @@ func TestVolumesFromWithVolumes(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
container, err := runtime.Create(&Config{
|
||||
container, _, err := runtime.Create(&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"sh", "-c", "echo -n bar > /test/foo"},
|
||||
Volumes: map[string]struct{}{"/test": {}},
|
||||
},
|
||||
"",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -1422,13 +1471,14 @@ func TestVolumesFromWithVolumes(t *testing.T) {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
container2, err := runtime.Create(
|
||||
container2, _, err := runtime.Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"cat", "/test/foo"},
|
||||
VolumesFrom: container.ID,
|
||||
Volumes: map[string]struct{}{"/test": {}},
|
||||
},
|
||||
"",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -1463,7 +1513,7 @@ func TestOnlyLoopbackExistsWhenUsingDisableNetworkOption(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
c, err := runtime.Create(config)
|
||||
c, _, err := runtime.Create(config, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -1529,11 +1579,12 @@ func TestMultipleVolumesFrom(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
container, err := runtime.Create(&Config{
|
||||
container, _, err := runtime.Create(&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"sh", "-c", "echo -n bar > /test/foo"},
|
||||
Volumes: map[string]struct{}{"/test": {}},
|
||||
},
|
||||
"",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -1556,12 +1607,13 @@ func TestMultipleVolumesFrom(t *testing.T) {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
container2, err := runtime.Create(
|
||||
container2, _, err := runtime.Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"sh", "-c", "echo -n bar > /other/foo"},
|
||||
Volumes: map[string]struct{}{"/other": {}},
|
||||
},
|
||||
"",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -1577,12 +1629,12 @@ func TestMultipleVolumesFrom(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
container3, err := runtime.Create(
|
||||
container3, _, err := runtime.Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"/bin/echo", "-n", "foobar"},
|
||||
VolumesFrom: strings.Join([]string{container.ID, container2.ID}, ","),
|
||||
})
|
||||
}, "")
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -1593,7 +1645,6 @@ func TestMultipleVolumesFrom(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Log(container3.Volumes)
|
||||
if container3.Volumes["/test"] != container.Volumes["/test"] {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
*.pyc
|
||||
@@ -1,78 +0,0 @@
|
||||
# docker-brew
|
||||
|
||||
docker-brew is a command-line tool used to build the docker standard library.
|
||||
|
||||
## Install instructions
|
||||
|
||||
1. Install python if it isn't already available on your OS of choice
|
||||
1. Install the easy_install tool (`sudo apt-get install python-setuptools`
|
||||
for Debian)
|
||||
1. Install the python package manager, `pip` (`easy_install pip`)
|
||||
1. Run the following command: `sudo pip install -r requirements.txt`
|
||||
1. You should now be able to use the `docker-brew` script as such.
|
||||
|
||||
## Basics
|
||||
|
||||
./docker-brew -h
|
||||
|
||||
Display usage and help.
|
||||
|
||||
./docker-brew
|
||||
|
||||
Default build from the default repo/branch. Images will be created under the
|
||||
`library/` namespace. Does not perform a remote push.
|
||||
|
||||
./docker-brew -n mycorp.com -b stable --push git://github.com/mycorp/docker
|
||||
|
||||
Will fetch the library definition files in the `stable` branch of the
|
||||
`git://github.com/mycorp/docker` repository and create images under the
|
||||
`mycorp.com` namespace (e.g. `mycorp.com/ubuntu`). Created images will then
|
||||
be pushed to the official docker repository (pending: support for private
|
||||
repositories)
|
||||
|
||||
## Library definition files
|
||||
|
||||
The library definition files are plain text files found in the `library/`
|
||||
subfolder of the docker repository.
|
||||
|
||||
### File names
|
||||
|
||||
The name of a definition file will determine the name of the image(s) it
|
||||
creates. For example, the `library/ubuntu` file will create images in the
|
||||
`<namespace>/ubuntu` repository. If multiple instructions are present in
|
||||
a single file, all images are expected to be created under a different tag.
|
||||
|
||||
### Instruction format
|
||||
|
||||
Each line represents a build instruction.
|
||||
There are different formats that `docker-brew` is able to parse.
|
||||
|
||||
<git-url>
|
||||
git://github.com/dotcloud/hipache
|
||||
https://github.com/dotcloud/docker.git
|
||||
|
||||
The simplest format. `docker-brew` will fetch data from the provided git
|
||||
repository from the `HEAD`of its `master` branch. Generated image will be
|
||||
tagged as `latest`. Use of this format is discouraged because there is no
|
||||
way to ensure stability.
|
||||
|
||||
<docker-tag> <git-url>
|
||||
bleeding-edge git://github.com/dotcloud/docker
|
||||
unstable https://github.com/dotcloud/docker-redis.git
|
||||
|
||||
A more advanced format. `docker-brew` will fetch data from the provided git
|
||||
repository from the `HEAD`of its `master` branch. Generated image will be
|
||||
tagged as `<docker-tag>`. Recommended if we always want to provide a snapshot
|
||||
of the latest development. Again, no way to ensure stability.
|
||||
|
||||
<docker-tag> <git-url> T:<git-tag>
|
||||
2.4.0 git://github.com/dotcloud/docker-redis T:2.4.0
|
||||
<docker-tag> <git-url> B:<git-branch>
|
||||
zfs git://github.com/dotcloud/docker B:zfs-support
|
||||
<docker-tag> <git-url> C:<git-commit-id>
|
||||
2.2.0 https://github.com/dotcloud/docker-redis.git C:a4bf8923ee4ec566d3ddc212
|
||||
|
||||
The most complete format. `docker-brew` will fetch data from the provided git
|
||||
repository from the provided reference (if it's a branch, brew will fetch its
|
||||
`HEAD`). Generated image will be tagged as `<docker-tag>`. Recommended whenever
|
||||
possible.
|
||||
@@ -1 +0,0 @@
|
||||
from brew import build_library, DEFAULT_REPOSITORY, DEFAULT_BRANCH
|
||||
@@ -1,185 +0,0 @@
|
||||
import os
|
||||
import logging
|
||||
from shutil import rmtree
|
||||
|
||||
import docker
|
||||
|
||||
import git
|
||||
|
||||
DEFAULT_REPOSITORY = 'git://github.com/dotcloud/docker'
|
||||
DEFAULT_BRANCH = 'master'
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s',
|
||||
level='INFO')
|
||||
client = docker.Client()
|
||||
processed = {}
|
||||
processed_folders = []
|
||||
|
||||
|
||||
def build_library(repository=None, branch=None, namespace=None, push=False,
|
||||
debug=False, prefill=True, registry=None):
|
||||
dst_folder = None
|
||||
summary = Summary()
|
||||
if repository is None:
|
||||
repository = DEFAULT_REPOSITORY
|
||||
if branch is None:
|
||||
branch = DEFAULT_BRANCH
|
||||
if debug:
|
||||
logger.setLevel('DEBUG')
|
||||
|
||||
if not (repository.startswith('https://') or repository.startswith('git://')):
|
||||
logger.info('Repository provided assumed to be a local path')
|
||||
dst_folder = repository
|
||||
|
||||
try:
|
||||
client.version()
|
||||
except Exception as e:
|
||||
logger.error('Could not reach the docker daemon. Please make sure it '
|
||||
'is running.')
|
||||
logger.warning('Also make sure you have access to the docker UNIX '
|
||||
'socket (use sudo)')
|
||||
return
|
||||
|
||||
#FIXME: set destination folder and only pull latest changes instead of
|
||||
# cloning the whole repo everytime
|
||||
if not dst_folder:
|
||||
logger.info('Cloning docker repo from {0}, branch: {1}'.format(
|
||||
repository, branch))
|
||||
try:
|
||||
rep, dst_folder = git.clone_branch(repository, branch)
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
logger.error('Source repository could not be fetched. Check '
|
||||
'that the address is correct and the branch exists.')
|
||||
return
|
||||
try:
|
||||
dirlist = os.listdir(os.path.join(dst_folder, 'library'))
|
||||
except OSError as e:
|
||||
logger.error('The path provided ({0}) could not be found or didn\'t'
|
||||
'contain a library/ folder.'.format(dst_folder))
|
||||
return
|
||||
for buildfile in dirlist:
|
||||
if buildfile == 'MAINTAINERS':
|
||||
continue
|
||||
f = open(os.path.join(dst_folder, 'library', buildfile))
|
||||
linecnt = 0
|
||||
for line in f:
|
||||
linecnt = linecnt + 1
|
||||
logger.debug('{0} ---> {1}'.format(buildfile, line))
|
||||
args = line.split()
|
||||
try:
|
||||
if len(args) > 3:
|
||||
raise RuntimeError('Incorrect line format, '
|
||||
'please refer to the docs')
|
||||
|
||||
url = None
|
||||
ref = 'refs/heads/master'
|
||||
tag = None
|
||||
if len(args) == 1: # Just a URL, simple mode
|
||||
url = args[0]
|
||||
elif len(args) == 2 or len(args) == 3: # docker-tag url
|
||||
url = args[1]
|
||||
tag = args[0]
|
||||
|
||||
if len(args) == 3: # docker-tag url B:branch or T:tag
|
||||
ref = None
|
||||
if args[2].startswith('B:'):
|
||||
ref = 'refs/heads/' + args[2][2:]
|
||||
elif args[2].startswith('T:'):
|
||||
ref = 'refs/tags/' + args[2][2:]
|
||||
elif args[2].startswith('C:'):
|
||||
ref = args[2][2:]
|
||||
else:
|
||||
raise RuntimeError('Incorrect line format, '
|
||||
'please refer to the docs')
|
||||
if prefill:
|
||||
logger.debug('Pulling {0} from official repository (cache '
|
||||
'fill)'.format(buildfile))
|
||||
client.pull(buildfile)
|
||||
img = build_repo(url, ref, buildfile, tag, namespace, push,
|
||||
registry)
|
||||
summary.add_success(buildfile, (linecnt, line), img)
|
||||
processed['{0}@{1}'.format(url, ref)] = img
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
summary.add_exception(buildfile, (linecnt, line), e)
|
||||
|
||||
f.close()
|
||||
if dst_folder != repository:
|
||||
rmtree(dst_folder, True)
|
||||
for d in processed_folders:
|
||||
rmtree(d, True)
|
||||
summary.print_summary(logger)
|
||||
|
||||
|
||||
def build_repo(repository, ref, docker_repo, docker_tag, namespace, push, registry):
|
||||
docker_repo = '{0}/{1}'.format(namespace or 'library', docker_repo)
|
||||
img_id = None
|
||||
dst_folder = None
|
||||
if '{0}@{1}'.format(repository, ref) not in processed.keys():
|
||||
logger.info('Cloning {0} (ref: {1})'.format(repository, ref))
|
||||
if repository not in processed:
|
||||
rep, dst_folder = git.clone(repository, ref)
|
||||
processed[repository] = rep
|
||||
processed_folders.append(dst_folder)
|
||||
else:
|
||||
dst_folder = git.checkout(processed[repository], ref)
|
||||
if not 'Dockerfile' in os.listdir(dst_folder):
|
||||
raise RuntimeError('Dockerfile not found in cloned repository')
|
||||
logger.info('Building using dockerfile...')
|
||||
img_id, logs = client.build(path=dst_folder, quiet=True)
|
||||
else:
|
||||
img_id = processed['{0}@{1}'.format(repository, ref)]
|
||||
logger.info('Committing to {0}:{1}'.format(docker_repo,
|
||||
docker_tag or 'latest'))
|
||||
client.tag(img_id, docker_repo, docker_tag)
|
||||
if push:
|
||||
logger.info('Pushing result to registry {0}'.format(
|
||||
registry or "default"))
|
||||
if registry is not None:
|
||||
docker_repo = '{0}/{1}'.format(registry, docker_repo)
|
||||
logger.info('Also tagging {0}'.format(docker_repo))
|
||||
client.tag(img_id, docker_repo, docker_tag)
|
||||
client.push(docker_repo)
|
||||
return img_id
|
||||
|
||||
|
||||
class Summary(object):
|
||||
def __init__(self):
|
||||
self._summary = {}
|
||||
self._has_exc = False
|
||||
|
||||
def _add_data(self, image, linestr, data):
|
||||
if image not in self._summary:
|
||||
self._summary[image] = { linestr: data }
|
||||
else:
|
||||
self._summary[image][linestr] = data
|
||||
|
||||
def add_exception(self, image, line, exc):
|
||||
lineno, linestr = line
|
||||
self._add_data(image, linestr, { 'line': lineno, 'exc': str(exc) })
|
||||
self._has_exc = True
|
||||
|
||||
def add_success(self, image, line, img_id):
|
||||
lineno, linestr = line
|
||||
self._add_data(image, linestr, { 'line': lineno, 'id': img_id })
|
||||
|
||||
def print_summary(self, logger=None):
|
||||
linesep = ''.center(61, '-') + '\n'
|
||||
s = 'BREW BUILD SUMMARY\n' + linesep
|
||||
success = 'OVERALL SUCCESS: {}\n'.format(not self._has_exc)
|
||||
details = linesep
|
||||
for image, lines in self._summary.iteritems():
|
||||
details = details + '{}\n{}'.format(image, linesep)
|
||||
for linestr, data in lines.iteritems():
|
||||
details = details + '{0:2} | {1} | {2:50}\n'.format(
|
||||
data['line'],
|
||||
'KO' if 'exc' in data else 'OK',
|
||||
data['exc'] if 'exc' in data else data['id']
|
||||
)
|
||||
details = details + linesep
|
||||
if logger:
|
||||
logger.info(s + success + details)
|
||||
else:
|
||||
print s, success, details
|
||||
@@ -1,63 +0,0 @@
|
||||
import tempfile
|
||||
import logging
|
||||
|
||||
from dulwich import index
|
||||
from dulwich.client import get_transport_and_path
|
||||
from dulwich.repo import Repo
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def clone_branch(repo_url, branch="master", folder=None):
|
||||
return clone(repo_url, 'refs/heads/' + branch, folder)
|
||||
|
||||
|
||||
def clone_tag(repo_url, tag, folder=None):
|
||||
return clone(repo_url, 'refs/tags/' + tag, folder)
|
||||
|
||||
|
||||
def checkout(rep, ref=None):
|
||||
is_commit = False
|
||||
if ref is None:
|
||||
ref = 'refs/heads/master'
|
||||
elif not ref.startswith('refs/'):
|
||||
is_commit = True
|
||||
if is_commit:
|
||||
rep['HEAD'] = rep.commit(ref)
|
||||
else:
|
||||
rep['HEAD'] = rep.refs[ref]
|
||||
indexfile = rep.index_path()
|
||||
tree = rep["HEAD"].tree
|
||||
index.build_index_from_tree(rep.path, indexfile, rep.object_store, tree)
|
||||
return rep.path
|
||||
|
||||
def clone(repo_url, ref=None, folder=None):
|
||||
is_commit = False
|
||||
if ref is None:
|
||||
ref = 'refs/heads/master'
|
||||
elif not ref.startswith('refs/'):
|
||||
is_commit = True
|
||||
logger.debug("clone repo_url={0}, ref={1}".format(repo_url, ref))
|
||||
if folder is None:
|
||||
folder = tempfile.mkdtemp()
|
||||
logger.debug("folder = {0}".format(folder))
|
||||
rep = Repo.init(folder)
|
||||
client, relative_path = get_transport_and_path(repo_url)
|
||||
logger.debug("client={0}".format(client))
|
||||
|
||||
remote_refs = client.fetch(relative_path, rep)
|
||||
for k, v in remote_refs.iteritems():
|
||||
try:
|
||||
rep.refs.add_if_new(k, v)
|
||||
except:
|
||||
pass
|
||||
|
||||
if is_commit:
|
||||
rep['HEAD'] = rep.commit(ref)
|
||||
else:
|
||||
rep['HEAD'] = remote_refs[ref]
|
||||
indexfile = rep.index_path()
|
||||
tree = rep["HEAD"].tree
|
||||
index.build_index_from_tree(rep.path, indexfile, rep.object_store, tree)
|
||||
logger.debug("done")
|
||||
return rep, folder
|
||||
@@ -1,35 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
try:
|
||||
import brew
|
||||
except ImportError as e:
|
||||
print str(e)
|
||||
print 'Please install the required dependencies first'
|
||||
print 'sudo pip install -r requirements.txt'
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser('Build the docker standard library')
|
||||
parser.add_argument('--push', action='store_true', default=False,
|
||||
help='Push generated repositories')
|
||||
parser.add_argument('--debug', default=False, action='store_true',
|
||||
help='Enable debugging output')
|
||||
parser.add_argument('--noprefill', default=True, action='store_false',
|
||||
dest='prefill', help='Disable cache prefill')
|
||||
parser.add_argument('-n', metavar='NAMESPACE', default='library',
|
||||
help='Namespace used for generated repositories.'
|
||||
' Default is library')
|
||||
parser.add_argument('-b', metavar='BRANCH', default=brew.DEFAULT_BRANCH,
|
||||
help='Branch in the repository where the library definition'
|
||||
' files will be fetched. Default is ' + brew.DEFAULT_BRANCH)
|
||||
parser.add_argument('repository', default=brew.DEFAULT_REPOSITORY,
|
||||
nargs='?', help='git repository containing the library definition'
|
||||
' files. Default is ' + brew.DEFAULT_REPOSITORY)
|
||||
parser.add_argument('--reg', default=None, help='Registry address to'
|
||||
' push build results to. Also sets push to true.')
|
||||
args = parser.parse_args()
|
||||
brew.build_library(args.repository, args.b, args.n,
|
||||
args.push or args.reg is not None, args.debug, args.prefill, args.reg)
|
||||
@@ -1,2 +0,0 @@
|
||||
dulwich==0.9.0
|
||||
-e git://github.com/dotcloud/docker-py.git#egg=docker-py
|
||||
@@ -1,22 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
import os
|
||||
from setuptools import setup
|
||||
|
||||
ROOT_DIR = os.path.dirname(__file__)
|
||||
SOURCE_DIR = os.path.join(ROOT_DIR)
|
||||
|
||||
test_requirements = []
|
||||
setup(
|
||||
name="docker-brew",
|
||||
version='0.0.1',
|
||||
description="-",
|
||||
packages=['dockerbrew'],
|
||||
install_requires=['dulwich', 'docker'] + test_requirements,
|
||||
zip_safe=False,
|
||||
classifiers=['Development Status :: 3 - Alpha',
|
||||
'Environment :: Other Environment',
|
||||
'Intended Audience :: Developers',
|
||||
'Operating System :: OS Independent',
|
||||
'Programming Language :: Python',
|
||||
'Topic :: Utilities'],
|
||||
)
|
||||
@@ -0,0 +1,27 @@
|
||||
#
|
||||
# This Dockerfile will create an image that allows to generate upstart and
|
||||
# systemd scripts (more to come)
|
||||
#
|
||||
# docker-version 0.6.2
|
||||
#
|
||||
|
||||
FROM ubuntu:12.10
|
||||
MAINTAINER Guillaume J. Charmes <guillaume@dotcloud.com>
|
||||
|
||||
RUN apt-get update && apt-get install -y wget git mercurial
|
||||
|
||||
# Install Go
|
||||
RUN wget --no-check-certificate https://go.googlecode.com/files/go1.1.2.linux-amd64.tar.gz -O go-1.1.2.tar.gz
|
||||
RUN tar -xzvf go-1.1.2.tar.gz && mv /go /goroot
|
||||
RUN mkdir /go
|
||||
|
||||
ENV GOROOT /goroot
|
||||
ENV GOPATH /go
|
||||
ENV PATH $GOROOT/bin:$PATH
|
||||
|
||||
RUN go get github.com/dotcloud/docker && cd /go/src/github.com/dotcloud/docker && git checkout v0.6.3
|
||||
ADD manager.go /manager/
|
||||
RUN cd /manager && go build -o /usr/bin/manager
|
||||
|
||||
ENTRYPOINT ["/usr/bin/manager"]
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
FROM busybox
|
||||
MAINTAINER Guillaume J. Charmes <guillaume@dotcloud.com>
|
||||
ADD manager /usr/bin/
|
||||
ENTRYPOINT ["/usr/bin/manager"]
|
||||
@@ -0,0 +1,130 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker"
|
||||
"os"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
var templates = map[string]string{
|
||||
|
||||
"upstart": `description "{{.description}}"
|
||||
author "{{.author}}"
|
||||
start on filesystem and started lxc-net and started docker
|
||||
stop on runlevel [!2345]
|
||||
respawn
|
||||
exec /home/vagrant/goroot/bin/docker start -a {{.container_id}}
|
||||
`,
|
||||
|
||||
"systemd": `[Unit]
|
||||
Description={{.description}}
|
||||
Author={{.author}}
|
||||
After=docker.service
|
||||
|
||||
[Service]
|
||||
Restart=always
|
||||
ExecStart=/usr/bin/docker start -a {{.container_id}}
|
||||
ExecStop=/usr/bin/docker stop -t 2 {{.container_id}}
|
||||
|
||||
[Install]
|
||||
WantedBy=local.target
|
||||
`,
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Parse command line for custom options
|
||||
kind := flag.String("t", "upstart", "Type of manager requested")
|
||||
author := flag.String("a", "<none>", "Author of the image")
|
||||
description := flag.String("d", "<none>", "Description of the image")
|
||||
flag.Usage = func() {
|
||||
fmt.Fprintf(os.Stderr, "\nUsage: manager <container id>\n\n")
|
||||
flag.PrintDefaults()
|
||||
}
|
||||
flag.Parse()
|
||||
|
||||
// We require at least the container ID
|
||||
if flag.NArg() != 1 {
|
||||
println(flag.NArg())
|
||||
flag.Usage()
|
||||
return
|
||||
}
|
||||
|
||||
// Check that the requested process manager is supported
|
||||
if _, exists := templates[*kind]; !exists {
|
||||
panic("Unkown script template")
|
||||
}
|
||||
|
||||
// Load the requested template
|
||||
tpl, err := template.New("processManager").Parse(templates[*kind])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Create stdout/stderr buffers
|
||||
bufOut := bytes.NewBuffer(nil)
|
||||
bufErr := bytes.NewBuffer(nil)
|
||||
|
||||
// Instanciate the Docker CLI
|
||||
cli := docker.NewDockerCli(nil, bufOut, bufErr, "unix", "/var/run/docker.sock")
|
||||
// Retrieve the container info
|
||||
if err := cli.CmdInspect(flag.Arg(0)); err != nil {
|
||||
// As of docker v0.6.3, CmdInspect always returns nil
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// If there is nothing in the error buffer, then the Docker daemon is there and the container has been found
|
||||
if bufErr.Len() == 0 {
|
||||
// Unmarshall the resulting container data
|
||||
c := []*docker.Container{{}}
|
||||
if err := json.Unmarshal(bufOut.Bytes(), &c); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// Reset the buffers
|
||||
bufOut.Reset()
|
||||
bufErr.Reset()
|
||||
// Retrieve the info of the linked image
|
||||
if err := cli.CmdInspect(c[0].Image); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// If there is nothing in the error buffer, then the image has been found.
|
||||
if bufErr.Len() == 0 {
|
||||
// Unmarshall the resulting image data
|
||||
img := []*docker.Image{{}}
|
||||
if err := json.Unmarshal(bufOut.Bytes(), &img); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// If no author has been set, use the one from the image
|
||||
if *author == "<none>" && img[0].Author != "" {
|
||||
*author = strings.Replace(img[0].Author, "\"", "", -1)
|
||||
}
|
||||
// If no description has been set, use the comment from the image
|
||||
if *description == "<none>" && img[0].Comment != "" {
|
||||
*description = strings.Replace(img[0].Comment, "\"", "", -1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Old version: Wrtie the resulting script to file
|
||||
// f, err := os.OpenFile(kind, os.O_CREATE|os.O_WRONLY, 0755)
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
// defer f.Close()
|
||||
|
||||
// Create a map with needed data
|
||||
data := map[string]string{
|
||||
"author": *author,
|
||||
"description": *description,
|
||||
"container_id": flag.Arg(0),
|
||||
}
|
||||
|
||||
// Process the template and output it on Stdout
|
||||
if err := tpl.Execute(os.Stdout, data); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
Executable
+53
@@ -0,0 +1,53 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
usage() {
|
||||
echo >&2 "usage: $0 [-a author] [-d description] container [manager]"
|
||||
echo >&2 " ie: $0 -a 'John Smith' 4ec9612a37cd systemd"
|
||||
echo >&2 " ie: $0 -d 'Super Cool System' 4ec9612a37cd # defaults to upstart"
|
||||
exit 1
|
||||
}
|
||||
|
||||
auth='<none>'
|
||||
desc='<none>'
|
||||
have_auth=
|
||||
have_desc=
|
||||
while getopts a:d: opt; do
|
||||
case "$opt" in
|
||||
a)
|
||||
auth="$OPTARG"
|
||||
have_auth=1
|
||||
;;
|
||||
d)
|
||||
desc="$OPTARG"
|
||||
have_desc=1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
shift $(($OPTIND - 1))
|
||||
|
||||
[ $# -ge 1 -a $# -le 2 ] || usage
|
||||
|
||||
cid="$1"
|
||||
script="${2:-upstart}"
|
||||
if [ ! -e "manager/$script" ]; then
|
||||
echo >&2 "Error: manager type '$script' is unknown (PRs always welcome!)."
|
||||
echo >&2 'The currently supported types are:'
|
||||
echo >&2 " $(cd manager && echo *)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# TODO https://github.com/dotcloud/docker/issues/734 (docker inspect formatting)
|
||||
#if command -v docker > /dev/null 2>&1; then
|
||||
# image="$(docker inspect -f '{{.Image}}' "$cid")"
|
||||
# if [ "$image" ]; then
|
||||
# if [ -z "$have_auth" ]; then
|
||||
# auth="$(docker inspect -f '{{.Author}}' "$image")"
|
||||
# fi
|
||||
# if [ -z "$have_desc" ]; then
|
||||
# desc="$(docker inspect -f '{{.Comment}}' "$image")"
|
||||
# fi
|
||||
# fi
|
||||
#fi
|
||||
|
||||
exec "manager/$script" "$cid" "$auth" "$desc"
|
||||
Executable
+20
@@ -0,0 +1,20 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
cid="$1"
|
||||
auth="$2"
|
||||
desc="$3"
|
||||
|
||||
cat <<-EOF
|
||||
[Unit]
|
||||
Description=$desc
|
||||
Author=$auth
|
||||
After=docker.service
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/bin/docker start -a $cid
|
||||
ExecStop=/usr/bin/docker stop -t 2 $cid
|
||||
|
||||
[Install]
|
||||
WantedBy=local.target
|
||||
EOF
|
||||
Executable
+15
@@ -0,0 +1,15 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
cid="$1"
|
||||
auth="$2"
|
||||
desc="$3"
|
||||
|
||||
cat <<-EOF
|
||||
description "$(echo "$desc" | sed 's/"/\\"/g')"
|
||||
author "$(echo "$auth" | sed 's/"/\\"/g')"
|
||||
start on filesystem and started lxc-net and started docker
|
||||
stop on runlevel [!2345]
|
||||
respawn
|
||||
exec /usr/bin/docker start -a "$cid"
|
||||
EOF
|
||||
@@ -0,0 +1,13 @@
|
||||
# /etc/conf.d/docker: config file for /etc/init.d/docker
|
||||
|
||||
# where the docker daemon output gets piped
|
||||
#DOCKER_LOGFILE="/var/log/docker.log"
|
||||
|
||||
# where docker's pid get stored
|
||||
#DOCKER_PIDFILE="/run/docker.pid"
|
||||
|
||||
# where the docker daemon itself is run from
|
||||
#DOCKER_BINARY="/usr/bin/docker"
|
||||
|
||||
# any other random options you want to pass to docker
|
||||
DOCKER_OPTS=""
|
||||
Executable
+31
@@ -0,0 +1,31 @@
|
||||
#!/sbin/runscript
|
||||
# Copyright 1999-2013 Gentoo Foundation
|
||||
# Distributed under the terms of the GNU General Public License v2
|
||||
# $Header: $
|
||||
|
||||
DOCKER_LOGFILE=${DOCKER_LOGFILE:-/var/log/${SVCNAME}.log}
|
||||
DOCKER_PIDFILE=${DOCKER_PIDFILE:-/run/${SVCNAME}.pid}
|
||||
DOCKER_BINARY=${DOCKER_BINARY:-/usr/bin/docker}
|
||||
DOCKER_OPTS=${DOCKER_OPTS:-}
|
||||
|
||||
start() {
|
||||
checkpath -f -m 0644 -o root:docker "$DOCKER_LOGFILE"
|
||||
|
||||
ebegin "Starting docker daemon"
|
||||
start-stop-daemon --start --background \
|
||||
--exec "$DOCKER_BINARY" \
|
||||
--pidfile "$DOCKER_PIDFILE" \
|
||||
--stdout "$DOCKER_LOGFILE" \
|
||||
--stderr "$DOCKER_LOGFILE" \
|
||||
-- -d -p "$DOCKER_PIDFILE" \
|
||||
$DOCKER_OPTS
|
||||
eend $?
|
||||
}
|
||||
|
||||
stop() {
|
||||
ebegin "Stopping docker daemon"
|
||||
start-stop-daemon --stop \
|
||||
--exec "$DOCKER_BINARY" \
|
||||
--pidfile "$DOCKER_PIDFILE"
|
||||
eend $?
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
[Unit]
|
||||
Description=Easily create lightweight, portable, self-sufficient containers from any application!
|
||||
Documentation=http://docs.docker.io
|
||||
Requires=network.target
|
||||
After=multi-user.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStartPre=/bin/mount --make-rprivate /
|
||||
ExecStart=/usr/bin/docker -d
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
Executable
+85
@@ -0,0 +1,85 @@
|
||||
#!/bin/sh
|
||||
|
||||
### BEGIN INIT INFO
|
||||
# Provides: docker
|
||||
# Required-Start: $syslog $remote_fs
|
||||
# Required-Stop: $syslog $remote_fs
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Short-Description: Linux container runtime
|
||||
# Description: Linux container runtime
|
||||
### END INIT INFO
|
||||
|
||||
DOCKER=/usr/bin/docker
|
||||
DOCKER_PIDFILE=/var/run/docker.pid
|
||||
DOCKER_OPTS=
|
||||
|
||||
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin
|
||||
|
||||
# Check lxc-docker is present
|
||||
[ -x $DOCKER ] || (log_failure_msg "docker not present"; exit 1)
|
||||
|
||||
# Get lsb functions
|
||||
. /lib/lsb/init-functions
|
||||
|
||||
if [ -f /etc/default/lxc ]; then
|
||||
. /etc/default/lxc
|
||||
fi
|
||||
|
||||
if [ "$1" = start ] && which initctl >/dev/null && initctl version | grep -q upstart; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
check_root_id ()
|
||||
{
|
||||
if [ "$(id -u)" != "0" ]; then
|
||||
log_failure_msg "Docker must be run as root"; exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
check_root_id || exit 1
|
||||
log_begin_msg "Starting Docker"
|
||||
mount | grep cgroup >/dev/null || mount -t cgroup none /sys/fs/cgroup 2>/dev/null
|
||||
start-stop-daemon --start --background $NO_CLOSE \
|
||||
--exec "$DOCKER" \
|
||||
--pidfile "$DOCKER_PIDFILE" \
|
||||
-- -d -p "$DOCKER_PIDFILE" \
|
||||
$DOCKER_OPTS
|
||||
log_end_msg $?
|
||||
;;
|
||||
|
||||
stop)
|
||||
check_root_id || exit 1
|
||||
log_begin_msg "Stopping Docker"
|
||||
start-stop-daemon --stop \
|
||||
--pidfile "$DOCKER_PIDFILE"
|
||||
log_end_msg $?
|
||||
;;
|
||||
|
||||
restart)
|
||||
check_root_id || exit 1
|
||||
docker_pid=`cat "$DOCKER_PIDFILE" 2>/dev/null`
|
||||
[ -n "$docker_pid" ] \
|
||||
&& ps -p $docker_pid > /dev/null 2>&1 \
|
||||
&& $0 stop
|
||||
$0 start
|
||||
;;
|
||||
|
||||
force-reload)
|
||||
check_root_id || exit 1
|
||||
$0 restart
|
||||
;;
|
||||
|
||||
status)
|
||||
status_of_proc -p "$DOCKER_PIDFILE" "$DOCKER" docker
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "Usage: $0 {start|stop|restart|status}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
exit 0
|
||||
@@ -0,0 +1,10 @@
|
||||
description "Docker daemon"
|
||||
|
||||
start on filesystem and started lxc-net
|
||||
stop on runlevel [!2345]
|
||||
|
||||
respawn
|
||||
|
||||
script
|
||||
/usr/bin/docker -d
|
||||
end script
|
||||
@@ -62,6 +62,6 @@ mknod -m 666 ${DEV}/full c 1 7
|
||||
mknod -m 600 ${DEV}/initctl p
|
||||
mknod -m 666 ${DEV}/ptmx c 5 2
|
||||
|
||||
tar -C $ROOTFS -c . | docker import - archlinux
|
||||
tar --numeric-owner -C $ROOTFS -c . | docker import - archlinux
|
||||
docker run -i -t archlinux echo Success.
|
||||
rm -rf $ROOTFS
|
||||
|
||||
@@ -35,5 +35,5 @@ do
|
||||
cp -a /dev/$X dev
|
||||
done
|
||||
|
||||
tar -cf- . | docker import - busybox
|
||||
tar --numeric-owner -cf- . | docker import - busybox
|
||||
docker run -i -u root busybox /bin/echo Success.
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# these should match the names found at http://www.debian.org/releases/
|
||||
stableSuite='wheezy'
|
||||
testingSuite='jessie'
|
||||
unstableSuite='sid'
|
||||
|
||||
variant='minbase'
|
||||
include='iproute,iputils-ping'
|
||||
|
||||
repo="$1"
|
||||
suite="$2"
|
||||
mirror="${3:-}" # stick to the default debootstrap mirror if one is not provided
|
||||
|
||||
if [ ! "$repo" ] || [ ! "$suite" ]; then
|
||||
echo >&2 "usage: $0 repo suite [mirror]"
|
||||
echo >&2
|
||||
echo >&2 " ie: $0 tianon/debian squeeze"
|
||||
echo >&2 " $0 tianon/debian squeeze http://ftp.uk.debian.org/debian/"
|
||||
echo >&2
|
||||
echo >&2 " ie: $0 tianon/ubuntu precise"
|
||||
echo >&2 " $0 tianon/ubuntu precise http://mirrors.melbourne.co.uk/ubuntu/"
|
||||
echo >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
target="/tmp/docker-rootfs-debian-$suite-$$-$RANDOM"
|
||||
|
||||
cd "$(dirname "$(readlink -f "$BASH_SOURCE")")"
|
||||
returnTo="$(pwd -P)"
|
||||
|
||||
set -x
|
||||
|
||||
# bootstrap
|
||||
mkdir -p "$target"
|
||||
sudo debootstrap --verbose --variant="$variant" --include="$include" "$suite" "$target" "$mirror"
|
||||
|
||||
cd "$target"
|
||||
|
||||
# prevent init scripts from running during install/update
|
||||
# policy-rc.d (for most scripts)
|
||||
echo $'#!/bin/sh\nexit 101' | sudo tee usr/sbin/policy-rc.d > /dev/null
|
||||
sudo chmod +x usr/sbin/policy-rc.d
|
||||
# initctl (for some pesky upstart scripts)
|
||||
sudo chroot . dpkg-divert --local --rename --add /sbin/initctl
|
||||
sudo ln -sf /bin/true sbin/initctl
|
||||
# see https://github.com/dotcloud/docker/issues/446#issuecomment-16953173
|
||||
|
||||
# shrink the image, since apt makes us fat (wheezy: ~157.5MB vs ~120MB)
|
||||
sudo chroot . apt-get clean
|
||||
|
||||
# while we're at it, apt is unnecessarily slow inside containers
|
||||
# this forces dpkg not to call sync() after package extraction and speeds up install
|
||||
# the benefit is huge on spinning disks, and the penalty is nonexistent on SSD or decent server virtualization
|
||||
echo 'force-unsafe-io' | sudo tee etc/dpkg/dpkg.cfg.d/02apt-speedup > /dev/null
|
||||
# we want to effectively run "apt-get clean" after every install to keep images small
|
||||
echo 'DPkg::Post-Invoke {"/bin/rm -f /var/cache/apt/archives/*.deb || true";};' | sudo tee etc/apt/apt.conf.d/no-cache > /dev/null
|
||||
|
||||
# helpful undo lines for each the above tweaks (for lack of a better home to keep track of them):
|
||||
# rm /usr/sbin/policy-rc.d
|
||||
# rm /sbin/initctl; dpkg-divert --rename --remove /sbin/initctl
|
||||
# rm /etc/dpkg/dpkg.cfg.d/02apt-speedup
|
||||
# rm /etc/apt/apt.conf.d/no-cache
|
||||
|
||||
# create the image (and tag $repo:$suite)
|
||||
sudo tar -c . | docker import - $repo $suite
|
||||
|
||||
# test the image
|
||||
docker run -i -t $repo:$suite echo success
|
||||
|
||||
if [ "$suite" = "$stableSuite" -o "$suite" = 'stable' ]; then
|
||||
# tag latest
|
||||
docker tag $repo:$suite $repo latest
|
||||
|
||||
# tag the specific debian release version
|
||||
ver=$(docker run $repo:$suite cat /etc/debian_version)
|
||||
docker tag $repo:$suite $repo $ver
|
||||
fi
|
||||
|
||||
# cleanup
|
||||
cd "$returnTo"
|
||||
sudo rm -rf "$target"
|
||||
Executable
+233
@@ -0,0 +1,233 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
variant='minbase'
|
||||
include='iproute,iputils-ping'
|
||||
arch='amd64' # intentionally undocumented for now
|
||||
skipDetection=
|
||||
strictDebootstrap=
|
||||
justTar=
|
||||
|
||||
usage() {
|
||||
echo >&2
|
||||
|
||||
echo >&2 "usage: $0 [options] repo suite [mirror]"
|
||||
|
||||
echo >&2
|
||||
echo >&2 'options: (not recommended)'
|
||||
echo >&2 " -p set an http_proxy for debootstrap"
|
||||
echo >&2 " -v $variant # change default debootstrap variant"
|
||||
echo >&2 " -i $include # change default package includes"
|
||||
echo >&2 " -d # strict debootstrap (do not apply any docker-specific tweaks)"
|
||||
echo >&2 " -s # skip version detection and tagging (ie, precise also tagged as 12.04)"
|
||||
echo >&2 " # note that this will also skip adding universe and/or security/updates to sources.list"
|
||||
echo >&2 " -t # just create a tarball, especially for dockerbrew (uses repo as tarball name)"
|
||||
|
||||
echo >&2
|
||||
echo >&2 " ie: $0 username/debian squeeze"
|
||||
echo >&2 " $0 username/debian squeeze http://ftp.uk.debian.org/debian/"
|
||||
|
||||
echo >&2
|
||||
echo >&2 " ie: $0 username/ubuntu precise"
|
||||
echo >&2 " $0 username/ubuntu precise http://mirrors.melbourne.co.uk/ubuntu/"
|
||||
|
||||
echo >&2
|
||||
echo >&2 " ie: $0 -t precise.tar.bz2 precise"
|
||||
echo >&2 " $0 -t wheezy.tgz wheezy"
|
||||
echo >&2 " $0 -t wheezy-uk.tar.xz wheezy http://ftp.uk.debian.org/debian/"
|
||||
|
||||
echo >&2
|
||||
}
|
||||
|
||||
# these should match the names found at http://www.debian.org/releases/
|
||||
debianStable=wheezy
|
||||
debianUnstable=sid
|
||||
# this should match the name found at http://releases.ubuntu.com/
|
||||
ubuntuLatestLTS=precise
|
||||
|
||||
while getopts v:i:a:p:dst name; do
|
||||
case "$name" in
|
||||
p)
|
||||
http_proxy="$OPTARG"
|
||||
;;
|
||||
v)
|
||||
variant="$OPTARG"
|
||||
;;
|
||||
i)
|
||||
include="$OPTARG"
|
||||
;;
|
||||
a)
|
||||
arch="$OPTARG"
|
||||
;;
|
||||
d)
|
||||
strictDebootstrap=1
|
||||
;;
|
||||
s)
|
||||
skipDetection=1
|
||||
;;
|
||||
t)
|
||||
justTar=1
|
||||
;;
|
||||
?)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
done
|
||||
shift $(($OPTIND - 1))
|
||||
|
||||
repo="$1"
|
||||
suite="$2"
|
||||
mirror="${3:-}" # stick to the default debootstrap mirror if one is not provided
|
||||
|
||||
if [ ! "$repo" ] || [ ! "$suite" ]; then
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# some rudimentary detection for whether we need to "sudo" our docker calls
|
||||
docker=''
|
||||
if docker version > /dev/null 2>&1; then
|
||||
docker='docker'
|
||||
elif sudo docker version > /dev/null 2>&1; then
|
||||
docker='sudo docker'
|
||||
elif command -v docker > /dev/null 2>&1; then
|
||||
docker='docker'
|
||||
else
|
||||
echo >&2 "warning: either docker isn't installed, or your current user cannot run it;"
|
||||
echo >&2 " this script is not likely to work as expected"
|
||||
sleep 3
|
||||
docker='docker' # give us a command-not-found later
|
||||
fi
|
||||
|
||||
# make sure we have an absolute path to our final tarball so we can still reference it properly after we change directory
|
||||
if [ "$justTar" ]; then
|
||||
if [ ! -d "$(dirname "$repo")" ]; then
|
||||
echo >&2 "error: $(dirname "$repo") does not exist"
|
||||
exit 1
|
||||
fi
|
||||
repo="$(cd "$(dirname "$repo")" && pwd -P)/$(basename "$repo")"
|
||||
fi
|
||||
|
||||
# will be filled in later, if [ -z "$skipDetection" ]
|
||||
lsbDist=''
|
||||
|
||||
target="/tmp/docker-rootfs-debootstrap-$suite-$$-$RANDOM"
|
||||
|
||||
cd "$(dirname "$(readlink -f "$BASH_SOURCE")")"
|
||||
returnTo="$(pwd -P)"
|
||||
|
||||
set -x
|
||||
|
||||
# bootstrap
|
||||
mkdir -p "$target"
|
||||
sudo http_proxy=$http_proxy debootstrap --verbose --variant="$variant" --include="$include" --arch="$arch" "$suite" "$target" "$mirror"
|
||||
|
||||
cd "$target"
|
||||
|
||||
if [ -z "$strictDebootstrap" ]; then
|
||||
# prevent init scripts from running during install/update
|
||||
# policy-rc.d (for most scripts)
|
||||
echo $'#!/bin/sh\nexit 101' | sudo tee usr/sbin/policy-rc.d > /dev/null
|
||||
sudo chmod +x usr/sbin/policy-rc.d
|
||||
# initctl (for some pesky upstart scripts)
|
||||
sudo chroot . dpkg-divert --local --rename --add /sbin/initctl
|
||||
sudo ln -sf /bin/true sbin/initctl
|
||||
# see https://github.com/dotcloud/docker/issues/446#issuecomment-16953173
|
||||
|
||||
# shrink the image, since apt makes us fat (wheezy: ~157.5MB vs ~120MB)
|
||||
sudo chroot . apt-get clean
|
||||
|
||||
# while we're at it, apt is unnecessarily slow inside containers
|
||||
# this forces dpkg not to call sync() after package extraction and speeds up install
|
||||
# the benefit is huge on spinning disks, and the penalty is nonexistent on SSD or decent server virtualization
|
||||
echo 'force-unsafe-io' | sudo tee etc/dpkg/dpkg.cfg.d/02apt-speedup > /dev/null
|
||||
# we want to effectively run "apt-get clean" after every install to keep images small
|
||||
echo 'DPkg::Post-Invoke {"/bin/rm -f /var/cache/apt/archives/*.deb || true";};' | sudo tee etc/apt/apt.conf.d/no-cache > /dev/null
|
||||
|
||||
# helpful undo lines for each the above tweaks (for lack of a better home to keep track of them):
|
||||
# rm /usr/sbin/policy-rc.d
|
||||
# rm /sbin/initctl; dpkg-divert --rename --remove /sbin/initctl
|
||||
# rm /etc/dpkg/dpkg.cfg.d/02apt-speedup
|
||||
# rm /etc/apt/apt.conf.d/no-cache
|
||||
|
||||
if [ -z "$skipDetection" ]; then
|
||||
# see also rudimentary platform detection in hack/install.sh
|
||||
lsbDist=''
|
||||
if [ -r etc/lsb-release ]; then
|
||||
lsbDist="$(. etc/lsb-release && echo "$DISTRIB_ID")"
|
||||
fi
|
||||
if [ -z "$lsbDist" ] && [ -r etc/debian_version ]; then
|
||||
lsbDist='Debian'
|
||||
fi
|
||||
|
||||
case "$lsbDist" in
|
||||
Debian)
|
||||
# add the updates and security repositories
|
||||
if [ "$suite" != "$debianUnstable" -a "$suite" != 'unstable' ]; then
|
||||
# ${suite}-updates only applies to non-unstable
|
||||
sudo sed -i "p; s/ $suite main$/ ${suite}-updates main/" etc/apt/sources.list
|
||||
|
||||
# same for security updates
|
||||
echo "deb http://security.debian.org/ $suite/updates main" | sudo tee -a etc/apt/sources.list > /dev/null
|
||||
fi
|
||||
;;
|
||||
Ubuntu)
|
||||
# add the universe, updates, and security repositories
|
||||
sudo sed -i "
|
||||
s/ $suite main$/ $suite main universe/; p;
|
||||
s/ $suite main/ ${suite}-updates main/; p;
|
||||
s/ $suite-updates main/ ${suite}-security main/
|
||||
" etc/apt/sources.list
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$justTar" ]; then
|
||||
# create the tarball file so it has the right permissions (ie, not root)
|
||||
touch "$repo"
|
||||
|
||||
# fill the tarball
|
||||
sudo tar --numeric-owner -caf "$repo" .
|
||||
else
|
||||
# create the image (and tag $repo:$suite)
|
||||
sudo tar --numeric-owner -c . | $docker import - $repo $suite
|
||||
|
||||
# test the image
|
||||
$docker run -i -t $repo:$suite echo success
|
||||
|
||||
if [ -z "$skipDetection" ]; then
|
||||
case "$lsbDist" in
|
||||
Debian)
|
||||
if [ "$suite" = "$debianStable" -o "$suite" = 'stable' ] && [ -r etc/debian_version ]; then
|
||||
# tag latest
|
||||
$docker tag $repo:$suite $repo latest
|
||||
|
||||
if [ -r etc/debian_version ]; then
|
||||
# tag the specific debian release version (which is only reasonable to tag on debian stable)
|
||||
ver=$(cat etc/debian_version)
|
||||
$docker tag $repo:$suite $repo $ver
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
Ubuntu)
|
||||
if [ "$suite" = "$ubuntuLatestLTS" ]; then
|
||||
# tag latest
|
||||
$docker tag $repo:$suite $repo latest
|
||||
fi
|
||||
if [ -r etc/lsb-release ]; then
|
||||
lsbRelease="$(. etc/lsb-release && echo "$DISTRIB_RELEASE")"
|
||||
if [ "$lsbRelease" ]; then
|
||||
# tag specific Ubuntu version number, if available (12.04, etc.)
|
||||
$docker tag $repo:$suite $repo $lsbRelease
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
fi
|
||||
|
||||
# cleanup
|
||||
cd "$returnTo"
|
||||
sudo rm -rf "$target"
|
||||
@@ -44,6 +44,6 @@ do
|
||||
done
|
||||
|
||||
chmod 0755 $ROOTFS # See #486
|
||||
tar -cf- . | docker import - docker-ut
|
||||
tar --numeric-owner -cf- . | docker import - docker-ut
|
||||
docker run -i -u root docker-ut /bin/echo Success.
|
||||
rm -rf $ROOTFS
|
||||
|
||||
@@ -20,3 +20,5 @@ highlight link dockerfileString String
|
||||
|
||||
syntax match dockerfileComment "\v^\s*#.*$"
|
||||
highlight link dockerfileComment Comment
|
||||
|
||||
set commentstring=#\ %s
|
||||
|
||||
+54
-26
@@ -4,9 +4,11 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker"
|
||||
"github.com/dotcloud/docker/sysinit"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
@@ -22,7 +24,7 @@ var (
|
||||
func main() {
|
||||
if selfPath := utils.SelfPath(); selfPath == "/sbin/init" || selfPath == "/.dockerinit" {
|
||||
// Running in init mode
|
||||
docker.SysInit()
|
||||
sysinit.SysInit()
|
||||
return
|
||||
}
|
||||
// FIXME: Switch d and D ? (to be more sshd like)
|
||||
@@ -35,9 +37,14 @@ func main() {
|
||||
flGraphPath := flag.String("g", "/var/lib/docker", "Path to graph storage base dir.")
|
||||
flEnableCors := flag.Bool("api-enable-cors", false, "Enable CORS requests in the remote api.")
|
||||
flDns := flag.String("dns", "", "Set custom dns servers")
|
||||
flHosts := docker.ListOpts{fmt.Sprintf("unix://%s", docker.DEFAULTUNIXSOCKET)}
|
||||
flHosts := utils.ListOpts{fmt.Sprintf("unix://%s", docker.DEFAULTUNIXSOCKET)}
|
||||
flag.Var(&flHosts, "H", "tcp://host:port to bind/connect to or unix://path/to/socket to use")
|
||||
flEnableIptables := flag.Bool("iptables", true, "Disable iptables within docker")
|
||||
flDefaultIp := flag.String("ip", "0.0.0.0", "Default ip address to use when binding a containers ports")
|
||||
flInterContainerComm := flag.Bool("icc", true, "Enable inter-container communication")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
if *flVersion {
|
||||
showVersion()
|
||||
return
|
||||
@@ -46,13 +53,17 @@ func main() {
|
||||
flHosts = flHosts[1:] //trick to display a nice default value in the usage
|
||||
}
|
||||
for i, flHost := range flHosts {
|
||||
flHosts[i] = utils.ParseHost(docker.DEFAULTHTTPHOST, docker.DEFAULTHTTPPORT, flHost)
|
||||
host, err := utils.ParseHost(docker.DEFAULTHTTPHOST, docker.DEFAULTHTTPPORT, flHost)
|
||||
if err == nil {
|
||||
flHosts[i] = host
|
||||
} else {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
bridge := docker.DefaultNetworkBridge
|
||||
if *bridgeName != "" {
|
||||
docker.NetworkBridgeIface = *bridgeName
|
||||
} else {
|
||||
docker.NetworkBridgeIface = docker.DefaultNetworkBridge
|
||||
bridge = *bridgeName
|
||||
}
|
||||
if *flDebug {
|
||||
os.Setenv("DEBUG", "1")
|
||||
@@ -64,14 +75,31 @@ func main() {
|
||||
flag.Usage()
|
||||
return
|
||||
}
|
||||
if err := daemon(*pidfile, *flGraphPath, flHosts, *flAutoRestart, *flEnableCors, *flDns); err != nil {
|
||||
var dns []string
|
||||
if *flDns != "" {
|
||||
dns = []string{*flDns}
|
||||
}
|
||||
|
||||
ip := net.ParseIP(*flDefaultIp)
|
||||
|
||||
config := &docker.DaemonConfig{
|
||||
Pidfile: *pidfile,
|
||||
GraphPath: *flGraphPath,
|
||||
AutoRestart: *flAutoRestart,
|
||||
EnableCors: *flEnableCors,
|
||||
Dns: dns,
|
||||
EnableIptables: *flEnableIptables,
|
||||
BridgeIface: bridge,
|
||||
ProtoAddresses: flHosts,
|
||||
DefaultIp: ip,
|
||||
InterContainerCommunication: *flInterContainerComm,
|
||||
}
|
||||
if err := daemon(config); err != nil {
|
||||
log.Fatal(err)
|
||||
os.Exit(-1)
|
||||
}
|
||||
} else {
|
||||
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 {
|
||||
@@ -79,7 +107,6 @@ func main() {
|
||||
os.Exit(sterr.Status)
|
||||
}
|
||||
log.Fatal(err)
|
||||
os.Exit(-1)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -115,30 +142,30 @@ func removePidFile(pidfile string) {
|
||||
}
|
||||
}
|
||||
|
||||
func daemon(pidfile string, flGraphPath string, protoAddrs []string, autoRestart, enableCors bool, flDns string) error {
|
||||
if err := createPidFile(pidfile); err != nil {
|
||||
func daemon(config *docker.DaemonConfig) error {
|
||||
if err := createPidFile(config.Pidfile); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer removePidFile(pidfile)
|
||||
defer removePidFile(config.Pidfile)
|
||||
|
||||
server, err := docker.NewServer(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer server.Close()
|
||||
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, os.Interrupt, os.Kill, os.Signal(syscall.SIGTERM))
|
||||
go func() {
|
||||
sig := <-c
|
||||
log.Printf("Received signal '%v', exiting\n", sig)
|
||||
removePidFile(pidfile)
|
||||
server.Close()
|
||||
removePidFile(config.Pidfile)
|
||||
os.Exit(0)
|
||||
}()
|
||||
var dns []string
|
||||
if flDns != "" {
|
||||
dns = []string{flDns}
|
||||
}
|
||||
server, err := docker.NewServer(flGraphPath, autoRestart, enableCors, dns)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
chErrors := make(chan error, len(protoAddrs))
|
||||
for _, protoAddr := range protoAddrs {
|
||||
|
||||
chErrors := make(chan error, len(config.ProtoAddresses))
|
||||
for _, protoAddr := range config.ProtoAddresses {
|
||||
protoAddrParts := strings.SplitN(protoAddr, "://", 2)
|
||||
if protoAddrParts[0] == "unix" {
|
||||
syscall.Unlink(protoAddrParts[1])
|
||||
@@ -147,14 +174,15 @@ func daemon(pidfile string, flGraphPath string, protoAddrs []string, autoRestart
|
||||
log.Println("/!\\ DON'T BIND ON ANOTHER IP ADDRESS THAN 127.0.0.1 IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\")
|
||||
}
|
||||
} else {
|
||||
server.Close()
|
||||
removePidFile(config.Pidfile)
|
||||
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 {
|
||||
for i := 0; i < len(config.ProtoAddresses); i += 1 {
|
||||
err := <-chErrors
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/dotcloud/docker/sysinit"
|
||||
)
|
||||
|
||||
var (
|
||||
GITCOMMIT string
|
||||
VERSION string
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Running in init mode
|
||||
sysinit.SysInit()
|
||||
return
|
||||
}
|
||||
+6
-1
@@ -1,11 +1,16 @@
|
||||
from ubuntu:12.04
|
||||
maintainer Nick Stinemates
|
||||
#
|
||||
# docker build -t docker:docs . && docker run -p 8000:8000 docker:docs
|
||||
#
|
||||
|
||||
run apt-get update
|
||||
run apt-get install -y python-setuptools make
|
||||
run easy_install pip
|
||||
#from docs/requirements.txt, but here to increase cacheability
|
||||
run pip install Sphinx==1.1.3
|
||||
run pip install sphinxcontrib-httpdomain==1.1.8
|
||||
add . /docs
|
||||
run pip install -r /docs/requirements.txt
|
||||
run cd /docs; make docs
|
||||
|
||||
expose 8000
|
||||
|
||||
+5
-4
@@ -39,8 +39,7 @@ Getting Started
|
||||
To edit and test the docs, you'll need to install the Sphinx tool and
|
||||
its dependencies. There are two main ways to install this tool:
|
||||
|
||||
Native Installation
|
||||
...................
|
||||
###Native Installation
|
||||
|
||||
* Install sphinx: `pip install sphinx`
|
||||
* Mac OS X: `[sudo] pip-2.7 install sphinx`
|
||||
@@ -48,8 +47,7 @@ Native Installation
|
||||
* Mac OS X: `[sudo] pip-2.7 install sphinxcontrib-httpdomain`
|
||||
* If pip is not available you can probably install it using your favorite package manager as **python-pip**
|
||||
|
||||
Alternative Installation: Docker Container
|
||||
..........................................
|
||||
###Alternative Installation: Docker Container
|
||||
|
||||
If you're running ``docker`` on your development machine then you may
|
||||
find it easier and cleaner to use the Dockerfile. This installs Sphinx
|
||||
@@ -59,6 +57,9 @@ docs inside the container, even starting a simple HTTP server on port
|
||||
build .`` and run the resulting image. This is the equivalent to
|
||||
``make clean server`` since each container starts clean.
|
||||
|
||||
In the ``docs/`` directory, run:
|
||||
```docker build -t docker:docs . && docker run -p 8000:8000 docker:docs```
|
||||
|
||||
Usage
|
||||
-----
|
||||
* Follow the contribution guidelines (``../CONTRIBUTING.md``)
|
||||
|
||||
@@ -970,28 +970,33 @@ Create a new image from a container's changes
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
|
||||
POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"Cmd": ["cat", "/world"],
|
||||
"PortSpecs":["22"]
|
||||
}
|
||||
|
||||
**Example response**:
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 201 OK
|
||||
Content-Type: application/vnd.docker.raw-stream
|
||||
HTTP/1.1 201 OK
|
||||
Content-Type: application/vnd.docker.raw-stream
|
||||
|
||||
{"Id":"596069db4bf5"}
|
||||
{"Id":"596069db4bf5"}
|
||||
|
||||
:query container: source container
|
||||
:query repo: repository
|
||||
:query tag: tag
|
||||
:query m: commit message
|
||||
:query author: author (eg. "John Hannibal Smith <hannibal@a-team.com>")
|
||||
:query run: config automatically applied when the image is run. (ex: {"Cmd": ["cat", "/world"], "PortSpecs":["22"]})
|
||||
:statuscode 201: no error
|
||||
:statuscode 404: no such container
|
||||
:statuscode 500: server error
|
||||
:query container: source container
|
||||
:query repo: repository
|
||||
:query tag: tag
|
||||
:query m: commit message
|
||||
:query author: author (eg. "John Hannibal Smith <hannibal@a-team.com>")
|
||||
:statuscode 201: no error
|
||||
:statuscode 404: no such container
|
||||
:statuscode 500: server error
|
||||
|
||||
|
||||
3. Going further
|
||||
|
||||
@@ -977,32 +977,37 @@ Create a new image from a container's changes
|
||||
|
||||
.. http:post:: /commit
|
||||
|
||||
Create a new image from a container's changes
|
||||
Create a new image from a container's changes
|
||||
|
||||
**Example request**:
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"Cmd": ["cat", "/world"],
|
||||
"PortSpecs":["22"]
|
||||
}
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 201 OK
|
||||
Content-Type: application/vnd.docker.raw-stream
|
||||
|
||||
.. sourcecode:: http
|
||||
{"Id":"596069db4bf5"}
|
||||
|
||||
POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 201 OK
|
||||
Content-Type: application/vnd.docker.raw-stream
|
||||
|
||||
{"Id":"596069db4bf5"}
|
||||
|
||||
:query container: source container
|
||||
:query repo: repository
|
||||
:query tag: tag
|
||||
:query m: commit message
|
||||
:query author: author (eg. "John Hannibal Smith <hannibal@a-team.com>")
|
||||
:query run: config automatically applied when the image is run. (ex: {"Cmd": ["cat", "/world"], "PortSpecs":["22"]})
|
||||
:statuscode 201: no error
|
||||
:statuscode 404: no such container
|
||||
:statuscode 500: server error
|
||||
:query container: source container
|
||||
:query repo: repository
|
||||
:query tag: tag
|
||||
:query m: commit message
|
||||
:query author: author (eg. "John Hannibal Smith <hannibal@a-team.com>")
|
||||
:statuscode 201: no error
|
||||
:statuscode 404: no such container
|
||||
:statuscode 500: server error
|
||||
|
||||
|
||||
3. Going further
|
||||
|
||||
@@ -985,32 +985,37 @@ Create a new image from a container's changes
|
||||
|
||||
.. http:post:: /commit
|
||||
|
||||
Create a new image from a container's changes
|
||||
Create a new image from a container's changes
|
||||
|
||||
**Example request**:
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
|
||||
POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"Cmd": ["cat", "/world"],
|
||||
"PortSpecs":["22"]
|
||||
}
|
||||
|
||||
**Example response**:
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 201 OK
|
||||
HTTP/1.1 201 OK
|
||||
Content-Type: application/vnd.docker.raw-stream
|
||||
|
||||
{"Id":"596069db4bf5"}
|
||||
{"Id":"596069db4bf5"}
|
||||
|
||||
:query container: source container
|
||||
:query repo: repository
|
||||
:query tag: tag
|
||||
:query m: commit message
|
||||
:query author: author (eg. "John Hannibal Smith <hannibal@a-team.com>")
|
||||
:query run: config automatically applied when the image is run. (ex: {"Cmd": ["cat", "/world"], "PortSpecs":["22"]})
|
||||
:statuscode 201: no error
|
||||
:statuscode 404: no such container
|
||||
:statuscode 500: server error
|
||||
:query container: source container
|
||||
:query repo: repository
|
||||
:query tag: tag
|
||||
:query m: commit message
|
||||
:query author: author (eg. "John Hannibal Smith <hannibal@a-team.com>")
|
||||
:statuscode 201: no error
|
||||
:statuscode 404: no such container
|
||||
:statuscode 500: server error
|
||||
|
||||
|
||||
3. Going further
|
||||
|
||||
@@ -1034,32 +1034,37 @@ Create a new image from a container's changes
|
||||
|
||||
.. http:post:: /commit
|
||||
|
||||
Create a new image from a container's changes
|
||||
Create a new image from a container's changes
|
||||
|
||||
**Example request**:
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
|
||||
POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"Cmd": ["cat", "/world"],
|
||||
"PortSpecs":["22"]
|
||||
}
|
||||
|
||||
**Example response**:
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 201 OK
|
||||
HTTP/1.1 201 OK
|
||||
Content-Type: application/vnd.docker.raw-stream
|
||||
|
||||
{"Id":"596069db4bf5"}
|
||||
{"Id":"596069db4bf5"}
|
||||
|
||||
:query container: source container
|
||||
:query repo: repository
|
||||
:query tag: tag
|
||||
:query m: commit message
|
||||
:query author: author (eg. "John Hannibal Smith <hannibal@a-team.com>")
|
||||
:query run: config automatically applied when the image is run. (ex: {"Cmd": ["cat", "/world"], "PortSpecs":["22"]})
|
||||
:statuscode 201: no error
|
||||
:statuscode 404: no such container
|
||||
:statuscode 500: server error
|
||||
:query container: source container
|
||||
:query repo: repository
|
||||
:query tag: tag
|
||||
:query m: commit message
|
||||
:query author: author (eg. "John Hannibal Smith <hannibal@a-team.com>")
|
||||
:statuscode 201: no error
|
||||
:statuscode 404: no such container
|
||||
:statuscode 500: server error
|
||||
|
||||
|
||||
Monitor Docker's events
|
||||
|
||||
@@ -1084,23 +1084,28 @@ Create a new image from a container's changes
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
|
||||
POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"Cmd": ["cat", "/world"],
|
||||
"PortSpecs":["22"]
|
||||
}
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 201 OK
|
||||
Content-Type: application/vnd.docker.raw-stream
|
||||
HTTP/1.1 201 OK
|
||||
Content-Type: application/vnd.docker.raw-stream
|
||||
|
||||
{"Id":"596069db4bf5"}
|
||||
{"Id":"596069db4bf5"}
|
||||
|
||||
:query container: source container
|
||||
:query repo: repository
|
||||
:query tag: tag
|
||||
:query m: commit message
|
||||
:query author: author (eg. "John Hannibal Smith <hannibal@a-team.com>")
|
||||
:query run: config automatically applied when the image is run. (ex: {"Cmd": ["cat", "/world"], "PortSpecs":["22"]})
|
||||
:statuscode 201: no error
|
||||
:statuscode 404: no such container
|
||||
:statuscode 500: server error
|
||||
|
||||
@@ -1050,32 +1050,37 @@ Create a new image from a container's changes
|
||||
|
||||
.. http:post:: /commit
|
||||
|
||||
Create a new image from a container's changes
|
||||
Create a new image from a container's changes
|
||||
|
||||
**Example request**:
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
|
||||
POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"Cmd": ["cat", "/world"],
|
||||
"PortSpecs":["22"]
|
||||
}
|
||||
|
||||
**Example response**:
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 201 OK
|
||||
Content-Type: application/vnd.docker.raw-stream
|
||||
HTTP/1.1 201 OK
|
||||
Content-Type: application/vnd.docker.raw-stream
|
||||
|
||||
{"Id":"596069db4bf5"}
|
||||
{"Id":"596069db4bf5"}
|
||||
|
||||
:query container: source container
|
||||
:query repo: repository
|
||||
:query tag: tag
|
||||
:query m: commit message
|
||||
:query author: author (eg. "John Hannibal Smith <hannibal@a-team.com>")
|
||||
:query run: config automatically applied when the image is run. (ex: {"Cmd": ["cat", "/world"], "PortSpecs":["22"]})
|
||||
:statuscode 201: no error
|
||||
:statuscode 404: no such container
|
||||
:statuscode 500: server error
|
||||
:query container: source container
|
||||
:query repo: repository
|
||||
:query tag: tag
|
||||
:query m: commit message
|
||||
:query author: author (eg. "John Hannibal Smith <hannibal@a-team.com>")
|
||||
:statuscode 201: no error
|
||||
:statuscode 404: no such container
|
||||
:statuscode 500: server error
|
||||
|
||||
Monitor Docker's events
|
||||
***********************
|
||||
|
||||
@@ -13,9 +13,12 @@ Docker Remote API v1.6
|
||||
1. Brief introduction
|
||||
=====================
|
||||
|
||||
- The Remote API is replacing rcli
|
||||
- Default port in the docker daemon is 4243
|
||||
- The API tends to be REST, but for some complex commands, like attach or pull, the HTTP connection is hijacked to transport stdout stdin and stderr
|
||||
- The Remote API has replaced rcli
|
||||
- The daemon listens on ``unix:///var/run/docker.sock``, but you can
|
||||
:ref:`bind_docker`.
|
||||
- The API tends to be REST, but for some complex commands, like
|
||||
``attach`` or ``pull``, the HTTP connection is hijacked to transport
|
||||
``stdout, stdin`` and ``stderr``
|
||||
|
||||
2. Endpoints
|
||||
============
|
||||
@@ -148,6 +151,7 @@ Create a container
|
||||
}
|
||||
|
||||
:jsonparam config: the container's configuration
|
||||
:query name: container name to use
|
||||
:statuscode 201: no error
|
||||
:statuscode 404: no such container
|
||||
:statuscode 406: impossible to attach (container not running)
|
||||
@@ -442,7 +446,8 @@ Kill a container
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 204 OK
|
||||
|
||||
|
||||
:query signal: Signal to send to the container (integer). When not set, SIGKILL is assumed and the call will waits for the container to exit.
|
||||
:statuscode 204: no error
|
||||
:statuscode 404: no such container
|
||||
:statuscode 500: server error
|
||||
@@ -1131,7 +1136,13 @@ Create a new image from a container's changes
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
|
||||
POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"Cmd": ["cat", "/world"],
|
||||
"PortSpecs":["22"]
|
||||
}
|
||||
|
||||
**Example response**:
|
||||
|
||||
@@ -1147,7 +1158,6 @@ Create a new image from a container's changes
|
||||
:query tag: tag
|
||||
:query m: commit message
|
||||
:query author: author (eg. "John Hannibal Smith <hannibal@a-team.com>")
|
||||
:query run: config automatically applied when the image is run. (ex: {"Cmd": ["cat", "/world"], "PortSpecs":["22"]})
|
||||
:statuscode 201: no error
|
||||
:statuscode 404: no such container
|
||||
:statuscode 500: server error
|
||||
|
||||
@@ -29,6 +29,9 @@ To list available commands, either run ``docker`` with no parameters or execute
|
||||
|
||||
Attach to a running container.
|
||||
|
||||
-nostdin=false: Do not attach stdin
|
||||
-sig-proxy=true: Proxify all received signal to the process (even in non-tty mode)
|
||||
|
||||
You can detach from the container again (and leave it running) with
|
||||
``CTRL-c`` (for a quiet exit) or ``CTRL-\`` to get a stacktrace of
|
||||
the Docker client when it quits.
|
||||
@@ -93,8 +96,8 @@ Examples:
|
||||
|
||||
.. _cli_build_examples:
|
||||
|
||||
Examples
|
||||
~~~~~~~~
|
||||
Examples:
|
||||
~~~~~~~~~
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
@@ -396,7 +399,7 @@ Insert file from github
|
||||
|
||||
::
|
||||
|
||||
Usage: docker kill [OPTIONS] CONTAINER [CONTAINER...]
|
||||
Usage: docker kill CONTAINER [CONTAINER...]
|
||||
|
||||
Kill a running container
|
||||
|
||||
@@ -427,7 +430,6 @@ Insert file from github
|
||||
``logs``
|
||||
--------
|
||||
|
||||
|
||||
::
|
||||
|
||||
Usage: docker logs [OPTIONS] CONTAINER
|
||||
@@ -507,6 +509,29 @@ Insert file from github
|
||||
Usage: docker rm [OPTIONS] CONTAINER
|
||||
|
||||
Remove one or more containers
|
||||
-link="": Remove the link instead of the actual container
|
||||
|
||||
|
||||
Examples:
|
||||
~~~~~~~~~
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ docker rm /redis
|
||||
/redis
|
||||
|
||||
|
||||
This will remove the container referenced under the link ``/redis``.
|
||||
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ docker rm -link /webapp/redis
|
||||
/webapp/redis
|
||||
|
||||
|
||||
This will remove the underlying link between ``/webapp`` and the ``/redis`` containers removing all
|
||||
network communication.
|
||||
|
||||
.. _cli_rmi:
|
||||
|
||||
@@ -530,7 +555,7 @@ Insert file from github
|
||||
|
||||
Run a command in a new container
|
||||
|
||||
-a=map[]: Attach to stdin, stdout or stderr.
|
||||
-a=map[]: Attach to stdin, stdout or stderr
|
||||
-c=0: CPU shares (relative weight)
|
||||
-cidfile="": Write the container ID to the file
|
||||
-d=false: Detached mode: Run container in the background, print new container id
|
||||
@@ -546,13 +571,17 @@ Insert file from github
|
||||
-u="": Username or UID
|
||||
-dns=[]: Set custom dns servers for the container
|
||||
-v=[]: Create a bind mount with: [host-dir]:[container-dir]:[rw|ro]. If "container-dir" is missing, then docker creates a new volume.
|
||||
-volumes-from="": Mount all volumes from the given container.
|
||||
-entrypoint="": Overwrite the default entrypoint set by the image.
|
||||
-volumes-from="": Mount all volumes from the given container
|
||||
-entrypoint="": Overwrite the default entrypoint set by the image
|
||||
-w="": Working directory inside the container
|
||||
-lxc-conf=[]: Add custom lxc options -lxc-conf="lxc.cgroup.cpuset.cpus = 0,1"
|
||||
-sig-proxy=true: Proxify all received signal to the process (even in non-tty mode)
|
||||
-expose=[]: Expose a port from the container without publishing it to your host
|
||||
-link="": Add link to another container (name:alias)
|
||||
-name="": Assign the specified name to the container. If no name is specific docker will generate a random name
|
||||
|
||||
Examples
|
||||
~~~~~~~~
|
||||
--------
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
@@ -600,6 +629,46 @@ working directory, by changing into the directory to the value
|
||||
returned by ``pwd``. So this combination executes the command
|
||||
using the container, but inside the current working directory.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker run -p 127.0.0.0::80 ubuntu bash
|
||||
|
||||
This the ``-p`` flag now allows you to bind a port to a specific
|
||||
interface of the host machine. In this example port ``80`` of the
|
||||
container will have a dynamically allocated port bound to 127.0.0.1
|
||||
of the host.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker run -p 127.0.0.1:80:80 ubuntu bash
|
||||
|
||||
This will bind port ``80`` of the container to port ``80`` on 127.0.0.1 of your
|
||||
host machine.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker run -expose 80 ubuntu bash
|
||||
|
||||
This will expose port ``80`` of the container for use within a link
|
||||
without publishing the port to the host system's interfaces.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker run -name console -t -i ubuntu bash
|
||||
|
||||
This will create and run a new container with the container name
|
||||
being ``console``.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker run -link /redis:redis -name console ubuntu bash
|
||||
|
||||
The ``-link`` flag will link the container named ``/redis`` into the
|
||||
newly created container with the alias ``redis``. The new container
|
||||
can access the network and environment of the redis container via
|
||||
environment variables. The ``-name`` flag will assign the name ``console``
|
||||
to the newly created container.
|
||||
|
||||
.. _cli_search:
|
||||
|
||||
``search``
|
||||
@@ -623,6 +692,9 @@ using the container, but inside the current working directory.
|
||||
|
||||
Start a stopped container
|
||||
|
||||
-a=false: Attach container's stdout/stderr and forward all signals to the process
|
||||
-i=false: Attach container's stdin
|
||||
|
||||
.. _cli_stop:
|
||||
|
||||
``stop``
|
||||
|
||||
@@ -5,5 +5,18 @@
|
||||
Contributing to Docker
|
||||
======================
|
||||
|
||||
Want to hack on Docker? Awesome! The repository includes `all the instructions you need to get started <https://github.com/dotcloud/docker/blob/master/CONTRIBUTING.md>`_.
|
||||
Want to hack on Docker? Awesome!
|
||||
|
||||
The repository includes `all the instructions you need to get
|
||||
started <https://github.com/dotcloud/docker/blob/master/CONTRIBUTING.md>`_.
|
||||
|
||||
The developer environment `Dockerfile <https://github.com/dotcloud/docker/blob/master/Dockerfile>`_
|
||||
specifies the tools and versions used to test and build Docker.
|
||||
|
||||
If you're making changes to the documentation, see the
|
||||
`README.md <https://github.com/dotcloud/docker/blob/master/docs/README.md>`_.
|
||||
|
||||
The documentation environment `Dockerfile <https://github.com/dotcloud/docker/blob/master/docs/Dockerfile>`_
|
||||
specifies the tools and versions used to build the Documentation.
|
||||
|
||||
Further interesting details can be found in the `Packaging hints <https://github.com/dotcloud/docker/blob/master/hack/PACKAGERS.md>`_.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
:title: Docker Examples
|
||||
:description: Examples on how to use Docker
|
||||
:keywords: docker, hello world, node, nodejs, python, couch, couchdb, redis, ssh, sshd, examples, postgresql
|
||||
:keywords: docker, hello world, node, nodejs, python, couch, couchdb, redis, ssh, sshd, examples, postgresql, link
|
||||
|
||||
|
||||
.. _example_list:
|
||||
@@ -24,3 +24,4 @@ to more substantial services like you might find in production.
|
||||
postgresql_service
|
||||
mongodb
|
||||
running_riak_service
|
||||
linking_into_redis
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
:title: Linking to an Redis container
|
||||
:description: Running redis linked into your web app
|
||||
:keywords: docker, example, networking, redis, link
|
||||
|
||||
.. _linking_redis:
|
||||
|
||||
Linking Redis
|
||||
=============
|
||||
|
||||
.. include:: example_header.inc
|
||||
|
||||
Building a redis container to link as a child of our web application.
|
||||
|
||||
Building the redis container
|
||||
----------------------------
|
||||
|
||||
Lets build a redis image with the following Dockerfile.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
git clone https://github.com/antirez/redis.git
|
||||
cd redis
|
||||
git checkout 2.6
|
||||
|
||||
# Save this Dockerfile to the root of the redis repository.
|
||||
|
||||
# Build redis from source
|
||||
# Make sure you have the redis source code checked out in
|
||||
# the same directory as this Dockerfile
|
||||
FROM ubuntu
|
||||
|
||||
RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list
|
||||
RUN apt-get update
|
||||
RUN apt-get upgrade -y
|
||||
|
||||
RUN apt-get install -y gcc make g++ build-essential libc6-dev tcl
|
||||
|
||||
ADD . /redis
|
||||
|
||||
RUN (cd /redis && make)
|
||||
|
||||
RUN mkdir -p /redis-data
|
||||
VOLUME ["/redis-data"]
|
||||
EXPOSE 6379
|
||||
|
||||
ENTRYPOINT ["/redis/src/redis-server"]
|
||||
CMD ["--dir", "/redis-data"]
|
||||
|
||||
# docker build our new redis image from source
|
||||
docker build -t redis-2.6 .
|
||||
|
||||
|
||||
We need to ``EXPOSE`` the default port of 6379 so that our link knows what ports
|
||||
to connect to our redis container on. If you do not expose any ports for the
|
||||
image then docker will not be able to establish the link between containers.
|
||||
|
||||
|
||||
Run the redis container
|
||||
-----------------------
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker run -d -e PASSWORD=docker -name redis redis-2.6 --requirepass docker
|
||||
|
||||
This will run our redis container wit the password docker
|
||||
to secure our service. By specifying the ``-name`` flag on run
|
||||
we will assign the name ``redis`` to this container. If we do not specify a name for
|
||||
our container via the ``-name`` flag docker will automatically generate a name for us.
|
||||
We can issue all the commands that you would expect; start, stop, attach, using the name for our container.
|
||||
The name also allows us to link other containers into this one.
|
||||
|
||||
Linking redis as a child
|
||||
------------------------
|
||||
|
||||
Next we can start a new web application that has a dependency on redis and apply a link
|
||||
to connect both containers. If you noticed when running our redis server we did not use
|
||||
the ``-p`` flag to publish the redis port to the host system. Redis exposed port 6379 via the Dockerfile
|
||||
and this is all we need to establish a link.
|
||||
|
||||
Now lets start our web application with a link into redis.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker run -t -i -link redis:db -name webapp ubuntu bash
|
||||
|
||||
root@4c01db0b339c:/# env
|
||||
|
||||
HOSTNAME=4c01db0b339c
|
||||
DB_NAME=/webapp/db
|
||||
TERM=xterm
|
||||
DB_PORT=tcp://172.17.0.8:6379
|
||||
DB_PORT_6379_TCP=tcp://172.17.0.8:6379
|
||||
DB_PORT_6379_TCP_PROTO=tcp
|
||||
DB_PORT_6379_TCP_ADDR=172.17.0.8
|
||||
DB_PORT_6379_TCP_PORT=6379
|
||||
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||
PWD=/
|
||||
DB_ENV_PASSWORD=dockerpass
|
||||
SHLVL=1
|
||||
HOME=/
|
||||
container=lxc
|
||||
_=/usr/bin/env
|
||||
root@4c01db0b339c:/#
|
||||
|
||||
|
||||
When we inspect the environment of the linked container we can see a few extra environment
|
||||
variables have been added. When you specified ``-link redis:db`` you are telling docker
|
||||
to link the container named ``redis`` into this new container with the alias ``db``.
|
||||
Environment variables are prefixed with the alias so that the parent container can access
|
||||
network and environment information from the containers that are linked into it.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# The name of the child container
|
||||
DB_NAME=/webapp/db
|
||||
|
||||
# The default protocol, ip, and port of the service running in the container
|
||||
DB_PORT=tcp://172.17.0.8:6379
|
||||
|
||||
# A specific protocol, ip, and port of various services
|
||||
DB_PORT_6379_TCP=tcp://172.17.0.8:6379
|
||||
DB_PORT_6379_TCP_PROTO=tcp
|
||||
DB_PORT_6379_TCP_ADDR=172.17.0.8
|
||||
DB_PORT_6379_TCP_PORT=6379
|
||||
|
||||
# Get environment variables of the container
|
||||
DB_ENV_PASSWORD=dockerpass
|
||||
|
||||
|
||||
Accessing the network information along with the environment of the child container allows
|
||||
us to easily connect to the redis service on the specific ip and port and use the password
|
||||
specified in the environment.
|
||||
@@ -69,7 +69,7 @@ to run LXC containers. Note that 2.6.32 has some documented issues regarding
|
||||
network namespace setup and teardown; those issues are not a risk if you
|
||||
run containers in a private environment, but can lead to denial-of-service
|
||||
attacks if you want to run untrusted code in your containers. For more details,
|
||||
see `[LP#720095 <https://bugs.launchpad.net/ubuntu/+source/linux/+bug/720095>`_.
|
||||
see `LP#720095 <https://bugs.launchpad.net/ubuntu/+source/linux/+bug/720095>`_.
|
||||
|
||||
Kernels 2.6.38, and every version since 3.2, have been deployed successfully
|
||||
to run containerized production workloads. Feature-wise, there is no huge
|
||||
|
||||
@@ -78,7 +78,8 @@ Docker is available as a Debian package, which makes installation easy.
|
||||
sudo sh -c "wget -qO- https://get.docker.io/gpg | apt-key add -"
|
||||
|
||||
# Add the Docker repository to your apt sources list.
|
||||
sudo sh -c "echo deb http://get.docker.io/ubuntu docker main > /etc/apt/sources.list.d/docker.list"
|
||||
sudo sh -c "echo deb http://get.docker.io/ubuntu docker main\
|
||||
> /etc/apt/sources.list.d/docker.list"
|
||||
|
||||
# Update your sources
|
||||
sudo apt-get update
|
||||
@@ -132,7 +133,8 @@ to follow them again.*
|
||||
sudo sh -c "wget -qO- https://get.docker.io/gpg | apt-key add -"
|
||||
|
||||
# Add the Docker repository to your apt sources list.
|
||||
sudo sh -c "echo deb http://get.docker.io/ubuntu docker main > /etc/apt/sources.list.d/docker.list"
|
||||
sudo sh -c "echo deb http://get.docker.io/ubuntu docker main\
|
||||
> /etc/apt/sources.list.d/docker.list"
|
||||
|
||||
# update
|
||||
sudo apt-get update
|
||||
|
||||
@@ -40,4 +40,4 @@ Docker Github Repo:
|
||||
* `CentOS
|
||||
<https://github.com/dotcloud/docker/blob/master/contrib/mkimage-centos.sh>`_
|
||||
* `Debian/Ubuntu
|
||||
<https://github.com/dotcloud/docker/blob/master/contrib/mkimage-debian.sh>`_
|
||||
<https://github.com/dotcloud/docker/blob/master/contrib/mkimage-debootstrap.sh>`_
|
||||
|
||||
@@ -67,6 +67,8 @@ you don't need to add ``sudo`` to all the client commands.
|
||||
# Restart the docker daemon
|
||||
sudo service docker restart
|
||||
|
||||
.. _bind_docker:
|
||||
|
||||
Bind Docker to another host/port or a Unix socket
|
||||
-------------------------------------------------
|
||||
|
||||
@@ -142,7 +144,7 @@ Expose a service on a TCP port
|
||||
.. code-block:: bash
|
||||
|
||||
# Expose port 4444 of this container, and tell netcat to listen on it
|
||||
JOB=$(sudo docker run -d -p 4444 ubuntu:12.10 /bin/nc -l -p 4444)
|
||||
JOB=$(sudo docker run -d -p 4444 ubuntu:12.10 /bin/nc -l 4444)
|
||||
|
||||
# Which public port is NATed to my container?
|
||||
PORT=$(sudo docker port $JOB 4444)
|
||||
|
||||
@@ -220,7 +220,7 @@ The copy obeys the following rules:
|
||||
(``http://example.com`` will not work).
|
||||
* If ``<src>`` is a directory, the entire directory is copied,
|
||||
including filesystem metadata.
|
||||
* If ``<src>``` is a tar archive in a recognized compression format
|
||||
* If ``<src>`` is a tar archive in a recognized compression format
|
||||
(identity, gzip, bzip2 or xz), it is unpacked as a directory.
|
||||
|
||||
When a directory is copied or unpacked, it has the same behavior as
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
:title: Host Integration
|
||||
:description: How to generate scripts for upstart, systemd, etc.
|
||||
:keywords: systemd, upstart, supervisor, docker, documentation, host integration
|
||||
|
||||
|
||||
|
||||
Host Integration
|
||||
================
|
||||
|
||||
You can use your Docker containers with process managers like ``upstart``,
|
||||
``systemd`` and ``supervisor``.
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
If you want a process manager to manage your containers you will need to run
|
||||
the docker daemon with the ``-r=false`` so that docker will not automatically
|
||||
restart your containers when the host is restarted.
|
||||
|
||||
When you have finished setting up your image and are happy with your
|
||||
running container, you may want to use a process manager to manage
|
||||
it. When your run ``docker start -a`` docker will automatically attach
|
||||
to the process and forward all signals so that the process manager can
|
||||
detect when a container stops and correctly restart it.
|
||||
|
||||
Here are a few sample scripts for systemd and upstart to integrate with docker.
|
||||
|
||||
|
||||
Sample Upstart Script
|
||||
---------------------
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
description "Redis container"
|
||||
author "Me"
|
||||
start on filesystem and started lxc-net and started docker
|
||||
stop on runlevel [!2345]
|
||||
respawn
|
||||
exec docker start -a 0a7e070b698b
|
||||
|
||||
|
||||
Sample systemd Script
|
||||
---------------------
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
[Unit]
|
||||
Description=Redis container
|
||||
Author=Me
|
||||
After=docker.service
|
||||
|
||||
[Service]
|
||||
Restart=always
|
||||
ExecStart=/usr/bin/docker start -a 0a7e070b698b
|
||||
ExecStop=/usr/bin/docker stop -t 2 0a7e070b698b
|
||||
|
||||
[Install]
|
||||
WantedBy=local.target
|
||||
|
||||
@@ -18,3 +18,4 @@ Contents:
|
||||
baseimages
|
||||
port_redirection
|
||||
puppet
|
||||
host_integration
|
||||
|
||||
Vendored
+7
-1
@@ -13,6 +13,12 @@
|
||||
{%- set url_root = pathto('', 1) %}
|
||||
{%- if url_root == '#' %}{% set url_root = '' %}{% endif %}
|
||||
|
||||
{%- if current_version == 'latest' %}
|
||||
{% set github_tag = 'master' %}
|
||||
{% else %}
|
||||
{% set github_tag = current_version %}
|
||||
{% endif %}
|
||||
|
||||
<script type="text/javascript">
|
||||
// This is included here for Javascript that doesn't have access to the templates.
|
||||
var doc_version = "{{ current_version }}";
|
||||
@@ -107,7 +113,7 @@
|
||||
{% block body %}{% endblock %}
|
||||
</section>
|
||||
|
||||
<div class="pull-right"><a href="https://github.com/dotcloud/docker/blob/{{ current_version }}/docs/sources/{{ pagename }}.rst" title="edit this article">Edit this article on GitHub</a></div>
|
||||
<div class="pull-right"><a href="https://github.com/dotcloud/docker/blob/{{ github_tag }}/docs/sources/{{ pagename }}.rst" title="edit this article">Edit this article on GitHub</a></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Michael Crosby <michael@crosbymichael.com> (@crosbymichael)
|
||||
@@ -0,0 +1,423 @@
|
||||
package gograph
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"path"
|
||||
)
|
||||
|
||||
const (
|
||||
createEntityTable = `
|
||||
CREATE TABLE IF NOT EXISTS entity (
|
||||
id text NOT NULL PRIMARY KEY
|
||||
);`
|
||||
|
||||
createEdgeTable = `
|
||||
CREATE TABLE IF NOT EXISTS edge (
|
||||
"entity_id" text NOT NULL,
|
||||
"parent_id" text NULL,
|
||||
"name" text NOT NULL,
|
||||
CONSTRAINT "parent_fk" FOREIGN KEY ("parent_id") REFERENCES "entity" ("id"),
|
||||
CONSTRAINT "entity_fk" FOREIGN KEY ("entity_id") REFERENCES "entity" ("id")
|
||||
);
|
||||
`
|
||||
|
||||
createEdgeIndices = `
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "name_parent_ix" ON "edge" (parent_id, name);
|
||||
`
|
||||
)
|
||||
|
||||
// Entity with a unique id
|
||||
type Entity struct {
|
||||
id string
|
||||
}
|
||||
|
||||
// An Edge connects two entities together
|
||||
type Edge struct {
|
||||
EntityID string
|
||||
Name string
|
||||
ParentID string
|
||||
}
|
||||
|
||||
type Entities map[string]*Entity
|
||||
type Edges []*Edge
|
||||
|
||||
type WalkFunc func(fullPath string, entity *Entity) error
|
||||
|
||||
// Graph database for storing entities and their relationships
|
||||
type Database struct {
|
||||
conn *sql.DB
|
||||
}
|
||||
|
||||
// Create a new graph database initialized with a root entity
|
||||
func NewDatabase(conn *sql.DB, init bool) (*Database, error) {
|
||||
if conn == nil {
|
||||
return nil, fmt.Errorf("Database connection cannot be nil")
|
||||
}
|
||||
db := &Database{conn}
|
||||
|
||||
if init {
|
||||
if _, err := conn.Exec(createEntityTable); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := conn.Exec(createEdgeTable); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := conn.Exec(createEdgeIndices); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rollback := func() {
|
||||
conn.Exec("ROLLBACK")
|
||||
}
|
||||
|
||||
// Create root entities
|
||||
if _, err := conn.Exec("BEGIN"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := conn.Exec("INSERT INTO entity (id) VALUES (?);", "0"); err != nil {
|
||||
rollback()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err := conn.Exec("INSERT INTO edge (entity_id, name) VALUES(?,?);", "0", "/"); err != nil {
|
||||
rollback()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err := conn.Exec("COMMIT"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return db, nil
|
||||
}
|
||||
|
||||
// Close the underlying connection to the database
|
||||
func (db *Database) Close() error {
|
||||
return db.conn.Close()
|
||||
}
|
||||
|
||||
// Set the entity id for a given path
|
||||
func (db *Database) Set(fullPath, id string) (*Entity, error) {
|
||||
// FIXME: is rollback implicit when closing the connection?
|
||||
rollback := func() {
|
||||
db.conn.Exec("ROLLBACK")
|
||||
}
|
||||
if _, err := db.conn.Exec("BEGIN EXCLUSIVE"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var entityId string
|
||||
if err := db.conn.QueryRow("SELECT id FROM entity WHERE id = ?;", id).Scan(&entityId); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
if _, err := db.conn.Exec("INSERT INTO entity (id) VALUES(?);", id); err != nil {
|
||||
rollback()
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
rollback()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
e := &Entity{id}
|
||||
|
||||
parentPath, name := splitPath(fullPath)
|
||||
if err := db.setEdge(parentPath, name, e); err != nil {
|
||||
rollback()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err := db.conn.Exec("COMMIT"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return e, nil
|
||||
}
|
||||
|
||||
// Return true if a name already exists in the database
|
||||
func (db *Database) Exists(name string) bool {
|
||||
return db.Get(name) != nil
|
||||
}
|
||||
|
||||
func (db *Database) setEdge(parentPath, name string, e *Entity) error {
|
||||
parent, err := db.get(parentPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if parent.id == e.id {
|
||||
return fmt.Errorf("Cannot set self as child")
|
||||
}
|
||||
|
||||
if _, err := db.conn.Exec("INSERT INTO edge (parent_id, name, entity_id) VALUES (?,?,?);", parent.id, name, e.id); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Return the root "/" entity for the database
|
||||
func (db *Database) RootEntity() *Entity {
|
||||
return &Entity{
|
||||
id: "0",
|
||||
}
|
||||
}
|
||||
|
||||
// Return the entity for a given path
|
||||
func (db *Database) Get(name string) *Entity {
|
||||
e, err := db.get(name)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
func (db *Database) get(name string) (*Entity, error) {
|
||||
e := db.RootEntity()
|
||||
// We always know the root name so return it if
|
||||
// it is requested
|
||||
if name == "/" {
|
||||
return e, nil
|
||||
}
|
||||
|
||||
parts := split(name)
|
||||
for i := 1; i < len(parts); i++ {
|
||||
p := parts[i]
|
||||
if p == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
next := db.child(e, p)
|
||||
if next == nil {
|
||||
return nil, fmt.Errorf("Cannot find child for %s", name)
|
||||
}
|
||||
e = next
|
||||
}
|
||||
return e, nil
|
||||
|
||||
}
|
||||
|
||||
// List all entities by from the name
|
||||
// The key will be the full path of the entity
|
||||
func (db *Database) List(name string, depth int) Entities {
|
||||
out := Entities{}
|
||||
e, err := db.get(name)
|
||||
if err != nil {
|
||||
return out
|
||||
}
|
||||
for c := range db.children(e, name, depth) {
|
||||
out[c.FullPath] = c.Entity
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (db *Database) Walk(name string, walkFunc WalkFunc, depth int) error {
|
||||
e, err := db.get(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for c := range db.children(e, name, depth) {
|
||||
if err := walkFunc(c.FullPath, c.Entity); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Return the refrence count for a specified id
|
||||
func (db *Database) Refs(id string) int {
|
||||
var count int
|
||||
if err := db.conn.QueryRow("SELECT COUNT(*) FROM edge WHERE entity_id = ?;", id).Scan(&count); err != nil {
|
||||
return 0
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
// Return all the id's path references
|
||||
func (db *Database) RefPaths(id string) Edges {
|
||||
refs := Edges{}
|
||||
|
||||
rows, err := db.conn.Query("SELECT name, parent_id FROM edge WHERE entity_id = ?;", id)
|
||||
if err != nil {
|
||||
return refs
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var name string
|
||||
var parentId string
|
||||
if err := rows.Scan(&name, &parentId); err != nil {
|
||||
return refs
|
||||
}
|
||||
refs = append(refs, &Edge{
|
||||
EntityID: id,
|
||||
Name: name,
|
||||
ParentID: parentId,
|
||||
})
|
||||
}
|
||||
return refs
|
||||
}
|
||||
|
||||
// Delete the reference to an entity at a given path
|
||||
func (db *Database) Delete(name string) error {
|
||||
if name == "/" {
|
||||
return fmt.Errorf("Cannot delete root entity")
|
||||
}
|
||||
|
||||
parentPath, n := splitPath(name)
|
||||
parent, err := db.get(parentPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := db.conn.Exec("DELETE FROM edge WHERE parent_id = ? AND name = ?;", parent.id, n); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove the entity with the specified id
|
||||
// Walk the graph to make sure all references to the entity
|
||||
// are removed and return the number of references removed
|
||||
func (db *Database) Purge(id string) (int, error) {
|
||||
rollback := func() {
|
||||
db.conn.Exec("ROLLBACK")
|
||||
}
|
||||
|
||||
if _, err := db.conn.Exec("BEGIN"); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
// Delete all edges
|
||||
rows, err := db.conn.Exec("DELETE FROM edge WHERE entity_id = ?;", id)
|
||||
if err != nil {
|
||||
rollback()
|
||||
return -1, err
|
||||
}
|
||||
|
||||
changes, err := rows.RowsAffected()
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
// Delete entity
|
||||
if _, err := db.conn.Exec("DELETE FROM entity where id = ?;", id); err != nil {
|
||||
rollback()
|
||||
return -1, err
|
||||
}
|
||||
|
||||
if _, err := db.conn.Exec("COMMIT"); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
return int(changes), nil
|
||||
}
|
||||
|
||||
// Rename an edge for a given path
|
||||
func (db *Database) Rename(currentName, newName string) error {
|
||||
parentPath, name := splitPath(currentName)
|
||||
newParentPath, newEdgeName := splitPath(newName)
|
||||
|
||||
if parentPath != newParentPath {
|
||||
return fmt.Errorf("Cannot rename when root paths do not match %s != %s", parentPath, newParentPath)
|
||||
}
|
||||
|
||||
parent, err := db.get(parentPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rows, err := db.conn.Exec("UPDATE edge SET name = ? WHERE parent_id = ? AND name = ?;", newEdgeName, parent.id, name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
i, err := rows.RowsAffected()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if i == 0 {
|
||||
return fmt.Errorf("Cannot locate edge for %s %s", parent.id, name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type WalkMeta struct {
|
||||
Parent *Entity
|
||||
Entity *Entity
|
||||
FullPath string
|
||||
Edge *Edge
|
||||
}
|
||||
|
||||
func (db *Database) children(e *Entity, name string, depth int) <-chan WalkMeta {
|
||||
out := make(chan WalkMeta)
|
||||
if e == nil {
|
||||
close(out)
|
||||
return out
|
||||
}
|
||||
|
||||
go func() {
|
||||
rows, err := db.conn.Query("SELECT entity_id, name FROM edge where parent_id = ?;", e.id)
|
||||
if err != nil {
|
||||
close(out)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var entityId, entityName string
|
||||
if err := rows.Scan(&entityId, &entityName); err != nil {
|
||||
// Log error
|
||||
continue
|
||||
}
|
||||
child := &Entity{entityId}
|
||||
edge := &Edge{
|
||||
ParentID: e.id,
|
||||
Name: entityName,
|
||||
EntityID: child.id,
|
||||
}
|
||||
|
||||
meta := WalkMeta{
|
||||
Parent: e,
|
||||
Entity: child,
|
||||
FullPath: path.Join(name, edge.Name),
|
||||
Edge: edge,
|
||||
}
|
||||
|
||||
out <- meta
|
||||
if depth == 0 {
|
||||
continue
|
||||
}
|
||||
nDepth := depth
|
||||
if depth != -1 {
|
||||
nDepth -= 1
|
||||
}
|
||||
sc := db.children(child, meta.FullPath, nDepth)
|
||||
for c := range sc {
|
||||
out <- c
|
||||
}
|
||||
}
|
||||
close(out)
|
||||
}()
|
||||
return out
|
||||
}
|
||||
|
||||
// Return the entity based on the parent path and name
|
||||
func (db *Database) child(parent *Entity, name string) *Entity {
|
||||
var id string
|
||||
if err := db.conn.QueryRow("SELECT entity_id FROM edge WHERE parent_id = ? AND name = ?;", parent.id, name).Scan(&id); err != nil {
|
||||
return nil
|
||||
}
|
||||
return &Entity{id}
|
||||
}
|
||||
|
||||
// Return the id used to reference this entity
|
||||
func (e *Entity) ID() string {
|
||||
return e.id
|
||||
}
|
||||
|
||||
// Return the paths sorted by depth
|
||||
func (e Entities) Paths() []string {
|
||||
out := make([]string, len(e))
|
||||
var i int
|
||||
for k := range e {
|
||||
out[i] = k
|
||||
i++
|
||||
}
|
||||
sortByDepth(out)
|
||||
|
||||
return out
|
||||
}
|
||||
@@ -0,0 +1,503 @@
|
||||
package gograph
|
||||
|
||||
import (
|
||||
_ "code.google.com/p/gosqlite/sqlite3"
|
||||
"database/sql"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func newTestDb(t *testing.T) (*Database, string) {
|
||||
p := path.Join(os.TempDir(), "sqlite.db")
|
||||
conn, err := sql.Open("sqlite3", p)
|
||||
db, err := NewDatabase(conn, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return db, p
|
||||
}
|
||||
|
||||
func destroyTestDb(dbPath string) {
|
||||
os.Remove(dbPath)
|
||||
}
|
||||
|
||||
func TestNewDatabase(t *testing.T) {
|
||||
db, dbpath := newTestDb(t)
|
||||
if db == nil {
|
||||
t.Fatal("Database should not be nil")
|
||||
}
|
||||
db.Close()
|
||||
defer destroyTestDb(dbpath)
|
||||
}
|
||||
|
||||
func TestCreateRootEnity(t *testing.T) {
|
||||
db, dbpath := newTestDb(t)
|
||||
defer destroyTestDb(dbpath)
|
||||
root := db.RootEntity()
|
||||
if root == nil {
|
||||
t.Fatal("Root entity should not be nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetRootEntity(t *testing.T) {
|
||||
db, dbpath := newTestDb(t)
|
||||
defer destroyTestDb(dbpath)
|
||||
|
||||
e := db.Get("/")
|
||||
if e == nil {
|
||||
t.Fatal("Entity should not be nil")
|
||||
}
|
||||
if e.ID() != "0" {
|
||||
t.Fatalf("Enity id should be 0, got %s", e.ID())
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetEntityWithDifferentName(t *testing.T) {
|
||||
db, dbpath := newTestDb(t)
|
||||
defer destroyTestDb(dbpath)
|
||||
|
||||
db.Set("/test", "1")
|
||||
if _, err := db.Set("/other", "1"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetDuplicateEntity(t *testing.T) {
|
||||
db, dbpath := newTestDb(t)
|
||||
defer destroyTestDb(dbpath)
|
||||
|
||||
if _, err := db.Set("/foo", "42"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := db.Set("/foo", "43"); err == nil {
|
||||
t.Fatalf("Creating an entry with a duplciate path did not cause an error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateChild(t *testing.T) {
|
||||
db, dbpath := newTestDb(t)
|
||||
defer destroyTestDb(dbpath)
|
||||
|
||||
child, err := db.Set("/db", "1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if child == nil {
|
||||
t.Fatal("Child should not be nil")
|
||||
}
|
||||
if child.ID() != "1" {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestListAllRootChildren(t *testing.T) {
|
||||
db, dbpath := newTestDb(t)
|
||||
defer destroyTestDb(dbpath)
|
||||
|
||||
for i := 1; i < 6; i++ {
|
||||
a := strconv.Itoa(i)
|
||||
if _, err := db.Set("/"+a, a); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
entries := db.List("/", -1)
|
||||
if len(entries) != 5 {
|
||||
t.Fatalf("Expect 5 entries for / got %d", len(entries))
|
||||
}
|
||||
}
|
||||
|
||||
func TestListAllSubChildren(t *testing.T) {
|
||||
db, dbpath := newTestDb(t)
|
||||
defer destroyTestDb(dbpath)
|
||||
|
||||
_, err := db.Set("/webapp", "1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
child2, err := db.Set("/db", "2")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
child4, err := db.Set("/logs", "4")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := db.Set("/db/logs", child4.ID()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
child3, err := db.Set("/sentry", "3")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
entries := db.List("/webapp", 1)
|
||||
if len(entries) != 3 {
|
||||
t.Fatalf("Expect 3 entries for / got %d", len(entries))
|
||||
}
|
||||
|
||||
entries = db.List("/webapp", 0)
|
||||
if len(entries) != 2 {
|
||||
t.Fatalf("Expect 2 entries for / got %d", len(entries))
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddSelfAsChild(t *testing.T) {
|
||||
db, dbpath := newTestDb(t)
|
||||
defer destroyTestDb(dbpath)
|
||||
|
||||
child, err := db.Set("/test", "1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := db.Set("/test/other", child.ID()); err == nil {
|
||||
t.Fatal("Error should not be nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddChildToNonExistantRoot(t *testing.T) {
|
||||
db, dbpath := newTestDb(t)
|
||||
defer destroyTestDb(dbpath)
|
||||
|
||||
if _, err := db.Set("/myapp", "1"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := db.Set("/myapp/proxy/db", "2"); err == nil {
|
||||
t.Fatal("Error should not be nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWalkAll(t *testing.T) {
|
||||
db, dbpath := newTestDb(t)
|
||||
defer destroyTestDb(dbpath)
|
||||
_, err := db.Set("/webapp", "1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
child2, err := db.Set("/db", "2")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
child4, err := db.Set("/db/logs", "4")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := db.Set("/webapp/logs", child4.ID()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
child3, err := db.Set("/sentry", "3")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
child5, err := db.Set("/gograph", "5")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := db.Set("/webapp/same-ref-diff-name", child5.ID()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := db.Walk("/", func(p string, e *Entity) error {
|
||||
t.Logf("Path: %s Entity: %s", p, e.ID())
|
||||
return nil
|
||||
}, -1); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetEntityByPath(t *testing.T) {
|
||||
db, dbpath := newTestDb(t)
|
||||
defer destroyTestDb(dbpath)
|
||||
_, err := db.Set("/webapp", "1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
child2, err := db.Set("/db", "2")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
child4, err := db.Set("/logs", "4")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := db.Set("/db/logs", child4.ID()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
child3, err := db.Set("/sentry", "3")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
child5, err := db.Set("/gograph", "5")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := db.Set("/webapp/same-ref-diff-name", child5.ID()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
entity := db.Get("/webapp/db/logs")
|
||||
if entity == nil {
|
||||
t.Fatal("Entity should not be nil")
|
||||
}
|
||||
if entity.ID() != "4" {
|
||||
t.Fatalf("Expected to get entity with id 4, got %s", entity.ID())
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnitiesPaths(t *testing.T) {
|
||||
db, dbpath := newTestDb(t)
|
||||
defer destroyTestDb(dbpath)
|
||||
_, err := db.Set("/webapp", "1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
child2, err := db.Set("/db", "2")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
child4, err := db.Set("/logs", "4")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := db.Set("/db/logs", child4.ID()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
child3, err := db.Set("/sentry", "3")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
child5, err := db.Set("/gograph", "5")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := db.Set("/webapp/same-ref-diff-name", child5.ID()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
out := db.List("/", -1)
|
||||
for _, p := range out.Paths() {
|
||||
t.Log(p)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteRootEntity(t *testing.T) {
|
||||
db, dbpath := newTestDb(t)
|
||||
defer destroyTestDb(dbpath)
|
||||
|
||||
if err := db.Delete("/"); err == nil {
|
||||
t.Fatal("Error should not be nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteEntity(t *testing.T) {
|
||||
db, dbpath := newTestDb(t)
|
||||
defer destroyTestDb(dbpath)
|
||||
_, err := db.Set("/webapp", "1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
child2, err := db.Set("/db", "2")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
child4, err := db.Set("/logs", "4")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := db.Set("/db/logs", child4.ID()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
child3, err := db.Set("/sentry", "3")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
child5, err := db.Set("/gograph", "5")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := db.Set("/webapp/same-ref-diff-name", child5.ID()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := db.Delete("/webapp/sentry"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
entity := db.Get("/webapp/sentry")
|
||||
if entity != nil {
|
||||
t.Fatal("Entity /webapp/sentry should be nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCountRefs(t *testing.T) {
|
||||
db, dbpath := newTestDb(t)
|
||||
defer destroyTestDb(dbpath)
|
||||
|
||||
db.Set("/webapp", "1")
|
||||
|
||||
if db.Refs("1") != 1 {
|
||||
t.Fatal("Expect reference count to be 1")
|
||||
}
|
||||
|
||||
db.Set("/db", "2")
|
||||
db.Set("/webapp/db", "2")
|
||||
if db.Refs("2") != 2 {
|
||||
t.Fatal("Expect reference count to be 2")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPurgeId(t *testing.T) {
|
||||
db, dbpath := newTestDb(t)
|
||||
defer destroyTestDb(dbpath)
|
||||
|
||||
db.Set("/webapp", "1")
|
||||
|
||||
if db.Refs("1") != 1 {
|
||||
t.Fatal("Expect reference count to be 1")
|
||||
}
|
||||
|
||||
db.Set("/db", "2")
|
||||
db.Set("/webapp/db", "2")
|
||||
|
||||
count, err := db.Purge("2")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if count != 2 {
|
||||
t.Fatal("Expected 2 references to be removed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRename(t *testing.T) {
|
||||
db, dbpath := newTestDb(t)
|
||||
defer destroyTestDb(dbpath)
|
||||
|
||||
db.Set("/webapp", "1")
|
||||
|
||||
if db.Refs("1") != 1 {
|
||||
t.Fatal("Expect reference count to be 1")
|
||||
}
|
||||
|
||||
db.Set("/db", "2")
|
||||
db.Set("/webapp/db", "2")
|
||||
|
||||
if db.Get("/webapp/db") == nil {
|
||||
t.Fatal("Cannot find entity at path /webapp/db")
|
||||
}
|
||||
|
||||
if err := db.Rename("/webapp/db", "/webapp/newdb"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if db.Get("/webapp/db") != nil {
|
||||
t.Fatal("Entity should not exist at /webapp/db")
|
||||
}
|
||||
if db.Get("/webapp/newdb") == nil {
|
||||
t.Fatal("Cannot find entity at path /webapp/newdb")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestCreateMultipleNames(t *testing.T) {
|
||||
db, dbpath := newTestDb(t)
|
||||
defer destroyTestDb(dbpath)
|
||||
|
||||
db.Set("/db", "1")
|
||||
if _, err := db.Set("/myapp", "1"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
db.Walk("/", func(p string, e *Entity) error {
|
||||
t.Logf("%s\n", p)
|
||||
return nil
|
||||
}, -1)
|
||||
}
|
||||
|
||||
func TestRefPaths(t *testing.T) {
|
||||
db, dbpath := newTestDb(t)
|
||||
defer destroyTestDb(dbpath)
|
||||
|
||||
db.Set("/webapp", "1")
|
||||
|
||||
db.Set("/db", "2")
|
||||
db.Set("/webapp/db", "2")
|
||||
|
||||
refs := db.RefPaths("2")
|
||||
if len(refs) != 2 {
|
||||
t.Fatalf("Expected reference count to be 2, got %d", len(refs))
|
||||
}
|
||||
}
|
||||
|
||||
func TestExistsTrue(t *testing.T) {
|
||||
db, dbpath := newTestDb(t)
|
||||
defer destroyTestDb(dbpath)
|
||||
|
||||
db.Set("/testing", "1")
|
||||
|
||||
if !db.Exists("/testing") {
|
||||
t.Fatalf("/tesing should exist")
|
||||
}
|
||||
}
|
||||
|
||||
func TestExistsFalse(t *testing.T) {
|
||||
db, dbpath := newTestDb(t)
|
||||
defer destroyTestDb(dbpath)
|
||||
|
||||
db.Set("/toerhe", "1")
|
||||
|
||||
if db.Exists("/testing") {
|
||||
t.Fatalf("/tesing should not exist")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestGetNameWithTrailingSlash(t *testing.T) {
|
||||
db, dbpath := newTestDb(t)
|
||||
defer destroyTestDb(dbpath)
|
||||
|
||||
db.Set("/todo", "1")
|
||||
|
||||
e := db.Get("/todo/")
|
||||
if e == nil {
|
||||
t.Fatalf("Entity should not be nil")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package gograph
|
||||
|
||||
import "sort"
|
||||
|
||||
type pathSorter struct {
|
||||
paths []string
|
||||
by func(i, j string) bool
|
||||
}
|
||||
|
||||
func sortByDepth(paths []string) {
|
||||
s := &pathSorter{paths, func(i, j string) bool {
|
||||
return PathDepth(i) > PathDepth(j)
|
||||
}}
|
||||
sort.Sort(s)
|
||||
}
|
||||
|
||||
func (s *pathSorter) Len() int {
|
||||
return len(s.paths)
|
||||
}
|
||||
|
||||
func (s *pathSorter) Swap(i, j int) {
|
||||
s.paths[i], s.paths[j] = s.paths[j], s.paths[i]
|
||||
}
|
||||
|
||||
func (s *pathSorter) Less(i, j int) bool {
|
||||
return s.by(s.paths[i], s.paths[j])
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package gograph
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSort(t *testing.T) {
|
||||
paths := []string{
|
||||
"/",
|
||||
"/myreallylongname",
|
||||
"/app/db",
|
||||
}
|
||||
|
||||
sortByDepth(paths)
|
||||
|
||||
if len(paths) != 3 {
|
||||
t.Fatalf("Expected 3 parts got %d", len(paths))
|
||||
}
|
||||
|
||||
if paths[0] != "/app/db" {
|
||||
t.Fatalf("Expected /app/db got %s", paths[0])
|
||||
}
|
||||
if paths[1] != "/myreallylongname" {
|
||||
t.Fatalf("Expected /myreallylongname got %s", paths[1])
|
||||
}
|
||||
if paths[2] != "/" {
|
||||
t.Fatalf("Expected / got %s", paths[2])
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package gograph
|
||||
|
||||
import (
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Split p on /
|
||||
func split(p string) []string {
|
||||
return strings.Split(p, "/")
|
||||
}
|
||||
|
||||
// Returns the depth or number of / in a given path
|
||||
func PathDepth(p string) int {
|
||||
parts := split(p)
|
||||
if len(parts) == 2 && parts[1] == "" {
|
||||
return 1
|
||||
}
|
||||
return len(parts)
|
||||
}
|
||||
|
||||
func splitPath(p string) (parent, name string) {
|
||||
if p[0] != '/' {
|
||||
p = "/" + p
|
||||
}
|
||||
parent, name = path.Split(p)
|
||||
l := len(parent)
|
||||
if parent[l-1] == '/' {
|
||||
parent = parent[:l-1]
|
||||
}
|
||||
return
|
||||
}
|
||||
+20
-11
@@ -36,10 +36,10 @@ To build docker, you will need the following system dependencies
|
||||
|
||||
* An amd64 machine
|
||||
* A recent version of git and mercurial
|
||||
* Go version 1.1.2
|
||||
* Go version 1.2rc1 or later (see notes below regarding using Go 1.1.2 and dynbinary)
|
||||
* SQLite version 3.7.9 or later
|
||||
* A clean checkout of the source must be added to a valid Go [workspace](http://golang.org/doc/code.html#Workspaces)
|
||||
under the path *src/github.com/dotcloud/docker*. See
|
||||
|
||||
under the path *src/github.com/dotcloud/docker*.
|
||||
|
||||
## Go dependencies
|
||||
|
||||
@@ -55,7 +55,6 @@ NOTE: if you''re not able to package the exact version (to the exact commit) of
|
||||
please get in touch so we can remediate! Who knows what discrepancies can be caused by even the
|
||||
slightest deviation. We promise to do our best to make everybody happy.
|
||||
|
||||
|
||||
## Disabling CGO
|
||||
|
||||
Make sure to disable CGO on your system, and then recompile the standard library on the build
|
||||
@@ -71,7 +70,7 @@ cd /tmp && echo 'package main' > t.go && go test -a -i -v
|
||||
To build the docker binary, run the following command with the source checkout as the
|
||||
working directory:
|
||||
|
||||
```
|
||||
```bash
|
||||
./hack/make.sh binary
|
||||
```
|
||||
|
||||
@@ -82,7 +81,7 @@ You are encouraged to use ./hack/make.sh without modification. If you must absol
|
||||
your own script (are you really, really sure you need to? make.sh is really not that complicated),
|
||||
then please take care the respect the following:
|
||||
|
||||
* In *./hack/make.sh*: $LDFLAGS, $VERSION and $GITCOMMIT
|
||||
* In *./hack/make.sh*: $LDFLAGS, $BUILDFLAGS, $VERSION and $GITCOMMIT
|
||||
* In *./hack/make/binary*: the exact build command to run
|
||||
|
||||
You may be tempted to tweak these settings. In particular, being a rigorous maintainer, you may want
|
||||
@@ -92,6 +91,17 @@ You would do the users of your distro a disservice and "void the docker warranty
|
||||
A good comparison is Busybox: all distros package it as a statically linked binary, because it just
|
||||
makes sense. Docker is the same way.
|
||||
|
||||
If you *must* have a non-static Docker binary, or require Go 1.1.2 (since Go 1.2 is not yet officially
|
||||
released at the time of this writing), please use:
|
||||
|
||||
```bash
|
||||
./hack/make.sh dynbinary
|
||||
```
|
||||
|
||||
This will create *./bundles/$VERSION/dynbinary/docker-$VERSION* and *./bundles/$VERSION/binary/dockerinit-$VERSION*.
|
||||
The first of these would usually be installed at */usr/bin/docker*, while the second must be installed
|
||||
at */usr/libexec/docker/dockerinit*.
|
||||
|
||||
## Testing Docker
|
||||
|
||||
Before releasing your binary, make sure to run the tests! Run the following command with the source
|
||||
@@ -106,16 +116,15 @@ dependencies to be installed (see below).
|
||||
|
||||
The test suite will also download a small test container, so you will need internet connectivity.
|
||||
|
||||
|
||||
## Runtime dependencies
|
||||
|
||||
To run properly, docker needs the following software to be installed at runtime:
|
||||
|
||||
* GNU Tar version 1.26 or later
|
||||
* iproute2 version 3.5 or later (build after 2012-05-21), and specifically the "ip" utility.
|
||||
* iproute2 version 3.5 or later (build after 2012-05-21), and specifically the "ip" utility
|
||||
* iptables version 1.4 or later
|
||||
* The lxc utility scripts (http://lxc.sourceforge.net) version 0.8 or later.
|
||||
* Git version 1.7 or later
|
||||
* The LXC utility scripts (http://lxc.sourceforge.net) version 0.8 or later
|
||||
* Git version 1.7 or later
|
||||
* XZ Utils 4.9 or later
|
||||
|
||||
## Kernel dependencies
|
||||
@@ -133,6 +142,6 @@ for your distro''s process supervisor of choice.
|
||||
|
||||
Docker should be run as root, with the following arguments:
|
||||
|
||||
```
|
||||
```bash
|
||||
docker -d
|
||||
```
|
||||
|
||||
@@ -27,6 +27,31 @@ do
|
||||
[ -d $CGROUP/$SUBSYS ] || mkdir $CGROUP/$SUBSYS
|
||||
mountpoint -q $CGROUP/$SUBSYS ||
|
||||
mount -n -t cgroup -o $SUBSYS cgroup $CGROUP/$SUBSYS
|
||||
|
||||
# The two following sections address a bug which manifests itself
|
||||
# by a cryptic "lxc-start: no ns_cgroup option specified" when
|
||||
# trying to start containers withina container.
|
||||
# The bug seems to appear when the cgroup hierarchies are not
|
||||
# mounted on the exact same directories in the host, and in the
|
||||
# container.
|
||||
|
||||
# Named, control-less cgroups are mounted with "-o name=foo"
|
||||
# (and appear as such under /proc/<pid>/cgroup) but are usually
|
||||
# mounted on a directory named "foo" (without the "name=" prefix).
|
||||
# Systemd and OpenRC (and possibly others) both create such a
|
||||
# cgroup. To avoid the aforementioned bug, we symlink "foo" to
|
||||
# "name=foo". This shouldn't have any adverse effect.
|
||||
echo $SUBSYS | grep -q ^name= && {
|
||||
NAME=$(echo $SUBSYS | sed s/^name=//)
|
||||
ln -s $SUBSYS $CGROUP/$NAME
|
||||
}
|
||||
|
||||
# Likewise, on at least one system, it has been reported that
|
||||
# systemd would mount the CPU and CPU accounting controllers
|
||||
# (respectively "cpu" and "cpuacct") with "-o cpuacct,cpu"
|
||||
# but on a directory called "cpu,cpuacct" (note the inversion
|
||||
# in the order of the groups). This tries to work around it.
|
||||
[ $SUBSYS = cpuacct,cpu ] && ln -s $SUBSYS $CGROUP/cpu,cpuacct
|
||||
done
|
||||
|
||||
# Note: as I write those lines, the LXC userland tools cannot setup
|
||||
@@ -38,7 +63,7 @@ grep -qw devices /proc/1/cgroup ||
|
||||
echo "WARNING: it looks like the 'devices' cgroup is not mounted."
|
||||
|
||||
# Now, close extraneous file descriptors.
|
||||
pushd /proc/self/fd
|
||||
pushd /proc/self/fd >/dev/null
|
||||
for FD in *
|
||||
do
|
||||
case "$FD" in
|
||||
@@ -51,9 +76,10 @@ do
|
||||
;;
|
||||
esac
|
||||
done
|
||||
popd
|
||||
popd >/dev/null
|
||||
|
||||
# Mount /tmp
|
||||
mount -t tmpfs none /tmp
|
||||
|
||||
exec $*
|
||||
[ "$1" ] && exec "$@"
|
||||
echo "You probably want to run hack/make.sh, or maybe a shell?"
|
||||
|
||||
@@ -61,23 +61,31 @@ URL | Service
|
||||
|
||||
This should be the list of all services referenced above.
|
||||
|
||||
Service | Maintainer(s) | How to update | Source
|
||||
--------------------|--------------------|------------------|-------
|
||||
blog | @jbarbier | dotcloud push | https://github.com/dotcloud/blog.docker.io
|
||||
cdn | @jpetazzo @samalba | cloudflare panel | N/A
|
||||
ci | @mzdaniel | See [docker-ci] | See [docker-ci]
|
||||
docs | @metalivedev | github webhook | docker repo
|
||||
docsproxy | @dhrp | dotcloud push | https://github.com/dotcloud/docker-docs-dotcloud-proxy
|
||||
index | @kencochrane | dotcloud push | private
|
||||
packages | @jpetazzo | hack/release | docker repo
|
||||
registry | @samalba | dotcloud push | https://github.com/dotcloud/docker-registry
|
||||
repository (github) | N/A | N/A | N/A
|
||||
ssl (dotcloud) | @jpetazzo | dotcloud ops | N/A
|
||||
ssl (cloudflare) | @jpetazzo | cloudflare panel | N/A
|
||||
stackbrew | @shin- | manual | https://github.com/dotcloud/stackbrew/stackbrew
|
||||
website | @dhrp | dotcloud push | https://github.com/dotcloud/www.docker.io
|
||||
Service | Maintainer(s) | How to update | Source
|
||||
--------------------|----------------------------|------------------|-------
|
||||
blog | [@jbarbier] | dotcloud push | https://github.com/dotcloud/blog.docker.io
|
||||
cdn | [@jpetazzo][] [@samalba][] | cloudflare panel | N/A
|
||||
ci | [@mzdaniel] | See [docker-ci] | See [docker-ci]
|
||||
docs | [@metalivedev] | github webhook | docker repo
|
||||
docsproxy | [@dhrp] | dotcloud push | https://github.com/dotcloud/docker-docs-dotcloud-proxy
|
||||
index | [@kencochrane] | dotcloud push | private
|
||||
packages | [@jpetazzo] | hack/release | docker repo
|
||||
registry | [@samalba] | dotcloud push | https://github.com/dotcloud/docker-registry
|
||||
repository (github) | N/A | N/A | N/A
|
||||
ssl (dotcloud) | [@jpetazzo] | dotcloud ops | N/A
|
||||
ssl (cloudflare) | [@jpetazzo] | cloudflare panel | N/A
|
||||
stackbrew | [@shin-] | manual | https://github.com/dotcloud/stackbrew/stackbrew
|
||||
website | [@dhrp] | dotcloud push | https://github.com/dotcloud/www.docker.io
|
||||
|
||||
*Ordered-by: lexicographic*
|
||||
|
||||
|
||||
[docker-ci]: docker-ci.rst
|
||||
[@dhrp]: https://github.com/dhrp
|
||||
[@jbarbier]: https://github.com/jbarbier
|
||||
[@jpetazzo]: https://github.com/jpetazzo
|
||||
[@kencochrane]: https://github.com/kencochrane
|
||||
[@metalivedev]: https://github.com/metalivedev
|
||||
[@mzdaniel]: https://github.com/mzdaniel
|
||||
[@samalba]: https://github.com/samalba
|
||||
[@shin-]: https://github.com/shin-
|
||||
|
||||
+25
-6
@@ -75,16 +75,35 @@ case "$lsb_dist" in
|
||||
Ubuntu|Debian)
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# TODO remove this comment/section once device-mapper lands
|
||||
echo 'Warning: Docker currently requires AUFS support in the kernel.'
|
||||
echo 'Please ensure that your kernel includes such support.'
|
||||
( set -x; sleep 10 )
|
||||
did_apt_get_update=
|
||||
apt_get_update() {
|
||||
if [ -z "$did_apt_get_update" ]; then
|
||||
( set -x; $sh_c 'sleep 3; apt-get update' )
|
||||
did_apt_get_update=1
|
||||
fi
|
||||
}
|
||||
|
||||
# TODO remove this section once device-mapper lands
|
||||
if ! grep -q aufs /proc/filesystems && ! $sh_c 'modprobe aufs'; then
|
||||
kern_extras="linux-image-extra-$(uname -r)"
|
||||
|
||||
apt_get_update
|
||||
( set -x; $sh_c 'sleep 3; apt-get install -y -q '"$kern_extras" ) || true
|
||||
|
||||
if ! grep -q aufs /proc/filesystems && ! $sh_c 'modprobe aufs'; then
|
||||
echo >&2 'Warning: tried to install '"$kern_extras"' (for AUFS)'
|
||||
echo >&2 ' but we still have no AUFS. Docker may not work. Proceeding anyways!'
|
||||
( set -x; sleep 10 )
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ ! -e /usr/lib/apt/methods/https ]; then
|
||||
( set -x; $sh_c 'sleep 3; apt-get update; apt-get install -y -q apt-transport-https' )
|
||||
apt_get_update
|
||||
( set -x; $sh_c 'sleep 3; apt-get install -y -q apt-transport-https' )
|
||||
fi
|
||||
if [ -z "$curl" ]; then
|
||||
( set -x; $sh_c 'sleep 3; apt-get update; apt-get install -y -q curl' )
|
||||
apt_get_update
|
||||
( set -x; $sh_c 'sleep 3; apt-get install -y -q curl' )
|
||||
curl='curl -sL'
|
||||
fi
|
||||
(
|
||||
|
||||
+9
-5
@@ -1,4 +1,5 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# This script builds various binary artifacts from a checkout of the docker
|
||||
# source code.
|
||||
@@ -19,7 +20,7 @@
|
||||
# "docker run hack/make.sh" in the resulting container image.
|
||||
#
|
||||
|
||||
set -e
|
||||
set -o pipefail
|
||||
|
||||
# We're a nice, sexy, little shell script, and people might try to run us;
|
||||
# but really, they shouldn't. We want to be in a container!
|
||||
@@ -32,8 +33,10 @@ grep -q "$RESOLVCONF" /proc/mounts || {
|
||||
|
||||
# List of bundles to create when no argument is passed
|
||||
DEFAULT_BUNDLES=(
|
||||
test
|
||||
binary
|
||||
test
|
||||
dynbinary
|
||||
dyntest
|
||||
ubuntu
|
||||
)
|
||||
|
||||
@@ -44,8 +47,9 @@ if [ -n "$(git status --porcelain)" ]; then
|
||||
fi
|
||||
|
||||
# Use these flags when compiling the tests and final binary
|
||||
LDFLAGS="-X main.GITCOMMIT $GITCOMMIT -X main.VERSION $VERSION -d -w"
|
||||
|
||||
LDFLAGS='-X main.GITCOMMIT "'$GITCOMMIT'" -X main.VERSION "'$VERSION'" -w'
|
||||
LDFLAGS_STATIC='-X github.com/dotcloud/docker/utils.IAMSTATIC true -linkmode external -extldflags "-lpthread -static -Wl,--unresolved-symbols=ignore-in-object-files"'
|
||||
BUILDFLAGS='-tags netgo'
|
||||
|
||||
bundle() {
|
||||
bundlescript=$1
|
||||
@@ -66,7 +70,7 @@ main() {
|
||||
fi
|
||||
SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
if [ $# -lt 1 ]; then
|
||||
bundles=($DEFAULT_BUNDLES)
|
||||
bundles=(${DEFAULT_BUNDLES[@]})
|
||||
else
|
||||
bundles=($@)
|
||||
fi
|
||||
|
||||
+2
-3
@@ -2,6 +2,5 @@
|
||||
|
||||
DEST=$1
|
||||
|
||||
if go build -o $DEST/docker-$VERSION -ldflags "$LDFLAGS" ./docker; then
|
||||
echo "Created binary: $DEST/docker-$VERSION"
|
||||
fi
|
||||
go build -o $DEST/docker-$VERSION -ldflags "$LDFLAGS $LDFLAGS_STATIC" $BUILDFLAGS ./docker
|
||||
echo "Created binary: $DEST/docker-$VERSION"
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
#!/bin/sh
|
||||
|
||||
DEST=$1
|
||||
|
||||
# dockerinit still needs to be a static binary, even if docker is dynamic
|
||||
CGO_ENABLED=0 go build -a -o $DEST/dockerinit-$VERSION -ldflags "$LDFLAGS -d" $BUILDFLAGS ./dockerinit
|
||||
echo "Created binary: $DEST/dockerinit-$VERSION"
|
||||
ln -sf dockerinit-$VERSION $DEST/dockerinit
|
||||
|
||||
# sha1 our new dockerinit to ensure separate docker and dockerinit always run in a perfect pair compiled for one another
|
||||
export DOCKER_INITSHA1="$(sha1sum $DEST/dockerinit-$VERSION | cut -d' ' -f1)"
|
||||
# exported so that "dyntest" can easily access it later without recalculating it
|
||||
|
||||
go build -o $DEST/docker-$VERSION -ldflags "$LDFLAGS -X github.com/dotcloud/docker/utils.INITSHA1 \"$DOCKER_INITSHA1\"" $BUILDFLAGS ./docker
|
||||
echo "Created binary: $DEST/docker-$VERSION"
|
||||
@@ -0,0 +1,42 @@
|
||||
#!/bin/sh
|
||||
|
||||
DEST=$1
|
||||
INIT=$DEST/../dynbinary/dockerinit-$VERSION
|
||||
|
||||
set -e
|
||||
|
||||
if [ ! -x "$INIT" ]; then
|
||||
echo >&2 'error: dynbinary must be run before dyntest'
|
||||
false
|
||||
fi
|
||||
|
||||
# Run Docker's test suite, including sub-packages, and store their output as a bundle
|
||||
# If $TESTFLAGS is set in the environment, it is passed as extra arguments to 'go test'.
|
||||
# You can use this to select certain tests to run, eg.
|
||||
#
|
||||
# TESTFLAGS='-run ^TestBuild$' ./hack/make.sh test
|
||||
#
|
||||
bundle_test() {
|
||||
{
|
||||
date
|
||||
for test_dir in $(find_test_dirs); do (
|
||||
set -x
|
||||
cd $test_dir
|
||||
go test -i -ldflags "$LDFLAGS" $BUILDFLAGS
|
||||
export TEST_DOCKERINIT_PATH=$DEST/../dynbinary/dockerinit-$VERSION
|
||||
go test -v -ldflags "$LDFLAGS -X github.com/dotcloud/docker/utils.INITSHA1 \"$DOCKER_INITSHA1\"" $BUILDFLAGS $TESTFLAGS
|
||||
) done
|
||||
} 2>&1 | tee $DEST/test.log
|
||||
}
|
||||
|
||||
|
||||
# This helper function walks the current directory looking for directories
|
||||
# holding Go test files, and prints their paths on standard output, one per
|
||||
# line.
|
||||
find_test_dirs() {
|
||||
find . -name '*_test.go' | grep -v '^./vendor' |
|
||||
{ while read f; do dirname $f; done; } |
|
||||
sort -u
|
||||
}
|
||||
|
||||
bundle_test
|
||||
+4
-1
@@ -1,3 +1,5 @@
|
||||
#!/bin/sh
|
||||
|
||||
DEST=$1
|
||||
|
||||
set -e
|
||||
@@ -14,7 +16,8 @@ bundle_test() {
|
||||
for test_dir in $(find_test_dirs); do (
|
||||
set -x
|
||||
cd $test_dir
|
||||
go test -v -ldflags "$LDFLAGS" $TESTFLAGS
|
||||
go test -i -ldflags "$LDFLAGS $LDFLAGS_STATIC" $BUILDFLAGS
|
||||
go test -v -ldflags "$LDFLAGS $LDFLAGS_STATIC" $BUILDFLAGS $TESTFLAGS
|
||||
) done
|
||||
} 2>&1 | tee $DEST/test.log
|
||||
}
|
||||
|
||||
+30
-29
@@ -3,8 +3,7 @@
|
||||
DEST=$1
|
||||
|
||||
PKGVERSION="$VERSION"
|
||||
if test -n "$(git status --porcelain)"
|
||||
then
|
||||
if [ -n "$(git status --porcelain)" ]; then
|
||||
PKGVERSION="$PKGVERSION-$(date +%Y%m%d%H%M%S)-$GITCOMMIT"
|
||||
fi
|
||||
|
||||
@@ -19,26 +18,17 @@ Docker is a great building block for automating distributed systems:
|
||||
large-scale web deployments, database clusters, continuous deployment systems,
|
||||
private PaaS, service-oriented architectures, etc."
|
||||
|
||||
UPSTART_SCRIPT='description "Docker daemon"
|
||||
|
||||
start on filesystem and started lxc-net
|
||||
stop on runlevel [!2345]
|
||||
|
||||
respawn
|
||||
|
||||
script
|
||||
/usr/bin/docker -d
|
||||
end script
|
||||
'
|
||||
|
||||
# Build docker as an ubuntu package using FPM and REPREPRO (sue me).
|
||||
# bundle_binary must be called first.
|
||||
bundle_ubuntu() {
|
||||
DIR=$DEST/build
|
||||
|
||||
# Generate an upstart config file (ubuntu-specific)
|
||||
mkdir -p $DIR/etc/init
|
||||
echo "$UPSTART_SCRIPT" > $DIR/etc/init/docker.conf
|
||||
# Include our init scripts
|
||||
mkdir -p $DIR/etc
|
||||
cp -R contrib/init/upstart $DIR/etc/init
|
||||
cp -R contrib/init/sysvinit $DIR/etc/init.d
|
||||
mkdir -p $DIR/lib/systemd
|
||||
cp -R contrib/init/systemd $DIR/lib/systemd/system
|
||||
|
||||
# Copy the binary
|
||||
# This will fail if the binary bundle hasn't been built
|
||||
@@ -47,29 +37,40 @@ bundle_ubuntu() {
|
||||
# This will fail if the binary bundle hasn't been built
|
||||
cp $DEST/../binary/docker-$VERSION $DIR/usr/bin/docker
|
||||
|
||||
# Generate postinstall/prerm scripts
|
||||
cat >/tmp/postinstall <<EOF
|
||||
# Generate postinst/prerm scripts
|
||||
cat >/tmp/postinst <<'EOF'
|
||||
#!/bin/sh
|
||||
/sbin/stop docker || true
|
||||
/bin/grep -q "^docker:" /etc/group || /usr/sbin/addgroup --system docker || true
|
||||
/sbin/start docker
|
||||
service docker stop || true
|
||||
grep -q '^docker:' /etc/group || groupadd --system docker || true
|
||||
service docker start
|
||||
EOF
|
||||
cat >/tmp/prerm <<EOF
|
||||
cat >/tmp/prerm <<'EOF'
|
||||
#!/bin/sh
|
||||
/sbin/stop docker || true
|
||||
/usr/sbin/delgroup docker || true
|
||||
service docker stop || true
|
||||
|
||||
case "$1" in
|
||||
purge|remove|abort-install)
|
||||
groupdel docker || true
|
||||
;;
|
||||
|
||||
upgrade|failed-upgrade|abort-upgrade)
|
||||
# don't touch docker group
|
||||
;;
|
||||
esac
|
||||
EOF
|
||||
chmod +x /tmp/postinstall /tmp/prerm
|
||||
chmod +x /tmp/postinst /tmp/prerm
|
||||
|
||||
(
|
||||
cd $DEST
|
||||
fpm -s dir -C $DIR \
|
||||
--name lxc-docker-$VERSION --version $PKGVERSION \
|
||||
--after-install /tmp/postinstall \
|
||||
--after-install /tmp/postinst \
|
||||
--before-remove /tmp/prerm \
|
||||
--architecture "$PACKAGE_ARCHITECTURE" \
|
||||
--prefix / \
|
||||
--depends lxc --depends aufs-tools \
|
||||
--depends lxc \
|
||||
--depends aufs-tools \
|
||||
--depends iptables \
|
||||
--description "$PACKAGE_DESCRIPTION" \
|
||||
--maintainer "$PACKAGE_MAINTAINER" \
|
||||
--conflicts lxc-docker-virtual-package \
|
||||
@@ -80,6 +81,7 @@ EOF
|
||||
--url "$PACKAGE_URL" \
|
||||
--vendor "$PACKAGE_VENDOR" \
|
||||
--config-files /etc/init/docker.conf \
|
||||
--config-files /etc/init.d/docker \
|
||||
-t deb .
|
||||
mkdir empty
|
||||
fpm -s dir -C empty \
|
||||
@@ -90,7 +92,6 @@ EOF
|
||||
--maintainer "$PACKAGE_MAINTAINER" \
|
||||
--url "$PACKAGE_URL" \
|
||||
--vendor "$PACKAGE_VENDOR" \
|
||||
--config-files /etc/init/docker.conf \
|
||||
-t deb .
|
||||
)
|
||||
}
|
||||
|
||||
+60
-15
@@ -1,4 +1,5 @@
|
||||
#!/bin/sh
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# This script looks for bundles built by make.sh, and releases them on a
|
||||
# public S3 bucket.
|
||||
@@ -9,11 +10,11 @@
|
||||
# official Dockerfile at the root of the Docker source code. The Dockerfile,
|
||||
# make.sh and release.sh should all be from the same source code revision.
|
||||
|
||||
set -e
|
||||
set -o pipefail
|
||||
|
||||
# Print a usage message and exit.
|
||||
usage() {
|
||||
cat <<EOF
|
||||
cat >&2 <<'EOF'
|
||||
To run, I need:
|
||||
- to be in a container generated by the Dockerfile at the top of the Docker
|
||||
repository;
|
||||
@@ -26,11 +27,12 @@ To run, I need:
|
||||
- a generous amount of good will and nice manners.
|
||||
The canonical way to run me is to run the image produced by the Dockerfile: e.g.:"
|
||||
|
||||
docker run -e AWS_S3_BUCKET=get-staging.docker.io \\
|
||||
AWS_ACCESS_KEY=AKI1234... \\
|
||||
AWS_SECRET_KEY=sEs4mE... \\
|
||||
GPG_PASSPHRASE=m0resEs4mE... \\
|
||||
f0058411
|
||||
docker run -e AWS_S3_BUCKET=get-staging.docker.io \
|
||||
-e AWS_ACCESS_KEY=AKI1234... \
|
||||
-e AWS_SECRET_KEY=sEs4mE... \
|
||||
-e GPG_PASSPHRASE=m0resEs4mE... \
|
||||
-i -t -privileged \
|
||||
docker ./hack/release.sh
|
||||
EOF
|
||||
exit 1
|
||||
}
|
||||
@@ -39,8 +41,41 @@ EOF
|
||||
[ "$AWS_ACCESS_KEY" ] || usage
|
||||
[ "$AWS_SECRET_KEY" ] || usage
|
||||
[ "$GPG_PASSPHRASE" ] || usage
|
||||
[ -d /go/src/github.com/dotcloud/docker/ ] || usage
|
||||
cd /go/src/github.com/dotcloud/docker/
|
||||
[ -d /go/src/github.com/dotcloud/docker ] || usage
|
||||
cd /go/src/github.com/dotcloud/docker
|
||||
[ -x hack/make.sh ] || usage
|
||||
|
||||
RELEASE_BUNDLES=(
|
||||
binary
|
||||
ubuntu
|
||||
)
|
||||
|
||||
if [ "$1" != '--release-regardless-of-test-failure' ]; then
|
||||
RELEASE_BUNDLES=( test "${RELEASE_BUNDLES[@]}" )
|
||||
fi
|
||||
|
||||
if ! ./hack/make.sh "${RELEASE_BUNDLES[@]}"; then
|
||||
echo >&2
|
||||
echo >&2 'The build or tests appear to have failed.'
|
||||
echo >&2
|
||||
echo >&2 'You, as the release maintainer, now have a couple options:'
|
||||
echo >&2 '- delay release and fix issues'
|
||||
echo >&2 '- delay release and fix issues'
|
||||
echo >&2 '- did we mention how important this is? issues need fixing :)'
|
||||
echo >&2
|
||||
echo >&2 'As a final LAST RESORT, you (because only you, the release maintainer,'
|
||||
echo >&2 ' really knows all the hairy problems at hand with the current release'
|
||||
echo >&2 ' issues) may bypass this checking by running this script again with the'
|
||||
echo >&2 ' single argument of "--release-regardless-of-test-failure", which will skip'
|
||||
echo >&2 ' running the test suite, and will only build the binaries and packages. Please'
|
||||
echo >&2 ' avoid using this if at all possible.'
|
||||
echo >&2
|
||||
echo >&2 'Regardless, we cannot stress enough the scarcity with which this bypass'
|
||||
echo >&2 ' should be used. If there are release issues, we should always err on the'
|
||||
echo >&2 ' side of caution.'
|
||||
echo >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
VERSION=$(cat VERSION)
|
||||
BUCKET=$AWS_S3_BUCKET
|
||||
@@ -81,11 +116,15 @@ s3_url() {
|
||||
# 1. A full APT repository is published at $BUCKET/ubuntu/
|
||||
# 2. Instructions for using the APT repository are uploaded at $BUCKET/ubuntu/info
|
||||
release_ubuntu() {
|
||||
[ -e bundles/$VERSION/ubuntu ] || {
|
||||
echo >&2 './hack/make.sh must be run before release_ubuntu'
|
||||
exit 1
|
||||
}
|
||||
# Make sure that we have our keys
|
||||
mkdir -p /.gnupg/
|
||||
s3cmd sync s3://$BUCKET/ubuntu/.gnupg/ /.gnupg/ || true
|
||||
gpg --list-keys releasedocker >/dev/null || {
|
||||
gpg --gen-key --batch <<EOF
|
||||
gpg --gen-key --batch <<EOF
|
||||
Key-Type: RSA
|
||||
Key-Length: 2048
|
||||
Passphrase: $GPG_PASSPHRASE
|
||||
@@ -99,7 +138,7 @@ EOF
|
||||
|
||||
# Sign our packages
|
||||
dpkg-sig -g "--passphrase $GPG_PASSPHRASE" -k releasedocker \
|
||||
--sign builder bundles/$VERSION/ubuntu/*.deb
|
||||
--sign builder bundles/$VERSION/ubuntu/*.deb
|
||||
|
||||
# Setup the APT repo
|
||||
APTDIR=bundles/$VERSION/ubuntu/apt
|
||||
@@ -116,8 +155,7 @@ EOF
|
||||
reprepro -b $APTDIR includedeb docker $DEBFILE
|
||||
|
||||
# Sign
|
||||
for F in $(find $APTDIR -name Release)
|
||||
do
|
||||
for F in $(find $APTDIR -name Release); do
|
||||
gpg -u releasedocker --passphrase $GPG_PASSPHRASE \
|
||||
--armor --sign --detach-sign \
|
||||
--output $F.gpg $F
|
||||
@@ -137,13 +175,20 @@ echo deb $(s3_url)/ubuntu docker main > /etc/apt/sources.list.d/docker.list
|
||||
curl $(s3_url)/gpg | apt-key add -
|
||||
# Install docker
|
||||
apt-get update ; apt-get install -y lxc-docker
|
||||
|
||||
#
|
||||
# Alternatively, just use the curl-able install.sh script provided at $(s3_url)
|
||||
#
|
||||
EOF
|
||||
echo "APT repository uploaded. Instructions available at $(s3_url)/ubuntu/info"
|
||||
}
|
||||
|
||||
# Upload a static binary to S3
|
||||
release_binary() {
|
||||
[ -e bundles/$VERSION ]
|
||||
[ -e bundles/$VERSION/binary/docker-$VERSION ] || {
|
||||
echo >&2 './hack/make.sh must be run before release_binary'
|
||||
exit 1
|
||||
}
|
||||
S3DIR=s3://$BUCKET/builds/Linux/x86_64
|
||||
s3cmd --acl-public put bundles/$VERSION/binary/docker-$VERSION $S3DIR/docker-$VERSION
|
||||
cat <<EOF | write_to_s3 s3://$BUCKET/builds/info
|
||||
|
||||
@@ -92,7 +92,7 @@ func StoreImage(img *Image, jsonData []byte, layerData Archive, root string) err
|
||||
if err := Untar(layerData, layer); err != nil {
|
||||
return err
|
||||
}
|
||||
utils.Debugf("Untar time: %vs\n", time.Now().Sub(start).Seconds())
|
||||
utils.Debugf("Untar time: %vs", time.Now().Sub(start).Seconds())
|
||||
}
|
||||
|
||||
// If raw json is provided, then use it
|
||||
@@ -335,7 +335,7 @@ func (img *Image) getParentsSize(size int64) int64 {
|
||||
func NewImgJSON(src []byte) (*Image, error) {
|
||||
ret := &Image{}
|
||||
|
||||
utils.Debugf("Json string: {%s}\n", src)
|
||||
utils.Debugf("Json string: {%s}", src)
|
||||
// FIXME: Is there a cleaner way to "purify" the input json?
|
||||
if err := json.Unmarshal(src, ret); err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Michael Crosby <michael@crosbymichael.com> (@crosbymichael)
|
||||
@@ -0,0 +1,110 @@
|
||||
package iptables
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Action string
|
||||
|
||||
const (
|
||||
Add Action = "-A"
|
||||
Delete Action = "-D"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrIptablesNotFound = errors.New("Iptables not found")
|
||||
nat = []string{"-t", "nat"}
|
||||
)
|
||||
|
||||
type Chain struct {
|
||||
Name string
|
||||
Bridge string
|
||||
}
|
||||
|
||||
func NewChain(name, bridge string) (*Chain, error) {
|
||||
if err := Raw("-t", "nat", "-N", name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
chain := &Chain{
|
||||
Name: name,
|
||||
Bridge: bridge,
|
||||
}
|
||||
|
||||
if err := chain.Prerouting(Add, "-m", "addrtype", "--dst-type", "LOCAL"); err != nil {
|
||||
return nil, fmt.Errorf("Failed to inject docker in PREROUTING chain: %s", err)
|
||||
}
|
||||
if err := chain.Output(Add, "-m", "addrtype", "--dst-type", "LOCAL", "!", "--dst", "127.0.0.0/8"); err != nil {
|
||||
return nil, fmt.Errorf("Failed to inject docker in OUTPUT chain: %s", err)
|
||||
}
|
||||
return chain, nil
|
||||
}
|
||||
|
||||
func RemoveExistingChain(name string) error {
|
||||
chain := &Chain{
|
||||
Name: name,
|
||||
}
|
||||
return chain.Remove()
|
||||
}
|
||||
|
||||
func (c *Chain) Forward(action Action, ip net.IP, port int, proto, dest_addr string, dest_port int) error {
|
||||
return Raw("-t", "nat", fmt.Sprint(action), c.Name,
|
||||
"-p", proto,
|
||||
"-d", ip.String(),
|
||||
"--dport", strconv.Itoa(port),
|
||||
"!", "-i", c.Bridge,
|
||||
"-j", "DNAT",
|
||||
"--to-destination", net.JoinHostPort(dest_addr, strconv.Itoa(dest_port)))
|
||||
}
|
||||
|
||||
func (c *Chain) Prerouting(action Action, args ...string) error {
|
||||
a := append(nat, fmt.Sprint(action), "PREROUTING")
|
||||
if len(args) > 0 {
|
||||
a = append(a, args...)
|
||||
}
|
||||
return Raw(append(a, "-j", c.Name)...)
|
||||
}
|
||||
|
||||
func (c *Chain) Output(action Action, args ...string) error {
|
||||
a := append(nat, fmt.Sprint(action), "OUTPUT")
|
||||
if len(args) > 0 {
|
||||
a = append(a, args...)
|
||||
}
|
||||
return Raw(append(a, "-j", c.Name)...)
|
||||
}
|
||||
|
||||
func (c *Chain) Remove() error {
|
||||
// Ignore errors - This could mean the chains were never set up
|
||||
c.Prerouting(Delete, "-m", "addrtype", "--dst-type", "LOCAL")
|
||||
c.Output(Delete, "-m", "addrtype", "--dst-type", "LOCAL", "!", "--dst", "127.0.0.0/8")
|
||||
c.Output(Delete, "-m", "addrtype", "--dst-type", "LOCAL") // Created in versions <= 0.1.6
|
||||
|
||||
c.Prerouting(Delete)
|
||||
c.Output(Delete)
|
||||
|
||||
Raw("-t", "nat", "-F", c.Name)
|
||||
Raw("-t", "nat", "-X", c.Name)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check if an existing rule exists
|
||||
func Exists(args ...string) bool {
|
||||
return Raw(append([]string{"-C"}, args...)...) == nil
|
||||
}
|
||||
|
||||
func Raw(args ...string) error {
|
||||
path, err := exec.LookPath("iptables")
|
||||
if err != nil {
|
||||
return ErrIptablesNotFound
|
||||
}
|
||||
if err := exec.Command(path, args...).Run(); err != nil {
|
||||
return fmt.Errorf("iptables failed: iptables %v", strings.Join(args, " "))
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package iptables
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIptables(t *testing.T) {
|
||||
if err := Raw("-L"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
path := os.Getenv("PATH")
|
||||
os.Setenv("PATH", "")
|
||||
defer os.Setenv("PATH", path)
|
||||
if err := Raw("-L"); err == nil {
|
||||
t.Fatal("Not finding iptables in the PATH should cause an error")
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
Joffrey Fuhrer <joffrey@dotcloud.com> (@shin-)
|
||||
@@ -1 +0,0 @@
|
||||
latest git://github.com/dotcloud/docker-busybox
|
||||
@@ -1,2 +0,0 @@
|
||||
latest git://github.com/dotcloud/hipache C:7362ff5b812f93eceafbdbf5e5959f676f731f80
|
||||
0.2.4 git://github.com/dotcloud/hipache C:7362ff5b812f93eceafbdbf5e5959f676f731f80
|
||||
@@ -1,7 +0,0 @@
|
||||
latest git://github.com/dotcloud/ubuntu-quantal
|
||||
quantal git://github.com/dotcloud/ubuntu-quantal
|
||||
12.10 git://github.com/dotcloud/ubuntu-quantal
|
||||
precise git://github.com/shin-/ubuntu B:precise
|
||||
12.04 git://github.com/shin-/ubuntu B:precise
|
||||
raring git://github.com/shin-/ubuntu B:raring
|
||||
13.04 git://github.com/shin-/ubuntu B:raring
|
||||
@@ -0,0 +1,144 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/iptables"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Link struct {
|
||||
ParentIP string
|
||||
ChildIP string
|
||||
Name string
|
||||
BridgeInterface string
|
||||
ChildEnvironment []string
|
||||
Ports []Port
|
||||
IsEnabled bool
|
||||
}
|
||||
|
||||
func NewLink(parent, child *Container, name, bridgeInterface string) (*Link, error) {
|
||||
if parent.ID == child.ID {
|
||||
return nil, fmt.Errorf("Cannot link to self: %s == %s", parent.ID, child.ID)
|
||||
}
|
||||
if !child.State.Running {
|
||||
return nil, fmt.Errorf("Cannot link to a non running container: %s AS %s", child.Name, name)
|
||||
}
|
||||
|
||||
ports := make([]Port, len(child.Config.ExposedPorts))
|
||||
var i int
|
||||
for p := range child.Config.ExposedPorts {
|
||||
ports[i] = p
|
||||
i++
|
||||
}
|
||||
|
||||
l := &Link{
|
||||
BridgeInterface: bridgeInterface,
|
||||
Name: name,
|
||||
ChildIP: child.NetworkSettings.IPAddress,
|
||||
ParentIP: parent.NetworkSettings.IPAddress,
|
||||
ChildEnvironment: child.Config.Env,
|
||||
Ports: ports,
|
||||
}
|
||||
return l, nil
|
||||
|
||||
}
|
||||
|
||||
func (l *Link) Alias() string {
|
||||
_, alias := path.Split(l.Name)
|
||||
return alias
|
||||
}
|
||||
|
||||
func (l *Link) ToEnv() []string {
|
||||
env := []string{}
|
||||
alias := strings.ToUpper(l.Alias())
|
||||
|
||||
if p := l.getDefaultPort(); p != nil {
|
||||
env = append(env, fmt.Sprintf("%s_PORT=%s://%s:%s", alias, p.Proto(), l.ChildIP, p.Port()))
|
||||
}
|
||||
|
||||
// Load exposed ports into the environment
|
||||
for _, p := range l.Ports {
|
||||
env = append(env, fmt.Sprintf("%s_PORT_%s_%s=%s://%s:%s", alias, p.Port(), strings.ToUpper(p.Proto()), p.Proto(), l.ChildIP, p.Port()))
|
||||
env = append(env, fmt.Sprintf("%s_PORT_%s_%s_ADDR=%s", alias, p.Port(), strings.ToUpper(p.Proto()), l.ChildIP))
|
||||
env = append(env, fmt.Sprintf("%s_PORT_%s_%s_PORT=%s", alias, p.Port(), strings.ToUpper(p.Proto()), p.Port()))
|
||||
env = append(env, fmt.Sprintf("%s_PORT_%s_%s_PROTO=%s", alias, p.Port(), strings.ToUpper(p.Proto()), p.Proto()))
|
||||
}
|
||||
|
||||
// Load the linked container's name into the environment
|
||||
env = append(env, fmt.Sprintf("%s_NAME=%s", alias, l.Name))
|
||||
|
||||
if l.ChildEnvironment != nil {
|
||||
for _, v := range l.ChildEnvironment {
|
||||
parts := strings.Split(v, "=")
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
// Ignore a few variables that are added during docker build
|
||||
if parts[0] == "HOME" || parts[0] == "PATH" {
|
||||
continue
|
||||
}
|
||||
env = append(env, fmt.Sprintf("%s_ENV_%s=%s", alias, parts[0], parts[1]))
|
||||
}
|
||||
}
|
||||
return env
|
||||
}
|
||||
|
||||
// Default port rules
|
||||
func (l *Link) getDefaultPort() *Port {
|
||||
var p Port
|
||||
i := len(l.Ports)
|
||||
|
||||
if i == 0 {
|
||||
return nil
|
||||
} else if i > 1 {
|
||||
sortPorts(l.Ports, func(ip, jp Port) bool {
|
||||
// If the two ports have the same number, tcp takes priority
|
||||
// Sort in desc order
|
||||
return ip.Int() < jp.Int() || (ip.Int() == jp.Int() && strings.ToLower(ip.Proto()) == "tcp")
|
||||
})
|
||||
}
|
||||
p = l.Ports[0]
|
||||
return &p
|
||||
}
|
||||
|
||||
func (l *Link) Enable() error {
|
||||
if err := l.toggle("-I", false); err != nil {
|
||||
return err
|
||||
}
|
||||
l.IsEnabled = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Link) Disable() {
|
||||
// We do not care about errors here because the link may not
|
||||
// exist in iptables
|
||||
l.toggle("-D", true)
|
||||
|
||||
l.IsEnabled = false
|
||||
}
|
||||
|
||||
func (l *Link) toggle(action string, ignoreErrors bool) error {
|
||||
for _, p := range l.Ports {
|
||||
if err := iptables.Raw(action, "FORWARD",
|
||||
"-i", l.BridgeInterface, "-o", l.BridgeInterface,
|
||||
"-p", p.Proto(),
|
||||
"-s", l.ParentIP,
|
||||
"--dport", p.Port(),
|
||||
"-d", l.ChildIP,
|
||||
"-j", "ACCEPT"); !ignoreErrors && err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := iptables.Raw(action, "FORWARD",
|
||||
"-i", l.BridgeInterface, "-o", l.BridgeInterface,
|
||||
"-p", p.Proto(),
|
||||
"-s", l.ChildIP,
|
||||
"--sport", p.Port(),
|
||||
"-d", l.ParentIP,
|
||||
"-j", "ACCEPT"); !ignoreErrors && err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func newMockLinkContainer(id string, ip string) *Container {
|
||||
return &Container{
|
||||
Config: &Config{},
|
||||
ID: id,
|
||||
NetworkSettings: &NetworkSettings{
|
||||
IPAddress: ip,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestLinkNew(t *testing.T) {
|
||||
toID := GenerateID()
|
||||
fromID := GenerateID()
|
||||
|
||||
from := newMockLinkContainer(fromID, "172.0.17.2")
|
||||
from.Config.Env = []string{}
|
||||
from.State = State{Running: true}
|
||||
ports := make(map[Port]struct{})
|
||||
|
||||
ports[Port("6379/tcp")] = struct{}{}
|
||||
|
||||
from.Config.ExposedPorts = ports
|
||||
|
||||
to := newMockLinkContainer(toID, "172.0.17.3")
|
||||
|
||||
link, err := NewLink(to, from, "/db/docker", "172.0.17.1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if link == nil {
|
||||
t.FailNow()
|
||||
}
|
||||
if link.Name != "/db/docker" {
|
||||
t.Fail()
|
||||
}
|
||||
if link.Alias() != "docker" {
|
||||
t.Fail()
|
||||
}
|
||||
if link.ParentIP != "172.0.17.3" {
|
||||
t.Fail()
|
||||
}
|
||||
if link.ChildIP != "172.0.17.2" {
|
||||
t.Fail()
|
||||
}
|
||||
if link.BridgeInterface != "172.0.17.1" {
|
||||
t.Fail()
|
||||
}
|
||||
for _, p := range link.Ports {
|
||||
if p != Port("6379/tcp") {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLinkEnv(t *testing.T) {
|
||||
toID := GenerateID()
|
||||
fromID := GenerateID()
|
||||
|
||||
from := newMockLinkContainer(fromID, "172.0.17.2")
|
||||
from.Config.Env = []string{"PASSWORD=gordon"}
|
||||
from.State = State{Running: true}
|
||||
ports := make(map[Port]struct{})
|
||||
|
||||
ports[Port("6379/tcp")] = struct{}{}
|
||||
|
||||
from.Config.ExposedPorts = ports
|
||||
|
||||
to := newMockLinkContainer(toID, "172.0.17.3")
|
||||
|
||||
link, err := NewLink(to, from, "/db/docker", "172.0.17.1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
rawEnv := link.ToEnv()
|
||||
env := make(map[string]string, len(rawEnv))
|
||||
for _, e := range rawEnv {
|
||||
parts := strings.Split(e, "=")
|
||||
if len(parts) != 2 {
|
||||
t.FailNow()
|
||||
}
|
||||
env[parts[0]] = parts[1]
|
||||
}
|
||||
if env["DOCKER_PORT"] != "tcp://172.0.17.2:6379" {
|
||||
t.Fatalf("Expected 172.0.17.2:6379, got %s", env["DOCKER_PORT"])
|
||||
}
|
||||
if env["DOCKER_PORT_6379_TCP"] != "tcp://172.0.17.2:6379" {
|
||||
t.Fatalf("Expected tcp://172.0.17.2:6379, got %s", env["DOCKER_PORT_6379_TCP"])
|
||||
}
|
||||
if env["DOCKER_PORT_6379_TCP_PROTO"] != "tcp" {
|
||||
t.Fatalf("Expected tcp, got %s", env["DOCKER_PORT_6379_TCP_PROTO"])
|
||||
}
|
||||
if env["DOCKER_PORT_6379_TCP_ADDR"] != "172.0.17.2" {
|
||||
t.Fatalf("Expected 172.0.17.2, got %s", env["DOCKER_PORT_6379_TCP_ADDR"])
|
||||
}
|
||||
if env["DOCKER_PORT_6379_TCP_PORT"] != "6379" {
|
||||
t.Fatalf("Expected 6379, got %s", env["DOCKER_PORT_6379_TCP_PORT"])
|
||||
}
|
||||
if env["DOCKER_NAME"] != "/db/docker" {
|
||||
t.Fatalf("Expected /db/docker, got %s", env["DOCKER_NAME"])
|
||||
}
|
||||
if env["DOCKER_ENV_PASSWORD"] != "gordon" {
|
||||
t.Fatalf("Expected gordon, got %s", env["DOCKER_ENV_PASSWORD"])
|
||||
}
|
||||
}
|
||||
+3
-1
@@ -81,6 +81,8 @@ lxc.cgroup.devices.allow = c 10:200 rwm
|
||||
{{end}}
|
||||
|
||||
# standard mount point
|
||||
# Use mnt.putold as per https://bugs.launchpad.net/ubuntu/+source/lxc/+bug/986385
|
||||
lxc.pivotdir = lxc_putold
|
||||
# WARNING: procfs is a known attack vector and should probably be disabled
|
||||
# if your userspace allows it. eg. see http://blog.zx2c4.com/749
|
||||
lxc.mount.entry = proc {{$ROOTFS}}/proc proc nosuid,nodev,noexec 0 0
|
||||
@@ -92,7 +94,7 @@ lxc.mount.entry = devpts {{$ROOTFS}}/dev/pts devpts newinstance,ptmxmode=0666,no
|
||||
#lxc.mount.entry = varlock {{$ROOTFS}}/var/lock tmpfs size=1024k,nosuid,nodev,noexec 0 0
|
||||
lxc.mount.entry = shm {{$ROOTFS}}/dev/shm tmpfs size=65536k,nosuid,nodev,noexec 0 0
|
||||
|
||||
# Inject docker-init
|
||||
# Inject dockerinit
|
||||
lxc.mount.entry = {{.SysInitPath}} {{$ROOTFS}}/.dockerinit none bind,ro 0 0
|
||||
|
||||
# In order to get a working DNS environment, mount bind (ro) the host's /etc/resolv.conf into the container
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package namesgenerator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
type NameChecker interface {
|
||||
Exists(name string) bool
|
||||
}
|
||||
|
||||
var (
|
||||
colors = [...]string{"white", "silver", "gray", "black", "blue", "green", "cyan", "yellow", "gold", "orange", "brown", "red", "violet", "pink", "magenta", "purple"}
|
||||
animals = [...]string{"ant", "bird", "cat", "chicken", "cow", "dog", "fish", "fox", "horse", "lion", "monkey", "pig", "sheep", "tiger", "whale", "wolf"}
|
||||
)
|
||||
|
||||
func GenerateRandomName(checker NameChecker) (string, error) {
|
||||
retry := 5
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
name := fmt.Sprintf("%s_%s", colors[rand.Intn(len(colors))], animals[rand.Intn(len(animals))])
|
||||
for checker != nil && checker.Exists(name) && retry > 0 {
|
||||
name = fmt.Sprintf("%s%d", name, rand.Intn(10))
|
||||
retry = retry - 1
|
||||
}
|
||||
if retry == 0 {
|
||||
return name, fmt.Errorf("Error generating random name")
|
||||
}
|
||||
return name, nil
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package namesgenerator
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
type FalseChecker struct{}
|
||||
|
||||
func (n *FalseChecker) Exists(name string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
type TrueChecker struct{}
|
||||
|
||||
func (n *TrueChecker) Exists(name string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func TestGenerateRandomName(t *testing.T) {
|
||||
if _, err := GenerateRandomName(&FalseChecker{}); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if _, err := GenerateRandomName(&TrueChecker{}); err == nil {
|
||||
t.Error("An error was expected")
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,548 @@
|
||||
package netlink
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"net"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var nextSeqNr int
|
||||
|
||||
func nativeEndian() binary.ByteOrder {
|
||||
var x uint32 = 0x01020304
|
||||
if *(*byte)(unsafe.Pointer(&x)) == 0x01 {
|
||||
return binary.BigEndian
|
||||
}
|
||||
return binary.LittleEndian
|
||||
}
|
||||
|
||||
func getSeq() int {
|
||||
nextSeqNr = nextSeqNr + 1
|
||||
return nextSeqNr
|
||||
}
|
||||
|
||||
func getIpFamily(ip net.IP) int {
|
||||
if len(ip) <= net.IPv4len {
|
||||
return syscall.AF_INET
|
||||
}
|
||||
if ip.To4() != nil {
|
||||
return syscall.AF_INET
|
||||
}
|
||||
return syscall.AF_INET6
|
||||
}
|
||||
|
||||
type NetlinkRequestData interface {
|
||||
ToWireFormat() []byte
|
||||
}
|
||||
|
||||
type IfInfomsg struct {
|
||||
syscall.IfInfomsg
|
||||
}
|
||||
|
||||
func newIfInfomsg(family int) *IfInfomsg {
|
||||
msg := &IfInfomsg{}
|
||||
msg.Family = uint8(family)
|
||||
msg.Type = uint16(0)
|
||||
msg.Index = int32(0)
|
||||
msg.Flags = uint32(0)
|
||||
msg.Change = uint32(0)
|
||||
|
||||
return msg
|
||||
}
|
||||
|
||||
func (msg *IfInfomsg) ToWireFormat() []byte {
|
||||
native := nativeEndian()
|
||||
|
||||
len := syscall.SizeofIfInfomsg
|
||||
b := make([]byte, len)
|
||||
b[0] = msg.Family
|
||||
b[1] = 0
|
||||
native.PutUint16(b[2:4], msg.Type)
|
||||
native.PutUint32(b[4:8], uint32(msg.Index))
|
||||
native.PutUint32(b[8:12], msg.Flags)
|
||||
native.PutUint32(b[12:16], msg.Change)
|
||||
return b
|
||||
}
|
||||
|
||||
type IfAddrmsg struct {
|
||||
syscall.IfAddrmsg
|
||||
}
|
||||
|
||||
func newIfAddrmsg(family int) *IfAddrmsg {
|
||||
msg := &IfAddrmsg{}
|
||||
msg.Family = uint8(family)
|
||||
msg.Prefixlen = uint8(0)
|
||||
msg.Flags = uint8(0)
|
||||
msg.Scope = uint8(0)
|
||||
msg.Index = uint32(0)
|
||||
|
||||
return msg
|
||||
}
|
||||
|
||||
func (msg *IfAddrmsg) ToWireFormat() []byte {
|
||||
native := nativeEndian()
|
||||
|
||||
len := syscall.SizeofIfAddrmsg
|
||||
b := make([]byte, len)
|
||||
b[0] = msg.Family
|
||||
b[1] = msg.Prefixlen
|
||||
b[2] = msg.Flags
|
||||
b[3] = msg.Scope
|
||||
native.PutUint32(b[4:8], msg.Index)
|
||||
return b
|
||||
}
|
||||
|
||||
type RtMsg struct {
|
||||
syscall.RtMsg
|
||||
}
|
||||
|
||||
func newRtMsg(family int) *RtMsg {
|
||||
msg := &RtMsg{}
|
||||
msg.Family = uint8(family)
|
||||
msg.Table = syscall.RT_TABLE_MAIN
|
||||
msg.Scope = syscall.RT_SCOPE_UNIVERSE
|
||||
msg.Protocol = syscall.RTPROT_BOOT
|
||||
msg.Type = syscall.RTN_UNICAST
|
||||
|
||||
return msg
|
||||
}
|
||||
|
||||
func (msg *RtMsg) ToWireFormat() []byte {
|
||||
native := nativeEndian()
|
||||
|
||||
len := syscall.SizeofRtMsg
|
||||
b := make([]byte, len)
|
||||
b[0] = msg.Family
|
||||
b[1] = msg.Dst_len
|
||||
b[2] = msg.Src_len
|
||||
b[3] = msg.Tos
|
||||
b[4] = msg.Table
|
||||
b[5] = msg.Protocol
|
||||
b[6] = msg.Scope
|
||||
b[7] = msg.Type
|
||||
native.PutUint32(b[8:12], msg.Flags)
|
||||
return b
|
||||
}
|
||||
|
||||
func rtaAlignOf(attrlen int) int {
|
||||
return (attrlen + syscall.RTA_ALIGNTO - 1) & ^(syscall.RTA_ALIGNTO - 1)
|
||||
}
|
||||
|
||||
type RtAttr struct {
|
||||
syscall.RtAttr
|
||||
Data []byte
|
||||
}
|
||||
|
||||
func newRtAttr(attrType int, data []byte) *RtAttr {
|
||||
attr := &RtAttr{}
|
||||
attr.Type = uint16(attrType)
|
||||
attr.Data = data
|
||||
|
||||
return attr
|
||||
}
|
||||
|
||||
func (attr *RtAttr) ToWireFormat() []byte {
|
||||
native := nativeEndian()
|
||||
|
||||
len := syscall.SizeofRtAttr + len(attr.Data)
|
||||
b := make([]byte, rtaAlignOf(len))
|
||||
native.PutUint16(b[0:2], uint16(len))
|
||||
native.PutUint16(b[2:4], attr.Type)
|
||||
for i, d := range attr.Data {
|
||||
b[4+i] = d
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
type NetlinkRequest struct {
|
||||
syscall.NlMsghdr
|
||||
Data []NetlinkRequestData
|
||||
}
|
||||
|
||||
func (rr *NetlinkRequest) ToWireFormat() []byte {
|
||||
native := nativeEndian()
|
||||
|
||||
length := rr.Len
|
||||
dataBytes := make([][]byte, len(rr.Data))
|
||||
for i, data := range rr.Data {
|
||||
dataBytes[i] = data.ToWireFormat()
|
||||
length = length + uint32(len(dataBytes[i]))
|
||||
}
|
||||
b := make([]byte, length)
|
||||
native.PutUint32(b[0:4], length)
|
||||
native.PutUint16(b[4:6], rr.Type)
|
||||
native.PutUint16(b[6:8], rr.Flags)
|
||||
native.PutUint32(b[8:12], rr.Seq)
|
||||
native.PutUint32(b[12:16], rr.Pid)
|
||||
|
||||
i := 16
|
||||
for _, data := range dataBytes {
|
||||
for _, dataByte := range data {
|
||||
b[i] = dataByte
|
||||
i = i + 1
|
||||
}
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (rr *NetlinkRequest) AddData(data NetlinkRequestData) {
|
||||
rr.Data = append(rr.Data, data)
|
||||
}
|
||||
|
||||
func newNetlinkRequest(proto, flags int) *NetlinkRequest {
|
||||
rr := &NetlinkRequest{}
|
||||
rr.Len = uint32(syscall.NLMSG_HDRLEN)
|
||||
rr.Type = uint16(proto)
|
||||
rr.Flags = syscall.NLM_F_REQUEST | uint16(flags)
|
||||
rr.Seq = uint32(getSeq())
|
||||
return rr
|
||||
}
|
||||
|
||||
type NetlinkSocket struct {
|
||||
fd int
|
||||
lsa syscall.SockaddrNetlink
|
||||
}
|
||||
|
||||
func getNetlinkSocket() (*NetlinkSocket, error) {
|
||||
fd, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_RAW, syscall.NETLINK_ROUTE)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s := &NetlinkSocket{
|
||||
fd: fd,
|
||||
}
|
||||
s.lsa.Family = syscall.AF_NETLINK
|
||||
if err := syscall.Bind(fd, &s.lsa); err != nil {
|
||||
syscall.Close(fd)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *NetlinkSocket) Close() {
|
||||
syscall.Close(s.fd)
|
||||
}
|
||||
|
||||
func (s *NetlinkSocket) Send(request *NetlinkRequest) error {
|
||||
if err := syscall.Sendto(s.fd, request.ToWireFormat(), 0, &s.lsa); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *NetlinkSocket) Recieve() ([]syscall.NetlinkMessage, error) {
|
||||
rb := make([]byte, syscall.Getpagesize())
|
||||
nr, _, err := syscall.Recvfrom(s.fd, rb, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if nr < syscall.NLMSG_HDRLEN {
|
||||
return nil, fmt.Errorf("Got short response from netlink")
|
||||
}
|
||||
rb = rb[:nr]
|
||||
return syscall.ParseNetlinkMessage(rb)
|
||||
}
|
||||
|
||||
func (s *NetlinkSocket) GetPid() (uint32, error) {
|
||||
lsa, err := syscall.Getsockname(s.fd)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
switch v := lsa.(type) {
|
||||
case *syscall.SockaddrNetlink:
|
||||
return v.Pid, nil
|
||||
}
|
||||
return 0, fmt.Errorf("Wrong socket type")
|
||||
}
|
||||
|
||||
func (s *NetlinkSocket) HandleAck(seq uint32) error {
|
||||
native := nativeEndian()
|
||||
|
||||
pid, err := s.GetPid()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
done:
|
||||
for {
|
||||
msgs, err := s.Recieve()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, m := range msgs {
|
||||
if m.Header.Seq != seq {
|
||||
return fmt.Errorf("Wrong Seq nr %d, expected %d", m.Header.Seq, seq)
|
||||
}
|
||||
if m.Header.Pid != pid {
|
||||
return fmt.Errorf("Wrong pid %d, expected %d", m.Header.Pid, pid)
|
||||
}
|
||||
if m.Header.Type == syscall.NLMSG_DONE {
|
||||
break done
|
||||
}
|
||||
if m.Header.Type == syscall.NLMSG_ERROR {
|
||||
error := int32(native.Uint32(m.Data[0:4]))
|
||||
if error == 0 {
|
||||
break done
|
||||
}
|
||||
return syscall.Errno(-error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Add a new default gateway. Identical to:
|
||||
// ip route add default via $ip
|
||||
func AddDefaultGw(ip net.IP) error {
|
||||
s, err := getNetlinkSocket()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
family := getIpFamily(ip)
|
||||
|
||||
wb := newNetlinkRequest(syscall.RTM_NEWROUTE, syscall.NLM_F_CREATE|syscall.NLM_F_EXCL|syscall.NLM_F_ACK)
|
||||
|
||||
msg := newRtMsg(family)
|
||||
wb.AddData(msg)
|
||||
|
||||
var ipData []byte
|
||||
if family == syscall.AF_INET {
|
||||
ipData = ip.To4()
|
||||
} else {
|
||||
ipData = ip.To16()
|
||||
}
|
||||
|
||||
gateway := newRtAttr(syscall.RTA_GATEWAY, ipData)
|
||||
|
||||
wb.AddData(gateway)
|
||||
|
||||
if err := s.Send(wb); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.HandleAck(wb.Seq)
|
||||
|
||||
}
|
||||
|
||||
// Bring up a particular network interface
|
||||
func NetworkLinkUp(iface *net.Interface) error {
|
||||
s, err := getNetlinkSocket()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
wb := newNetlinkRequest(syscall.RTM_NEWLINK, syscall.NLM_F_ACK)
|
||||
|
||||
msg := newIfInfomsg(syscall.AF_UNSPEC)
|
||||
msg.Change = syscall.IFF_UP
|
||||
msg.Flags = syscall.IFF_UP
|
||||
msg.Index = int32(iface.Index)
|
||||
wb.AddData(msg)
|
||||
|
||||
if err := s.Send(wb); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.HandleAck(wb.Seq)
|
||||
}
|
||||
|
||||
// Add an Ip address to an interface. This is identical to:
|
||||
// ip addr add $ip/$ipNet dev $iface
|
||||
func NetworkLinkAddIp(iface *net.Interface, ip net.IP, ipNet *net.IPNet) error {
|
||||
s, err := getNetlinkSocket()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
family := getIpFamily(ip)
|
||||
|
||||
wb := newNetlinkRequest(syscall.RTM_NEWADDR, syscall.NLM_F_CREATE|syscall.NLM_F_EXCL|syscall.NLM_F_ACK)
|
||||
|
||||
msg := newIfAddrmsg(family)
|
||||
msg.Index = uint32(iface.Index)
|
||||
prefixLen, _ := ipNet.Mask.Size()
|
||||
msg.Prefixlen = uint8(prefixLen)
|
||||
wb.AddData(msg)
|
||||
|
||||
var ipData []byte
|
||||
if family == syscall.AF_INET {
|
||||
ipData = ip.To4()
|
||||
} else {
|
||||
ipData = ip.To16()
|
||||
}
|
||||
|
||||
localData := newRtAttr(syscall.IFA_LOCAL, ipData)
|
||||
wb.AddData(localData)
|
||||
|
||||
addrData := newRtAttr(syscall.IFA_ADDRESS, ipData)
|
||||
wb.AddData(addrData)
|
||||
|
||||
if err := s.Send(wb); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.HandleAck(wb.Seq)
|
||||
}
|
||||
|
||||
func zeroTerminated(s string) []byte {
|
||||
bytes := make([]byte, len(s)+1)
|
||||
for i := 0; i < len(s); i++ {
|
||||
bytes[i] = s[i]
|
||||
}
|
||||
bytes[len(s)] = 0
|
||||
return bytes
|
||||
}
|
||||
|
||||
func nonZeroTerminated(s string) []byte {
|
||||
bytes := make([]byte, len(s))
|
||||
for i := 0; i < len(s); i++ {
|
||||
bytes[i] = s[i]
|
||||
}
|
||||
return bytes
|
||||
}
|
||||
|
||||
// Add a new network link of a specified type. This is identical to
|
||||
// running: ip add link $name type $linkType
|
||||
func NetworkLinkAdd(name string, linkType string) error {
|
||||
s, err := getNetlinkSocket()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
wb := newNetlinkRequest(syscall.RTM_NEWLINK, syscall.NLM_F_CREATE|syscall.NLM_F_EXCL|syscall.NLM_F_ACK)
|
||||
|
||||
msg := newIfInfomsg(syscall.AF_UNSPEC)
|
||||
wb.AddData(msg)
|
||||
|
||||
nameData := newRtAttr(syscall.IFLA_IFNAME, zeroTerminated(name))
|
||||
wb.AddData(nameData)
|
||||
|
||||
IFLA_INFO_KIND := 1
|
||||
|
||||
kindData := newRtAttr(IFLA_INFO_KIND, nonZeroTerminated(linkType))
|
||||
|
||||
infoData := newRtAttr(syscall.IFLA_LINKINFO, kindData.ToWireFormat())
|
||||
wb.AddData(infoData)
|
||||
|
||||
if err := s.Send(wb); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.HandleAck(wb.Seq)
|
||||
}
|
||||
|
||||
// Returns an array of IPNet for all the currently routed subnets on ipv4
|
||||
// This is similar to the first column of "ip route" output
|
||||
func NetworkGetRoutes() ([]*net.IPNet, error) {
|
||||
native := nativeEndian()
|
||||
|
||||
s, err := getNetlinkSocket()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
wb := newNetlinkRequest(syscall.RTM_GETROUTE, syscall.NLM_F_DUMP)
|
||||
|
||||
msg := newIfInfomsg(syscall.AF_UNSPEC)
|
||||
wb.AddData(msg)
|
||||
|
||||
if err := s.Send(wb); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pid, err := s.GetPid()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res := make([]*net.IPNet, 0)
|
||||
|
||||
done:
|
||||
for {
|
||||
msgs, err := s.Recieve()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, m := range msgs {
|
||||
if m.Header.Seq != wb.Seq {
|
||||
return nil, fmt.Errorf("Wrong Seq nr %d, expected 1", m.Header.Seq)
|
||||
}
|
||||
if m.Header.Pid != pid {
|
||||
return nil, fmt.Errorf("Wrong pid %d, expected %d", m.Header.Pid, pid)
|
||||
}
|
||||
if m.Header.Type == syscall.NLMSG_DONE {
|
||||
break done
|
||||
}
|
||||
if m.Header.Type == syscall.NLMSG_ERROR {
|
||||
error := int32(native.Uint32(m.Data[0:4]))
|
||||
if error == 0 {
|
||||
break done
|
||||
}
|
||||
return nil, syscall.Errno(-error)
|
||||
}
|
||||
if m.Header.Type != syscall.RTM_NEWROUTE {
|
||||
continue
|
||||
}
|
||||
|
||||
var iface *net.Interface = nil
|
||||
var ipNet *net.IPNet = nil
|
||||
|
||||
msg := (*RtMsg)(unsafe.Pointer(&m.Data[0:syscall.SizeofRtMsg][0]))
|
||||
|
||||
if msg.Flags&syscall.RTM_F_CLONED != 0 {
|
||||
// Ignore cloned routes
|
||||
continue
|
||||
}
|
||||
|
||||
if msg.Table != syscall.RT_TABLE_MAIN {
|
||||
// Ignore non-main tables
|
||||
continue
|
||||
}
|
||||
|
||||
if msg.Family != syscall.AF_INET {
|
||||
// Ignore non-ipv4 routes
|
||||
continue
|
||||
}
|
||||
|
||||
if msg.Dst_len == 0 {
|
||||
// Ignore default routes
|
||||
continue
|
||||
}
|
||||
|
||||
attrs, err := syscall.ParseNetlinkRouteAttr(&m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, attr := range attrs {
|
||||
switch attr.Attr.Type {
|
||||
case syscall.RTA_DST:
|
||||
ip := attr.Value
|
||||
ipNet = &net.IPNet{
|
||||
IP: ip,
|
||||
Mask: net.CIDRMask(int(msg.Dst_len), 8*len(ip)),
|
||||
}
|
||||
case syscall.RTA_OIF:
|
||||
index := int(native.Uint32(attr.Value[0:4]))
|
||||
iface, _ = net.InterfaceByIndex(index)
|
||||
_ = iface
|
||||
}
|
||||
}
|
||||
if ipNet != nil {
|
||||
res = append(res, ipNet)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
+148
-208
@@ -4,17 +4,16 @@ import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/iptables"
|
||||
"github.com/dotcloud/docker/netlink"
|
||||
"github.com/dotcloud/docker/proxy"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"log"
|
||||
"net"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var NetworkBridgeIface string
|
||||
|
||||
const (
|
||||
DefaultNetworkBridge = "docker0"
|
||||
DisableNetworkBridge = "none"
|
||||
@@ -68,54 +67,10 @@ func networkSize(mask net.IPMask) int32 {
|
||||
return int32(binary.BigEndian.Uint32(m)) + 1
|
||||
}
|
||||
|
||||
//Wrapper around the ip command
|
||||
func ip(args ...string) (string, error) {
|
||||
path, err := exec.LookPath("ip")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("command not found: ip")
|
||||
}
|
||||
output, err := exec.Command(path, args...).CombinedOutput()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("ip failed: ip %v", strings.Join(args, " "))
|
||||
}
|
||||
return string(output), nil
|
||||
}
|
||||
|
||||
// Wrapper around the iptables command
|
||||
func iptables(args ...string) error {
|
||||
path, err := exec.LookPath("iptables")
|
||||
if err != nil {
|
||||
return fmt.Errorf("command not found: iptables")
|
||||
}
|
||||
if err := exec.Command(path, args...).Run(); err != nil {
|
||||
return fmt.Errorf("iptables failed: iptables %v", strings.Join(args, " "))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkRouteOverlaps(routes string, dockerNetwork *net.IPNet) error {
|
||||
utils.Debugf("Routes:\n\n%s", routes)
|
||||
for _, line := range strings.Split(routes, "\n") {
|
||||
if strings.Trim(line, "\r\n\t ") == "" || strings.Contains(line, "default") {
|
||||
continue
|
||||
}
|
||||
_, network, err := net.ParseCIDR(strings.Split(line, " ")[0])
|
||||
if err != nil {
|
||||
// is this a mask-less IP address?
|
||||
if ip := net.ParseIP(strings.Split(line, " ")[0]); ip == nil {
|
||||
// fail only if it's neither a network nor a mask-less IP address
|
||||
return fmt.Errorf("Unexpected ip route output: %s (%s)", err, line)
|
||||
} else {
|
||||
_, network, err = net.ParseCIDR(ip.String() + "/32")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if err == nil && network != nil {
|
||||
if networkOverlaps(dockerNetwork, network) {
|
||||
return fmt.Errorf("Network %s is already routed: '%s'", dockerNetwork, line)
|
||||
}
|
||||
func checkRouteOverlaps(networks []*net.IPNet, dockerNetwork *net.IPNet) error {
|
||||
for _, network := range networks {
|
||||
if networkOverlaps(dockerNetwork, network) {
|
||||
return fmt.Errorf("Network %s is already routed: '%s'", dockerNetwork, network)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -124,7 +79,7 @@ func checkRouteOverlaps(routes string, dockerNetwork *net.IPNet) error {
|
||||
// CreateBridgeIface creates a network bridge interface on the host system with the name `ifaceName`,
|
||||
// and attempts to configure it with an address which doesn't conflict with any other interface on the host.
|
||||
// If it can't find an address which doesn't conflict, it will return an error.
|
||||
func CreateBridgeIface(ifaceName string) error {
|
||||
func CreateBridgeIface(config *DaemonConfig) error {
|
||||
addrs := []string{
|
||||
// Here we don't follow the convention of using the 1st IP of the range for the gateway.
|
||||
// This is to use the same gateway IPs as the /24 ranges, which predate the /16 ranges.
|
||||
@@ -151,7 +106,7 @@ func CreateBridgeIface(ifaceName string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
routes, err := ip("route")
|
||||
routes, err := netlink.NetworkGetRoutes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -163,23 +118,33 @@ func CreateBridgeIface(ifaceName string) error {
|
||||
}
|
||||
}
|
||||
if ifaceAddr == "" {
|
||||
return fmt.Errorf("Could not find a free IP address range for interface '%s'. Please configure its address manually and run 'docker -b %s'", ifaceName, ifaceName)
|
||||
return fmt.Errorf("Could not find a free IP address range for interface '%s'. Please configure its address manually and run 'docker -b %s'", config.BridgeIface, config.BridgeIface)
|
||||
}
|
||||
utils.Debugf("Creating bridge %s with network %s", ifaceName, ifaceAddr)
|
||||
utils.Debugf("Creating bridge %s with network %s", config.BridgeIface, ifaceAddr)
|
||||
|
||||
if output, err := ip("link", "add", ifaceName, "type", "bridge"); err != nil {
|
||||
return fmt.Errorf("Error creating bridge: %s (output: %s)", err, output)
|
||||
if err := netlink.NetworkLinkAdd(config.BridgeIface, "bridge"); err != nil {
|
||||
return fmt.Errorf("Error creating bridge: %s", err)
|
||||
}
|
||||
iface, err := net.InterfaceByName(config.BridgeIface)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ipAddr, ipNet, err := net.ParseCIDR(ifaceAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if netlink.NetworkLinkAddIp(iface, ipAddr, ipNet); err != nil {
|
||||
return fmt.Errorf("Unable to add private network: %s", err)
|
||||
}
|
||||
if err := netlink.NetworkLinkUp(iface); err != nil {
|
||||
return fmt.Errorf("Unable to start network bridge: %s", err)
|
||||
}
|
||||
|
||||
if output, err := ip("addr", "add", ifaceAddr, "dev", ifaceName); err != nil {
|
||||
return fmt.Errorf("Unable to add private network: %s (%s)", err, output)
|
||||
}
|
||||
if output, err := ip("link", "set", ifaceName, "up"); err != nil {
|
||||
return fmt.Errorf("Unable to start network bridge: %s (%s)", err, output)
|
||||
}
|
||||
if err := iptables("-t", "nat", "-A", "POSTROUTING", "-s", ifaceAddr,
|
||||
"!", "-d", ifaceAddr, "-j", "MASQUERADE"); err != nil {
|
||||
return fmt.Errorf("Unable to enable network bridge NAT: %s", err)
|
||||
if config.EnableIptables {
|
||||
if err := iptables.Raw("-t", "nat", "-A", "POSTROUTING", "-s", ifaceAddr,
|
||||
"!", "-d", ifaceAddr, "-j", "MASQUERADE"); err != nil {
|
||||
return fmt.Errorf("Unable to enable network bridge NAT: %s", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -216,58 +181,27 @@ func getIfaceAddr(name string) (net.Addr, error) {
|
||||
// It keeps track of all mappings and is able to unmap at will
|
||||
type PortMapper struct {
|
||||
tcpMapping map[int]*net.TCPAddr
|
||||
tcpProxies map[int]Proxy
|
||||
tcpProxies map[int]proxy.Proxy
|
||||
udpMapping map[int]*net.UDPAddr
|
||||
udpProxies map[int]Proxy
|
||||
udpProxies map[int]proxy.Proxy
|
||||
|
||||
iptables *iptables.Chain
|
||||
defaultIp net.IP
|
||||
}
|
||||
|
||||
func (mapper *PortMapper) cleanup() error {
|
||||
// Ignore errors - This could mean the chains were never set up
|
||||
iptables("-t", "nat", "-D", "PREROUTING", "-m", "addrtype", "--dst-type", "LOCAL", "-j", "DOCKER")
|
||||
iptables("-t", "nat", "-D", "OUTPUT", "-m", "addrtype", "--dst-type", "LOCAL", "!", "--dst", "127.0.0.0/8", "-j", "DOCKER")
|
||||
iptables("-t", "nat", "-D", "OUTPUT", "-m", "addrtype", "--dst-type", "LOCAL", "-j", "DOCKER") // Created in versions <= 0.1.6
|
||||
// Also cleanup rules created by older versions, or -X might fail.
|
||||
iptables("-t", "nat", "-D", "PREROUTING", "-j", "DOCKER")
|
||||
iptables("-t", "nat", "-D", "OUTPUT", "-j", "DOCKER")
|
||||
iptables("-t", "nat", "-F", "DOCKER")
|
||||
iptables("-t", "nat", "-X", "DOCKER")
|
||||
mapper.tcpMapping = make(map[int]*net.TCPAddr)
|
||||
mapper.tcpProxies = make(map[int]Proxy)
|
||||
mapper.udpMapping = make(map[int]*net.UDPAddr)
|
||||
mapper.udpProxies = make(map[int]Proxy)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mapper *PortMapper) setup() error {
|
||||
if err := iptables("-t", "nat", "-N", "DOCKER"); err != nil {
|
||||
return fmt.Errorf("Failed to create DOCKER chain: %s", err)
|
||||
}
|
||||
if err := iptables("-t", "nat", "-A", "PREROUTING", "-m", "addrtype", "--dst-type", "LOCAL", "-j", "DOCKER"); err != nil {
|
||||
return fmt.Errorf("Failed to inject docker in PREROUTING chain: %s", err)
|
||||
}
|
||||
if err := iptables("-t", "nat", "-A", "OUTPUT", "-m", "addrtype", "--dst-type", "LOCAL", "!", "--dst", "127.0.0.0/8", "-j", "DOCKER"); err != nil {
|
||||
return fmt.Errorf("Failed to inject docker in OUTPUT chain: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mapper *PortMapper) iptablesForward(rule string, port int, proto string, dest_addr string, dest_port int) error {
|
||||
return iptables("-t", "nat", rule, "DOCKER", "-p", proto, "--dport", strconv.Itoa(port),
|
||||
"!", "-i", NetworkBridgeIface,
|
||||
"-j", "DNAT", "--to-destination", net.JoinHostPort(dest_addr, strconv.Itoa(dest_port)))
|
||||
}
|
||||
|
||||
func (mapper *PortMapper) Map(port int, backendAddr net.Addr) error {
|
||||
func (mapper *PortMapper) Map(ip net.IP, port int, backendAddr net.Addr) error {
|
||||
if _, isTCP := backendAddr.(*net.TCPAddr); isTCP {
|
||||
backendPort := backendAddr.(*net.TCPAddr).Port
|
||||
backendIP := backendAddr.(*net.TCPAddr).IP
|
||||
if err := mapper.iptablesForward("-A", port, "tcp", backendIP.String(), backendPort); err != nil {
|
||||
return err
|
||||
if mapper.iptables != nil {
|
||||
if err := mapper.iptables.Forward(iptables.Add, ip, port, "tcp", backendIP.String(), backendPort); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
mapper.tcpMapping[port] = backendAddr.(*net.TCPAddr)
|
||||
proxy, err := NewProxy(&net.TCPAddr{IP: net.IPv4(0, 0, 0, 0), Port: port}, backendAddr)
|
||||
proxy, err := proxy.NewProxy(&net.TCPAddr{IP: ip, Port: port}, backendAddr)
|
||||
if err != nil {
|
||||
mapper.Unmap(port, "tcp")
|
||||
mapper.Unmap(ip, port, "tcp")
|
||||
return err
|
||||
}
|
||||
mapper.tcpProxies[port] = proxy
|
||||
@@ -275,13 +209,15 @@ func (mapper *PortMapper) Map(port int, backendAddr net.Addr) error {
|
||||
} else {
|
||||
backendPort := backendAddr.(*net.UDPAddr).Port
|
||||
backendIP := backendAddr.(*net.UDPAddr).IP
|
||||
if err := mapper.iptablesForward("-A", port, "udp", backendIP.String(), backendPort); err != nil {
|
||||
return err
|
||||
if mapper.iptables != nil {
|
||||
if err := mapper.iptables.Forward(iptables.Add, ip, port, "udp", backendIP.String(), backendPort); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
mapper.udpMapping[port] = backendAddr.(*net.UDPAddr)
|
||||
proxy, err := NewProxy(&net.UDPAddr{IP: net.IPv4(0, 0, 0, 0), Port: port}, backendAddr)
|
||||
proxy, err := proxy.NewProxy(&net.UDPAddr{IP: ip, Port: port}, backendAddr)
|
||||
if err != nil {
|
||||
mapper.Unmap(port, "udp")
|
||||
mapper.Unmap(ip, port, "udp")
|
||||
return err
|
||||
}
|
||||
mapper.udpProxies[port] = proxy
|
||||
@@ -290,7 +226,7 @@ func (mapper *PortMapper) Map(port int, backendAddr net.Addr) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mapper *PortMapper) Unmap(port int, proto string) error {
|
||||
func (mapper *PortMapper) Unmap(ip net.IP, port int, proto string) error {
|
||||
if proto == "tcp" {
|
||||
backendAddr, ok := mapper.tcpMapping[port]
|
||||
if !ok {
|
||||
@@ -300,8 +236,10 @@ func (mapper *PortMapper) Unmap(port int, proto string) error {
|
||||
proxy.Close()
|
||||
delete(mapper.tcpProxies, port)
|
||||
}
|
||||
if err := mapper.iptablesForward("-D", port, proto, backendAddr.IP.String(), backendAddr.Port); err != nil {
|
||||
return err
|
||||
if mapper.iptables != nil {
|
||||
if err := mapper.iptables.Forward(iptables.Delete, ip, port, proto, backendAddr.IP.String(), backendAddr.Port); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
delete(mapper.tcpMapping, port)
|
||||
} else {
|
||||
@@ -313,21 +251,37 @@ func (mapper *PortMapper) Unmap(port int, proto string) error {
|
||||
proxy.Close()
|
||||
delete(mapper.udpProxies, port)
|
||||
}
|
||||
if err := mapper.iptablesForward("-D", port, proto, backendAddr.IP.String(), backendAddr.Port); err != nil {
|
||||
return err
|
||||
if mapper.iptables != nil {
|
||||
if err := mapper.iptables.Forward(iptables.Delete, ip, port, proto, backendAddr.IP.String(), backendAddr.Port); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
delete(mapper.udpMapping, port)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func newPortMapper() (*PortMapper, error) {
|
||||
mapper := &PortMapper{}
|
||||
if err := mapper.cleanup(); err != nil {
|
||||
func newPortMapper(config *DaemonConfig) (*PortMapper, error) {
|
||||
// We can always try removing the iptables
|
||||
if err := iptables.RemoveExistingChain("DOCKER"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := mapper.setup(); err != nil {
|
||||
return nil, err
|
||||
var chain *iptables.Chain
|
||||
if config.EnableIptables {
|
||||
var err error
|
||||
chain, err = iptables.NewChain("DOCKER", config.BridgeIface)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to create DOCKER chain: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
mapper := &PortMapper{
|
||||
tcpMapping: make(map[int]*net.TCPAddr),
|
||||
tcpProxies: make(map[int]proxy.Proxy),
|
||||
udpMapping: make(map[int]*net.UDPAddr),
|
||||
udpProxies: make(map[int]proxy.Proxy),
|
||||
iptables: chain,
|
||||
defaultIp: config.DefaultIp,
|
||||
}
|
||||
return mapper, nil
|
||||
}
|
||||
@@ -519,40 +473,56 @@ type NetworkInterface struct {
|
||||
disabled bool
|
||||
}
|
||||
|
||||
// Allocate an external TCP port and map it to the interface
|
||||
func (iface *NetworkInterface) AllocatePort(spec string) (*Nat, error) {
|
||||
// Allocate an external port and map it to the interface
|
||||
func (iface *NetworkInterface) AllocatePort(port Port, binding PortBinding) (*Nat, error) {
|
||||
|
||||
if iface.disabled {
|
||||
return nil, fmt.Errorf("Trying to allocate port for interface %v, which is disabled", iface) // FIXME
|
||||
}
|
||||
|
||||
nat, err := parseNat(spec)
|
||||
ip := iface.manager.portMapper.defaultIp
|
||||
|
||||
if binding.HostIp != "" {
|
||||
ip = net.ParseIP(binding.HostIp)
|
||||
} else {
|
||||
binding.HostIp = ip.String()
|
||||
}
|
||||
|
||||
nat := &Nat{
|
||||
Port: port,
|
||||
Binding: binding,
|
||||
}
|
||||
|
||||
containerPort, err := parsePort(port.Port())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if nat.Proto == "tcp" {
|
||||
extPort, err := iface.manager.tcpPortAllocator.Acquire(nat.Frontend)
|
||||
hostPort, _ := parsePort(nat.Binding.HostPort)
|
||||
|
||||
if nat.Port.Proto() == "tcp" {
|
||||
extPort, err := iface.manager.tcpPortAllocator.Acquire(hostPort)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
backend := &net.TCPAddr{IP: iface.IPNet.IP, Port: nat.Backend}
|
||||
if err := iface.manager.portMapper.Map(extPort, backend); err != nil {
|
||||
|
||||
backend := &net.TCPAddr{IP: iface.IPNet.IP, Port: containerPort}
|
||||
if err := iface.manager.portMapper.Map(ip, extPort, backend); err != nil {
|
||||
iface.manager.tcpPortAllocator.Release(extPort)
|
||||
return nil, err
|
||||
}
|
||||
nat.Frontend = extPort
|
||||
nat.Binding.HostPort = strconv.Itoa(extPort)
|
||||
} else {
|
||||
extPort, err := iface.manager.udpPortAllocator.Acquire(nat.Frontend)
|
||||
extPort, err := iface.manager.udpPortAllocator.Acquire(hostPort)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
backend := &net.UDPAddr{IP: iface.IPNet.IP, Port: nat.Backend}
|
||||
if err := iface.manager.portMapper.Map(extPort, backend); err != nil {
|
||||
backend := &net.UDPAddr{IP: iface.IPNet.IP, Port: containerPort}
|
||||
if err := iface.manager.portMapper.Map(ip, extPort, backend); err != nil {
|
||||
iface.manager.udpPortAllocator.Release(extPort)
|
||||
return nil, err
|
||||
}
|
||||
nat.Frontend = extPort
|
||||
nat.Binding.HostPort = strconv.Itoa(extPort)
|
||||
}
|
||||
iface.extPorts = append(iface.extPorts, nat)
|
||||
|
||||
@@ -560,83 +530,37 @@ func (iface *NetworkInterface) AllocatePort(spec string) (*Nat, error) {
|
||||
}
|
||||
|
||||
type Nat struct {
|
||||
Proto string
|
||||
Frontend int
|
||||
Backend int
|
||||
Port Port
|
||||
Binding PortBinding
|
||||
}
|
||||
|
||||
func parseNat(spec string) (*Nat, error) {
|
||||
var nat Nat
|
||||
|
||||
if strings.Contains(spec, "/") {
|
||||
specParts := strings.Split(spec, "/")
|
||||
if len(specParts) != 2 {
|
||||
return nil, fmt.Errorf("Invalid port format.")
|
||||
}
|
||||
proto := specParts[1]
|
||||
spec = specParts[0]
|
||||
if proto != "tcp" && proto != "udp" {
|
||||
return nil, fmt.Errorf("Invalid port format: unknown protocol %v.", proto)
|
||||
}
|
||||
nat.Proto = proto
|
||||
} else {
|
||||
nat.Proto = "tcp"
|
||||
}
|
||||
|
||||
if strings.Contains(spec, ":") {
|
||||
specParts := strings.Split(spec, ":")
|
||||
if len(specParts) != 2 {
|
||||
return nil, fmt.Errorf("Invalid port format.")
|
||||
}
|
||||
// If spec starts with ':', external and internal ports must be the same.
|
||||
// This might fail if the requested external port is not available.
|
||||
var sameFrontend bool
|
||||
if len(specParts[0]) == 0 {
|
||||
sameFrontend = true
|
||||
} else {
|
||||
front, err := strconv.ParseUint(specParts[0], 10, 16)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nat.Frontend = int(front)
|
||||
}
|
||||
back, err := strconv.ParseUint(specParts[1], 10, 16)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nat.Backend = int(back)
|
||||
if sameFrontend {
|
||||
nat.Frontend = nat.Backend
|
||||
}
|
||||
} else {
|
||||
port, err := strconv.ParseUint(spec, 10, 16)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nat.Backend = int(port)
|
||||
}
|
||||
|
||||
return &nat, nil
|
||||
func (n *Nat) String() string {
|
||||
return fmt.Sprintf("%s:%d:%d/%s", n.Binding.HostIp, n.Binding.HostPort, n.Port.Port(), n.Port.Proto())
|
||||
}
|
||||
|
||||
// Release: Network cleanup - release all resources
|
||||
func (iface *NetworkInterface) Release() {
|
||||
|
||||
if iface.disabled {
|
||||
return
|
||||
}
|
||||
|
||||
for _, nat := range iface.extPorts {
|
||||
utils.Debugf("Unmaping %v/%v", nat.Proto, nat.Frontend)
|
||||
if err := iface.manager.portMapper.Unmap(nat.Frontend, nat.Proto); err != nil {
|
||||
log.Printf("Unable to unmap port %v/%v: %v", nat.Proto, nat.Frontend, err)
|
||||
hostPort, err := parsePort(nat.Binding.HostPort)
|
||||
if err != nil {
|
||||
log.Printf("Unable to get host port: %s", err)
|
||||
continue
|
||||
}
|
||||
if nat.Proto == "tcp" {
|
||||
if err := iface.manager.tcpPortAllocator.Release(nat.Frontend); err != nil {
|
||||
log.Printf("Unable to release port tcp/%v: %v", nat.Frontend, err)
|
||||
ip := net.ParseIP(nat.Binding.HostIp)
|
||||
utils.Debugf("Unmaping %s/%s", nat.Port.Proto, nat.Binding.HostPort)
|
||||
if err := iface.manager.portMapper.Unmap(ip, hostPort, nat.Port.Proto()); err != nil {
|
||||
log.Printf("Unable to unmap port %s: %s", nat, err)
|
||||
}
|
||||
if nat.Port.Proto() == "tcp" {
|
||||
if err := iface.manager.tcpPortAllocator.Release(hostPort); err != nil {
|
||||
log.Printf("Unable to release port %s", nat)
|
||||
}
|
||||
} else if err := iface.manager.udpPortAllocator.Release(nat.Frontend); err != nil {
|
||||
log.Printf("Unable to release port udp/%v: %v", nat.Frontend, err)
|
||||
} else if err := iface.manager.udpPortAllocator.Release(hostPort); err != nil {
|
||||
log.Printf("Unable to release port %s: %s", nat, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -704,28 +628,44 @@ func (manager *NetworkManager) Close() error {
|
||||
return err3
|
||||
}
|
||||
|
||||
func newNetworkManager(bridgeIface string) (*NetworkManager, error) {
|
||||
|
||||
if bridgeIface == DisableNetworkBridge {
|
||||
func newNetworkManager(config *DaemonConfig) (*NetworkManager, error) {
|
||||
if config.BridgeIface == DisableNetworkBridge {
|
||||
manager := &NetworkManager{
|
||||
disabled: true,
|
||||
}
|
||||
return manager, nil
|
||||
}
|
||||
|
||||
addr, err := getIfaceAddr(bridgeIface)
|
||||
addr, err := getIfaceAddr(config.BridgeIface)
|
||||
if err != nil {
|
||||
// If the iface is not found, try to create it
|
||||
if err := CreateBridgeIface(bridgeIface); err != nil {
|
||||
if err := CreateBridgeIface(config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addr, err = getIfaceAddr(bridgeIface)
|
||||
addr, err = getIfaceAddr(config.BridgeIface)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
network := addr.(*net.IPNet)
|
||||
|
||||
// Configure iptables for link support
|
||||
if config.EnableIptables {
|
||||
args := []string{"FORWARD", "-i", config.BridgeIface, "-o", config.BridgeIface, "-j", "DROP"}
|
||||
|
||||
if !config.InterContainerCommunication {
|
||||
if !iptables.Exists(args...) {
|
||||
utils.Debugf("Disable inter-container communication")
|
||||
if err := iptables.Raw(append([]string{"-A"}, args...)...); err != nil {
|
||||
return nil, fmt.Errorf("Unable to prevent intercontainer communication: %s", err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
utils.Debugf("Enable inter-container communication")
|
||||
iptables.Raw(append([]string{"-D"}, args...)...)
|
||||
}
|
||||
}
|
||||
|
||||
ipAllocator := newIPAllocator(network)
|
||||
|
||||
tcpPortAllocator, err := newPortAllocator()
|
||||
@@ -737,13 +677,13 @@ func newNetworkManager(bridgeIface string) (*NetworkManager, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
portMapper, err := newPortMapper()
|
||||
portMapper, err := newPortMapper(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
manager := &NetworkManager{
|
||||
bridgeIface: bridgeIface,
|
||||
bridgeIface: config.BridgeIface,
|
||||
bridgeNetwork: network,
|
||||
ipAllocator: ipAllocator,
|
||||
tcpPortAllocator: tcpPortAllocator,
|
||||
|
||||
+7
-114
@@ -2,117 +2,9 @@ package docker
|
||||
|
||||
import (
|
||||
"net"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIptables(t *testing.T) {
|
||||
if err := iptables("-L"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
path := os.Getenv("PATH")
|
||||
os.Setenv("PATH", "")
|
||||
defer os.Setenv("PATH", path)
|
||||
if err := iptables("-L"); err == nil {
|
||||
t.Fatal("Not finding iptables in the PATH should cause an error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseNat(t *testing.T) {
|
||||
if nat, err := parseNat("4500"); err == nil {
|
||||
if nat.Frontend != 0 || nat.Backend != 4500 || nat.Proto != "tcp" {
|
||||
t.Errorf("-p 4500 should produce 0->4500/tcp, got %d->%d/%s",
|
||||
nat.Frontend, nat.Backend, nat.Proto)
|
||||
}
|
||||
} else {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if nat, err := parseNat(":4501"); err == nil {
|
||||
if nat.Frontend != 4501 || nat.Backend != 4501 || nat.Proto != "tcp" {
|
||||
t.Errorf("-p :4501 should produce 4501->4501/tcp, got %d->%d/%s",
|
||||
nat.Frontend, nat.Backend, nat.Proto)
|
||||
}
|
||||
} else {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if nat, err := parseNat("4502:4503"); err == nil {
|
||||
if nat.Frontend != 4502 || nat.Backend != 4503 || nat.Proto != "tcp" {
|
||||
t.Errorf("-p 4502:4503 should produce 4502->4503/tcp, got %d->%d/%s",
|
||||
nat.Frontend, nat.Backend, nat.Proto)
|
||||
}
|
||||
} else {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if nat, err := parseNat("4502:4503/tcp"); err == nil {
|
||||
if nat.Frontend != 4502 || nat.Backend != 4503 || nat.Proto != "tcp" {
|
||||
t.Errorf("-p 4502:4503/tcp should produce 4502->4503/tcp, got %d->%d/%s",
|
||||
nat.Frontend, nat.Backend, nat.Proto)
|
||||
}
|
||||
} else {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if nat, err := parseNat("4502:4503/udp"); err == nil {
|
||||
if nat.Frontend != 4502 || nat.Backend != 4503 || nat.Proto != "udp" {
|
||||
t.Errorf("-p 4502:4503/udp should produce 4502->4503/udp, got %d->%d/%s",
|
||||
nat.Frontend, nat.Backend, nat.Proto)
|
||||
}
|
||||
} else {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if nat, err := parseNat(":4503/udp"); err == nil {
|
||||
if nat.Frontend != 4503 || nat.Backend != 4503 || nat.Proto != "udp" {
|
||||
t.Errorf("-p :4503/udp should produce 4503->4503/udp, got %d->%d/%s",
|
||||
nat.Frontend, nat.Backend, nat.Proto)
|
||||
}
|
||||
} else {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if nat, err := parseNat(":4503/tcp"); err == nil {
|
||||
if nat.Frontend != 4503 || nat.Backend != 4503 || nat.Proto != "tcp" {
|
||||
t.Errorf("-p :4503/tcp should produce 4503->4503/tcp, got %d->%d/%s",
|
||||
nat.Frontend, nat.Backend, nat.Proto)
|
||||
}
|
||||
} else {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if nat, err := parseNat("4503/tcp"); err == nil {
|
||||
if nat.Frontend != 0 || nat.Backend != 4503 || nat.Proto != "tcp" {
|
||||
t.Errorf("-p 4503/tcp should produce 0->4503/tcp, got %d->%d/%s",
|
||||
nat.Frontend, nat.Backend, nat.Proto)
|
||||
}
|
||||
} else {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if nat, err := parseNat("4503/udp"); err == nil {
|
||||
if nat.Frontend != 0 || nat.Backend != 4503 || nat.Proto != "udp" {
|
||||
t.Errorf("-p 4503/udp should produce 0->4503/udp, got %d->%d/%s",
|
||||
nat.Frontend, nat.Backend, nat.Proto)
|
||||
}
|
||||
} else {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := parseNat("4503/tcpgarbage"); err == nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := parseNat("4503/tcp/udp"); err == nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := parseNat("4503/"); err == nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPortAllocation(t *testing.T) {
|
||||
allocator, err := newPortAllocator()
|
||||
if err != nil {
|
||||
@@ -385,12 +277,13 @@ func TestNetworkOverlaps(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCheckRouteOverlaps(t *testing.T) {
|
||||
routes := `default via 10.0.2.2 dev eth0
|
||||
10.0.2.0 dev eth0 proto kernel scope link src 10.0.2.15
|
||||
10.0.3.0/24 dev lxcbr0 proto kernel scope link src 10.0.3.1
|
||||
10.0.42.0/24 dev testdockbr0 proto kernel scope link src 10.0.42.1
|
||||
172.16.42.0/24 dev docker0 proto kernel scope link src 172.16.42.1
|
||||
192.168.142.0/24 dev eth1 proto kernel scope link src 192.168.142.142`
|
||||
routesData := []string{"10.0.2.0/32", "10.0.3.0/24", "10.0.42.0/24", "172.16.42.0/24", "192.168.142.0/24"}
|
||||
|
||||
routes := []*net.IPNet{}
|
||||
for _, addr := range routesData {
|
||||
_, netX, _ := net.ParseCIDR(addr)
|
||||
routes = append(routes, netX)
|
||||
}
|
||||
|
||||
_, netX, _ := net.ParseCIDR("172.16.0.1/24")
|
||||
if err := checkRouteOverlaps(routes, netX); err != nil {
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Michael Crosby <michael@crosbymichael.com> (@crosbymichael)
|
||||
@@ -1,4 +1,4 @@
|
||||
package docker
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -44,7 +44,6 @@ func NewEchoServer(t *testing.T, proto, address string) EchoServer {
|
||||
}
|
||||
server = &UDPEchoServer{conn: socket, testCtx: t}
|
||||
}
|
||||
t.Logf("EchoServer listening on %v/%v\n", proto, server.LocalAddr().String())
|
||||
return server
|
||||
}
|
||||
|
||||
@@ -56,10 +55,7 @@ func (server *TCPEchoServer) Run() {
|
||||
return
|
||||
}
|
||||
go func(client net.Conn) {
|
||||
server.testCtx.Logf("TCP client accepted on the EchoServer\n")
|
||||
written, err := io.Copy(client, client)
|
||||
server.testCtx.Logf("%v bytes echoed back to the client\n", written)
|
||||
if err != nil {
|
||||
if _, err := io.Copy(client, client); err != nil {
|
||||
server.testCtx.Logf("can't echo to the client: %v\n", err.Error())
|
||||
}
|
||||
client.Close()
|
||||
@@ -79,7 +75,6 @@ func (server *UDPEchoServer) Run() {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
server.testCtx.Logf("Writing UDP datagram back")
|
||||
for i := 0; i != read; {
|
||||
written, err := server.conn.WriteTo(readBuf[i:read], from)
|
||||
if err != nil {
|
||||
@@ -0,0 +1,29 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
)
|
||||
|
||||
type Proxy interface {
|
||||
// Start forwarding traffic back and forth the front and back-end
|
||||
// addresses.
|
||||
Run()
|
||||
// Stop forwarding traffic and close both ends of the Proxy.
|
||||
Close()
|
||||
// Return the address on which the proxy is listening.
|
||||
FrontendAddr() net.Addr
|
||||
// Return the proxied address.
|
||||
BackendAddr() net.Addr
|
||||
}
|
||||
|
||||
func NewProxy(frontendAddr, backendAddr net.Addr) (Proxy, error) {
|
||||
switch frontendAddr.(type) {
|
||||
case *net.UDPAddr:
|
||||
return NewUDPProxy(frontendAddr.(*net.UDPAddr), backendAddr.(*net.UDPAddr))
|
||||
case *net.TCPAddr:
|
||||
return NewTCPProxy(frontendAddr.(*net.TCPAddr), backendAddr.(*net.TCPAddr))
|
||||
default:
|
||||
panic(fmt.Errorf("Unsupported protocol"))
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user