1
0
Fork 0
mirror of https://github.com/moby/moby.git synced 2022-11-09 12:21:53 -05:00

Add links for container relationships and introspection

This commit is contained in:
Michael Crosby 2013-10-04 19:25:15 -07:00 committed by Victor Vieux
parent ad7c6bc950
commit 4c27690cdd
46 changed files with 4252 additions and 741 deletions

View file

@ -33,15 +33,13 @@ run apt-get update
run apt-get install -y -q curl run apt-get install -y -q curl
run apt-get install -y -q git run apt-get install -y -q git
run apt-get install -y -q mercurial 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) # Install Go
env CGO_ENABLED 0 run curl -s https://go.googlecode.com/files/go1.2rc1.src.tar.gz | tar -v -C /usr/local -xz
run curl -s https://go.googlecode.com/files/go1.1.2.src.tar.gz | tar -v -C / -xz && mv /go /goroot env PATH /usr/local/go/bin:/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin
run cd /goroot/src && ./make.bash
env GOROOT /goroot
env PATH $PATH:/goroot/bin
env GOPATH /go:/go/src/github.com/dotcloud/docker/vendor 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 # Ubuntu stuff
run apt-get install -y -q ruby1.9.3 rubygems libffi-dev run apt-get install -y -q ruby1.9.3 rubygems libffi-dev

104
api.go
View file

@ -6,6 +6,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/dotcloud/docker/auth" "github.com/dotcloud/docker/auth"
"github.com/dotcloud/docker/gograph"
"github.com/dotcloud/docker/utils" "github.com/dotcloud/docker/utils"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"io" "io"
@ -14,6 +15,7 @@ import (
"mime" "mime"
"net" "net"
"net/http" "net/http"
"net/url"
"os" "os"
"os/exec" "os/exec"
"regexp" "regexp"
@ -136,6 +138,7 @@ func postContainersKill(srv *Server, version float64, w http.ResponseWriter, r *
return fmt.Errorf("Missing parameter") return fmt.Errorf("Missing parameter")
} }
name := vars["name"] name := vars["name"]
name = decodeName(name)
if err := srv.ContainerKill(name); err != nil { if err := srv.ContainerKill(name); err != nil {
return err return err
} }
@ -148,6 +151,7 @@ func getContainersExport(srv *Server, version float64, w http.ResponseWriter, r
return fmt.Errorf("Missing parameter") return fmt.Errorf("Missing parameter")
} }
name := vars["name"] name := vars["name"]
name = decodeName(name)
if err := srv.ContainerExport(name, w); err != nil { if err := srv.ContainerExport(name, w); err != nil {
utils.Errorf("%s", err) utils.Errorf("%s", err)
@ -515,16 +519,19 @@ func postContainersCreate(srv *Server, version float64, w http.ResponseWriter, r
return err 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)) out.Warnings = append(out.Warnings, fmt.Sprintf("Docker detected local DNS server on resolv.conf. Using default external servers: %v", defaultDns))
config.Dns = defaultDns config.Dns = defaultDns
} }
id, err := srv.ContainerCreate(config) id, warnings, err := srv.ContainerCreate(config)
if err != nil { if err != nil {
return err return err
} }
out.ID = id out.ID = id
for _, warning := range warnings {
out.Warnings = append(out.Warnings, warning)
}
if config.Memory > 0 && !srv.runtime.capabilities.MemoryLimit { if config.Memory > 0 && !srv.runtime.capabilities.MemoryLimit {
log.Println("WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.") log.Println("WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.")
@ -555,6 +562,7 @@ func postContainersRestart(srv *Server, version float64, w http.ResponseWriter,
return fmt.Errorf("Missing parameter") return fmt.Errorf("Missing parameter")
} }
name := vars["name"] name := vars["name"]
name = decodeName(name)
if err := srv.ContainerRestart(name, t); err != nil { if err := srv.ContainerRestart(name, t); err != nil {
return err return err
} }
@ -570,12 +578,18 @@ func deleteContainers(srv *Server, version float64, w http.ResponseWriter, r *ht
return fmt.Errorf("Missing parameter") return fmt.Errorf("Missing parameter")
} }
name := vars["name"] name := vars["name"]
name = decodeName(name)
removeVolume, err := getBoolParam(r.Form.Get("v")) removeVolume, err := getBoolParam(r.Form.Get("v"))
if err != nil { if err != nil {
return err 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 return err
} }
w.WriteHeader(http.StatusNoContent) w.WriteHeader(http.StatusNoContent)
@ -621,7 +635,12 @@ func postContainersStart(srv *Server, version float64, w http.ResponseWriter, r
if vars == nil { if vars == nil {
return fmt.Errorf("Missing parameter") return fmt.Errorf("Missing parameter")
} }
var err error
name := vars["name"] name := vars["name"]
name = decodeName(name)
if err != nil {
return err
}
if err := srv.ContainerStart(name, hostConfig); err != nil { if err := srv.ContainerStart(name, hostConfig); err != nil {
return err return err
} }
@ -642,6 +661,7 @@ func postContainersStop(srv *Server, version float64, w http.ResponseWriter, r *
return fmt.Errorf("Missing parameter") return fmt.Errorf("Missing parameter")
} }
name := vars["name"] name := vars["name"]
name = decodeName(name)
if err := srv.ContainerStop(name, t); err != nil { if err := srv.ContainerStop(name, t); err != nil {
return err return err
@ -655,6 +675,8 @@ func postContainersWait(srv *Server, version float64, w http.ResponseWriter, r *
return fmt.Errorf("Missing parameter") return fmt.Errorf("Missing parameter")
} }
name := vars["name"] name := vars["name"]
name = decodeName(name)
status, err := srv.ContainerWait(name) status, err := srv.ContainerWait(name)
if err != nil { if err != nil {
return err return err
@ -714,6 +736,7 @@ func postContainersAttach(srv *Server, version float64, w http.ResponseWriter, r
return fmt.Errorf("Missing parameter") return fmt.Errorf("Missing parameter")
} }
name := vars["name"] name := vars["name"]
name = decodeName(name)
c, err := srv.ContainerInspect(name) c, err := srv.ContainerInspect(name)
if err != nil { if err != nil {
@ -786,6 +809,7 @@ func wsContainersAttach(srv *Server, version float64, w http.ResponseWriter, r *
return fmt.Errorf("Missing parameter") return fmt.Errorf("Missing parameter")
} }
name := vars["name"] name := vars["name"]
name = decodeName(name)
if _, err := srv.ContainerInspect(name); err != nil { if _, err := srv.ContainerInspect(name); err != nil {
return err return err
@ -808,6 +832,7 @@ func getContainersByName(srv *Server, version float64, w http.ResponseWriter, r
return fmt.Errorf("Missing parameter") return fmt.Errorf("Missing parameter")
} }
name := vars["name"] name := vars["name"]
name = decodeName(name)
container, err := srv.ContainerInspect(name) container, err := srv.ContainerInspect(name)
if err != nil { if err != nil {
@ -975,7 +1000,7 @@ func makeHttpHandler(srv *Server, logging bool, localMethod string, localRoute s
if err != nil { if err != nil {
version = APIVERSION version = APIVERSION
} }
if srv.enableCors { if srv.runtime.config.EnableCors {
writeCorsHeaders(w, r) writeCorsHeaders(w, r)
} }
@ -991,6 +1016,75 @@ func makeHttpHandler(srv *Server, logging bool, localMethod string, localRoute s
} }
} }
func getContainersLinks(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if err := parseForm(r); err != nil {
return err
}
runtime := srv.runtime
all, err := getBoolParam(r.Form.Get("all"))
if err != nil {
return err
}
out := []APILink{}
err = runtime.containerGraph.Walk("/", func(p string, e *gograph.Entity) error {
if container := runtime.Get(e.ID()); container != nil {
if !all && strings.Contains(p, container.ID) {
return nil
}
out = append(out, APILink{
Path: p,
ContainerID: container.ID,
Image: runtime.repositories.ImageName(container.Image),
})
}
return nil
}, -1)
if err != nil {
return err
}
return writeJSON(w, http.StatusOK, out)
}
func postContainerLink(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if vars == nil {
return fmt.Errorf("Missing parameter")
}
values := make(map[string]string)
if matchesContentType(r.Header.Get("Content-Type"), "application/json") && r.Body != nil {
defer r.Body.Close()
dec := json.NewDecoder(r.Body)
if err := dec.Decode(&values); err != nil {
return err
}
} else {
return fmt.Errorf("Invalid json body")
}
currentName := values["currentName"]
newName := values["newName"]
if currentName == "" {
return fmt.Errorf("currentName cannot be empty")
}
if newName == "" {
return fmt.Errorf("newName cannot be empty")
}
if err := srv.runtime.RenameLink(currentName, newName); err != nil {
return err
}
return nil
}
func decodeName(name string) string {
s, _ := url.QueryUnescape(name)
return s
}
func createRouter(srv *Server, logging bool) (*mux.Router, error) { func createRouter(srv *Server, logging bool) (*mux.Router, error) {
r := mux.NewRouter() r := mux.NewRouter()
@ -1011,6 +1105,7 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
"/containers/{name:.*}/json": getContainersByName, "/containers/{name:.*}/json": getContainersByName,
"/containers/{name:.*}/top": getContainersTop, "/containers/{name:.*}/top": getContainersTop,
"/containers/{name:.*}/attach/ws": wsContainersAttach, "/containers/{name:.*}/attach/ws": wsContainersAttach,
"/containers/links": getContainersLinks,
}, },
"POST": { "POST": {
"/auth": postAuth, "/auth": postAuth,
@ -1029,6 +1124,7 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
"/containers/{name:.*}/resize": postContainersResize, "/containers/{name:.*}/resize": postContainersResize,
"/containers/{name:.*}/attach": postContainersAttach, "/containers/{name:.*}/attach": postContainersAttach,
"/containers/{name:.*}/copy": postContainersCopy, "/containers/{name:.*}/copy": postContainersCopy,
"/containers/link": postContainerLink,
}, },
"DELETE": { "DELETE": {
"/containers/{name:.*}": deleteContainers, "/containers/{name:.*}": deleteContainers,

View file

@ -1,7 +1,5 @@
package docker package docker
import "encoding/json"
type APIHistory struct { type APIHistory struct {
ID string `json:"Id"` ID string `json:"Id"`
Tags []string `json:",omitempty"` Tags []string `json:",omitempty"`
@ -52,17 +50,18 @@ type APIContainers struct {
Ports []APIPort Ports []APIPort
SizeRw int64 SizeRw int64
SizeRootFs int64 SizeRootFs int64
Names []string
} }
func (self *APIContainers) ToLegacy() APIContainersOld { func (self *APIContainers) ToLegacy() APIContainersOld {
return APIContainersOld{ return APIContainersOld{
ID: self.ID, ID: self.ID,
Image: self.Image, Image: self.Image,
Command: self.Command, Command: self.Command,
Created: self.Created, Created: self.Created,
Status: self.Status, Status: self.Status,
Ports: displayablePorts(self.Ports), Ports: displayablePorts(self.Ports),
SizeRw: self.SizeRw, SizeRw: self.SizeRw,
SizeRootFs: self.SizeRootFs, SizeRootFs: self.SizeRootFs,
} }
} }
@ -96,14 +95,7 @@ type APIPort struct {
PrivatePort int64 PrivatePort int64
PublicPort int64 PublicPort int64
Type string Type string
} IP string
func (port *APIPort) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]interface{}{
"PrivatePort": port.PrivatePort,
"PublicPort": port.PublicPort,
"Type": port.Type,
})
} }
type APIVersion struct { type APIVersion struct {
@ -129,3 +121,9 @@ type APICopy struct {
Resource string Resource string
HostPath string HostPath string
} }
type APILink struct {
Path string
ContainerID string
Image string
}

View file

@ -347,7 +347,7 @@ func TestGetContainersJSON(t *testing.T) {
srv := &Server{runtime: runtime} srv := &Server{runtime: runtime}
container, err := runtime.Create(&Config{ container, _, err := runtime.Create(&Config{
Image: GetTestImage(runtime).ID, Image: GetTestImage(runtime).ID,
Cmd: []string{"echo", "test"}, Cmd: []string{"echo", "test"},
}) })
@ -384,7 +384,7 @@ func TestGetContainersExport(t *testing.T) {
srv := &Server{runtime: runtime} srv := &Server{runtime: runtime}
// Create a container and remove a file // Create a container and remove a file
container, err := runtime.Create( container, _, err := runtime.Create(
&Config{ &Config{
Image: GetTestImage(runtime).ID, Image: GetTestImage(runtime).ID,
Cmd: []string{"touch", "/test"}, Cmd: []string{"touch", "/test"},
@ -434,7 +434,7 @@ func TestGetContainersChanges(t *testing.T) {
srv := &Server{runtime: runtime} srv := &Server{runtime: runtime}
// Create a container and remove a file // Create a container and remove a file
container, err := runtime.Create( container, _, err := runtime.Create(
&Config{ &Config{
Image: GetTestImage(runtime).ID, Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/rm", "/etc/passwd"}, Cmd: []string{"/bin/rm", "/etc/passwd"},
@ -477,7 +477,7 @@ func TestGetContainersTop(t *testing.T) {
srv := &Server{runtime: runtime} srv := &Server{runtime: runtime}
container, err := runtime.Create( container, _, err := runtime.Create(
&Config{ &Config{
Image: GetTestImage(runtime).ID, Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/sh", "-c", "cat"}, Cmd: []string{"/bin/sh", "-c", "cat"},
@ -559,7 +559,7 @@ func TestGetContainersByName(t *testing.T) {
srv := &Server{runtime: runtime} srv := &Server{runtime: runtime}
// Create a container and remove a file // Create a container and remove a file
container, err := runtime.Create( container, _, err := runtime.Create(
&Config{ &Config{
Image: GetTestImage(runtime).ID, Image: GetTestImage(runtime).ID,
Cmd: []string{"echo", "test"}, Cmd: []string{"echo", "test"},
@ -590,7 +590,7 @@ func TestPostCommit(t *testing.T) {
srv := &Server{runtime: runtime} srv := &Server{runtime: runtime}
// Create a container and remove a file // Create a container and remove a file
container, err := runtime.Create( container, _, err := runtime.Create(
&Config{ &Config{
Image: GetTestImage(runtime).ID, Image: GetTestImage(runtime).ID,
Cmd: []string{"touch", "/test"}, Cmd: []string{"touch", "/test"},
@ -684,7 +684,7 @@ func TestPostContainersKill(t *testing.T) {
srv := &Server{runtime: runtime} srv := &Server{runtime: runtime}
container, err := runtime.Create( container, _, err := runtime.Create(
&Config{ &Config{
Image: GetTestImage(runtime).ID, Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/cat"}, Cmd: []string{"/bin/cat"},
@ -726,7 +726,7 @@ func TestPostContainersRestart(t *testing.T) {
srv := &Server{runtime: runtime} srv := &Server{runtime: runtime}
container, err := runtime.Create( container, _, err := runtime.Create(
&Config{ &Config{
Image: GetTestImage(runtime).ID, Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/cat"}, Cmd: []string{"/bin/cat"},
@ -780,7 +780,7 @@ func TestPostContainersStart(t *testing.T) {
srv := &Server{runtime: runtime} srv := &Server{runtime: runtime}
container, err := runtime.Create( container, _, err := runtime.Create(
&Config{ &Config{
Image: GetTestImage(runtime).ID, Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/cat"}, Cmd: []string{"/bin/cat"},
@ -832,7 +832,7 @@ func TestPostContainersStop(t *testing.T) {
srv := &Server{runtime: runtime} srv := &Server{runtime: runtime}
container, err := runtime.Create( container, _, err := runtime.Create(
&Config{ &Config{
Image: GetTestImage(runtime).ID, Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/cat"}, Cmd: []string{"/bin/cat"},
@ -879,7 +879,7 @@ func TestPostContainersWait(t *testing.T) {
srv := &Server{runtime: runtime} srv := &Server{runtime: runtime}
container, err := runtime.Create( container, _, err := runtime.Create(
&Config{ &Config{
Image: GetTestImage(runtime).ID, Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/sleep", "1"}, Cmd: []string{"/bin/sleep", "1"},
@ -921,7 +921,7 @@ func TestPostContainersAttach(t *testing.T) {
srv := &Server{runtime: runtime} srv := &Server{runtime: runtime}
container, err := runtime.Create( container, _, err := runtime.Create(
&Config{ &Config{
Image: GetTestImage(runtime).ID, Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/cat"}, Cmd: []string{"/bin/cat"},
@ -1010,7 +1010,7 @@ func TestPostContainersAttachStderr(t *testing.T) {
srv := &Server{runtime: runtime} srv := &Server{runtime: runtime}
container, err := runtime.Create( container, _, err := runtime.Create(
&Config{ &Config{
Image: GetTestImage(runtime).ID, Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/sh", "-c", "/bin/cat >&2"}, Cmd: []string{"/bin/sh", "-c", "/bin/cat >&2"},
@ -1102,7 +1102,7 @@ func TestDeleteContainers(t *testing.T) {
srv := &Server{runtime: runtime} srv := &Server{runtime: runtime}
container, err := runtime.Create(&Config{ container, _, err := runtime.Create(&Config{
Image: GetTestImage(runtime).ID, Image: GetTestImage(runtime).ID,
Cmd: []string{"touch", "/test"}, Cmd: []string{"touch", "/test"},
}) })
@ -1140,7 +1140,8 @@ func TestOptionsRoute(t *testing.T) {
runtime := mkRuntime(t) runtime := mkRuntime(t)
defer nuke(runtime) defer nuke(runtime)
srv := &Server{runtime: runtime, enableCors: true} runtime.config.EnableCors = true
srv := &Server{runtime: runtime}
r := httptest.NewRecorder() r := httptest.NewRecorder()
router, err := createRouter(srv, false) router, err := createRouter(srv, false)
@ -1163,7 +1164,8 @@ func TestGetEnabledCors(t *testing.T) {
runtime := mkRuntime(t) runtime := mkRuntime(t)
defer nuke(runtime) defer nuke(runtime)
srv := &Server{runtime: runtime, enableCors: true} runtime.config.EnableCors = true
srv := &Server{runtime: runtime}
r := httptest.NewRecorder() r := httptest.NewRecorder()
@ -1290,7 +1292,7 @@ func TestPostContainersCopy(t *testing.T) {
srv := &Server{runtime: runtime} srv := &Server{runtime: runtime}
// Create a container and remove a file // Create a container and remove a file
container, err := runtime.Create( container, _, err := runtime.Create(
&Config{ &Config{
Image: GetTestImage(runtime).ID, Image: GetTestImage(runtime).ID,
Cmd: []string{"touch", "/test.txt"}, Cmd: []string{"touch", "/test.txt"},

View file

@ -187,6 +187,9 @@ func (b *buildFile) CmdCmd(args string) error {
} }
func (b *buildFile) CmdExpose(args string) error { func (b *buildFile) CmdExpose(args string) error {
if strings.Contains(args, ":") {
return fmt.Errorf("EXPOSE cannot be used to bind to a host ip or port")
}
ports := strings.Split(args, " ") ports := strings.Split(args, " ")
b.config.PortSpecs = append(ports, b.config.PortSpecs...) b.config.PortSpecs = append(ports, b.config.PortSpecs...)
return b.commit("", b.config.Cmd, fmt.Sprintf("EXPOSE %v", ports)) return b.commit("", b.config.Cmd, fmt.Sprintf("EXPOSE %v", ports))
@ -332,7 +335,7 @@ func (b *buildFile) CmdAdd(args string) error {
b.config.Image = b.image b.config.Image = b.image
// Create the container and start it // Create the container and start it
container, err := b.runtime.Create(b.config) container, _, err := b.runtime.Create(b.config)
if err != nil { if err != nil {
return err return err
} }
@ -367,7 +370,7 @@ func (b *buildFile) run() (string, error) {
b.config.Image = b.image b.config.Image = b.image
// Create the container and start it // Create the container and start it
c, err := b.runtime.Create(b.config) c, _, err := b.runtime.Create(b.config)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -423,7 +426,7 @@ func (b *buildFile) commit(id string, autoCmd []string, comment string) error {
} }
} }
container, err := b.runtime.Create(b.config) container, _, err := b.runtime.Create(b.config)
if err != nil { if err != nil {
return err return err
} }

View file

@ -98,6 +98,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
{"kill", "Kill a running container"}, {"kill", "Kill a running container"},
{"login", "Register or Login to the docker registry server"}, {"login", "Register or Login to the docker registry server"},
{"logs", "Fetch the logs of a container"}, {"logs", "Fetch the logs of a container"},
{"ls", "List links for containers"},
{"port", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT"}, {"port", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT"},
{"top", "Lookup the running processes of a container"}, {"top", "Lookup the running processes of a container"},
{"ps", "List containers"}, {"ps", "List containers"},
@ -510,7 +511,8 @@ func (cli *DockerCli) CmdStop(args ...string) error {
v.Set("t", strconv.Itoa(*nSeconds)) v.Set("t", strconv.Itoa(*nSeconds))
for _, name := range cmd.Args() { for _, name := range cmd.Args() {
_, _, err := cli.call("POST", "/containers/"+name+"/stop?"+v.Encode(), nil) encName := cleanName(name)
_, _, err := cli.call("POST", "/containers/"+encName+"/stop?"+v.Encode(), nil)
if err != nil { if err != nil {
fmt.Fprintf(cli.err, "%s\n", err) fmt.Fprintf(cli.err, "%s\n", err)
} else { } else {
@ -535,7 +537,8 @@ func (cli *DockerCli) CmdRestart(args ...string) error {
v.Set("t", strconv.Itoa(*nSeconds)) v.Set("t", strconv.Itoa(*nSeconds))
for _, name := range cmd.Args() { for _, name := range cmd.Args() {
_, _, err := cli.call("POST", "/containers/"+name+"/restart?"+v.Encode(), nil) encName := cleanName(name)
_, _, err := cli.call("POST", "/containers/"+encName+"/restart?"+v.Encode(), nil)
if err != nil { if err != nil {
fmt.Fprintf(cli.err, "%s\n", err) fmt.Fprintf(cli.err, "%s\n", err)
} else { } else {
@ -557,7 +560,8 @@ func (cli *DockerCli) CmdStart(args ...string) error {
var encounteredError error var encounteredError error
for _, name := range args { for _, name := range args {
_, _, err := cli.call("POST", "/containers/"+name+"/start", nil) encName := cleanName(name)
_, _, err := cli.call("POST", "/containers/"+encName+"/start", nil)
if err != nil { if err != nil {
fmt.Fprintf(cli.err, "%s\n", err) fmt.Fprintf(cli.err, "%s\n", err)
encounteredError = fmt.Errorf("Error: failed to start one or more containers") encounteredError = fmt.Errorf("Error: failed to start one or more containers")
@ -579,10 +583,11 @@ func (cli *DockerCli) CmdInspect(args ...string) error {
} }
fmt.Fprintf(cli.out, "[") fmt.Fprintf(cli.out, "[")
for i, name := range args { for i, name := range args {
encName := cleanName(name)
if i > 0 { if i > 0 {
fmt.Fprintf(cli.out, ",") fmt.Fprintf(cli.out, ",")
} }
obj, _, err := cli.call("GET", "/containers/"+name+"/json", nil) obj, _, err := cli.call("GET", "/containers/"+encName+"/json", nil)
if err != nil { if err != nil {
obj, _, err = cli.call("GET", "/images/"+name+"/json", nil) obj, _, err = cli.call("GET", "/images/"+name+"/json", nil)
if err != nil { if err != nil {
@ -740,6 +745,8 @@ func (cli *DockerCli) CmdHistory(args ...string) error {
func (cli *DockerCli) CmdRm(args ...string) error { func (cli *DockerCli) CmdRm(args ...string) error {
cmd := Subcmd("rm", "[OPTIONS] CONTAINER [CONTAINER...]", "Remove one or more containers") cmd := Subcmd("rm", "[OPTIONS] CONTAINER [CONTAINER...]", "Remove one or more containers")
v := cmd.Bool("v", false, "Remove the volumes associated to the container") 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 { if err := cmd.Parse(args); err != nil {
return nil return nil
} }
@ -751,8 +758,12 @@ func (cli *DockerCli) CmdRm(args ...string) error {
if *v { if *v {
val.Set("v", "1") val.Set("v", "1")
} }
if *link {
val.Set("link", "1")
}
for _, name := range cmd.Args() { for _, name := range cmd.Args() {
_, _, err := cli.call("DELETE", "/containers/"+name+"?"+val.Encode(), nil) encName := cleanName(name)
_, _, err := cli.call("DELETE", "/containers/"+encName+"?"+val.Encode(), nil)
if err != nil { if err != nil {
fmt.Fprintf(cli.err, "%s\n", err) fmt.Fprintf(cli.err, "%s\n", err)
} else { } else {
@ -774,7 +785,8 @@ func (cli *DockerCli) CmdKill(args ...string) error {
} }
for _, name := range args { for _, name := range args {
_, _, err := cli.call("POST", "/containers/"+name+"/kill", nil) encName := cleanName(name)
_, _, err := cli.call("POST", "/containers/"+encName+"/kill", nil)
if err != nil { if err != nil {
fmt.Fprintf(cli.err, "%s\n", err) fmt.Fprintf(cli.err, "%s\n", err)
} else { } else {
@ -1017,10 +1029,10 @@ func (cli *DockerCli) CmdImages(args ...string) error {
func displayablePorts(ports []APIPort) string { func displayablePorts(ports []APIPort) string {
result := []string{} result := []string{}
for _, port := range ports { for _, port := range ports {
if port.Type == "tcp" { if port.IP == "" {
result = append(result, fmt.Sprintf("%d->%d", port.PublicPort, port.PrivatePort)) result = append(result, fmt.Sprintf("%d/%s", port.PublicPort, port.Type))
} else { } 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) sort.Strings(result)
@ -1073,7 +1085,7 @@ func (cli *DockerCli) CmdPs(args ...string) error {
} }
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
if !*quiet { if !*quiet {
fmt.Fprint(w, "ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS") fmt.Fprint(w, "ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS\tNAMES")
if *size { if *size {
fmt.Fprintln(w, "\tSIZE") fmt.Fprintln(w, "\tSIZE")
} else { } else {
@ -1082,11 +1094,16 @@ func (cli *DockerCli) CmdPs(args ...string) error {
} }
for _, out := range outs { for _, out := range outs {
for i := 0; i < len(out.Names); i++ {
out.Names[i] = utils.Trunc(out.Names[i], 10)
}
names := strings.Join(out.Names, ",")
if !*quiet { if !*quiet {
if *noTrunc { 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)) fmt.Fprintf(w, "%s\t%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), names)
} else { } 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)) fmt.Fprintf(w, "%s\t%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), names)
} }
if *size { if *size {
if out.SizeRootFs > 0 { if out.SizeRootFs > 0 {
@ -1112,6 +1129,64 @@ func (cli *DockerCli) CmdPs(args ...string) error {
return nil return nil
} }
func (cli *DockerCli) CmdLs(args ...string) error {
cmd := Subcmd("ls", "", "List links for containers")
flAll := cmd.Bool("a", false, "Show all links")
if err := cmd.Parse(args); err != nil {
return nil
}
v := url.Values{}
if *flAll {
v.Set("all", "1")
}
body, _, err := cli.call("GET", "/containers/links?"+v.Encode(), nil)
if err != nil {
return err
}
var links []APILink
if err := json.Unmarshal(body, &links); err != nil {
return err
}
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
fmt.Fprintf(w, "NAME\tID\tIMAGE")
fmt.Fprintf(w, "\n")
sortLinks(links, func(i, j APILink) bool {
return len(i.Path) < len(j.Path)
})
for _, link := range links {
fmt.Fprintf(w, "%s\t%s\t%s", link.Path, utils.TruncateID(link.ContainerID), link.Image)
fmt.Fprintf(w, "\n")
}
w.Flush()
return nil
}
func (cli *DockerCli) CmdLink(args ...string) error {
cmd := Subcmd("link", "CURRENT_NAME NEW_NAME", "Link the container with a new name")
if err := cmd.Parse(args); err != nil {
return nil
}
if cmd.NArg() != 2 {
cmd.Usage()
return nil
}
body := map[string]string{
"currentName": cmd.Arg(0),
"newName": cmd.Arg(1),
}
_, _, err := cli.call("POST", "/containers/link", body)
if err != nil {
return err
}
return nil
}
func (cli *DockerCli) CmdCommit(args ...string) error { func (cli *DockerCli) CmdCommit(args ...string) error {
cmd := Subcmd("commit", "[OPTIONS] CONTAINER [REPOSITORY [TAG]]", "Create a new image from a container's changes") cmd := Subcmd("commit", "[OPTIONS] CONTAINER [REPOSITORY [TAG]]", "Create a new image from a container's changes")
flComment := cmd.String("m", "", "Commit message") flComment := cmd.String("m", "", "Commit message")
@ -1229,8 +1304,9 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
cmd.Usage() cmd.Usage()
return nil return nil
} }
name := cleanName(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); err != nil {
return err return err
} }
return nil return nil
@ -1245,8 +1321,9 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
cmd.Usage() cmd.Usage()
return nil return nil
} }
name := cmd.Arg(0)
body, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil) name = cleanName(name)
body, _, err := cli.call("GET", "/containers/"+name+"/json", nil)
if err != nil { if err != nil {
return err return err
} }
@ -1920,6 +1997,10 @@ func getExitCode(cli *DockerCli, containerId string) (int, error) {
return c.State.ExitCode, nil return c.State.ExitCode, nil
} }
func cleanName(name string) string {
return strings.Replace(name, "/", "%252F", -1)
}
func NewDockerCli(in io.ReadCloser, out, err io.Writer, proto, addr string) *DockerCli { func NewDockerCli(in io.ReadCloser, out, err io.Writer, proto, addr string) *DockerCli {
var ( var (
isTerminal = false isTerminal = false

17
config.go Normal file
View file

@ -0,0 +1,17 @@
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
}

View file

@ -16,7 +16,6 @@ import (
"os/exec" "os/exec"
"path" "path"
"path/filepath" "path/filepath"
"strconv"
"strings" "strings"
"syscall" "syscall"
"time" "time"
@ -58,6 +57,8 @@ type Container struct {
// Store rw/ro in a separate structure to preserve reverse-compatibility on-disk. // Store rw/ro in a separate structure to preserve reverse-compatibility on-disk.
// Easier than migrating older container configs :) // Easier than migrating older container configs :)
VolumesRW map[string]bool VolumesRW map[string]bool
activeLinks map[string]*Link
} }
type Config struct { type Config struct {
@ -70,7 +71,8 @@ type Config struct {
AttachStdin bool AttachStdin bool
AttachStdout bool AttachStdout bool
AttachStderr 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. Tty bool // Attach standard streams to a tty, including stdin if it is not closed.
OpenStdin bool // Open stdin OpenStdin bool // Open stdin
StdinOnce bool // If true, close stdin after the 1 attached client disconnects. StdinOnce bool // If true, close stdin after the 1 attached client disconnects.
@ -90,6 +92,8 @@ type HostConfig struct {
Binds []string Binds []string
ContainerIDFile string ContainerIDFile string
LxcConf []KeyValuePair LxcConf []KeyValuePair
PortBindings map[Port][]PortBinding
Links []string
} }
type BindMap struct { type BindMap struct {
@ -107,6 +111,34 @@ type KeyValuePair struct {
Value string 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) { 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") cmd := Subcmd("run", "[OPTIONS] IMAGE [COMMAND] [ARG...]", "Run a command in a new container")
if os.Getenv("TEST") != "" { if os.Getenv("TEST") != "" {
@ -135,8 +167,11 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
flCpuShares := cmd.Int64("c", 0, "CPU shares (relative weight)") flCpuShares := cmd.Int64("c", 0, "CPU shares (relative weight)")
var flPorts ListOpts var flPublish ListOpts
cmd.Var(&flPorts, "p", "Expose a container's port to the host (use 'docker port' to see the actual mapping)") cmd.Var(&flPublish, "p", "Publish a container's port to the host (use 'docker port' to see the actual mapping)")
var flExpose ListOpts
cmd.Var(&flExpose, "expose", "Expose a port from the container without publishing it to your host")
var flEnv ListOpts var flEnv ListOpts
cmd.Var(&flEnv, "e", "Set environment variables") cmd.Var(&flEnv, "e", "Set environment variables")
@ -155,6 +190,9 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
var flLxcOpts ListOpts var flLxcOpts ListOpts
cmd.Var(&flLxcOpts, "lxc-conf", "Add custom lxc options -lxc-conf=\"lxc.cgroup.cpuset.cpus = 0,1\"") cmd.Var(&flLxcOpts, "lxc-conf", "Add custom lxc options -lxc-conf=\"lxc.cgroup.cpuset.cpus = 0,1\"")
var flLinks ListOpts
cmd.Var(&flLinks, "link", "Add link to another container (containerid:alias)")
if err := cmd.Parse(args); err != nil { if err := cmd.Parse(args); err != nil {
return nil, nil, cmd, err return nil, nil, cmd, err
} }
@ -220,10 +258,28 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
hostname = parts[0] hostname = parts[0]
domainname = parts[1] 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{ config := &Config{
Hostname: hostname, Hostname: *flHostname,
Domainname: domainname, Domainname: domainname,
PortSpecs: flPorts, PortSpecs: nil, // Deprecated
ExposedPorts: ports,
User: *flUser, User: *flUser,
Tty: *flTty, Tty: *flTty,
NetworkDisabled: !*flNetwork, NetworkDisabled: !*flNetwork,
@ -243,10 +299,13 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
Privileged: *flPrivileged, Privileged: *flPrivileged,
WorkingDir: *flWorkingDir, WorkingDir: *flWorkingDir,
} }
hostConfig := &HostConfig{ hostConfig := &HostConfig{
Binds: binds, Binds: binds,
ContainerIDFile: *flContainerIDFile, ContainerIDFile: *flContainerIDFile,
LxcConf: lxcConf, LxcConf: lxcConf,
PortBindings: portBindings,
Links: flLinks,
} }
if capabilities != nil && *flMemory > 0 && !capabilities.SwapLimit { if capabilities != nil && *flMemory > 0 && !capabilities.SwapLimit {
@ -261,36 +320,38 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
return config, hostConfig, cmd, nil return config, hostConfig, cmd, nil
} }
type PortMapping map[string]string type PortMapping map[string]string // Deprecated
type NetworkSettings struct { type NetworkSettings struct {
IPAddress string IPAddress string
IPPrefixLen int IPPrefixLen int
Gateway string Gateway string
Bridge 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 { func (settings *NetworkSettings) PortMappingAPI() []APIPort {
var mapping []APIPort var mapping []APIPort
for private, public := range settings.PortMapping["Tcp"] { for port, bindings := range settings.Ports {
pubint, _ := strconv.ParseInt(public, 0, 0) p, _ := parsePort(port.Port())
privint, _ := strconv.ParseInt(private, 0, 0) if len(bindings) == 0 {
mapping = append(mapping, APIPort{ mapping = append(mapping, APIPort{
PrivatePort: privint, PublicPort: int64(p),
PublicPort: pubint, Type: port.Proto(),
Type: "tcp", })
}) continue
} }
for private, public := range settings.PortMapping["Udp"] { for _, binding := range bindings {
pubint, _ := strconv.ParseInt(public, 0, 0) p, _ := parsePort(port.Port())
privint, _ := strconv.ParseInt(private, 0, 0) h, _ := parsePort(binding.HostPort)
mapping = append(mapping, APIPort{ mapping = append(mapping, APIPort{
PrivatePort: privint, PrivatePort: int64(p),
PublicPort: pubint, PublicPort: int64(h),
Type: "udp", Type: port.Proto(),
}) IP: binding.HostIp,
})
}
} }
return mapping return mapping
} }
@ -590,7 +651,7 @@ func (container *Container) Start(hostConfig *HostConfig) (err error) {
if container.runtime.networkManager.disabled { if container.runtime.networkManager.disabled {
container.Config.NetworkDisabled = true container.Config.NetworkDisabled = true
} else { } else {
if err := container.allocateNetwork(); err != nil { if err := container.allocateNetwork(hostConfig); err != nil {
return err return err
} }
} }
@ -780,6 +841,46 @@ func (container *Container) Start(hostConfig *HostConfig) (err error) {
"-e", "container=lxc", "-e", "container=lxc",
"-e", "HOSTNAME="+container.Config.Hostname, "-e", "HOSTNAME="+container.Config.Hostname,
) )
// Init any links between the parent and children
runtime := container.runtime
children, err := runtime.Children(fmt.Sprintf("/%s", container.ID))
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 != "" { if container.Config.WorkingDir != "" {
workingDir := path.Clean(container.Config.WorkingDir) workingDir := path.Clean(container.Config.WorkingDir)
utils.Debugf("[working dir] working dir is %s", workingDir) utils.Debugf("[working dir] working dir is %s", workingDir)
@ -882,7 +983,7 @@ func (container *Container) StderrPipe() (io.ReadCloser, error) {
return utils.NewBufReader(reader), nil return utils.NewBufReader(reader), nil
} }
func (container *Container) allocateNetwork() error { func (container *Container) allocateNetwork(hostConfig *HostConfig) error {
if container.Config.NetworkDisabled { if container.Config.NetworkDisabled {
return nil return nil
} }
@ -909,36 +1010,59 @@ func (container *Container) allocateNetwork() error {
} }
} }
var portSpecs []string if container.Config.PortSpecs != nil {
if !container.State.Ghost { utils.Debugf("Migrating port mappings for container: %s", strings.Join(container.Config.PortSpecs, ", "))
portSpecs = container.Config.PortSpecs if err := migratePortMappings(container.Config); err != nil {
} else { return err
for backend, frontend := range container.NetworkSettings.PortMapping["Tcp"] {
portSpecs = append(portSpecs, fmt.Sprintf("%s:%s/tcp", frontend, backend))
} }
for backend, frontend := range container.NetworkSettings.PortMapping["Udp"] { container.Config.PortSpecs = nil
portSpecs = append(portSpecs, fmt.Sprintf("%s:%s/udp", frontend, backend)) }
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
}
} }
} }
container.NetworkSettings.PortMapping = make(map[string]PortMapping) container.NetworkSettings.PortMapping = nil
container.NetworkSettings.PortMapping["Tcp"] = make(PortMapping)
container.NetworkSettings.PortMapping["Udp"] = make(PortMapping) for port := range portSpecs {
for _, spec := range portSpecs { binding := bindings[port]
nat, err := iface.AllocatePort(spec) for i := 0; i < len(binding); i++ {
if err != nil { b := binding[i]
iface.Release() nat, err := iface.AllocatePort(port, b)
return err 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
} }
proto := strings.Title(nat.Proto) bindings[port] = binding
backend, frontend := strconv.Itoa(nat.Backend), strconv.Itoa(nat.Frontend)
container.NetworkSettings.PortMapping[proto][backend] = frontend
} }
container.SaveHostConfig(hostConfig)
container.NetworkSettings.Ports = bindings
container.network = iface container.network = iface
container.NetworkSettings.Bridge = container.runtime.networkManager.bridgeIface container.NetworkSettings.Bridge = container.runtime.networkManager.bridgeIface
container.NetworkSettings.IPAddress = iface.IPNet.IP.String() container.NetworkSettings.IPAddress = iface.IPNet.IP.String()
container.NetworkSettings.IPPrefixLen, _ = iface.IPNet.Mask.Size() container.NetworkSettings.IPPrefixLen, _ = iface.IPNet.Mask.Size()
container.NetworkSettings.Gateway = iface.Gateway.String() container.NetworkSettings.Gateway = iface.Gateway.String()
return nil return nil
} }
@ -1021,6 +1145,14 @@ func (container *Container) monitor(hostConfig *HostConfig) {
func (container *Container) cleanup() { func (container *Container) cleanup() {
container.releaseNetwork() container.releaseNetwork()
// Disable all active links
if container.activeLinks != nil {
for _, link := range container.activeLinks {
link.Disable()
}
}
if container.Config.OpenStdin { if container.Config.OpenStdin {
if err := container.stdin.Close(); err != nil { if err := container.stdin.Close(); err != nil {
utils.Errorf("%s: Error close stdin: %s", container.ID, err) utils.Errorf("%s: Error close stdin: %s", container.ID, err)
@ -1292,3 +1424,9 @@ func (container *Container) Copy(resource string) (Archive, error) {
} }
return TarFilter(basePath, Uncompressed, filter) 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
}

View file

@ -18,7 +18,7 @@ import (
func TestIDFormat(t *testing.T) { func TestIDFormat(t *testing.T) {
runtime := mkRuntime(t) runtime := mkRuntime(t)
defer nuke(runtime) defer nuke(runtime)
container1, err := runtime.Create( container1, _, err := runtime.Create(
&Config{ &Config{
Image: GetTestImage(runtime).ID, Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/sh", "-c", "echo hello world"}, Cmd: []string{"/bin/sh", "-c", "echo hello world"},
@ -388,7 +388,7 @@ func TestRun(t *testing.T) {
func TestOutput(t *testing.T) { func TestOutput(t *testing.T) {
runtime := mkRuntime(t) runtime := mkRuntime(t)
defer nuke(runtime) defer nuke(runtime)
container, err := runtime.Create( container, _, err := runtime.Create(
&Config{ &Config{
Image: GetTestImage(runtime).ID, Image: GetTestImage(runtime).ID,
Cmd: []string{"echo", "-n", "foobar"}, Cmd: []string{"echo", "-n", "foobar"},
@ -411,7 +411,7 @@ func TestKillDifferentUser(t *testing.T) {
runtime := mkRuntime(t) runtime := mkRuntime(t)
defer nuke(runtime) defer nuke(runtime)
container, err := runtime.Create(&Config{ container, _, err := runtime.Create(&Config{
Image: GetTestImage(runtime).ID, Image: GetTestImage(runtime).ID,
Cmd: []string{"cat"}, Cmd: []string{"cat"},
OpenStdin: true, OpenStdin: true,
@ -471,7 +471,7 @@ func TestCreateVolume(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
c, err := runtime.Create(config) c, _, err := runtime.Create(config)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -486,7 +486,7 @@ func TestCreateVolume(t *testing.T) {
func TestKill(t *testing.T) { func TestKill(t *testing.T) {
runtime := mkRuntime(t) runtime := mkRuntime(t)
defer nuke(runtime) defer nuke(runtime)
container, err := runtime.Create(&Config{ container, _, err := runtime.Create(&Config{
Image: GetTestImage(runtime).ID, Image: GetTestImage(runtime).ID,
Cmd: []string{"sleep", "2"}, Cmd: []string{"sleep", "2"},
}, },
@ -530,7 +530,7 @@ func TestExitCode(t *testing.T) {
runtime := mkRuntime(t) runtime := mkRuntime(t)
defer nuke(runtime) defer nuke(runtime)
trueContainer, err := runtime.Create(&Config{ trueContainer, _, err := runtime.Create(&Config{
Image: GetTestImage(runtime).ID, Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/true", ""}, Cmd: []string{"/bin/true", ""},
}) })
@ -545,7 +545,7 @@ func TestExitCode(t *testing.T) {
t.Errorf("Unexpected exit code %d (expected 0)", trueContainer.State.ExitCode) 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, Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/false", ""}, Cmd: []string{"/bin/false", ""},
}) })
@ -564,7 +564,7 @@ func TestExitCode(t *testing.T) {
func TestRestart(t *testing.T) { func TestRestart(t *testing.T) {
runtime := mkRuntime(t) runtime := mkRuntime(t)
defer nuke(runtime) defer nuke(runtime)
container, err := runtime.Create(&Config{ container, _, err := runtime.Create(&Config{
Image: GetTestImage(runtime).ID, Image: GetTestImage(runtime).ID,
Cmd: []string{"echo", "-n", "foobar"}, Cmd: []string{"echo", "-n", "foobar"},
}, },
@ -594,7 +594,7 @@ func TestRestart(t *testing.T) {
func TestRestartStdin(t *testing.T) { func TestRestartStdin(t *testing.T) {
runtime := mkRuntime(t) runtime := mkRuntime(t)
defer nuke(runtime) defer nuke(runtime)
container, err := runtime.Create(&Config{ container, _, err := runtime.Create(&Config{
Image: GetTestImage(runtime).ID, Image: GetTestImage(runtime).ID,
Cmd: []string{"cat"}, Cmd: []string{"cat"},
@ -672,7 +672,7 @@ func TestUser(t *testing.T) {
defer nuke(runtime) defer nuke(runtime)
// Default user must be root // Default user must be root
container, err := runtime.Create(&Config{ container, _, err := runtime.Create(&Config{
Image: GetTestImage(runtime).ID, Image: GetTestImage(runtime).ID,
Cmd: []string{"id"}, Cmd: []string{"id"},
}, },
@ -690,7 +690,7 @@ func TestUser(t *testing.T) {
} }
// Set a username // Set a username
container, err = runtime.Create(&Config{ container, _, err = runtime.Create(&Config{
Image: GetTestImage(runtime).ID, Image: GetTestImage(runtime).ID,
Cmd: []string{"id"}, Cmd: []string{"id"},
@ -710,7 +710,7 @@ func TestUser(t *testing.T) {
} }
// Set a UID // Set a UID
container, err = runtime.Create(&Config{ container, _, err = runtime.Create(&Config{
Image: GetTestImage(runtime).ID, Image: GetTestImage(runtime).ID,
Cmd: []string{"id"}, Cmd: []string{"id"},
@ -730,7 +730,7 @@ func TestUser(t *testing.T) {
} }
// Set a different user by uid // Set a different user by uid
container, err = runtime.Create(&Config{ container, _, err = runtime.Create(&Config{
Image: GetTestImage(runtime).ID, Image: GetTestImage(runtime).ID,
Cmd: []string{"id"}, Cmd: []string{"id"},
@ -752,7 +752,7 @@ func TestUser(t *testing.T) {
} }
// Set a different user by username // Set a different user by username
container, err = runtime.Create(&Config{ container, _, err = runtime.Create(&Config{
Image: GetTestImage(runtime).ID, Image: GetTestImage(runtime).ID,
Cmd: []string{"id"}, Cmd: []string{"id"},
@ -772,7 +772,7 @@ func TestUser(t *testing.T) {
} }
// Test an wrong username // Test an wrong username
container, err = runtime.Create(&Config{ container, _, err = runtime.Create(&Config{
Image: GetTestImage(runtime).ID, Image: GetTestImage(runtime).ID,
Cmd: []string{"id"}, Cmd: []string{"id"},
@ -793,7 +793,7 @@ func TestMultipleContainers(t *testing.T) {
runtime := mkRuntime(t) runtime := mkRuntime(t)
defer nuke(runtime) defer nuke(runtime)
container1, err := runtime.Create(&Config{ container1, _, err := runtime.Create(&Config{
Image: GetTestImage(runtime).ID, Image: GetTestImage(runtime).ID,
Cmd: []string{"sleep", "2"}, Cmd: []string{"sleep", "2"},
}, },
@ -803,7 +803,7 @@ func TestMultipleContainers(t *testing.T) {
} }
defer runtime.Destroy(container1) defer runtime.Destroy(container1)
container2, err := runtime.Create(&Config{ container2, _, err := runtime.Create(&Config{
Image: GetTestImage(runtime).ID, Image: GetTestImage(runtime).ID,
Cmd: []string{"sleep", "2"}, Cmd: []string{"sleep", "2"},
}, },
@ -847,7 +847,7 @@ func TestMultipleContainers(t *testing.T) {
func TestStdin(t *testing.T) { func TestStdin(t *testing.T) {
runtime := mkRuntime(t) runtime := mkRuntime(t)
defer nuke(runtime) defer nuke(runtime)
container, err := runtime.Create(&Config{ container, _, err := runtime.Create(&Config{
Image: GetTestImage(runtime).ID, Image: GetTestImage(runtime).ID,
Cmd: []string{"cat"}, Cmd: []string{"cat"},
@ -892,7 +892,7 @@ func TestStdin(t *testing.T) {
func TestTty(t *testing.T) { func TestTty(t *testing.T) {
runtime := mkRuntime(t) runtime := mkRuntime(t)
defer nuke(runtime) defer nuke(runtime)
container, err := runtime.Create(&Config{ container, _, err := runtime.Create(&Config{
Image: GetTestImage(runtime).ID, Image: GetTestImage(runtime).ID,
Cmd: []string{"cat"}, Cmd: []string{"cat"},
@ -937,7 +937,7 @@ func TestTty(t *testing.T) {
func TestEnv(t *testing.T) { func TestEnv(t *testing.T) {
runtime := mkRuntime(t) runtime := mkRuntime(t)
defer nuke(runtime) defer nuke(runtime)
container, err := runtime.Create(&Config{ container, _, err := runtime.Create(&Config{
Image: GetTestImage(runtime).ID, Image: GetTestImage(runtime).ID,
Cmd: []string{"env"}, Cmd: []string{"env"},
}, },
@ -986,7 +986,7 @@ func TestEnv(t *testing.T) {
func TestEntrypoint(t *testing.T) { func TestEntrypoint(t *testing.T) {
runtime := mkRuntime(t) runtime := mkRuntime(t)
defer nuke(runtime) defer nuke(runtime)
container, err := runtime.Create( container, _, err := runtime.Create(
&Config{ &Config{
Image: GetTestImage(runtime).ID, Image: GetTestImage(runtime).ID,
Entrypoint: []string{"/bin/echo"}, Entrypoint: []string{"/bin/echo"},
@ -1009,7 +1009,7 @@ func TestEntrypoint(t *testing.T) {
func TestEntrypointNoCmd(t *testing.T) { func TestEntrypointNoCmd(t *testing.T) {
runtime := mkRuntime(t) runtime := mkRuntime(t)
defer nuke(runtime) defer nuke(runtime)
container, err := runtime.Create( container, _, err := runtime.Create(
&Config{ &Config{
Image: GetTestImage(runtime).ID, Image: GetTestImage(runtime).ID,
Entrypoint: []string{"/bin/echo", "foobar"}, Entrypoint: []string{"/bin/echo", "foobar"},
@ -1060,7 +1060,7 @@ func TestLXCConfig(t *testing.T) {
cpuMin := 100 cpuMin := 100
cpuMax := 10000 cpuMax := 10000
cpu := cpuMin + rand.Intn(cpuMax-cpuMin) cpu := cpuMin + rand.Intn(cpuMax-cpuMin)
container, err := runtime.Create(&Config{ container, _, err := runtime.Create(&Config{
Image: GetTestImage(runtime).ID, Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/true"}, Cmd: []string{"/bin/true"},
@ -1084,7 +1084,7 @@ func TestLXCConfig(t *testing.T) {
func TestCustomLxcConfig(t *testing.T) { func TestCustomLxcConfig(t *testing.T) {
runtime := mkRuntime(t) runtime := mkRuntime(t)
defer nuke(runtime) defer nuke(runtime)
container, err := runtime.Create(&Config{ container, _, err := runtime.Create(&Config{
Image: GetTestImage(runtime).ID, Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/true"}, Cmd: []string{"/bin/true"},
@ -1115,7 +1115,7 @@ func BenchmarkRunSequencial(b *testing.B) {
runtime := mkRuntime(b) runtime := mkRuntime(b)
defer nuke(runtime) defer nuke(runtime)
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
container, err := runtime.Create(&Config{ container, _, err := runtime.Create(&Config{
Image: GetTestImage(runtime).ID, Image: GetTestImage(runtime).ID,
Cmd: []string{"echo", "-n", "foo"}, Cmd: []string{"echo", "-n", "foo"},
}, },
@ -1147,7 +1147,7 @@ func BenchmarkRunParallel(b *testing.B) {
complete := make(chan error) complete := make(chan error)
tasks = append(tasks, complete) tasks = append(tasks, complete)
go func(i int, complete chan error) { go func(i int, complete chan error) {
container, err := runtime.Create(&Config{ container, _, err := runtime.Create(&Config{
Image: GetTestImage(runtime).ID, Image: GetTestImage(runtime).ID,
Cmd: []string{"echo", "-n", "foo"}, Cmd: []string{"echo", "-n", "foo"},
}, },
@ -1297,7 +1297,7 @@ func TestBindMounts(t *testing.T) {
func TestVolumesFromReadonlyMount(t *testing.T) { func TestVolumesFromReadonlyMount(t *testing.T) {
runtime := mkRuntime(t) runtime := mkRuntime(t)
defer nuke(runtime) defer nuke(runtime)
container, err := runtime.Create( container, _, err := runtime.Create(
&Config{ &Config{
Image: GetTestImage(runtime).ID, Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/echo", "-n", "foobar"}, Cmd: []string{"/bin/echo", "-n", "foobar"},
@ -1316,7 +1316,7 @@ func TestVolumesFromReadonlyMount(t *testing.T) {
t.Fail() t.Fail()
} }
container2, err := runtime.Create( container2, _, err := runtime.Create(
&Config{ &Config{
Image: GetTestImage(runtime).ID, Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/echo", "-n", "foobar"}, Cmd: []string{"/bin/echo", "-n", "foobar"},
@ -1352,7 +1352,7 @@ func TestRestartWithVolumes(t *testing.T) {
runtime := mkRuntime(t) runtime := mkRuntime(t)
defer nuke(runtime) defer nuke(runtime)
container, err := runtime.Create(&Config{ container, _, err := runtime.Create(&Config{
Image: GetTestImage(runtime).ID, Image: GetTestImage(runtime).ID,
Cmd: []string{"echo", "-n", "foobar"}, Cmd: []string{"echo", "-n", "foobar"},
Volumes: map[string]struct{}{"/test": {}}, Volumes: map[string]struct{}{"/test": {}},
@ -1395,7 +1395,7 @@ func TestVolumesFromWithVolumes(t *testing.T) {
runtime := mkRuntime(t) runtime := mkRuntime(t)
defer nuke(runtime) defer nuke(runtime)
container, err := runtime.Create(&Config{ container, _, err := runtime.Create(&Config{
Image: GetTestImage(runtime).ID, Image: GetTestImage(runtime).ID,
Cmd: []string{"sh", "-c", "echo -n bar > /test/foo"}, Cmd: []string{"sh", "-c", "echo -n bar > /test/foo"},
Volumes: map[string]struct{}{"/test": {}}, Volumes: map[string]struct{}{"/test": {}},
@ -1422,7 +1422,7 @@ func TestVolumesFromWithVolumes(t *testing.T) {
t.Fail() t.Fail()
} }
container2, err := runtime.Create( container2, _, err := runtime.Create(
&Config{ &Config{
Image: GetTestImage(runtime).ID, Image: GetTestImage(runtime).ID,
Cmd: []string{"cat", "/test/foo"}, Cmd: []string{"cat", "/test/foo"},
@ -1463,7 +1463,7 @@ func TestOnlyLoopbackExistsWhenUsingDisableNetworkOption(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
c, err := runtime.Create(config) c, _, err := runtime.Create(config)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -1529,7 +1529,7 @@ func TestMultipleVolumesFrom(t *testing.T) {
runtime := mkRuntime(t) runtime := mkRuntime(t)
defer nuke(runtime) defer nuke(runtime)
container, err := runtime.Create(&Config{ container, _, err := runtime.Create(&Config{
Image: GetTestImage(runtime).ID, Image: GetTestImage(runtime).ID,
Cmd: []string{"sh", "-c", "echo -n bar > /test/foo"}, Cmd: []string{"sh", "-c", "echo -n bar > /test/foo"},
Volumes: map[string]struct{}{"/test": {}}, Volumes: map[string]struct{}{"/test": {}},
@ -1556,7 +1556,7 @@ func TestMultipleVolumesFrom(t *testing.T) {
t.Fail() t.Fail()
} }
container2, err := runtime.Create( container2, _, err := runtime.Create(
&Config{ &Config{
Image: GetTestImage(runtime).ID, Image: GetTestImage(runtime).ID,
Cmd: []string{"sh", "-c", "echo -n bar > /other/foo"}, Cmd: []string{"sh", "-c", "echo -n bar > /other/foo"},
@ -1577,7 +1577,7 @@ func TestMultipleVolumesFrom(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
container3, err := runtime.Create( container3, _, err := runtime.Create(
&Config{ &Config{
Image: GetTestImage(runtime).ID, Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/echo", "-n", "foobar"}, Cmd: []string{"/bin/echo", "-n", "foobar"},

View file

@ -7,6 +7,7 @@ import (
"github.com/dotcloud/docker/utils" "github.com/dotcloud/docker/utils"
"io/ioutil" "io/ioutil"
"log" "log"
"net"
"os" "os"
"os/signal" "os/signal"
"strconv" "strconv"
@ -37,7 +38,11 @@ func main() {
flDns := flag.String("dns", "", "Set custom dns servers") flDns := flag.String("dns", "", "Set custom dns servers")
flHosts := docker.ListOpts{fmt.Sprintf("unix://%s", docker.DEFAULTUNIXSOCKET)} flHosts := docker.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") 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")
flag.Parse() flag.Parse()
if *flVersion { if *flVersion {
showVersion() showVersion()
return return
@ -49,10 +54,9 @@ func main() {
flHosts[i] = utils.ParseHost(docker.DEFAULTHTTPHOST, docker.DEFAULTHTTPPORT, flHost) flHosts[i] = utils.ParseHost(docker.DEFAULTHTTPHOST, docker.DEFAULTHTTPPORT, flHost)
} }
bridge := docker.DefaultNetworkBridge
if *bridgeName != "" { if *bridgeName != "" {
docker.NetworkBridgeIface = *bridgeName bridge = *bridgeName
} else {
docker.NetworkBridgeIface = docker.DefaultNetworkBridge
} }
if *flDebug { if *flDebug {
os.Setenv("DEBUG", "1") os.Setenv("DEBUG", "1")
@ -64,7 +68,25 @@ func main() {
flag.Usage() flag.Usage()
return 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,
}
if err := daemon(config); err != nil {
log.Fatal(err) log.Fatal(err)
os.Exit(-1) os.Exit(-1)
} }
@ -115,30 +137,26 @@ func removePidFile(pidfile string) {
} }
} }
func daemon(pidfile string, flGraphPath string, protoAddrs []string, autoRestart, enableCors bool, flDns string) error { func daemon(config *docker.DaemonConfig) error {
if err := createPidFile(pidfile); err != nil { if err := createPidFile(config.Pidfile); err != nil {
log.Fatal(err) log.Fatal(err)
} }
defer removePidFile(pidfile) defer removePidFile(config.Pidfile)
c := make(chan os.Signal, 1) c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, os.Kill, os.Signal(syscall.SIGTERM)) signal.Notify(c, os.Interrupt, os.Kill, os.Signal(syscall.SIGTERM))
go func() { go func() {
sig := <-c sig := <-c
log.Printf("Received signal '%v', exiting\n", sig) log.Printf("Received signal '%v', exiting\n", sig)
removePidFile(pidfile) removePidFile(config.Pidfile)
os.Exit(0) os.Exit(0)
}() }()
var dns []string server, err := docker.NewServer(config)
if flDns != "" {
dns = []string{flDns}
}
server, err := docker.NewServer(flGraphPath, autoRestart, enableCors, dns)
if err != nil { if err != nil {
return err return err
} }
chErrors := make(chan error, len(protoAddrs)) chErrors := make(chan error, len(config.ProtoAddresses))
for _, protoAddr := range protoAddrs { for _, protoAddr := range config.ProtoAddresses {
protoAddrParts := strings.SplitN(protoAddr, "://", 2) protoAddrParts := strings.SplitN(protoAddr, "://", 2)
if protoAddrParts[0] == "unix" { if protoAddrParts[0] == "unix" {
syscall.Unlink(protoAddrParts[1]) syscall.Unlink(protoAddrParts[1])
@ -154,7 +172,7 @@ func daemon(pidfile string, flGraphPath string, protoAddrs []string, autoRestart
chErrors <- docker.ListenAndServe(protoAddrParts[0], protoAddrParts[1], server, true) 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 err := <-chErrors
if err != nil { if err != nil {
return err return err

View file

@ -93,8 +93,8 @@ Examples:
.. _cli_build_examples: .. _cli_build_examples:
Examples Examples:
~~~~~~~~ ~~~~~~~~~
.. code-block:: bash .. code-block:: bash
@ -400,6 +400,33 @@ Insert file from github
Kill a running container Kill a running container
.. _cli_link:
``link``
--------
::
Usage: docker link CURRENT_NAME NEW_NAME
Link a container to a new name.
Examples:
~~~~~~~~~
.. code-block:: bash
$ docker link /59669e088202c2ebe150b4346cb3301562d073b51261176a354a74e8f618bfbc /redis
$ docker ls
NAME ID IMAGE
/redis 59669e088202c2ebe150b4346cb3301562d073b51261176a354a74e8f618bfbc crosbymichael/redis:latest
/59669e088202c2ebe150b4346cb3301562d073b51261176a354a74e8f618bfbc 59669e088202c2ebe150b4346cb3301562d073b51261176a354a74e8f618bfbc crosbymichael/redis:latest
This will create a new link for the existing name ``/59669e088202c2ebe150b4346cb3301562d073b51261176a354a74e8f618bfbc``
with the new name ``/redis`` so that we can new reference the same container under the new name ``/redis``.
.. _cli_login: .. _cli_login:
``login`` ``login``
@ -427,7 +454,6 @@ Insert file from github
``logs`` ``logs``
-------- --------
:: ::
Usage: docker logs [OPTIONS] CONTAINER Usage: docker logs [OPTIONS] CONTAINER
@ -507,6 +533,29 @@ Insert file from github
Usage: docker rm [OPTIONS] CONTAINER Usage: docker rm [OPTIONS] CONTAINER
Remove one or more containers 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: .. _cli_rmi:
@ -530,7 +579,7 @@ Insert file from github
Run a command in a new container 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) -c=0: CPU shares (relative weight)
-cidfile="": Write the container ID to the file -cidfile="": Write the container ID to the file
-d=false: Detached mode: Run container in the background, print new container id -d=false: Detached mode: Run container in the background, print new container id
@ -546,13 +595,15 @@ Insert file from github
-u="": Username or UID -u="": Username or UID
-dns=[]: Set custom dns servers for the container -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. -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. -volumes-from="": Mount all volumes from the given container
-entrypoint="": Overwrite the default entrypoint set by the image. -entrypoint="": Overwrite the default entrypoint set by the image
-w="": Working directory inside the container -w="": Working directory inside the container
-lxc-conf=[]: Add custom lxc options -lxc-conf="lxc.cgroup.cpuset.cpus = 0,1" -lxc-conf=[]: Add custom lxc options -lxc-conf="lxc.cgroup.cpuset.cpus = 0,1"
-expose=[]: Expose a port from the container without publishing it to your host
-link="": Add link to another container (containerid:alias)
Examples Examples
~~~~~~~~ --------
.. code-block:: bash .. code-block:: bash
@ -600,6 +651,38 @@ working directory, by changing into the directory to the value
returned by ``pwd``. So this combination executes the command returned by ``pwd``. So this combination executes the command
using the container, but inside the current working directory. 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 -link /redis:redis 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.
.. _cli_search: .. _cli_search:
``search`` ``search``

View file

@ -1,6 +1,6 @@
:title: Docker Examples :title: Docker Examples
:description: Examples on how to use Docker :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: .. _example_list:
@ -24,3 +24,4 @@ to more substantial services like you might find in production.
postgresql_service postgresql_service
mongodb mongodb
running_riak_service running_riak_service
linking_into_redis

View file

@ -0,0 +1,146 @@
: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
----------------------------
We will use a pre-build version of redis from the index under
the name ``crosbymichael/redis``. If you are interested in the
Dockerfile that was used to build this container here it is.
.. code-block:: bash
# 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 (cd /redis && make test)
RUN mkdir -p /redis-data
VOLUME ["/redis-data"]
EXPOSE 6379
ENTRYPOINT ["/redis/src/redis-server"]
CMD ["--dir", "/redis-data"]
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 crosbymichael/redis --requirepass=docker
This will run our redis container using the default port of 6379 and using
as password to secure our service. Next we will link the redis container to
a new name using ``docker link`` and ``docker ls``.
Linking an existing container
-----------------------------
.. code-block:: bash
docker ls
NAME ID IMAGE
/39588b6a45100ef5b328b2c302ea085624f29e6cbab70f88be04793af02cec89 39588b6a45100ef5b328b2c302ea085624f29e6cbab70f88be04793af02cec89 crosbymichael/redis:latest
Docker will automatically create an initial link with the container's id but
because the is long and not very user friendly we can link the container with
a new name.
.. code-block:: bash
docker link /39588b6a45100ef5b328b2c302ea085624f29e6cbab70f88be04793af02cec89 /redis
docker ls
NAME ID IMAGE
/redis 39588b6a45100ef5b328b2c302ea085624f29e6cbab70f88be04793af02cec89 crosbymichael/redis:latest
/39588b6a45100ef5b328b2c302ea085624f29e6cbab70f88be04793af02cec89 39588b6a45100ef5b328b2c302ea085624f29e6cbab70f88be04793af02cec89 crosbymichael/redis:latest
Now we can reference our running redis service using the friendly name ``/redis``.
We can issue all the commands that you would expect; start, stop, attach, using the new name.
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 service we did not use
the ``-p`` option to publish the redis port to the host system. Redis exposed port 6379
but we did not publish the port. This allows docker to prevent all network traffic to
the redis container except when explicitly specified within a link. This is a big win
for security.
Now lets start our web application with a link into redis.
.. code-block:: bash
docker run -t -i -link /redis:db ubuntu bash
root@4c01db0b339c:/# env
HOSTNAME=4c01db0b339c
DB_NAME=/4c01db0b339cf19958731255a796ee072040a652f51652a4ade190ab8c27006f/db
TERM=xterm
DB_PORT=tcp://172.17.0.8:6379
DB_PORT_6379_TCP=tcp://172.17.0.8: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 child.
.. code-block:: bash
# The name of the child container
DB_NAME=/4c01db0b339cf19958731255a796ee072040a652f51652a4ade190ab8c27006f/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
# 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.

1
gograph/MAINTAINERS Normal file
View file

@ -0,0 +1 @@
Michael Crosby <michael@crosbymichael.com> (@crosbymichael)

455
gograph/gograph.go Normal file
View file

@ -0,0 +1,455 @@
package gograph
import (
_ "code.google.com/p/gosqlite/sqlite3"
"database/sql"
"fmt"
"os"
"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")
);
CREATE UNIQUE INDEX "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 {
dbPath string
}
// Create a new graph database initialized with a root entity
func NewDatabase(dbPath string) (*Database, error) {
db := &Database{dbPath}
if _, err := os.Stat(dbPath); err == nil {
return db, nil
}
conn, err := db.openConn()
if err != nil {
return nil, err
}
defer conn.Close()
if _, err := conn.Exec(createEntityTable); err != nil {
return nil, err
}
if _, err := conn.Exec(createEdgeTable); 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
}
// Set the entity id for a given path
func (db *Database) Set(fullPath, id string) (*Entity, error) {
conn, err := db.openConn()
if err != nil {
return nil, err
}
defer conn.Close()
rollback := func() {
conn.Exec("ROLLBACK")
}
if _, err := conn.Exec("BEGIN"); err != nil {
return nil, err
}
var entityId string
if err := conn.QueryRow("SELECT id FROM entity WHERE id = ?;", id).Scan(&entityId); err != nil {
if err == sql.ErrNoRows {
if _, err := 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(conn, parentPath, name, e); err != nil {
rollback()
return nil, err
}
if _, err := conn.Exec("COMMIT"); err != nil {
return nil, err
}
return e, nil
}
func (db *Database) setEdge(conn *sql.DB, parentPath, name string, e *Entity) error {
parent, err := db.get(conn, parentPath)
if err != nil {
return err
}
if parent.id == e.id {
return fmt.Errorf("Cannot set self as child")
}
if _, err := 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 {
conn, err := db.openConn()
if err != nil {
return nil
}
e, err := db.get(conn, name)
if err != nil {
return nil
}
return e
}
func (db *Database) get(conn *sql.DB, 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]
next := db.child(conn, e, p)
if next == nil {
return nil, fmt.Errorf("Cannot find child")
}
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{}
conn, err := db.openConn()
if err != nil {
return out
}
defer conn.Close()
for c := range db.children(conn, name, depth) {
out[c.FullPath] = c.Entity
}
return out
}
func (db *Database) Walk(name string, walkFunc WalkFunc, depth int) error {
conn, err := db.openConn()
if err != nil {
return err
}
defer conn.Close()
for c := range db.children(conn, 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 {
conn, err := db.openConn()
if err != nil {
return -1
}
defer conn.Close()
var count int
if err := 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{}
conn, err := db.openConn()
if err != nil {
return refs
}
defer conn.Close()
rows, err := 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")
}
conn, err := db.openConn()
if err != nil {
return err
}
defer conn.Close()
parentPath, n := splitPath(name)
parent, err := db.get(conn, parentPath)
if err != nil {
return err
}
if _, err := 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) {
conn, err := db.openConn()
if err != nil {
return -1, err
}
defer conn.Close()
rollback := func() {
conn.Exec("ROLLBACK")
}
if _, err := conn.Exec("BEGIN"); err != nil {
return -1, err
}
// Delete all edges
rows, err := 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 := conn.Exec("DELETE FROM entity where id = ?;", id); err != nil {
rollback()
return -1, err
}
if _, err := 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)
}
conn, err := db.openConn()
if err != nil {
return err
}
defer conn.Close()
parent, err := db.get(conn, parentPath)
if err != nil {
return err
}
rows, err := 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(conn *sql.DB, name string, depth int) <-chan WalkMeta {
out := make(chan WalkMeta)
e, err := db.get(conn, name)
if err != nil {
close(out)
return out
}
go func() {
rows, err := 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(conn, 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(conn *sql.DB, parent *Entity, name string) *Entity {
var id string
if err := conn.QueryRow("SELECT entity_id FROM edge WHERE parent_id = ? AND name = ?;", parent.id, name).Scan(&id); err != nil {
return nil
}
return &Entity{id}
}
func (db *Database) openConn() (*sql.DB, error) {
return sql.Open("sqlite3", db.dbPath)
}
// 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
}

452
gograph/gograph_test.go Normal file
View file

@ -0,0 +1,452 @@
package gograph
import (
"os"
"path"
"strconv"
"testing"
)
func newTestDb(t *testing.T) *Database {
db, err := NewDatabase(path.Join(os.TempDir(), "sqlite.db"))
if err != nil {
t.Fatal(err)
}
return db
}
func destroyTestDb(db *Database) {
os.Remove(db.dbPath)
}
func TestNewDatabase(t *testing.T) {
db := newTestDb(t)
if db == nil {
t.Fatal("Datbase should not be nil")
}
defer destroyTestDb(db)
}
func TestCreateRootEnity(t *testing.T) {
db := newTestDb(t)
defer destroyTestDb(db)
root := db.RootEntity()
if root == nil {
t.Fatal("Root entity should not be nil")
}
}
func TestGetRootEntity(t *testing.T) {
db := newTestDb(t)
defer destroyTestDb(db)
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 := newTestDb(t)
defer destroyTestDb(db)
db.Set("/test", "1")
if _, err := db.Set("/other", "1"); err != nil {
t.Fatal(err)
}
}
func TestCreateChild(t *testing.T) {
db := newTestDb(t)
defer destroyTestDb(db)
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 := newTestDb(t)
defer destroyTestDb(db)
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 := newTestDb(t)
defer destroyTestDb(db)
_, 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 := newTestDb(t)
defer destroyTestDb(db)
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 := newTestDb(t)
defer destroyTestDb(db)
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 := newTestDb(t)
defer destroyTestDb(db)
_, 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 := newTestDb(t)
defer destroyTestDb(db)
_, 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 := newTestDb(t)
defer destroyTestDb(db)
_, 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 := newTestDb(t)
defer destroyTestDb(db)
if err := db.Delete("/"); err == nil {
t.Fatal("Error should not be nil")
}
}
func TestDeleteEntity(t *testing.T) {
db := newTestDb(t)
defer destroyTestDb(db)
_, 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 := newTestDb(t)
defer destroyTestDb(db)
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 := newTestDb(t)
defer destroyTestDb(db)
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 := newTestDb(t)
defer destroyTestDb(db)
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 := newTestDb(t)
defer destroyTestDb(db)
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 := newTestDb(t)
defer destroyTestDb(db)
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))
}
}

27
gograph/sort.go Normal file
View file

@ -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])
}

29
gograph/sort_test.go Normal file
View file

@ -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])
}
}

32
gograph/utils.go Normal file
View file

@ -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
}

View file

@ -44,7 +44,8 @@ if [ -n "$(git status --porcelain)" ]; then
fi fi
# Use these flags when compiling the tests and final binary # 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 -linkmode external -extldflags "-lpthread -static -Wl,--unresolved-symbols=ignore-in-object-files"'
BUILDFLAGS='-tags netgo'
bundle() { bundle() {

View file

@ -2,6 +2,6 @@
DEST=$1 DEST=$1
if go build -o $DEST/docker-$VERSION -ldflags "$LDFLAGS" ./docker; then go build -o $DEST/docker-$VERSION -ldflags "$LDFLAGS" $BUILDFLAGS ./docker
echo "Created binary: $DEST/docker-$VERSION"
fi echo "Created binary: $DEST/docker-$VERSION"

View file

@ -14,7 +14,7 @@ bundle_test() {
for test_dir in $(find_test_dirs); do ( for test_dir in $(find_test_dirs); do (
set -x set -x
cd $test_dir cd $test_dir
go test -v -ldflags "$LDFLAGS" $TESTFLAGS go test -v -ldflags "$LDFLAGS" $BUILDFLAGS
) done ) done
} 2>&1 | tee $DEST/test.log } 2>&1 | tee $DEST/test.log
} }

1
iptables/MAINTAINERS Normal file
View file

@ -0,0 +1 @@
Michael Crosby <michael@crosbymichael.com> (@crosbymichael)

105
iptables/iptables.go Normal file
View file

@ -0,0 +1,105 @@
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
}
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
}

18
iptables/iptables_test.go Normal file
View file

@ -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")
}
}

141
links.go Normal file
View file

@ -0,0 +1,141 @@
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.ID, 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()))
}
// 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
}

104
links_test.go Normal file
View file

@ -0,0 +1,104 @@
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 tcp://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_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"])
}
}

View file

@ -4,6 +4,8 @@ import (
"encoding/binary" "encoding/binary"
"errors" "errors"
"fmt" "fmt"
"github.com/dotcloud/docker/iptables"
"github.com/dotcloud/docker/proxy"
"github.com/dotcloud/docker/utils" "github.com/dotcloud/docker/utils"
"log" "log"
"net" "net"
@ -13,8 +15,6 @@ import (
"sync" "sync"
) )
var NetworkBridgeIface string
const ( const (
DefaultNetworkBridge = "docker0" DefaultNetworkBridge = "docker0"
DisableNetworkBridge = "none" DisableNetworkBridge = "none"
@ -81,18 +81,6 @@ func ip(args ...string) (string, error) {
return string(output), nil 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 { func checkRouteOverlaps(routes string, dockerNetwork *net.IPNet) error {
utils.Debugf("Routes:\n\n%s", routes) utils.Debugf("Routes:\n\n%s", routes)
for _, line := range strings.Split(routes, "\n") { for _, line := range strings.Split(routes, "\n") {
@ -124,7 +112,7 @@ func checkRouteOverlaps(routes string, dockerNetwork *net.IPNet) error {
// CreateBridgeIface creates a network bridge interface on the host system with the name `ifaceName`, // 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. // 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. // 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{ addrs := []string{
// Here we don't follow the convention of using the 1st IP of the range for the gateway. // 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. // This is to use the same gateway IPs as the /24 ranges, which predate the /16 ranges.
@ -163,23 +151,29 @@ func CreateBridgeIface(ifaceName string) error {
} }
} }
if ifaceAddr == "" { 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 { if output, err := ip("link", "add", config.BridgeIface, "type", "bridge"); err != nil {
return fmt.Errorf("Error creating bridge: %s (output: %s)", err, output) return fmt.Errorf("Error creating bridge: %s (output: %s)", err, output)
} }
if output, err := ip("addr", "add", ifaceAddr, "dev", ifaceName); err != nil { if output, err := ip("addr", "add", ifaceAddr, "dev", config.BridgeIface); err != nil {
return fmt.Errorf("Unable to add private network: %s (%s)", err, output) return fmt.Errorf("Unable to add private network: %s (%s)", err, output)
} }
if output, err := ip("link", "set", ifaceName, "up"); err != nil { if output, err := ip("link", "set", config.BridgeIface, "up"); err != nil {
return fmt.Errorf("Unable to start network bridge: %s (%s)", err, output) return fmt.Errorf("Unable to start network bridge: %s (%s)", err, output)
} }
if err := iptables("-t", "nat", "-A", "POSTROUTING", "-s", ifaceAddr, if config.EnableIptables {
"!", "-d", ifaceAddr, "-j", "MASQUERADE"); err != nil { if err := iptables.Raw("-t", "nat", "-A", "POSTROUTING", "-s", ifaceAddr,
return fmt.Errorf("Unable to enable network bridge NAT: %s", err) "!", "-d", ifaceAddr, "-j", "MASQUERADE"); err != nil {
return fmt.Errorf("Unable to enable network bridge NAT: %s", err)
}
// Prevent inter-container communication by default
if err := iptables.Raw("-A", "FORWARD", "-i", config.BridgeIface, "-o", config.BridgeIface, "-j", "DROP"); err != nil {
return fmt.Errorf("Unable to prevent intercontainer communication: %s", err)
}
} }
return nil return nil
} }
@ -216,58 +210,27 @@ func getIfaceAddr(name string) (net.Addr, error) {
// It keeps track of all mappings and is able to unmap at will // It keeps track of all mappings and is able to unmap at will
type PortMapper struct { type PortMapper struct {
tcpMapping map[int]*net.TCPAddr tcpMapping map[int]*net.TCPAddr
tcpProxies map[int]Proxy tcpProxies map[int]proxy.Proxy
udpMapping map[int]*net.UDPAddr 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 { func (mapper *PortMapper) Map(ip net.IP, port int, backendAddr net.Addr) 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 {
if _, isTCP := backendAddr.(*net.TCPAddr); isTCP { if _, isTCP := backendAddr.(*net.TCPAddr); isTCP {
backendPort := backendAddr.(*net.TCPAddr).Port backendPort := backendAddr.(*net.TCPAddr).Port
backendIP := backendAddr.(*net.TCPAddr).IP backendIP := backendAddr.(*net.TCPAddr).IP
if err := mapper.iptablesForward("-A", port, "tcp", backendIP.String(), backendPort); err != nil { if mapper.iptables != nil {
return err if err := mapper.iptables.Forward(iptables.Add, ip, port, "tcp", backendIP.String(), backendPort); err != nil {
return err
}
} }
mapper.tcpMapping[port] = backendAddr.(*net.TCPAddr) 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 { if err != nil {
mapper.Unmap(port, "tcp") mapper.Unmap(ip, port, "tcp")
return err return err
} }
mapper.tcpProxies[port] = proxy mapper.tcpProxies[port] = proxy
@ -275,13 +238,15 @@ func (mapper *PortMapper) Map(port int, backendAddr net.Addr) error {
} else { } else {
backendPort := backendAddr.(*net.UDPAddr).Port backendPort := backendAddr.(*net.UDPAddr).Port
backendIP := backendAddr.(*net.UDPAddr).IP backendIP := backendAddr.(*net.UDPAddr).IP
if err := mapper.iptablesForward("-A", port, "udp", backendIP.String(), backendPort); err != nil { if mapper.iptables != nil {
return err if err := mapper.iptables.Forward(iptables.Add, ip, port, "udp", backendIP.String(), backendPort); err != nil {
return err
}
} }
mapper.udpMapping[port] = backendAddr.(*net.UDPAddr) 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 { if err != nil {
mapper.Unmap(port, "udp") mapper.Unmap(ip, port, "udp")
return err return err
} }
mapper.udpProxies[port] = proxy mapper.udpProxies[port] = proxy
@ -290,7 +255,7 @@ func (mapper *PortMapper) Map(port int, backendAddr net.Addr) error {
return nil 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" { if proto == "tcp" {
backendAddr, ok := mapper.tcpMapping[port] backendAddr, ok := mapper.tcpMapping[port]
if !ok { if !ok {
@ -300,8 +265,10 @@ func (mapper *PortMapper) Unmap(port int, proto string) error {
proxy.Close() proxy.Close()
delete(mapper.tcpProxies, port) delete(mapper.tcpProxies, port)
} }
if err := mapper.iptablesForward("-D", port, proto, backendAddr.IP.String(), backendAddr.Port); err != nil { if mapper.iptables != nil {
return err if err := mapper.iptables.Forward(iptables.Delete, ip, port, proto, backendAddr.IP.String(), backendAddr.Port); err != nil {
return err
}
} }
delete(mapper.tcpMapping, port) delete(mapper.tcpMapping, port)
} else { } else {
@ -313,21 +280,37 @@ func (mapper *PortMapper) Unmap(port int, proto string) error {
proxy.Close() proxy.Close()
delete(mapper.udpProxies, port) delete(mapper.udpProxies, port)
} }
if err := mapper.iptablesForward("-D", port, proto, backendAddr.IP.String(), backendAddr.Port); err != nil { if mapper.iptables != nil {
return err if err := mapper.iptables.Forward(iptables.Delete, ip, port, proto, backendAddr.IP.String(), backendAddr.Port); err != nil {
return err
}
} }
delete(mapper.udpMapping, port) delete(mapper.udpMapping, port)
} }
return nil return nil
} }
func newPortMapper() (*PortMapper, error) { func newPortMapper(config *DaemonConfig) (*PortMapper, error) {
mapper := &PortMapper{} // We can always try removing the iptables
if err := mapper.cleanup(); err != nil { if err := iptables.RemoveExistingChain("DOCKER"); err != nil {
return nil, err return nil, err
} }
if err := mapper.setup(); err != nil { var chain *iptables.Chain
return nil, err 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 return mapper, nil
} }
@ -519,40 +502,56 @@ type NetworkInterface struct {
disabled bool disabled bool
} }
// Allocate an external TCP port and map it to the interface // Allocate an external port and map it to the interface
func (iface *NetworkInterface) AllocatePort(spec string) (*Nat, error) { func (iface *NetworkInterface) AllocatePort(port Port, binding PortBinding) (*Nat, error) {
if iface.disabled { if iface.disabled {
return nil, fmt.Errorf("Trying to allocate port for interface %v, which is disabled", iface) // FIXME 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 { if err != nil {
return nil, err return nil, err
} }
if nat.Proto == "tcp" { hostPort, _ := parsePort(nat.Binding.HostPort)
extPort, err := iface.manager.tcpPortAllocator.Acquire(nat.Frontend)
if nat.Port.Proto() == "tcp" {
extPort, err := iface.manager.tcpPortAllocator.Acquire(hostPort)
if err != nil { if err != nil {
return nil, err 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) iface.manager.tcpPortAllocator.Release(extPort)
return nil, err return nil, err
} }
nat.Frontend = extPort nat.Binding.HostPort = strconv.Itoa(extPort)
} else { } else {
extPort, err := iface.manager.udpPortAllocator.Acquire(nat.Frontend) extPort, err := iface.manager.udpPortAllocator.Acquire(hostPort)
if err != nil { if err != nil {
return nil, err return nil, err
} }
backend := &net.UDPAddr{IP: iface.IPNet.IP, Port: nat.Backend} backend := &net.UDPAddr{IP: iface.IPNet.IP, Port: containerPort}
if err := iface.manager.portMapper.Map(extPort, backend); err != nil { if err := iface.manager.portMapper.Map(ip, extPort, backend); err != nil {
iface.manager.udpPortAllocator.Release(extPort) iface.manager.udpPortAllocator.Release(extPort)
return nil, err return nil, err
} }
nat.Frontend = extPort nat.Binding.HostPort = strconv.Itoa(extPort)
} }
iface.extPorts = append(iface.extPorts, nat) iface.extPorts = append(iface.extPorts, nat)
@ -560,83 +559,37 @@ func (iface *NetworkInterface) AllocatePort(spec string) (*Nat, error) {
} }
type Nat struct { type Nat struct {
Proto string Port Port
Frontend int Binding PortBinding
Backend int
} }
func parseNat(spec string) (*Nat, error) { func (n *Nat) String() string {
var nat Nat return fmt.Sprintf("%s:%d:%d/%s", n.Binding.HostIp, n.Binding.HostPort, n.Port.Port(), n.Port.Proto())
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
} }
// Release: Network cleanup - release all resources // Release: Network cleanup - release all resources
func (iface *NetworkInterface) Release() { func (iface *NetworkInterface) Release() {
if iface.disabled { if iface.disabled {
return return
} }
for _, nat := range iface.extPorts { for _, nat := range iface.extPorts {
utils.Debugf("Unmaping %v/%v", nat.Proto, nat.Frontend) hostPort, err := parsePort(nat.Binding.HostPort)
if err := iface.manager.portMapper.Unmap(nat.Frontend, nat.Proto); err != nil { if err != nil {
log.Printf("Unable to unmap port %v/%v: %v", nat.Proto, nat.Frontend, err) log.Printf("Unable to get host port: %s", err)
continue
} }
if nat.Proto == "tcp" { ip := net.ParseIP(nat.Binding.HostIp)
if err := iface.manager.tcpPortAllocator.Release(nat.Frontend); err != nil { utils.Debugf("Unmaping %s/%s", nat.Port.Proto, nat.Binding.HostPort)
log.Printf("Unable to release port tcp/%v: %v", nat.Frontend, err) 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 { } else if err := iface.manager.udpPortAllocator.Release(hostPort); err != nil {
log.Printf("Unable to release port udp/%v: %v", nat.Frontend, err) log.Printf("Unable to release port %s: %s", nat, err)
} }
} }
@ -704,22 +657,21 @@ func (manager *NetworkManager) Close() error {
return err3 return err3
} }
func newNetworkManager(bridgeIface string) (*NetworkManager, error) { func newNetworkManager(config *DaemonConfig) (*NetworkManager, error) {
if config.BridgeIface == DisableNetworkBridge {
if bridgeIface == DisableNetworkBridge {
manager := &NetworkManager{ manager := &NetworkManager{
disabled: true, disabled: true,
} }
return manager, nil return manager, nil
} }
addr, err := getIfaceAddr(bridgeIface) addr, err := getIfaceAddr(config.BridgeIface)
if err != nil { if err != nil {
// If the iface is not found, try to create it // 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 return nil, err
} }
addr, err = getIfaceAddr(bridgeIface) addr, err = getIfaceAddr(config.BridgeIface)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -737,13 +689,13 @@ func newNetworkManager(bridgeIface string) (*NetworkManager, error) {
return nil, err return nil, err
} }
portMapper, err := newPortMapper() portMapper, err := newPortMapper(config)
if err != nil { if err != nil {
return nil, err return nil, err
} }
manager := &NetworkManager{ manager := &NetworkManager{
bridgeIface: bridgeIface, bridgeIface: config.BridgeIface,
bridgeNetwork: network, bridgeNetwork: network,
ipAllocator: ipAllocator, ipAllocator: ipAllocator,
tcpPortAllocator: tcpPortAllocator, tcpPortAllocator: tcpPortAllocator,

View file

@ -2,117 +2,9 @@ package docker
import ( import (
"net" "net"
"os"
"testing" "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) { func TestPortAllocation(t *testing.T) {
allocator, err := newPortAllocator() allocator, err := newPortAllocator()
if err != nil { if err != nil {

1
proxy/MAINTAINERS Normal file
View file

@ -0,0 +1 @@
Michael Crosby <michael@crosbymichael.com> (@crosbymichael)

View file

@ -1,4 +1,4 @@
package docker package proxy
import ( import (
"bytes" "bytes"

29
proxy/proxy.go Normal file
View file

@ -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"))
}
}

93
proxy/tcp_proxy.go Normal file
View file

@ -0,0 +1,93 @@
package proxy
import (
"github.com/dotcloud/docker/utils"
"io"
"log"
"net"
"syscall"
)
type TCPProxy struct {
listener *net.TCPListener
frontendAddr *net.TCPAddr
backendAddr *net.TCPAddr
}
func NewTCPProxy(frontendAddr, backendAddr *net.TCPAddr) (*TCPProxy, error) {
listener, err := net.ListenTCP("tcp", frontendAddr)
if err != nil {
return nil, err
}
// If the port in frontendAddr was 0 then ListenTCP will have a picked
// a port to listen on, hence the call to Addr to get that actual port:
return &TCPProxy{
listener: listener,
frontendAddr: listener.Addr().(*net.TCPAddr),
backendAddr: backendAddr,
}, nil
}
func (proxy *TCPProxy) clientLoop(client *net.TCPConn, quit chan bool) {
backend, err := net.DialTCP("tcp", nil, proxy.backendAddr)
if err != nil {
log.Printf("Can't forward traffic to backend tcp/%v: %v\n", proxy.backendAddr, err.Error())
client.Close()
return
}
event := make(chan int64)
var broker = func(to, from *net.TCPConn) {
written, err := io.Copy(to, from)
if err != nil {
// If the socket we are writing to is shutdown with
// SHUT_WR, forward it to the other end of the pipe:
if err, ok := err.(*net.OpError); ok && err.Err == syscall.EPIPE {
from.CloseWrite()
}
}
to.CloseRead()
event <- written
}
utils.Debugf("Forwarding traffic between tcp/%v and tcp/%v", client.RemoteAddr(), backend.RemoteAddr())
go broker(client, backend)
go broker(backend, client)
var transferred int64 = 0
for i := 0; i < 2; i++ {
select {
case written := <-event:
transferred += written
case <-quit:
// Interrupt the two brokers and "join" them.
client.Close()
backend.Close()
for ; i < 2; i++ {
transferred += <-event
}
goto done
}
}
client.Close()
backend.Close()
done:
utils.Debugf("%v bytes transferred between tcp/%v and tcp/%v", transferred, client.RemoteAddr(), backend.RemoteAddr())
}
func (proxy *TCPProxy) Run() {
quit := make(chan bool)
defer close(quit)
utils.Debugf("Starting proxy on tcp/%v for tcp/%v", proxy.frontendAddr, proxy.backendAddr)
for {
client, err := proxy.listener.Accept()
if err != nil {
utils.Debugf("Stopping proxy on tcp/%v for tcp/%v (%v)", proxy.frontendAddr, proxy.backendAddr, err.Error())
return
}
go proxy.clientLoop(client.(*net.TCPConn), quit)
}
}
func (proxy *TCPProxy) Close() { proxy.listener.Close() }
func (proxy *TCPProxy) FrontendAddr() net.Addr { return proxy.frontendAddr }
func (proxy *TCPProxy) BackendAddr() net.Addr { return proxy.backendAddr }

View file

@ -1,10 +1,8 @@
package docker package proxy
import ( import (
"encoding/binary" "encoding/binary"
"fmt"
"github.com/dotcloud/docker/utils" "github.com/dotcloud/docker/utils"
"io"
"log" "log"
"net" "net"
"sync" "sync"
@ -17,107 +15,6 @@ const (
UDPBufSize = 2048 UDPBufSize = 2048
) )
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
}
type TCPProxy struct {
listener *net.TCPListener
frontendAddr *net.TCPAddr
backendAddr *net.TCPAddr
}
func NewTCPProxy(frontendAddr, backendAddr *net.TCPAddr) (*TCPProxy, error) {
listener, err := net.ListenTCP("tcp", frontendAddr)
if err != nil {
return nil, err
}
// If the port in frontendAddr was 0 then ListenTCP will have a picked
// a port to listen on, hence the call to Addr to get that actual port:
return &TCPProxy{
listener: listener,
frontendAddr: listener.Addr().(*net.TCPAddr),
backendAddr: backendAddr,
}, nil
}
func (proxy *TCPProxy) clientLoop(client *net.TCPConn, quit chan bool) {
backend, err := net.DialTCP("tcp", nil, proxy.backendAddr)
if err != nil {
log.Printf("Can't forward traffic to backend tcp/%v: %v\n", proxy.backendAddr, err.Error())
client.Close()
return
}
event := make(chan int64)
var broker = func(to, from *net.TCPConn) {
written, err := io.Copy(to, from)
if err != nil {
// If the socket we are writing to is shutdown with
// SHUT_WR, forward it to the other end of the pipe:
if err, ok := err.(*net.OpError); ok && err.Err == syscall.EPIPE {
from.CloseWrite()
}
}
to.CloseRead()
event <- written
}
utils.Debugf("Forwarding traffic between tcp/%v and tcp/%v", client.RemoteAddr(), backend.RemoteAddr())
go broker(client, backend)
go broker(backend, client)
var transferred int64 = 0
for i := 0; i < 2; i++ {
select {
case written := <-event:
transferred += written
case <-quit:
// Interrupt the two brokers and "join" them.
client.Close()
backend.Close()
for ; i < 2; i++ {
transferred += <-event
}
goto done
}
}
client.Close()
backend.Close()
done:
utils.Debugf("%v bytes transferred between tcp/%v and tcp/%v", transferred, client.RemoteAddr(), backend.RemoteAddr())
}
func (proxy *TCPProxy) Run() {
quit := make(chan bool)
defer close(quit)
utils.Debugf("Starting proxy on tcp/%v for tcp/%v", proxy.frontendAddr, proxy.backendAddr)
for {
client, err := proxy.listener.Accept()
if err != nil {
if utils.IsClosedError(err) {
utils.Debugf("Stopping proxy on tcp/%v for tcp/%v (socket was closed)", proxy.frontendAddr, proxy.backendAddr)
} else {
utils.Errorf("Stopping proxy on tcp/%v for tcp/%v (%v)", proxy.frontendAddr, proxy.backendAddr, err.Error())
}
return
}
go proxy.clientLoop(client.(*net.TCPConn), quit)
}
}
func (proxy *TCPProxy) Close() { proxy.listener.Close() }
func (proxy *TCPProxy) FrontendAddr() net.Addr { return proxy.frontendAddr }
func (proxy *TCPProxy) BackendAddr() net.Addr { return proxy.backendAddr }
// A net.Addr where the IP is split into two fields so you can use it as a key // A net.Addr where the IP is split into two fields so you can use it as a key
// in a map: // in a map:
type connTrackKey struct { type connTrackKey struct {
@ -253,14 +150,3 @@ func (proxy *UDPProxy) Close() {
func (proxy *UDPProxy) FrontendAddr() net.Addr { return proxy.frontendAddr } func (proxy *UDPProxy) FrontendAddr() net.Addr { return proxy.frontendAddr }
func (proxy *UDPProxy) BackendAddr() net.Addr { return proxy.backendAddr } func (proxy *UDPProxy) BackendAddr() net.Addr { return proxy.backendAddr }
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"))
}
}

View file

@ -3,6 +3,7 @@ package docker
import ( import (
"container/list" "container/list"
"fmt" "fmt"
"github.com/dotcloud/docker/gograph"
"github.com/dotcloud/docker/utils" "github.com/dotcloud/docker/utils"
"io" "io"
"io/ioutil" "io/ioutil"
@ -24,7 +25,6 @@ type Capabilities struct {
} }
type Runtime struct { type Runtime struct {
root string
repository string repository string
containers *list.List containers *list.List
networkManager *NetworkManager networkManager *NetworkManager
@ -33,10 +33,10 @@ type Runtime struct {
idIndex *utils.TruncIndex idIndex *utils.TruncIndex
capabilities *Capabilities capabilities *Capabilities
kernelVersion *utils.KernelVersionInfo kernelVersion *utils.KernelVersionInfo
autoRestart bool
volumes *Graph volumes *Graph
srv *Server srv *Server
Dns []string config *DaemonConfig
containerGraph *gograph.Database
} }
var sysInitPath string var sysInitPath string
@ -67,10 +67,15 @@ func (runtime *Runtime) getContainerElement(id string) *list.Element {
// Get looks for a container by the specified ID or name, and returns it. // Get looks for a container by the specified ID or name, and returns it.
// If the container is not found, or if an error occurs, nil is returned. // If the container is not found, or if an error occurs, nil is returned.
func (runtime *Runtime) Get(name string) *Container { func (runtime *Runtime) Get(name string) *Container {
if c, _ := runtime.GetByName(name); c != nil {
return c
}
id, err := runtime.idIndex.Get(name) id, err := runtime.idIndex.Get(name)
if err != nil { if err != nil {
return nil return nil
} }
e := runtime.getContainerElement(id) e := runtime.getContainerElement(id)
if e == nil { if e == nil {
return nil return nil
@ -88,10 +93,9 @@ func (runtime *Runtime) containerRoot(id string) string {
return path.Join(runtime.repository, id) return path.Join(runtime.repository, id)
} }
// Load reads the contents of a container from disk and registers // Load reads the contents of a container from disk
// it with Register.
// This is typically done at startup. // This is typically done at startup.
func (runtime *Runtime) Load(id string) (*Container, error) { func (runtime *Runtime) load(id string) (*Container, error) {
container := &Container{root: runtime.containerRoot(id)} container := &Container{root: runtime.containerRoot(id)}
if err := container.FromDisk(); err != nil { if err := container.FromDisk(); err != nil {
return nil, err return nil, err
@ -102,9 +106,6 @@ func (runtime *Runtime) Load(id string) (*Container, error) {
if container.State.Running { if container.State.Running {
container.State.Ghost = true container.State.Ghost = true
} }
if err := runtime.Register(container); err != nil {
return nil, err
}
return container, nil return container, nil
} }
@ -149,11 +150,11 @@ func (runtime *Runtime) Register(container *Container) error {
} }
if !strings.Contains(string(output), "RUNNING") { if !strings.Contains(string(output), "RUNNING") {
utils.Debugf("Container %s was supposed to be running be is not.", container.ID) utils.Debugf("Container %s was supposed to be running be is not.", container.ID)
if runtime.autoRestart { if runtime.config.AutoRestart {
utils.Debugf("Restarting") utils.Debugf("Restarting")
container.State.Ghost = false container.State.Ghost = false
container.State.setStopped(0) container.State.setStopped(0)
hostConfig := &HostConfig{} hostConfig, _ := container.ReadHostConfig()
if err := container.Start(hostConfig); err != nil { if err := container.Start(hostConfig); err != nil {
return err return err
} }
@ -173,9 +174,9 @@ func (runtime *Runtime) Register(container *Container) error {
if !container.State.Running { if !container.State.Running {
close(container.waitLock) close(container.waitLock)
} else if !nomonitor { } else if !nomonitor {
container.allocateNetwork() hostConfig, _ := container.ReadHostConfig()
// hostConfig isn't needed here and can be nil container.allocateNetwork(hostConfig)
go container.monitor(nil) go container.monitor(hostConfig)
} }
return nil return nil
} }
@ -203,6 +204,7 @@ func (runtime *Runtime) Destroy(container *Container) error {
if err := container.Stop(3); err != nil { if err := container.Stop(3); err != nil {
return err return err
} }
if mounted, err := container.Mounted(); err != nil { if mounted, err := container.Mounted(); err != nil {
return err return err
} else if mounted { } else if mounted {
@ -210,6 +212,11 @@ func (runtime *Runtime) Destroy(container *Container) error {
return fmt.Errorf("Unable to unmount container %v: %v", container.ID, err) return fmt.Errorf("Unable to unmount container %v: %v", container.ID, err)
} }
} }
if _, err := runtime.containerGraph.Purge(container.ID); err != nil {
utils.Debugf("Unable to remove container from link graph: %s", err)
}
// Deregister the container before removing its directory, to avoid race conditions // Deregister the container before removing its directory, to avoid race conditions
runtime.idIndex.Delete(container.ID) runtime.idIndex.Delete(container.ID)
runtime.containers.Remove(element) runtime.containers.Remove(element)
@ -228,9 +235,10 @@ func (runtime *Runtime) restore() error {
if err != nil { if err != nil {
return err return err
} }
containers := []*Container{}
for i, v := range dir { for i, v := range dir {
id := v.Name() id := v.Name()
container, err := runtime.Load(id) container, err := runtime.load(id)
if i%21 == 0 && os.Getenv("DEBUG") == "" && os.Getenv("TEST") == "" { if i%21 == 0 && os.Getenv("DEBUG") == "" && os.Getenv("TEST") == "" {
fmt.Printf("\b%c", wheel[i%4]) fmt.Printf("\b%c", wheel[i%4])
} }
@ -239,10 +247,30 @@ func (runtime *Runtime) restore() error {
continue continue
} }
utils.Debugf("Loaded container %v", container.ID) utils.Debugf("Loaded container %v", container.ID)
containers = append(containers, container)
}
sortContainers(containers, func(i, j *Container) bool {
ic, _ := i.ReadHostConfig()
jc, _ := j.ReadHostConfig()
if ic == nil || ic.Links == nil {
return true
}
if jc == nil || jc.Links == nil {
return false
}
return len(ic.Links) < len(jc.Links)
})
for _, container := range containers {
if err := runtime.Register(container); err != nil {
utils.Debugf("Failed to register container %s: %s", container.ID, err)
continue
}
} }
if os.Getenv("DEBUG") == "" && os.Getenv("TEST") == "" { if os.Getenv("DEBUG") == "" && os.Getenv("TEST") == "" {
fmt.Printf("\bdone.\n") fmt.Printf("\bdone.\n")
} }
return nil return nil
} }
@ -275,27 +303,45 @@ func (runtime *Runtime) UpdateCapabilities(quiet bool) {
} }
// Create creates a new container from the given configuration. // Create creates a new container from the given configuration.
func (runtime *Runtime) Create(config *Config) (*Container, error) { func (runtime *Runtime) Create(config *Config) (*Container, []string, error) {
// Lookup image // Lookup image
img, err := runtime.repositories.LookupImage(config.Image) img, err := runtime.repositories.LookupImage(config.Image)
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
warnings := []string{}
if img.Config != nil { if img.Config != nil {
if err := MergeConfig(config, img.Config); err != nil { if img.Config.PortSpecs != nil && warnings != nil {
return nil, err for _, p := range img.Config.PortSpecs {
if strings.Contains(p, ":") {
warnings = append(warnings, "This image expects private ports to be mapped to public ports on your host. "+
"This has been deprecated and the public mappings will not be honored."+
"Use -p to publish the ports.")
break
}
}
} }
if err := MergeConfig(config, img.Config); err != nil {
return nil, nil, err
}
} }
if len(config.Entrypoint) != 0 && config.Cmd == nil { if len(config.Entrypoint) != 0 && config.Cmd == nil {
config.Cmd = []string{} config.Cmd = []string{}
} else if config.Cmd == nil || len(config.Cmd) == 0 { } else if config.Cmd == nil || len(config.Cmd) == 0 {
return nil, fmt.Errorf("No command specified") return nil, nil, fmt.Errorf("No command specified")
} }
// Generate id // Generate id
id := GenerateID() id := GenerateID()
// Set the default enitity in the graph
if _, err := runtime.containerGraph.Set(fmt.Sprintf("/%s", id), id); err != nil {
return nil, nil, err
}
// Generate default hostname // Generate default hostname
// FIXME: the lxc template no longer needs to set a default hostname // FIXME: the lxc template no longer needs to set a default hostname
if config.Hostname == "" { if config.Hostname == "" {
@ -329,36 +375,36 @@ func (runtime *Runtime) Create(config *Config) (*Container, error) {
// Step 1: create the container directory. // Step 1: create the container directory.
// This doubles as a barrier to avoid race conditions. // This doubles as a barrier to avoid race conditions.
if err := os.Mkdir(container.root, 0700); err != nil { if err := os.Mkdir(container.root, 0700); err != nil {
return nil, err return nil, nil, err
} }
resolvConf, err := utils.GetResolvConf() resolvConf, err := utils.GetResolvConf()
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
if len(config.Dns) == 0 && len(runtime.Dns) == 0 && utils.CheckLocalDns(resolvConf) { if len(config.Dns) == 0 && len(runtime.config.Dns) == 0 && utils.CheckLocalDns(resolvConf) {
//"WARNING: Docker detected local DNS server on resolv.conf. Using default external servers: %v", defaultDns //"WARNING: Docker detected local DNS server on resolv.conf. Using default external servers: %v", defaultDns
runtime.Dns = defaultDns runtime.config.Dns = defaultDns
} }
// If custom dns exists, then create a resolv.conf for the container // If custom dns exists, then create a resolv.conf for the container
if len(config.Dns) > 0 || len(runtime.Dns) > 0 { if len(config.Dns) > 0 || len(runtime.config.Dns) > 0 {
var dns []string var dns []string
if len(config.Dns) > 0 { if len(config.Dns) > 0 {
dns = config.Dns dns = config.Dns
} else { } else {
dns = runtime.Dns dns = runtime.config.Dns
} }
container.ResolvConfPath = path.Join(container.root, "resolv.conf") container.ResolvConfPath = path.Join(container.root, "resolv.conf")
f, err := os.Create(container.ResolvConfPath) f, err := os.Create(container.ResolvConfPath)
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
defer f.Close() defer f.Close()
for _, dns := range dns { for _, dns := range dns {
if _, err := f.Write([]byte("nameserver " + dns + "\n")); err != nil { if _, err := f.Write([]byte("nameserver " + dns + "\n")); err != nil {
return nil, err return nil, nil, err
} }
} }
} else { } else {
@ -367,7 +413,7 @@ func (runtime *Runtime) Create(config *Config) (*Container, error) {
// Step 2: save the container json // Step 2: save the container json
if err := container.ToDisk(); err != nil { if err := container.ToDisk(); err != nil {
return nil, err return nil, nil, err
} }
// Step 3: if hostname, build hostname and hosts files // Step 3: if hostname, build hostname and hosts files
@ -397,9 +443,9 @@ ff02::2 ip6-allrouters
// Step 4: register the container // Step 4: register the container
if err := runtime.Register(container); err != nil { if err := runtime.Register(container); err != nil {
return nil, err return nil, nil, err
} }
return container, nil return container, warnings, nil
} }
// Commit creates a new filesystem image from the current state of a container. // Commit creates a new filesystem image from the current state of a container.
@ -429,13 +475,85 @@ func (runtime *Runtime) Commit(container *Container, repository, tag, comment, a
return img, nil return img, nil
} }
// FIXME: harmonize with NewGraph() func (runtime *Runtime) GetByName(name string) (*Container, error) {
func NewRuntime(flGraphPath string, autoRestart bool, dns []string) (*Runtime, error) { if id, err := runtime.idIndex.Get(name); err == nil {
runtime, err := NewRuntimeFromDirectory(flGraphPath, autoRestart) name = id
}
entity := runtime.containerGraph.Get(name)
if entity == nil {
return nil, fmt.Errorf("Could not find entity for %s", name)
}
e := runtime.getContainerElement(entity.ID())
if e == nil {
return nil, fmt.Errorf("Could not find container for entity id %s", entity.ID())
}
return e.Value.(*Container), nil
}
func (runtime *Runtime) Children(name string) (map[string]*Container, error) {
children := make(map[string]*Container)
err := runtime.containerGraph.Walk(name, func(p string, e *gograph.Entity) error {
c := runtime.Get(e.ID())
if c == nil {
return fmt.Errorf("Could not get container for name %s and id %s", e.ID(), p)
}
children[p] = c
return nil
}, 0)
if err != nil {
return nil, err
}
return children, nil
}
func (runtime *Runtime) RenameLink(oldName, newName string) error {
if id, err := runtime.idIndex.Get(oldName); err == nil {
oldName = id
}
entity := runtime.containerGraph.Get(oldName)
if entity == nil {
return fmt.Errorf("Could not find entity for %s", oldName)
}
// This is not rename but adding a new link for the default name
// Strip the leading '/'
if entity.ID() == oldName[1:] {
_, err := runtime.containerGraph.Set(newName, entity.ID())
return err
}
return runtime.containerGraph.Rename(oldName, newName)
}
func (runtime *Runtime) Link(parentName, childName, alias string) error {
if id, err := runtime.idIndex.Get(parentName); err == nil {
parentName = id
}
parent := runtime.containerGraph.Get(parentName)
if parent == nil {
return fmt.Errorf("Could not get container for %s", parentName)
}
if id, err := runtime.idIndex.Get(childName); err == nil {
childName = id
}
child := runtime.containerGraph.Get(childName)
if child == nil {
return fmt.Errorf("Could not get container for %s", childName)
}
cc := runtime.Get(child.ID())
_, err := runtime.containerGraph.Set(path.Join(parentName, alias), cc.ID)
return err
}
// FIXME: harmonize with NewGraph()
func NewRuntime(config *DaemonConfig) (*Runtime, error) {
runtime, err := NewRuntimeFromDirectory(config)
if err != nil { if err != nil {
return nil, err return nil, err
} }
runtime.Dns = dns
if k, err := utils.GetKernelVersion(); err != nil { if k, err := utils.GetKernelVersion(); err != nil {
log.Printf("WARNING: %s\n", err) log.Printf("WARNING: %s\n", err)
@ -449,34 +567,39 @@ func NewRuntime(flGraphPath string, autoRestart bool, dns []string) (*Runtime, e
return runtime, nil return runtime, nil
} }
func NewRuntimeFromDirectory(root string, autoRestart bool) (*Runtime, error) { func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) {
runtimeRepo := path.Join(root, "containers") runtimeRepo := path.Join(config.GraphPath, "containers")
if err := os.MkdirAll(runtimeRepo, 0700); err != nil && !os.IsExist(err) { if err := os.MkdirAll(runtimeRepo, 0700); err != nil && !os.IsExist(err) {
return nil, err return nil, err
} }
g, err := NewGraph(path.Join(root, "graph")) g, err := NewGraph(path.Join(config.GraphPath, "graph"))
if err != nil { if err != nil {
return nil, err return nil, err
} }
volumes, err := NewGraph(path.Join(root, "volumes")) volumes, err := NewGraph(path.Join(config.GraphPath, "volumes"))
if err != nil { if err != nil {
return nil, err return nil, err
} }
repositories, err := NewTagStore(path.Join(root, "repositories"), g) repositories, err := NewTagStore(path.Join(config.GraphPath, "repositories"), g)
if err != nil { if err != nil {
return nil, fmt.Errorf("Couldn't create Tag store: %s", err) return nil, fmt.Errorf("Couldn't create Tag store: %s", err)
} }
if NetworkBridgeIface == "" { if config.BridgeIface == "" {
NetworkBridgeIface = DefaultNetworkBridge config.BridgeIface = DefaultNetworkBridge
} }
netManager, err := newNetworkManager(NetworkBridgeIface) netManager, err := newNetworkManager(config)
if err != nil { if err != nil {
return nil, err return nil, err
} }
graph, err := gograph.NewDatabase(path.Join(config.GraphPath, "linkgraph.db"))
if err != nil {
return nil, err
}
runtime := &Runtime{ runtime := &Runtime{
root: root,
repository: runtimeRepo, repository: runtimeRepo,
containers: list.New(), containers: list.New(),
networkManager: netManager, networkManager: netManager,
@ -484,8 +607,9 @@ func NewRuntimeFromDirectory(root string, autoRestart bool) (*Runtime, error) {
repositories: repositories, repositories: repositories,
idIndex: utils.NewTruncIndex(), idIndex: utils.NewTruncIndex(),
capabilities: &Capabilities{}, capabilities: &Capabilities{},
autoRestart: autoRestart,
volumes: volumes, volumes: volumes,
config: config,
containerGraph: graph,
} }
if err := runtime.restore(); err != nil { if err := runtime.restore(); err != nil {

View file

@ -43,7 +43,7 @@ func nuke(runtime *Runtime) error {
} }
wg.Wait() wg.Wait()
runtime.networkManager.Close() runtime.networkManager.Close()
return os.RemoveAll(runtime.root) return os.RemoveAll(runtime.config.GraphPath)
} }
func cleanup(runtime *Runtime) error { func cleanup(runtime *Runtime) error {
@ -85,8 +85,6 @@ func init() {
log.Fatal("docker tests need to be run as root") log.Fatal("docker tests need to be run as root")
} }
NetworkBridgeIface = unitTestNetworkBridge
// Setup the base runtime, which will be duplicated for each test. // Setup the base runtime, which will be duplicated for each test.
// (no tests are run directly in the base) // (no tests are run directly in the base)
setupBaseImage() setupBaseImage()
@ -98,7 +96,12 @@ func init() {
func setupBaseImage() { func setupBaseImage() {
runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, false) config := &DaemonConfig{
GraphPath: unitTestStoreBase,
AutoRestart: false,
BridgeIface: unitTestNetworkBridge,
}
runtime, err := NewRuntimeFromDirectory(config)
if err != nil { if err != nil {
log.Fatalf("Unable to create a runtime for tests:", err) log.Fatalf("Unable to create a runtime for tests:", err)
} }
@ -106,7 +109,6 @@ func setupBaseImage() {
// Create the "Server" // Create the "Server"
srv := &Server{ srv := &Server{
runtime: runtime, runtime: runtime,
enableCors: false,
pullingPool: make(map[string]struct{}), pullingPool: make(map[string]struct{}),
pushingPool: make(map[string]struct{}), pushingPool: make(map[string]struct{}),
} }
@ -171,7 +173,7 @@ func TestRuntimeCreate(t *testing.T) {
t.Errorf("Expected 0 containers, %v found", len(runtime.List())) t.Errorf("Expected 0 containers, %v found", len(runtime.List()))
} }
container, err := runtime.Create(&Config{ container, _, err := runtime.Create(&Config{
Image: GetTestImage(runtime).ID, Image: GetTestImage(runtime).ID,
Cmd: []string{"ls", "-al"}, Cmd: []string{"ls", "-al"},
}, },
@ -212,7 +214,7 @@ func TestRuntimeCreate(t *testing.T) {
} }
// Make sure crete with bad parameters returns an error // Make sure crete with bad parameters returns an error
_, err = runtime.Create( _, _, err = runtime.Create(
&Config{ &Config{
Image: GetTestImage(runtime).ID, Image: GetTestImage(runtime).ID,
}, },
@ -221,7 +223,7 @@ func TestRuntimeCreate(t *testing.T) {
t.Fatal("Builder.Create should throw an error when Cmd is missing") t.Fatal("Builder.Create should throw an error when Cmd is missing")
} }
_, err = runtime.Create( _, _, err = runtime.Create(
&Config{ &Config{
Image: GetTestImage(runtime).ID, Image: GetTestImage(runtime).ID,
Cmd: []string{}, Cmd: []string{},
@ -258,7 +260,7 @@ func TestRuntimeCreate(t *testing.T) {
func TestDestroy(t *testing.T) { func TestDestroy(t *testing.T) {
runtime := mkRuntime(t) runtime := mkRuntime(t)
defer nuke(runtime) defer nuke(runtime)
container, err := runtime.Create(&Config{ container, _, err := runtime.Create(&Config{
Image: GetTestImage(runtime).ID, Image: GetTestImage(runtime).ID,
Cmd: []string{"ls", "-al"}, Cmd: []string{"ls", "-al"},
}, },
@ -332,6 +334,7 @@ func startEchoServerContainer(t *testing.T, proto string) (*Runtime, *Container,
port := 5554 port := 5554
var container *Container var container *Container
var strPort string var strPort string
var p Port
for { for {
port += 1 port += 1
strPort = strconv.Itoa(port) strPort = strconv.Itoa(port)
@ -344,22 +347,33 @@ func startEchoServerContainer(t *testing.T, proto string) (*Runtime, *Container,
t.Fatal(fmt.Errorf("Unknown protocol %v", proto)) t.Fatal(fmt.Errorf("Unknown protocol %v", proto))
} }
t.Log("Trying port", strPort) t.Log("Trying port", strPort)
container, err = runtime.Create(&Config{ ep := make(map[Port]struct{}, 1)
Image: GetTestImage(runtime).ID, p = Port(fmt.Sprintf("%s/%s", strPort, proto))
Cmd: []string{"sh", "-c", cmd}, ep[p] = struct{}{}
PortSpecs: []string{fmt.Sprintf("%s/%s", strPort, proto)},
container, _, err = runtime.Create(&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"sh", "-c", cmd},
PortSpecs: []string{fmt.Sprintf("%s/%s", strPort, proto)},
ExposedPorts: ep,
}) })
if container != nil {
break
}
if err != nil { if err != nil {
nuke(runtime) nuke(runtime)
t.Fatal(err) t.Fatal(err)
} }
if container != nil {
break
}
t.Logf("Port %v already in use", strPort) t.Logf("Port %v already in use", strPort)
} }
hostConfig := &HostConfig{} hostConfig := &HostConfig{
PortBindings: make(map[Port][]PortBinding),
}
hostConfig.PortBindings[p] = []PortBinding{
{},
}
if err := container.Start(hostConfig); err != nil { if err := container.Start(hostConfig); err != nil {
nuke(runtime) nuke(runtime)
t.Fatal(err) t.Fatal(err)
@ -374,7 +388,7 @@ func startEchoServerContainer(t *testing.T, proto string) (*Runtime, *Container,
// Even if the state is running, lets give some time to lxc to spawn the process // Even if the state is running, lets give some time to lxc to spawn the process
container.WaitTimeout(500 * time.Millisecond) container.WaitTimeout(500 * time.Millisecond)
strPort = container.NetworkSettings.PortMapping[strings.Title(proto)][strPort] strPort = container.NetworkSettings.Ports[p][0].HostPort
return runtime, container, strPort return runtime, container, strPort
} }
@ -506,7 +520,8 @@ func TestRestore(t *testing.T) {
// Here are are simulating a docker restart - that is, reloading all containers // Here are are simulating a docker restart - that is, reloading all containers
// from scratch // from scratch
runtime2, err := NewRuntimeFromDirectory(runtime1.root, false) runtime1.config.AutoRestart = false
runtime2, err := NewRuntimeFromDirectory(runtime1.config)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -533,3 +548,271 @@ func TestRestore(t *testing.T) {
} }
container2.State.Running = false container2.State.Running = false
} }
func TestReloadContainerLinks(t *testing.T) {
runtime1 := mkRuntime(t)
defer nuke(runtime1)
// Create a container with one instance of docker
container1, _, _ := mkContainer(runtime1, []string{"_", "ls", "-al"}, t)
defer runtime1.Destroy(container1)
// Create a second container meant to be killed
container2, _, _ := mkContainer(runtime1, []string{"-i", "_", "/bin/cat"}, t)
defer runtime1.Destroy(container2)
// Start the container non blocking
hostConfig := &HostConfig{}
if err := container2.Start(hostConfig); err != nil {
t.Fatal(err)
}
h1 := &HostConfig{}
// Add a link to container 2
h1.Links = []string{utils.TruncateID(container2.ID) + ":first"}
if err := container1.Start(h1); err != nil {
t.Fatal(err)
}
if !container2.State.Running {
t.Fatalf("Container %v should appear as running but isn't", container2.ID)
}
if !container1.State.Running {
t.Fatalf("Container %s should appear as running bu isn't", container1.ID)
}
if len(runtime1.List()) != 2 {
t.Errorf("Expected 2 container, %v found", len(runtime1.List()))
}
if !container2.State.Running {
t.Fatalf("Container %v should appear as running but isn't", container2.ID)
}
// Here are are simulating a docker restart - that is, reloading all containers
// from scratch
runtime1.config.AutoRestart = true
runtime2, err := NewRuntimeFromDirectory(runtime1.config)
if err != nil {
t.Fatal(err)
}
defer nuke(runtime2)
if len(runtime2.List()) != 2 {
t.Errorf("Expected 2 container, %v found", len(runtime2.List()))
}
runningCount := 0
for _, c := range runtime2.List() {
if c.State.Running {
t.Logf("Running container found: %v (%v)", c.ID, c.Path)
runningCount++
}
}
if runningCount != 2 {
t.Fatalf("Expected 2 container alive, %d found", runningCount)
}
// Make sure container 2 ( the child of container 1 ) was registered and started first
// with the runtime
first := runtime2.containers.Front()
if first.Value.(*Container).ID != container2.ID {
t.Fatalf("Container 2 %s should be registered first in the runtime", container2.ID)
}
t.Logf("Number of links: %d", runtime2.containerGraph.Refs("engine"))
// Verify that the link is still registered in the runtime
entity := runtime2.containerGraph.Get(fmt.Sprintf("/%s", container1.ID))
if entity == nil {
t.Fatal("Entity should not be nil")
}
}
func TestDefaultContainerName(t *testing.T) {
runtime := mkRuntime(t)
defer nuke(runtime)
srv := &Server{runtime: runtime}
config, _, _, err := ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil)
if err != nil {
t.Fatal(err)
}
shortId, _, err := srv.ContainerCreate(config)
if err != nil {
t.Fatal(err)
}
container := runtime.Get(shortId)
containerID := container.ID
paths := runtime.containerGraph.RefPaths(containerID)
if paths == nil || len(paths) == 0 {
t.Fatalf("Could not find edges for %s", containerID)
}
edge := paths[0]
if edge.ParentID != "0" {
t.Fatalf("Expected engine got %s", edge.ParentID)
}
if edge.EntityID != containerID {
t.Fatalf("Expected %s got %s", containerID, edge.EntityID)
}
if edge.Name != containerID {
t.Fatalf("Expected %s got %s", containerID, edge.Name)
}
}
func TestDefaultContainerRename(t *testing.T) {
runtime := mkRuntime(t)
defer nuke(runtime)
srv := &Server{runtime: runtime}
config, _, _, err := ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil)
if err != nil {
t.Fatal(err)
}
shortId, _, err := srv.ContainerCreate(config)
if err != nil {
t.Fatal(err)
}
container := runtime.Get(shortId)
containerID := container.ID
if err := runtime.RenameLink(fmt.Sprintf("/%s", containerID), "/webapp"); err != nil {
t.Fatal(err)
}
webapp, err := runtime.GetByName("/webapp")
if err != nil {
t.Fatal(err)
}
if webapp.ID != container.ID {
t.Fatalf("Expect webapp id to match container id: %s != %s", webapp.ID, container.ID)
}
}
func TestLinkChildContainer(t *testing.T) {
runtime := mkRuntime(t)
defer nuke(runtime)
srv := &Server{runtime: runtime}
config, _, _, err := ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil)
if err != nil {
t.Fatal(err)
}
shortId, _, err := srv.ContainerCreate(config)
if err != nil {
t.Fatal(err)
}
container := runtime.Get(shortId)
if err := runtime.RenameLink(fmt.Sprintf("/%s", container.ID), "/webapp"); err != nil {
t.Fatal(err)
}
webapp, err := runtime.GetByName("/webapp")
if err != nil {
t.Fatal(err)
}
if webapp.ID != container.ID {
t.Fatalf("Expect webapp id to match container id: %s != %s", webapp.ID, container.ID)
}
config, _, _, err = ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil)
if err != nil {
t.Fatal(err)
}
shortId, _, err = srv.ContainerCreate(config)
if err != nil {
t.Fatal(err)
}
childContainer := runtime.Get(shortId)
if err := runtime.RenameLink(fmt.Sprintf("/%s", childContainer.ID), "/db"); err != nil {
t.Fatal(err)
}
if err := runtime.Link("/webapp", "/db", "db"); err != nil {
t.Fatal(err)
}
// Get the child by it's new name
db, err := runtime.GetByName("/webapp/db")
if err != nil {
t.Fatal(err)
}
if db.ID != childContainer.ID {
t.Fatalf("Expect db id to match container id: %s != %s", db.ID, childContainer.ID)
}
}
func TestGetAllChildren(t *testing.T) {
runtime := mkRuntime(t)
defer nuke(runtime)
srv := &Server{runtime: runtime}
config, _, _, err := ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil)
if err != nil {
t.Fatal(err)
}
shortId, _, err := srv.ContainerCreate(config)
if err != nil {
t.Fatal(err)
}
container := runtime.Get(shortId)
if err := runtime.RenameLink(fmt.Sprintf("/%s", container.ID), "/webapp"); err != nil {
t.Fatal(err)
}
webapp, err := runtime.GetByName("/webapp")
if err != nil {
t.Fatal(err)
}
if webapp.ID != container.ID {
t.Fatalf("Expect webapp id to match container id: %s != %s", webapp.ID, container.ID)
}
config, _, _, err = ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil)
if err != nil {
t.Fatal(err)
}
shortId, _, err = srv.ContainerCreate(config)
if err != nil {
t.Fatal(err)
}
childContainer := runtime.Get(shortId)
if err := runtime.RenameLink(fmt.Sprintf("/%s", childContainer.ID), "/db"); err != nil {
t.Fatal(err)
}
if err := runtime.Link("/webapp", "/db", "db"); err != nil {
t.Fatal(err)
}
children, err := runtime.Children("/webapp")
if err != nil {
t.Fatal(err)
}
if children == nil {
t.Fatal("Children should not be nil")
}
if len(children) == 0 {
t.Fatal("Children should not be empty")
}
for key, value := range children {
if key != "/webapp/db" {
t.Fatalf("Expected /webapp/db got %s", key)
}
if value.ID != childContainer.ID {
t.Fatalf("Expected id %s got %s", childContainer.ID, value.ID)
}
}
}

129
server.go
View file

@ -6,6 +6,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"github.com/dotcloud/docker/auth" "github.com/dotcloud/docker/auth"
"github.com/dotcloud/docker/gograph"
"github.com/dotcloud/docker/registry" "github.com/dotcloud/docker/registry"
"github.com/dotcloud/docker/utils" "github.com/dotcloud/docker/utils"
"io" "io"
@ -103,7 +104,7 @@ func (srv *Server) ContainerExport(name string, out io.Writer) error {
} }
func (srv *Server) ImagesSearch(term string) ([]APISearch, error) { func (srv *Server) ImagesSearch(term string) ([]APISearch, error) {
r, err := registry.NewRegistry(srv.runtime.root, nil, srv.HTTPRequestFactory(nil)) r, err := registry.NewRegistry(srv.runtime.config.GraphPath, nil, srv.HTTPRequestFactory(nil))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -140,7 +141,7 @@ func (srv *Server) ImageInsert(name, url, path string, out io.Writer, sf *utils.
return "", err return "", err
} }
c, err := srv.runtime.Create(config) c, _, err := srv.runtime.Create(config)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -358,7 +359,7 @@ func (srv *Server) ContainerChanges(name string) ([]Change, error) {
func (srv *Server) Containers(all, size bool, n int, since, before string) []APIContainers { func (srv *Server) Containers(all, size bool, n int, since, before string) []APIContainers {
var foundBefore bool var foundBefore bool
var displayed int var displayed int
retContainers := []APIContainers{} out := []APIContainers{}
for _, container := range srv.runtime.List() { for _, container := range srv.runtime.List() {
if !container.State.Running && !all && n == -1 && since == "" && before == "" { if !container.State.Running && !all && n == -1 && since == "" && before == "" {
@ -380,23 +381,35 @@ func (srv *Server) Containers(all, size bool, n int, since, before string) []API
break break
} }
displayed++ displayed++
c := createAPIContainer(container, size, srv.runtime)
c := APIContainers{ out = append(out, c)
ID: container.ID,
}
c.Image = srv.runtime.repositories.ImageName(container.Image)
c.Command = fmt.Sprintf("%s %s", container.Path, strings.Join(container.Args, " "))
c.Created = container.Created.Unix()
c.Status = container.State.String()
c.Ports = container.NetworkSettings.PortMappingAPI()
if size {
c.SizeRw, c.SizeRootFs = container.GetSize()
}
retContainers = append(retContainers, c)
} }
return retContainers return out
} }
func createAPIContainer(container *Container, size bool, runtime *Runtime) APIContainers {
c := APIContainers{
ID: container.ID,
}
names := []string{}
runtime.containerGraph.Walk("/", func(p string, e *gograph.Entity) error {
if e.ID() == container.ID {
names = append(names, p)
}
return nil
}, -1)
c.Names = names
c.Image = runtime.repositories.ImageName(container.Image)
c.Command = fmt.Sprintf("%s %s", container.Path, strings.Join(container.Args, " "))
c.Created = container.Created.Unix()
c.Status = container.State.String()
c.Ports = container.NetworkSettings.PortMappingAPI()
if size {
c.SizeRw, c.SizeRootFs = container.GetSize()
}
return c
}
func (srv *Server) ContainerCommit(name, repo, tag, author, comment string, config *Config) (string, error) { func (srv *Server) ContainerCommit(name, repo, tag, author, comment string, config *Config) (string, error) {
container := srv.runtime.Get(name) container := srv.runtime.Get(name)
if container == nil { if container == nil {
@ -635,7 +648,7 @@ func (srv *Server) poolRemove(kind, key string) error {
} }
func (srv *Server) ImagePull(localName string, tag string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig, metaHeaders map[string][]string, parallel bool) error { func (srv *Server) ImagePull(localName string, tag string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig, metaHeaders map[string][]string, parallel bool) error {
r, err := registry.NewRegistry(srv.runtime.root, authConfig, srv.HTTPRequestFactory(metaHeaders)) r, err := registry.NewRegistry(srv.runtime.config.GraphPath, authConfig, srv.HTTPRequestFactory(metaHeaders))
if err != nil { if err != nil {
return err return err
} }
@ -844,7 +857,7 @@ func (srv *Server) ImagePush(localName string, out io.Writer, sf *utils.StreamFo
out = utils.NewWriteFlusher(out) out = utils.NewWriteFlusher(out)
img, err := srv.runtime.graph.Get(localName) img, err := srv.runtime.graph.Get(localName)
r, err2 := registry.NewRegistry(srv.runtime.root, authConfig, srv.HTTPRequestFactory(metaHeaders)) r, err2 := registry.NewRegistry(srv.runtime.config.GraphPath, authConfig, srv.HTTPRequestFactory(metaHeaders))
if err2 != nil { if err2 != nil {
return err2 return err2
} }
@ -909,10 +922,9 @@ func (srv *Server) ImageImport(src, repo, tag string, in io.Reader, out io.Write
return nil return nil
} }
func (srv *Server) ContainerCreate(config *Config) (string, error) { func (srv *Server) ContainerCreate(config *Config) (string, []string, error) {
if config.Memory != 0 && config.Memory < 524288 { if config.Memory != 0 && config.Memory < 524288 {
return "", fmt.Errorf("Memory limit must be given in bytes (minimum 524288 bytes)") return "", nil, fmt.Errorf("Memory limit must be given in bytes (minimum 524288 bytes)")
} }
if config.Memory > 0 && !srv.runtime.capabilities.MemoryLimit { if config.Memory > 0 && !srv.runtime.capabilities.MemoryLimit {
@ -922,7 +934,7 @@ func (srv *Server) ContainerCreate(config *Config) (string, error) {
if config.Memory > 0 && !srv.runtime.capabilities.SwapLimit { if config.Memory > 0 && !srv.runtime.capabilities.SwapLimit {
config.MemorySwap = -1 config.MemorySwap = -1
} }
container, err := srv.runtime.Create(config) container, buildWarnings, err := srv.runtime.Create(config)
if err != nil { if err != nil {
if srv.runtime.graph.IsNotExist(err) { if srv.runtime.graph.IsNotExist(err) {
@ -931,12 +943,12 @@ func (srv *Server) ContainerCreate(config *Config) (string, error) {
tag = DEFAULTTAG tag = DEFAULTTAG
} }
return "", fmt.Errorf("No such image: %s (tag: %s)", config.Image, tag) return "", nil, fmt.Errorf("No such image: %s (tag: %s)", config.Image, tag)
} }
return "", err return "", nil, err
} }
srv.LogEvent("create", container.ShortID(), srv.runtime.repositories.ImageName(container.Image)) srv.LogEvent("create", container.ShortID(), srv.runtime.repositories.ImageName(container.Image))
return container.ShortID(), nil return container.ShortID(), buildWarnings, nil
} }
func (srv *Server) ContainerRestart(name string, t int) error { func (srv *Server) ContainerRestart(name string, t int) error {
@ -951,7 +963,34 @@ func (srv *Server) ContainerRestart(name string, t int) error {
return nil return nil
} }
func (srv *Server) ContainerDestroy(name string, removeVolume bool) error { func (srv *Server) ContainerDestroy(name string, removeVolume, removeLink bool) error {
if removeLink {
p := name
if p[0] != '/' {
p = "/" + p
}
parent, n := path.Split(p)
l := len(parent)
if parent[l-1] == '/' {
parent = parent[:l-1]
}
pe := srv.runtime.containerGraph.Get(parent)
parentContainer := srv.runtime.Get(pe.ID())
if parentContainer != nil && parentContainer.activeLinks != nil {
if link, exists := parentContainer.activeLinks[n]; exists {
link.Disable()
} else {
utils.Debugf("Could not find active link for %s", name)
}
}
if err := srv.runtime.containerGraph.Delete(name); err != nil {
return err
}
return nil
}
if container := srv.runtime.Get(name); container != nil { if container := srv.runtime.Get(name); container != nil {
if container.State.Running { if container.State.Running {
return fmt.Errorf("Impossible to remove a running container, please stop it first") return fmt.Errorf("Impossible to remove a running container, please stop it first")
@ -1143,14 +1182,32 @@ func (srv *Server) ImageGetCached(imgID string, config *Config) (*Image, error)
} }
func (srv *Server) ContainerStart(name string, hostConfig *HostConfig) error { func (srv *Server) ContainerStart(name string, hostConfig *HostConfig) error {
if container := srv.runtime.Get(name); container != nil { runtime := srv.runtime
if err := container.Start(hostConfig); err != nil { container := runtime.Get(name)
return fmt.Errorf("Error starting container %s: %s", name, err) if container == nil {
}
srv.LogEvent("start", container.ShortID(), srv.runtime.repositories.ImageName(container.Image))
} else {
return fmt.Errorf("No such container: %s", name) return fmt.Errorf("No such container: %s", name)
} }
// Register links
if hostConfig != nil && hostConfig.Links != nil {
for _, l := range hostConfig.Links {
parts, err := parseLink(l)
if err != nil {
return err
}
childName := parts["name"]
if err := runtime.Link(fmt.Sprintf("/%s", container.ID), childName, parts["alias"]); err != nil {
return err
}
}
}
if err := container.Start(hostConfig); err != nil {
return fmt.Errorf("Error starting container %s: %s", name, err)
}
srv.LogEvent("start", container.ShortID(), runtime.repositories.ImageName(container.Image))
return nil return nil
} }
@ -1302,17 +1359,16 @@ func (srv *Server) ContainerCopy(name string, resource string, out io.Writer) er
} }
func NewServer(flGraphPath string, autoRestart, enableCors bool, dns ListOpts) (*Server, error) { func NewServer(config *DaemonConfig) (*Server, error) {
if runtime.GOARCH != "amd64" { if runtime.GOARCH != "amd64" {
log.Fatalf("The docker runtime currently only supports amd64 (not %s). This will change in the future. Aborting.", runtime.GOARCH) log.Fatalf("The docker runtime currently only supports amd64 (not %s). This will change in the future. Aborting.", runtime.GOARCH)
} }
runtime, err := NewRuntime(flGraphPath, autoRestart, dns) runtime, err := NewRuntime(config)
if err != nil { if err != nil {
return nil, err return nil, err
} }
srv := &Server{ srv := &Server{
runtime: runtime, runtime: runtime,
enableCors: enableCors,
pullingPool: make(map[string]struct{}), pullingPool: make(map[string]struct{}),
pushingPool: make(map[string]struct{}), pushingPool: make(map[string]struct{}),
events: make([]utils.JSONMessage, 0, 64), //only keeps the 64 last events events: make([]utils.JSONMessage, 0, 64), //only keeps the 64 last events
@ -1350,7 +1406,6 @@ func (srv *Server) LogEvent(action, id, from string) {
type Server struct { type Server struct {
sync.Mutex sync.Mutex
runtime *Runtime runtime *Runtime
enableCors bool
pullingPool map[string]struct{} pullingPool map[string]struct{}
pushingPool map[string]struct{} pushingPool map[string]struct{}
events []utils.JSONMessage events []utils.JSONMessage

View file

@ -89,7 +89,7 @@ func TestCreateRm(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
id, err := srv.ContainerCreate(config) id, _, err := srv.ContainerCreate(config)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -98,7 +98,7 @@ func TestCreateRm(t *testing.T) {
t.Errorf("Expected 1 container, %v found", len(runtime.List())) t.Errorf("Expected 1 container, %v found", len(runtime.List()))
} }
if err = srv.ContainerDestroy(id, true); err != nil { if err = srv.ContainerDestroy(id, true, false); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -119,7 +119,7 @@ func TestCreateRmVolumes(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
id, err := srv.ContainerCreate(config) id, _, err := srv.ContainerCreate(config)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -138,7 +138,7 @@ func TestCreateRmVolumes(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
if err = srv.ContainerDestroy(id, true); err != nil { if err = srv.ContainerDestroy(id, true, false); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -158,7 +158,7 @@ func TestCommit(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
id, err := srv.ContainerCreate(config) id, _, err := srv.ContainerCreate(config)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -179,7 +179,7 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
id, err := srv.ContainerCreate(config) id, _, err := srv.ContainerCreate(config)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -214,7 +214,7 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) {
} }
// FIXME: this failed once with a race condition ("Unable to remove filesystem for xxx: directory not empty") // FIXME: this failed once with a race condition ("Unable to remove filesystem for xxx: directory not empty")
if err = srv.ContainerDestroy(id, true); err != nil { if err = srv.ContainerDestroy(id, true, false); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -230,7 +230,7 @@ func TestRunWithTooLowMemoryLimit(t *testing.T) {
srv := &Server{runtime: runtime} srv := &Server{runtime: runtime}
defer nuke(runtime) defer nuke(runtime)
// Try to create a container with a memory limit of 1 byte less than the minimum allowed limit. // Try to create a container with a memory limit of 1 byte less than the minimum allowed limit.
_, err = srv.ContainerCreate( _, _, err = srv.ContainerCreate(
&Config{ &Config{
Image: GetTestImage(runtime).ID, Image: GetTestImage(runtime).ID,
Memory: 524287, Memory: 524287,
@ -402,7 +402,7 @@ func TestRmi(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
containerID, err := srv.ContainerCreate(config) containerID, _, err := srv.ContainerCreate(config)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -423,7 +423,7 @@ func TestRmi(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
containerID, err = srv.ContainerCreate(config) containerID, _, err = srv.ContainerCreate(config)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View file

@ -34,3 +34,72 @@ func sortImagesByCreationAndTag(images []APIImages) {
sort.Sort(sorter) sort.Sort(sorter)
} }
type portSorter struct {
ports []Port
by func(i, j Port) bool
}
func (s *portSorter) Len() int {
return len(s.ports)
}
func (s *portSorter) Swap(i, j int) {
s.ports[i], s.ports[j] = s.ports[j], s.ports[i]
}
func (s *portSorter) Less(i, j int) bool {
ip := s.ports[i]
jp := s.ports[j]
return s.by(ip, jp)
}
func sortPorts(ports []Port, predicate func(i, j Port) bool) {
s := &portSorter{ports, predicate}
sort.Sort(s)
}
type containerSorter struct {
containers []*Container
by func(i, j *Container) bool
}
func (s *containerSorter) Len() int {
return len(s.containers)
}
func (s *containerSorter) Swap(i, j int) {
s.containers[i], s.containers[j] = s.containers[j], s.containers[i]
}
func (s *containerSorter) Less(i, j int) bool {
return s.by(s.containers[i], s.containers[j])
}
func sortContainers(containers []*Container, predicate func(i, j *Container) bool) {
s := &containerSorter{containers, predicate}
sort.Sort(s)
}
type apiLinkSorter struct {
links []APILink
by func(i, j APILink) bool
}
func (s *apiLinkSorter) Len() int {
return len(s.links)
}
func (s *apiLinkSorter) Swap(i, j int) {
s.links[i], s.links[j] = s.links[j], s.links[i]
}
func (s *apiLinkSorter) Less(i, j int) bool {
return s.by(s.links[i], s.links[j])
}
func sortLinks(links []APILink, predicate func(i, j APILink) bool) {
s := &apiLinkSorter{links, predicate}
sort.Sort(s)
}

View file

@ -1,6 +1,7 @@
package docker package docker
import ( import (
"fmt"
"testing" "testing"
) )
@ -55,3 +56,38 @@ func TestServerListOrderedImagesByCreationDateAndTag(t *testing.T) {
t.Error("Expected []APIImges to be ordered by most recent creation date and tag name.") t.Error("Expected []APIImges to be ordered by most recent creation date and tag name.")
} }
} }
func TestSortUniquePorts(t *testing.T) {
ports := []Port{
Port("6379/tcp"),
Port("22/tcp"),
}
sortPorts(ports, func(ip, jp Port) bool {
return ip.Int() < jp.Int() || (ip.Int() == jp.Int() && ip.Proto() == "tcp")
})
first := ports[0]
if fmt.Sprint(first) != "22/tcp" {
t.Log(fmt.Sprint(first))
t.Fail()
}
}
func TestSortSamePortWithDifferentProto(t *testing.T) {
ports := []Port{
Port("8888/tcp"),
Port("8888/udp"),
Port("6379/tcp"),
Port("6379/udp"),
}
sortPorts(ports, func(ip, jp Port) bool {
return ip.Int() < jp.Int() || (ip.Int() == jp.Int() && ip.Proto() == "tcp")
})
first := ports[0]
if fmt.Sprint(first) != "6379/tcp" {
t.Fail()
}
}

153
utils.go
View file

@ -2,6 +2,8 @@ package docker
import ( import (
"fmt" "fmt"
"github.com/dotcloud/docker/utils"
"strconv"
"strings" "strings"
) )
@ -27,6 +29,7 @@ func CompareConfig(a, b *Config) bool {
len(a.Dns) != len(b.Dns) || len(a.Dns) != len(b.Dns) ||
len(a.Env) != len(b.Env) || len(a.Env) != len(b.Env) ||
len(a.PortSpecs) != len(b.PortSpecs) || len(a.PortSpecs) != len(b.PortSpecs) ||
len(a.ExposedPorts) != len(b.ExposedPorts) ||
len(a.Entrypoint) != len(b.Entrypoint) || len(a.Entrypoint) != len(b.Entrypoint) ||
len(a.Volumes) != len(b.Volumes) { len(a.Volumes) != len(b.Volumes) {
return false return false
@ -52,6 +55,11 @@ func CompareConfig(a, b *Config) bool {
return false return false
} }
} }
for k := range a.ExposedPorts {
if _, exists := b.ExposedPorts[k]; !exists {
return false
}
}
for i := 0; i < len(a.Entrypoint); i++ { for i := 0; i < len(a.Entrypoint); i++ {
if a.Entrypoint[i] != b.Entrypoint[i] { if a.Entrypoint[i] != b.Entrypoint[i] {
return false return false
@ -78,26 +86,38 @@ func MergeConfig(userConf, imageConf *Config) error {
if userConf.CpuShares == 0 { if userConf.CpuShares == 0 {
userConf.CpuShares = imageConf.CpuShares userConf.CpuShares = imageConf.CpuShares
} }
if userConf.PortSpecs == nil || len(userConf.PortSpecs) == 0 { if userConf.ExposedPorts == nil || len(userConf.ExposedPorts) == 0 {
userConf.PortSpecs = imageConf.PortSpecs userConf.ExposedPorts = imageConf.ExposedPorts
} else { }
for _, imagePortSpec := range imageConf.PortSpecs {
found := false if userConf.PortSpecs != nil && len(userConf.PortSpecs) > 0 {
imageNat, err := parseNat(imagePortSpec) if userConf.ExposedPorts == nil {
if err != nil { userConf.ExposedPorts = make(map[Port]struct{})
return err }
ports, _, err := parsePortSpecs(userConf.PortSpecs)
if err != nil {
return err
}
for port := range ports {
if _, exists := userConf.ExposedPorts[port]; !exists {
userConf.ExposedPorts[port] = struct{}{}
} }
for _, userPortSpec := range userConf.PortSpecs { }
userNat, err := parseNat(userPortSpec) userConf.PortSpecs = nil
if err != nil { }
return err if imageConf.PortSpecs != nil && len(imageConf.PortSpecs) > 0 {
} utils.Debugf("Migrating image port specs to containter: %s", strings.Join(imageConf.PortSpecs, ", "))
if imageNat.Proto == userNat.Proto && imageNat.Backend == userNat.Backend { if userConf.ExposedPorts == nil {
found = true userConf.ExposedPorts = make(map[Port]struct{})
} }
}
if !found { ports, _, err := parsePortSpecs(imageConf.PortSpecs)
userConf.PortSpecs = append(userConf.PortSpecs, imagePortSpec) if err != nil {
return err
}
for port := range ports {
if _, exists := userConf.ExposedPorts[port]; !exists {
userConf.ExposedPorts[port] = struct{}{}
} }
} }
} }
@ -174,3 +194,98 @@ func parseLxcOpt(opt string) (string, string, error) {
} }
return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]), nil return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]), nil
} }
// We will receive port specs in the format of ip:public:private/proto and these need to be
// parsed in the internal types
func parsePortSpecs(ports []string) (map[Port]struct{}, map[Port][]PortBinding, error) {
exposedPorts := make(map[Port]struct{}, len(ports))
bindings := make(map[Port][]PortBinding)
for _, rawPort := range ports {
proto := "tcp"
if i := strings.LastIndex(rawPort, "/"); i != -1 {
proto = rawPort[i+1:]
rawPort = rawPort[:i]
}
if !strings.Contains(rawPort, ":") {
rawPort = fmt.Sprintf("::%s", rawPort)
} else if len(strings.Split(rawPort, ":")) == 2 {
rawPort = fmt.Sprintf(":%s", rawPort)
}
parts, err := utils.PartParser("ip:hostPort:containerPort", rawPort)
if err != nil {
return nil, nil, err
}
containerPort := parts["containerPort"]
rawIp := parts["ip"]
hostPort := parts["hostPort"]
if containerPort == "" {
return nil, nil, fmt.Errorf("No port specified: %s<empty>", rawPort)
}
port := NewPort(proto, containerPort)
if _, exists := exposedPorts[port]; !exists {
exposedPorts[port] = struct{}{}
}
binding := PortBinding{
HostIp: rawIp,
HostPort: hostPort,
}
bslice, exists := bindings[port]
if !exists {
bslice = []PortBinding{}
}
bindings[port] = append(bslice, binding)
}
return exposedPorts, bindings, nil
}
// Splits a port in the format of port/proto
func splitProtoPort(rawPort string) (string, string) {
parts := strings.Split(rawPort, "/")
l := len(parts)
if l == 0 {
return "", ""
}
if l == 1 {
return "tcp", rawPort
}
return parts[0], parts[1]
}
func parsePort(rawPort string) (int, error) {
port, err := strconv.ParseUint(rawPort, 10, 16)
if err != nil {
return 0, err
}
return int(port), nil
}
func migratePortMappings(config *Config) error {
if config.PortSpecs != nil {
// We don't have to worry about migrating the bindings to the host
// This is our breaking change
ports, _, err := parsePortSpecs(config.PortSpecs)
if err != nil {
return err
}
config.PortSpecs = nil
if config.ExposedPorts == nil {
config.ExposedPorts = make(map[Port]struct{}, len(ports))
}
for k, v := range ports {
config.ExposedPorts[k] = v
}
}
return nil
}
// Links come in the format of
// name:alias
func parseLink(rawLink string) (map[string]string, error) {
return utils.PartParser("name:alias", rawLink)
}

View file

@ -1038,3 +1038,22 @@ func IsClosedError(err error) bool {
*/ */
return strings.HasSuffix(err.Error(), "use of closed network connection") return strings.HasSuffix(err.Error(), "use of closed network connection")
} }
func PartParser(template, data string) (map[string]string, error) {
// ip:public:private
templateParts := strings.Split(template, ":")
parts := strings.Split(data, ":")
if len(parts) != len(templateParts) {
return nil, fmt.Errorf("Invalid format to parse. %s should match template %s", data, template)
}
out := make(map[string]string, len(templateParts))
for i, t := range templateParts {
value := ""
if len(parts) > i {
value = parts[i]
}
out[t] = value
}
return out, nil
}

View file

@ -421,3 +421,23 @@ func TestDependencyGraph(t *testing.T) {
t.Fatalf("Expected [d], found %v instead", res[2]) t.Fatalf("Expected [d], found %v instead", res[2])
} }
} }
func TestParsePortMapping(t *testing.T) {
data, err := PartParser("ip:public:private", "192.168.1.1:80:8080")
if err != nil {
t.Fatal(err)
}
if len(data) != 3 {
t.FailNow()
}
if data["ip"] != "192.168.1.1" {
t.Fail()
}
if data["public"] != "80" {
t.Fail()
}
if data["private"] != "8080" {
t.Fail()
}
}

View file

@ -66,7 +66,11 @@ func newTestRuntime(prefix string) (runtime *Runtime, err error) {
return nil, err return nil, err
} }
runtime, err = NewRuntimeFromDirectory(root, false) config := &DaemonConfig{
GraphPath: root,
AutoRestart: false,
}
runtime, err = NewRuntimeFromDirectory(config)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -125,7 +129,7 @@ func mkContainer(r *Runtime, args []string, t *testing.T) (*Container, *HostConf
if config.Image == "_" { if config.Image == "_" {
config.Image = GetTestImage(r).ID config.Image = GetTestImage(r).ID
} }
c, err := r.Create(config) c, _, err := r.Create(config)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -253,12 +257,12 @@ func TestMergeConfig(t *testing.T) {
} }
} }
if len(configUser.PortSpecs) != 3 { if len(configUser.ExposedPorts) != 3 {
t.Fatalf("Expected 3 portSpecs, 1111:1111, 3333:2222 and 3333:3333, found %d", len(configUser.PortSpecs)) t.Fatalf("Expected 3 portSpecs, 1111, 2222 and 3333, found %d", len(configUser.PortSpecs))
} }
for _, portSpecs := range configUser.PortSpecs { for portSpecs := range configUser.ExposedPorts {
if portSpecs != "1111:1111" && portSpecs != "3333:2222" && portSpecs != "3333:3333" { if portSpecs.Port() != "1111" && portSpecs.Port() != "2222" && portSpecs.Port() != "3333" {
t.Fatalf("Expected 1111:1111 or 3333:2222 or 3333:3333, found %s", portSpecs) t.Fatalf("Expected 1111 or 2222 or 3333, found %s", portSpecs)
} }
} }
if len(configUser.Env) != 3 { if len(configUser.Env) != 3 {
@ -284,48 +288,6 @@ func TestMergeConfig(t *testing.T) {
} }
} }
func TestMergeConfigPublicPortNotHonored(t *testing.T) {
volumesImage := make(map[string]struct{})
volumesImage["/test1"] = struct{}{}
volumesImage["/test2"] = struct{}{}
configImage := &Config{
Dns: []string{"1.1.1.1", "2.2.2.2"},
PortSpecs: []string{"1111", "2222"},
Env: []string{"VAR1=1", "VAR2=2"},
Volumes: volumesImage,
}
volumesUser := make(map[string]struct{})
volumesUser["/test3"] = struct{}{}
configUser := &Config{
Dns: []string{"3.3.3.3"},
PortSpecs: []string{"1111:3333"},
Env: []string{"VAR2=3", "VAR3=3"},
Volumes: volumesUser,
}
MergeConfig(configUser, configImage)
contains := func(a []string, expect string) bool {
for _, p := range a {
if p == expect {
return true
}
}
return false
}
if !contains(configUser.PortSpecs, "2222") {
t.Logf("Expected '2222' Ports: %v", configUser.PortSpecs)
t.Fail()
}
if !contains(configUser.PortSpecs, "1111:3333") {
t.Logf("Expected '1111:3333' Ports: %v", configUser.PortSpecs)
t.Fail()
}
}
func TestParseLxcConfOpt(t *testing.T) { func TestParseLxcConfOpt(t *testing.T) {
opts := []string{"lxc.utsname=docker", "lxc.utsname = docker "} opts := []string{"lxc.utsname=docker", "lxc.utsname = docker "}
@ -342,3 +304,129 @@ func TestParseLxcConfOpt(t *testing.T) {
} }
} }
} }
func TestParseNetworkOptsPrivateOnly(t *testing.T) {
ports, bindings, err := parsePortSpecs([]string{"192.168.1.100::80"})
if err != nil {
t.Fatal(err)
}
if len(ports) != 1 {
t.Logf("Expected 1 got %d", len(ports))
t.FailNow()
}
if len(bindings) != 1 {
t.Logf("Expected 1 got %d", len(bindings))
t.FailNow()
}
for k := range ports {
if k.Proto() != "tcp" {
t.Logf("Expected tcp got %s", k.Proto())
t.Fail()
}
if k.Port() != "80" {
t.Logf("Expected 80 got %s", k.Port())
t.Fail()
}
b, exists := bindings[k]
if !exists {
t.Log("Binding does not exist")
t.FailNow()
}
if len(b) != 1 {
t.Logf("Expected 1 got %d", len(b))
t.FailNow()
}
s := b[0]
if s.HostPort != "" {
t.Logf("Expected \"\" got %s", s.HostPort)
t.Fail()
}
if s.HostIp != "192.168.1.100" {
t.Fail()
}
}
}
func TestParseNetworkOptsPublic(t *testing.T) {
ports, bindings, err := parsePortSpecs([]string{"192.168.1.100:8080:80"})
if err != nil {
t.Fatal(err)
}
if len(ports) != 1 {
t.Logf("Expected 1 got %d", len(ports))
t.FailNow()
}
if len(bindings) != 1 {
t.Logf("Expected 1 got %d", len(bindings))
t.FailNow()
}
for k := range ports {
if k.Proto() != "tcp" {
t.Logf("Expected tcp got %s", k.Proto())
t.Fail()
}
if k.Port() != "80" {
t.Logf("Expected 80 got %s", k.Port())
t.Fail()
}
b, exists := bindings[k]
if !exists {
t.Log("Binding does not exist")
t.FailNow()
}
if len(b) != 1 {
t.Logf("Expected 1 got %d", len(b))
t.FailNow()
}
s := b[0]
if s.HostPort != "8080" {
t.Logf("Expected 8080 got %s", s.HostPort)
t.Fail()
}
if s.HostIp != "192.168.1.100" {
t.Fail()
}
}
}
func TestParseNetworkOptsUdp(t *testing.T) {
ports, bindings, err := parsePortSpecs([]string{"192.168.1.100::6000/udp"})
if err != nil {
t.Fatal(err)
}
if len(ports) != 1 {
t.Logf("Expected 1 got %d", len(ports))
t.FailNow()
}
if len(bindings) != 1 {
t.Logf("Expected 1 got %d", len(bindings))
t.FailNow()
}
for k := range ports {
if k.Proto() != "udp" {
t.Logf("Expected udp got %s", k.Proto())
t.Fail()
}
if k.Port() != "6000" {
t.Logf("Expected 6000 got %s", k.Port())
t.Fail()
}
b, exists := bindings[k]
if !exists {
t.Log("Binding does not exist")
t.FailNow()
}
if len(b) != 1 {
t.Logf("Expected 1 got %d", len(b))
t.FailNow()
}
s := b[0]
if s.HostPort != "" {
t.Logf("Expected \"\" got %s", s.HostPort)
t.Fail()
}
if s.HostIp != "192.168.1.100" {
t.Fail()
}
}
}

View file

@ -0,0 +1,404 @@
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package sqlite provides access to the SQLite library, version 3.
package sqlite
/*
#cgo LDFLAGS: -lsqlite3
#include <sqlite3.h>
#include <stdlib.h>
// These wrappers are necessary because SQLITE_TRANSIENT
// is a pointer constant, and cgo doesn't translate them correctly.
// The definition in sqlite3.h is:
//
// typedef void (*sqlite3_destructor_type)(void*);
// #define SQLITE_STATIC ((sqlite3_destructor_type)0)
// #define SQLITE_TRANSIENT ((sqlite3_destructor_type)-1)
static int my_bind_text(sqlite3_stmt *stmt, int n, char *p, int np) {
return sqlite3_bind_text(stmt, n, p, np, SQLITE_TRANSIENT);
}
static int my_bind_blob(sqlite3_stmt *stmt, int n, void *p, int np) {
return sqlite3_bind_blob(stmt, n, p, np, SQLITE_TRANSIENT);
}
*/
import "C"
import (
"errors"
"fmt"
"reflect"
"strconv"
"time"
"unsafe"
)
type Errno int
func (e Errno) Error() string {
s := errText[e]
if s == "" {
return fmt.Sprintf("errno %d", int(e))
}
return s
}
var (
ErrError error = Errno(1) // /* SQL error or missing database */
ErrInternal error = Errno(2) // /* Internal logic error in SQLite */
ErrPerm error = Errno(3) // /* Access permission denied */
ErrAbort error = Errno(4) // /* Callback routine requested an abort */
ErrBusy error = Errno(5) // /* The database file is locked */
ErrLocked error = Errno(6) // /* A table in the database is locked */
ErrNoMem error = Errno(7) // /* A malloc() failed */
ErrReadOnly error = Errno(8) // /* Attempt to write a readonly database */
ErrInterrupt error = Errno(9) // /* Operation terminated by sqlite3_interrupt()*/
ErrIOErr error = Errno(10) // /* Some kind of disk I/O error occurred */
ErrCorrupt error = Errno(11) // /* The database disk image is malformed */
ErrFull error = Errno(13) // /* Insertion failed because database is full */
ErrCantOpen error = Errno(14) // /* Unable to open the database file */
ErrEmpty error = Errno(16) // /* Database is empty */
ErrSchema error = Errno(17) // /* The database schema changed */
ErrTooBig error = Errno(18) // /* String or BLOB exceeds size limit */
ErrConstraint error = Errno(19) // /* Abort due to constraint violation */
ErrMismatch error = Errno(20) // /* Data type mismatch */
ErrMisuse error = Errno(21) // /* Library used incorrectly */
ErrNolfs error = Errno(22) // /* Uses OS features not supported on host */
ErrAuth error = Errno(23) // /* Authorization denied */
ErrFormat error = Errno(24) // /* Auxiliary database format error */
ErrRange error = Errno(25) // /* 2nd parameter to sqlite3_bind out of range */
ErrNotDB error = Errno(26) // /* File opened that is not a database file */
Row = Errno(100) // /* sqlite3_step() has another row ready */
Done = Errno(101) // /* sqlite3_step() has finished executing */
)
var errText = map[Errno]string{
1: "SQL error or missing database",
2: "Internal logic error in SQLite",
3: "Access permission denied",
4: "Callback routine requested an abort",
5: "The database file is locked",
6: "A table in the database is locked",
7: "A malloc() failed",
8: "Attempt to write a readonly database",
9: "Operation terminated by sqlite3_interrupt()*/",
10: "Some kind of disk I/O error occurred",
11: "The database disk image is malformed",
12: "NOT USED. Table or record not found",
13: "Insertion failed because database is full",
14: "Unable to open the database file",
15: "NOT USED. Database lock protocol error",
16: "Database is empty",
17: "The database schema changed",
18: "String or BLOB exceeds size limit",
19: "Abort due to constraint violation",
20: "Data type mismatch",
21: "Library used incorrectly",
22: "Uses OS features not supported on host",
23: "Authorization denied",
24: "Auxiliary database format error",
25: "2nd parameter to sqlite3_bind out of range",
26: "File opened that is not a database file",
100: "sqlite3_step() has another row ready",
101: "sqlite3_step() has finished executing",
}
func (c *Conn) error(rv C.int) error {
if c == nil || c.db == nil {
return errors.New("nil sqlite database")
}
if rv == 0 {
return nil
}
if rv == 21 { // misuse
return Errno(rv)
}
return errors.New(Errno(rv).Error() + ": " + C.GoString(C.sqlite3_errmsg(c.db)))
}
type Conn struct {
db *C.sqlite3
}
func Version() string {
p := C.sqlite3_libversion()
return C.GoString(p)
}
func Open(filename string) (*Conn, error) {
if C.sqlite3_threadsafe() == 0 {
return nil, errors.New("sqlite library was not compiled for thread-safe operation")
}
var db *C.sqlite3
name := C.CString(filename)
defer C.free(unsafe.Pointer(name))
rv := C.sqlite3_open_v2(name, &db,
C.SQLITE_OPEN_FULLMUTEX|
C.SQLITE_OPEN_READWRITE|
C.SQLITE_OPEN_CREATE,
nil)
if rv != 0 {
return nil, Errno(rv)
}
if db == nil {
return nil, errors.New("sqlite succeeded without returning a database")
}
return &Conn{db}, nil
}
func NewBackup(dst *Conn, dstTable string, src *Conn, srcTable string) (*Backup, error) {
dname := C.CString(dstTable)
sname := C.CString(srcTable)
defer C.free(unsafe.Pointer(dname))
defer C.free(unsafe.Pointer(sname))
sb := C.sqlite3_backup_init(dst.db, dname, src.db, sname)
if sb == nil {
return nil, dst.error(C.sqlite3_errcode(dst.db))
}
return &Backup{sb, dst, src}, nil
}
type Backup struct {
sb *C.sqlite3_backup
dst, src *Conn
}
func (b *Backup) Step(npage int) error {
rv := C.sqlite3_backup_step(b.sb, C.int(npage))
if rv == 0 || Errno(rv) == ErrBusy || Errno(rv) == ErrLocked {
return nil
}
return Errno(rv)
}
type BackupStatus struct {
Remaining int
PageCount int
}
func (b *Backup) Status() BackupStatus {
return BackupStatus{int(C.sqlite3_backup_remaining(b.sb)), int(C.sqlite3_backup_pagecount(b.sb))}
}
func (b *Backup) Run(npage int, period time.Duration, c chan<- BackupStatus) error {
var err error
for {
err = b.Step(npage)
if err != nil {
break
}
if c != nil {
c <- b.Status()
}
time.Sleep(period)
}
return b.dst.error(C.sqlite3_errcode(b.dst.db))
}
func (b *Backup) Close() error {
if b.sb == nil {
return errors.New("backup already closed")
}
C.sqlite3_backup_finish(b.sb)
b.sb = nil
return nil
}
func (c *Conn) BusyTimeout(ms int) error {
rv := C.sqlite3_busy_timeout(c.db, C.int(ms))
if rv == 0 {
return nil
}
return Errno(rv)
}
func (c *Conn) Exec(cmd string, args ...interface{}) error {
s, err := c.Prepare(cmd)
if err != nil {
return err
}
defer s.Finalize()
err = s.Exec(args...)
if err != nil {
return err
}
rv := C.sqlite3_step(s.stmt)
if Errno(rv) != Done {
return c.error(rv)
}
return nil
}
type Stmt struct {
c *Conn
stmt *C.sqlite3_stmt
err error
t0 time.Time
sql string
args string
}
func (c *Conn) Prepare(cmd string) (*Stmt, error) {
if c == nil || c.db == nil {
return nil, errors.New("nil sqlite database")
}
cmdstr := C.CString(cmd)
defer C.free(unsafe.Pointer(cmdstr))
var stmt *C.sqlite3_stmt
var tail *C.char
rv := C.sqlite3_prepare_v2(c.db, cmdstr, C.int(len(cmd)+1), &stmt, &tail)
if rv != 0 {
return nil, c.error(rv)
}
return &Stmt{c: c, stmt: stmt, sql: cmd, t0: time.Now()}, nil
}
func (s *Stmt) Exec(args ...interface{}) error {
s.args = fmt.Sprintf(" %v", []interface{}(args))
rv := C.sqlite3_reset(s.stmt)
if rv != 0 {
return s.c.error(rv)
}
n := int(C.sqlite3_bind_parameter_count(s.stmt))
if n != len(args) {
return errors.New(fmt.Sprintf("incorrect argument count for Stmt.Exec: have %d want %d", len(args), n))
}
for i, v := range args {
var str string
switch v := v.(type) {
case []byte:
var p *byte
if len(v) > 0 {
p = &v[0]
}
if rv := C.my_bind_blob(s.stmt, C.int(i+1), unsafe.Pointer(p), C.int(len(v))); rv != 0 {
return s.c.error(rv)
}
continue
case bool:
if v {
str = "1"
} else {
str = "0"
}
default:
str = fmt.Sprint(v)
}
cstr := C.CString(str)
rv := C.my_bind_text(s.stmt, C.int(i+1), cstr, C.int(len(str)))
C.free(unsafe.Pointer(cstr))
if rv != 0 {
return s.c.error(rv)
}
}
return nil
}
func (s *Stmt) Error() error {
return s.err
}
func (s *Stmt) Next() bool {
rv := C.sqlite3_step(s.stmt)
err := Errno(rv)
if err == Row {
return true
}
if err != Done {
s.err = s.c.error(rv)
}
return false
}
func (s *Stmt) Reset() error {
C.sqlite3_reset(s.stmt)
return nil
}
func (s *Stmt) Scan(args ...interface{}) error {
n := int(C.sqlite3_column_count(s.stmt))
if n != len(args) {
return errors.New(fmt.Sprintf("incorrect argument count for Stmt.Scan: have %d want %d", len(args), n))
}
for i, v := range args {
n := C.sqlite3_column_bytes(s.stmt, C.int(i))
p := C.sqlite3_column_blob(s.stmt, C.int(i))
if p == nil && n > 0 {
return errors.New("got nil blob")
}
var data []byte
if n > 0 {
data = (*[1 << 30]byte)(unsafe.Pointer(p))[0:n]
}
switch v := v.(type) {
case *[]byte:
*v = data
case *string:
*v = string(data)
case *bool:
*v = string(data) == "1"
case *int:
x, err := strconv.Atoi(string(data))
if err != nil {
return errors.New("arg " + strconv.Itoa(i) + " as int: " + err.Error())
}
*v = x
case *int64:
x, err := strconv.ParseInt(string(data), 10, 64)
if err != nil {
return errors.New("arg " + strconv.Itoa(i) + " as int64: " + err.Error())
}
*v = x
case *float64:
x, err := strconv.ParseFloat(string(data), 64)
if err != nil {
return errors.New("arg " + strconv.Itoa(i) + " as float64: " + err.Error())
}
*v = x
default:
return errors.New("unsupported type in Scan: " + reflect.TypeOf(v).String())
}
}
return nil
}
func (s *Stmt) SQL() string {
return s.sql + s.args
}
func (s *Stmt) Nanoseconds() int64 {
return time.Now().Sub(s.t0).Nanoseconds()
}
func (s *Stmt) Finalize() error {
rv := C.sqlite3_finalize(s.stmt)
if rv != 0 {
return s.c.error(rv)
}
return nil
}
func (c *Conn) Close() error {
if c == nil || c.db == nil {
return errors.New("nil sqlite database")
}
rv := C.sqlite3_close(c.db)
if rv != 0 {
return c.error(rv)
}
c.db = nil
return nil
}

View file

@ -0,0 +1,498 @@
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package sqlite3 provides access to the SQLite library, version 3.
//
// The package has no exported API.
// It registers a driver for the standard Go database/sql package.
//
// import _ "code.google.com/p/gosqlite/sqlite3"
//
// (For an alternate, earlier API, see the code.google.com/p/gosqlite/sqlite package.)
package sqlite
/*
#cgo LDFLAGS: -lsqlite3
#include <sqlite3.h>
#include <stdlib.h>
// These wrappers are necessary because SQLITE_TRANSIENT
// is a pointer constant, and cgo doesn't translate them correctly.
// The definition in sqlite3.h is:
//
// typedef void (*sqlite3_destructor_type)(void*);
// #define SQLITE_STATIC ((sqlite3_destructor_type)0)
// #define SQLITE_TRANSIENT ((sqlite3_destructor_type)-1)
static int my_bind_text(sqlite3_stmt *stmt, int n, char *p, int np) {
return sqlite3_bind_text(stmt, n, p, np, SQLITE_TRANSIENT);
}
static int my_bind_blob(sqlite3_stmt *stmt, int n, void *p, int np) {
return sqlite3_bind_blob(stmt, n, p, np, SQLITE_TRANSIENT);
}
*/
import "C"
import (
"database/sql"
"database/sql/driver"
"errors"
"fmt"
"io"
"strings"
"time"
"unsafe"
)
func init() {
sql.Register("sqlite3", impl{})
}
type errno int
func (e errno) Error() string {
s := errText[e]
if s == "" {
return fmt.Sprintf("errno %d", int(e))
}
return s
}
var (
errError error = errno(1) // /* SQL error or missing database */
errInternal error = errno(2) // /* Internal logic error in SQLite */
errPerm error = errno(3) // /* Access permission denied */
errAbort error = errno(4) // /* Callback routine requested an abort */
errBusy error = errno(5) // /* The database file is locked */
errLocked error = errno(6) // /* A table in the database is locked */
errNoMem error = errno(7) // /* A malloc() failed */
errReadOnly error = errno(8) // /* Attempt to write a readonly database */
errInterrupt error = errno(9) // /* Operation terminated by sqlite3_interrupt()*/
errIOErr error = errno(10) // /* Some kind of disk I/O error occurred */
errCorrupt error = errno(11) // /* The database disk image is malformed */
errFull error = errno(13) // /* Insertion failed because database is full */
errCantOpen error = errno(14) // /* Unable to open the database file */
errEmpty error = errno(16) // /* Database is empty */
errSchema error = errno(17) // /* The database schema changed */
errTooBig error = errno(18) // /* String or BLOB exceeds size limit */
errConstraint error = errno(19) // /* Abort due to constraint violation */
errMismatch error = errno(20) // /* Data type mismatch */
errMisuse error = errno(21) // /* Library used incorrectly */
errNolfs error = errno(22) // /* Uses OS features not supported on host */
errAuth error = errno(23) // /* Authorization denied */
errFormat error = errno(24) // /* Auxiliary database format error */
errRange error = errno(25) // /* 2nd parameter to sqlite3_bind out of range */
errNotDB error = errno(26) // /* File opened that is not a database file */
stepRow = errno(100) // /* sqlite3_step() has another row ready */
stepDone = errno(101) // /* sqlite3_step() has finished executing */
)
var errText = map[errno]string{
1: "SQL error or missing database",
2: "Internal logic error in SQLite",
3: "Access permission denied",
4: "Callback routine requested an abort",
5: "The database file is locked",
6: "A table in the database is locked",
7: "A malloc() failed",
8: "Attempt to write a readonly database",
9: "Operation terminated by sqlite3_interrupt()*/",
10: "Some kind of disk I/O error occurred",
11: "The database disk image is malformed",
12: "NOT USED. Table or record not found",
13: "Insertion failed because database is full",
14: "Unable to open the database file",
15: "NOT USED. Database lock protocol error",
16: "Database is empty",
17: "The database schema changed",
18: "String or BLOB exceeds size limit",
19: "Abort due to constraint violation",
20: "Data type mismatch",
21: "Library used incorrectly",
22: "Uses OS features not supported on host",
23: "Authorization denied",
24: "Auxiliary database format error",
25: "2nd parameter to sqlite3_bind out of range",
26: "File opened that is not a database file",
100: "sqlite3_step() has another row ready",
101: "sqlite3_step() has finished executing",
}
type impl struct{}
func (impl) Open(name string) (driver.Conn, error) {
if C.sqlite3_threadsafe() == 0 {
return nil, errors.New("sqlite library was not compiled for thread-safe operation")
}
var db *C.sqlite3
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
rv := C.sqlite3_open_v2(cname, &db,
C.SQLITE_OPEN_FULLMUTEX|
C.SQLITE_OPEN_READWRITE|
C.SQLITE_OPEN_CREATE,
nil)
if rv != 0 {
return nil, errno(rv)
}
if db == nil {
return nil, errors.New("sqlite succeeded without returning a database")
}
return &conn{db: db}, nil
}
type conn struct {
db *C.sqlite3
closed bool
tx bool
}
func (c *conn) error(rv C.int) error {
if rv == 0 {
return nil
}
if rv == 21 || c.closed {
return errno(rv)
}
return errors.New(errno(rv).Error() + ": " + C.GoString(C.sqlite3_errmsg(c.db)))
}
func (c *conn) Prepare(cmd string) (driver.Stmt, error) {
if c.closed {
panic("database/sql/driver: misuse of sqlite driver: Prepare after Close")
}
cmdstr := C.CString(cmd)
defer C.free(unsafe.Pointer(cmdstr))
var s *C.sqlite3_stmt
var tail *C.char
rv := C.sqlite3_prepare_v2(c.db, cmdstr, C.int(len(cmd)+1), &s, &tail)
if rv != 0 {
return nil, c.error(rv)
}
return &stmt{c: c, stmt: s, sql: cmd, t0: time.Now()}, nil
}
func (c *conn) Close() error {
if c.closed {
panic("database/sql/driver: misuse of sqlite driver: multiple Close")
}
c.closed = true
rv := C.sqlite3_close(c.db)
c.db = nil
return c.error(rv)
}
func (c *conn) exec(cmd string) error {
cstring := C.CString(cmd)
defer C.free(unsafe.Pointer(cstring))
rv := C.sqlite3_exec(c.db, cstring, nil, nil, nil)
return c.error(rv)
}
func (c *conn) Begin() (driver.Tx, error) {
if c.tx {
panic("database/sql/driver: misuse of sqlite driver: multiple Tx")
}
if err := c.exec("BEGIN TRANSACTION"); err != nil {
return nil, err
}
c.tx = true
return &tx{c}, nil
}
type tx struct {
c *conn
}
func (t *tx) Commit() error {
if t.c == nil || !t.c.tx {
panic("database/sql/driver: misuse of sqlite driver: extra Commit")
}
t.c.tx = false
err := t.c.exec("COMMIT TRANSACTION")
t.c = nil
return err
}
func (t *tx) Rollback() error {
if t.c == nil || !t.c.tx {
panic("database/sql/driver: misuse of sqlite driver: extra Rollback")
}
t.c.tx = false
err := t.c.exec("ROLLBACK")
t.c = nil
return err
}
type stmt struct {
c *conn
stmt *C.sqlite3_stmt
err error
t0 time.Time
sql string
args string
closed bool
rows bool
colnames []string
coltypes []string
}
func (s *stmt) Close() error {
if s.rows {
panic("database/sql/driver: misuse of sqlite driver: Close with active Rows")
}
if s.closed {
panic("database/sql/driver: misuse of sqlite driver: double Close of Stmt")
}
s.closed = true
rv := C.sqlite3_finalize(s.stmt)
if rv != 0 {
return s.c.error(rv)
}
return nil
}
func (s *stmt) NumInput() int {
if s.closed {
panic("database/sql/driver: misuse of sqlite driver: NumInput after Close")
}
return int(C.sqlite3_bind_parameter_count(s.stmt))
}
func (s *stmt) reset() error {
return s.c.error(C.sqlite3_reset(s.stmt))
}
func (s *stmt) start(args []driver.Value) error {
if err := s.reset(); err != nil {
return err
}
n := int(C.sqlite3_bind_parameter_count(s.stmt))
if n != len(args) {
return fmt.Errorf("incorrect argument count for command: have %d want %d", len(args), n)
}
for i, v := range args {
var str string
switch v := v.(type) {
case nil:
if rv := C.sqlite3_bind_null(s.stmt, C.int(i+1)); rv != 0 {
return s.c.error(rv)
}
continue
case float64:
if rv := C.sqlite3_bind_double(s.stmt, C.int(i+1), C.double(v)); rv != 0 {
return s.c.error(rv)
}
continue
case int64:
if rv := C.sqlite3_bind_int64(s.stmt, C.int(i+1), C.sqlite3_int64(v)); rv != 0 {
return s.c.error(rv)
}
continue
case []byte:
var p *byte
if len(v) > 0 {
p = &v[0]
}
if rv := C.my_bind_blob(s.stmt, C.int(i+1), unsafe.Pointer(p), C.int(len(v))); rv != 0 {
return s.c.error(rv)
}
continue
case bool:
var vi int64
if v {
vi = 1
}
if rv := C.sqlite3_bind_int64(s.stmt, C.int(i+1), C.sqlite3_int64(vi)); rv != 0 {
return s.c.error(rv)
}
continue
case time.Time:
str = v.UTC().Format(timefmt[0])
case string:
str = v
default:
str = fmt.Sprint(v)
}
cstr := C.CString(str)
rv := C.my_bind_text(s.stmt, C.int(i+1), cstr, C.int(len(str)))
C.free(unsafe.Pointer(cstr))
if rv != 0 {
return s.c.error(rv)
}
}
return nil
}
func (s *stmt) Exec(args []driver.Value) (driver.Result, error) {
if s.closed {
panic("database/sql/driver: misuse of sqlite driver: Exec after Close")
}
if s.rows {
panic("database/sql/driver: misuse of sqlite driver: Exec with active Rows")
}
err := s.start(args)
if err != nil {
return nil, err
}
rv := C.sqlite3_step(s.stmt)
if errno(rv) != stepDone {
if rv == 0 {
rv = 21 // errMisuse
}
return nil, s.c.error(rv)
}
id := int64(C.sqlite3_last_insert_rowid(s.c.db))
rows := int64(C.sqlite3_changes(s.c.db))
return &result{id, rows}, nil
}
func (s *stmt) Query(args []driver.Value) (driver.Rows, error) {
if s.closed {
panic("database/sql/driver: misuse of sqlite driver: Query after Close")
}
if s.rows {
panic("database/sql/driver: misuse of sqlite driver: Query with active Rows")
}
err := s.start(args)
if err != nil {
return nil, err
}
s.rows = true
if s.colnames == nil {
n := int64(C.sqlite3_column_count(s.stmt))
s.colnames = make([]string, n)
s.coltypes = make([]string, n)
for i := range s.colnames {
s.colnames[i] = C.GoString(C.sqlite3_column_name(s.stmt, C.int(i)))
s.coltypes[i] = strings.ToLower(C.GoString(C.sqlite3_column_decltype(s.stmt, C.int(i))))
}
}
return &rows{s}, nil
}
type rows struct {
s *stmt
}
func (r *rows) Columns() []string {
if r.s == nil {
panic("database/sql/driver: misuse of sqlite driver: Columns of closed Rows")
}
return r.s.colnames
}
const maxslice = 1<<31 - 1
var timefmt = []string{
"2006-01-02 15:04:05.999999999",
"2006-01-02T15:04:05.999999999",
"2006-01-02 15:04:05",
"2006-01-02T15:04:05",
"2006-01-02 15:04",
"2006-01-02T15:04",
"2006-01-02",
}
func (r *rows) Next(dst []driver.Value) error {
if r.s == nil {
panic("database/sql/driver: misuse of sqlite driver: Next of closed Rows")
}
rv := C.sqlite3_step(r.s.stmt)
if errno(rv) != stepRow {
if errno(rv) == stepDone {
return io.EOF
}
if rv == 0 {
rv = 21
}
return r.s.c.error(rv)
}
for i := range dst {
switch typ := C.sqlite3_column_type(r.s.stmt, C.int(i)); typ {
default:
return fmt.Errorf("unexpected sqlite3 column type %d", typ)
case C.SQLITE_INTEGER:
val := int64(C.sqlite3_column_int64(r.s.stmt, C.int(i)))
switch r.s.coltypes[i] {
case "timestamp", "datetime":
dst[i] = time.Unix(val, 0).UTC()
case "boolean":
dst[i] = val > 0
default:
dst[i] = val
}
case C.SQLITE_FLOAT:
dst[i] = float64(C.sqlite3_column_double(r.s.stmt, C.int(i)))
case C.SQLITE_BLOB, C.SQLITE_TEXT:
n := int(C.sqlite3_column_bytes(r.s.stmt, C.int(i)))
var b []byte
if n > 0 {
p := C.sqlite3_column_blob(r.s.stmt, C.int(i))
b = (*[maxslice]byte)(unsafe.Pointer(p))[:n]
}
dst[i] = b
switch r.s.coltypes[i] {
case "timestamp", "datetime":
dst[i] = time.Time{}
s := string(b)
for _, f := range timefmt {
if t, err := time.Parse(f, s); err == nil {
dst[i] = t
break
}
}
}
case C.SQLITE_NULL:
dst[i] = nil
}
}
return nil
}
func (r *rows) Close() error {
if r.s == nil {
panic("database/sql/driver: misuse of sqlite driver: Close of closed Rows")
}
r.s.rows = false
r.s = nil
return nil
}
type result struct {
id int64
rows int64
}
func (r *result) LastInsertId() (int64, error) {
return r.id, nil
}
func (r *result) RowsAffected() (int64, error) {
return r.rows, nil
}