Browse Source

Add links for container relationships and introspection

Michael Crosby 11 years ago
parent
commit
1cbdaebaa1

+ 5 - 7
Dockerfile

@@ -33,15 +33,13 @@ run	apt-get update
 run	apt-get install -y -q curl
 run	apt-get install -y -q git
 run	apt-get install -y -q mercurial
-run	apt-get install -y -q build-essential
+run apt-get install -y -q build-essential libsqlite3-dev
 
-# Install Go from source (for eventual cross-compiling)
-env	CGO_ENABLED 0
-run	curl -s https://go.googlecode.com/files/go1.1.2.src.tar.gz | tar -v -C / -xz && mv /go /goroot
-run	cd /goroot/src && ./make.bash
-env GOROOT	/goroot
-env	PATH	$PATH:/goroot/bin
+# Install Go
+run	curl -s https://go.googlecode.com/files/go1.2rc1.src.tar.gz | tar -v -C /usr/local -xz
+env	PATH	/usr/local/go/bin:/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin
 env	GOPATH	/go:/go/src/github.com/dotcloud/docker/vendor
+run cd /usr/local/go/src && ./make.bash && go install -ldflags '-w -linkmode external -extldflags "-static -Wl,--unresolved-symbols=ignore-in-shared-libs"' -tags netgo -a std
 
 # Ubuntu stuff
 run	apt-get install -y -q ruby1.9.3 rubygems libffi-dev

+ 100 - 5
api.go

@@ -6,6 +6,7 @@ import (
 	"encoding/json"
 	"fmt"
 	"github.com/dotcloud/docker/auth"
+	"github.com/dotcloud/docker/gograph"
 	"github.com/dotcloud/docker/utils"
 	"github.com/gorilla/mux"
 	"io"
@@ -14,6 +15,7 @@ import (
 	"mime"
 	"net"
 	"net/http"
+	"net/url"
 	"os"
 	"os/exec"
 	"regexp"
@@ -154,7 +156,7 @@ func postContainersKill(srv *Server, version float64, w http.ResponseWriter, r *
 			}
 		}
 	}
-
+	name = decodeName(name)
 	if err := srv.ContainerKill(name, signal); err != nil {
 		return err
 	}
@@ -167,6 +169,7 @@ func getContainersExport(srv *Server, version float64, w http.ResponseWriter, r
 		return fmt.Errorf("Missing parameter")
 	}
 	name := vars["name"]
+	name = decodeName(name)
 
 	if err := srv.ContainerExport(name, w); err != nil {
 		utils.Errorf("%s", err)
@@ -534,16 +537,19 @@ func postContainersCreate(srv *Server, version float64, w http.ResponseWriter, r
 		return err
 	}
 
-	if !config.NetworkDisabled && len(config.Dns) == 0 && len(srv.runtime.Dns) == 0 && utils.CheckLocalDns(resolvConf) {
+	if !config.NetworkDisabled && len(config.Dns) == 0 && len(srv.runtime.config.Dns) == 0 && utils.CheckLocalDns(resolvConf) {
 		out.Warnings = append(out.Warnings, fmt.Sprintf("Docker detected local DNS server on resolv.conf. Using default external servers: %v", defaultDns))
 		config.Dns = defaultDns
 	}
 
-	id, err := srv.ContainerCreate(config)
+	id, warnings, err := srv.ContainerCreate(config)
 	if err != nil {
 		return err
 	}
 	out.ID = id
+	for _, warning := range warnings {
+		out.Warnings = append(out.Warnings, warning)
+	}
 
 	if config.Memory > 0 && !srv.runtime.capabilities.MemoryLimit {
 		log.Println("WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.")
@@ -574,6 +580,7 @@ func postContainersRestart(srv *Server, version float64, w http.ResponseWriter,
 		return fmt.Errorf("Missing parameter")
 	}
 	name := vars["name"]
+	name = decodeName(name)
 	if err := srv.ContainerRestart(name, t); err != nil {
 		return err
 	}
@@ -589,12 +596,18 @@ func deleteContainers(srv *Server, version float64, w http.ResponseWriter, r *ht
 		return fmt.Errorf("Missing parameter")
 	}
 	name := vars["name"]
+	name = decodeName(name)
+
 	removeVolume, err := getBoolParam(r.Form.Get("v"))
 	if err != nil {
 		return err
 	}
+	removeLink, err := getBoolParam(r.Form.Get("link"))
+	if err != nil {
+		return err
+	}
 
-	if err := srv.ContainerDestroy(name, removeVolume); err != nil {
+	if err := srv.ContainerDestroy(name, removeVolume, removeLink); err != nil {
 		return err
 	}
 	w.WriteHeader(http.StatusNoContent)
@@ -640,7 +653,12 @@ func postContainersStart(srv *Server, version float64, w http.ResponseWriter, r
 	if vars == nil {
 		return fmt.Errorf("Missing parameter")
 	}
+	var err error
 	name := vars["name"]
+	name = decodeName(name)
+	if err != nil {
+		return err
+	}
 	if err := srv.ContainerStart(name, hostConfig); err != nil {
 		return err
 	}
@@ -661,6 +679,7 @@ func postContainersStop(srv *Server, version float64, w http.ResponseWriter, r *
 		return fmt.Errorf("Missing parameter")
 	}
 	name := vars["name"]
+	name = decodeName(name)
 
 	if err := srv.ContainerStop(name, t); err != nil {
 		return err
@@ -674,6 +693,8 @@ func postContainersWait(srv *Server, version float64, w http.ResponseWriter, r *
 		return fmt.Errorf("Missing parameter")
 	}
 	name := vars["name"]
+	name = decodeName(name)
+
 	status, err := srv.ContainerWait(name)
 	if err != nil {
 		return err
@@ -733,6 +754,7 @@ func postContainersAttach(srv *Server, version float64, w http.ResponseWriter, r
 		return fmt.Errorf("Missing parameter")
 	}
 	name := vars["name"]
+	name = decodeName(name)
 
 	c, err := srv.ContainerInspect(name)
 	if err != nil {
@@ -805,6 +827,7 @@ func wsContainersAttach(srv *Server, version float64, w http.ResponseWriter, r *
 		return fmt.Errorf("Missing parameter")
 	}
 	name := vars["name"]
+	name = decodeName(name)
 
 	if _, err := srv.ContainerInspect(name); err != nil {
 		return err
@@ -827,6 +850,7 @@ func getContainersByName(srv *Server, version float64, w http.ResponseWriter, r
 		return fmt.Errorf("Missing parameter")
 	}
 	name := vars["name"]
+	name = decodeName(name)
 
 	container, err := srv.ContainerInspect(name)
 	if err != nil {
@@ -994,7 +1018,7 @@ func makeHttpHandler(srv *Server, logging bool, localMethod string, localRoute s
 		if err != nil {
 			version = APIVERSION
 		}
-		if srv.enableCors {
+		if srv.runtime.config.EnableCors {
 			writeCorsHeaders(w, r)
 		}
 
@@ -1010,6 +1034,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) {
 	r := mux.NewRouter()
 
@@ -1030,6 +1123,7 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
 			"/containers/{name:.*}/json":      getContainersByName,
 			"/containers/{name:.*}/top":       getContainersTop,
 			"/containers/{name:.*}/attach/ws": wsContainersAttach,
+			"/containers/links":               getContainersLinks,
 		},
 		"POST": {
 			"/auth":                         postAuth,
@@ -1048,6 +1142,7 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
 			"/containers/{name:.*}/resize":  postContainersResize,
 			"/containers/{name:.*}/attach":  postContainersAttach,
 			"/containers/{name:.*}/copy":    postContainersCopy,
+			"/containers/link":              postContainerLink,
 		},
 		"DELETE": {
 			"/containers/{name:.*}": deleteContainers,

+ 8 - 10
api_params.go

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

+ 19 - 17
api_test.go

@@ -349,7 +349,7 @@ func TestGetContainersJSON(t *testing.T) {
 
 	beginLen := runtime.containers.Len()
 
-	container, err := runtime.Create(&Config{
+	container, _, err := runtime.Create(&Config{
 		Image: GetTestImage(runtime).ID,
 		Cmd:   []string{"echo", "test"},
 	})
@@ -386,7 +386,7 @@ func TestGetContainersExport(t *testing.T) {
 	srv := &Server{runtime: runtime}
 
 	// Create a container and remove a file
-	container, err := runtime.Create(
+	container, _, err := runtime.Create(
 		&Config{
 			Image: GetTestImage(runtime).ID,
 			Cmd:   []string{"touch", "/test"},
@@ -436,7 +436,7 @@ func TestGetContainersChanges(t *testing.T) {
 	srv := &Server{runtime: runtime}
 
 	// Create a container and remove a file
-	container, err := runtime.Create(
+	container, _, err := runtime.Create(
 		&Config{
 			Image: GetTestImage(runtime).ID,
 			Cmd:   []string{"/bin/rm", "/etc/passwd"},
@@ -479,7 +479,7 @@ func TestGetContainersTop(t *testing.T) {
 
 	srv := &Server{runtime: runtime}
 
-	container, err := runtime.Create(
+	container, _, err := runtime.Create(
 		&Config{
 			Image:     GetTestImage(runtime).ID,
 			Cmd:       []string{"/bin/sh", "-c", "cat"},
@@ -561,7 +561,7 @@ func TestGetContainersByName(t *testing.T) {
 	srv := &Server{runtime: runtime}
 
 	// Create a container and remove a file
-	container, err := runtime.Create(
+	container, _, err := runtime.Create(
 		&Config{
 			Image: GetTestImage(runtime).ID,
 			Cmd:   []string{"echo", "test"},
@@ -592,7 +592,7 @@ func TestPostCommit(t *testing.T) {
 	srv := &Server{runtime: runtime}
 
 	// Create a container and remove a file
-	container, err := runtime.Create(
+	container, _, err := runtime.Create(
 		&Config{
 			Image: GetTestImage(runtime).ID,
 			Cmd:   []string{"touch", "/test"},
@@ -686,7 +686,7 @@ func TestPostContainersKill(t *testing.T) {
 
 	srv := &Server{runtime: runtime}
 
-	container, err := runtime.Create(
+	container, _, err := runtime.Create(
 		&Config{
 			Image:     GetTestImage(runtime).ID,
 			Cmd:       []string{"/bin/cat"},
@@ -728,7 +728,7 @@ func TestPostContainersRestart(t *testing.T) {
 
 	srv := &Server{runtime: runtime}
 
-	container, err := runtime.Create(
+	container, _, err := runtime.Create(
 		&Config{
 			Image:     GetTestImage(runtime).ID,
 			Cmd:       []string{"/bin/top"},
@@ -782,7 +782,7 @@ func TestPostContainersStart(t *testing.T) {
 
 	srv := &Server{runtime: runtime}
 
-	container, err := runtime.Create(
+	container, _, err := runtime.Create(
 		&Config{
 			Image:     GetTestImage(runtime).ID,
 			Cmd:       []string{"/bin/cat"},
@@ -834,7 +834,7 @@ func TestPostContainersStop(t *testing.T) {
 
 	srv := &Server{runtime: runtime}
 
-	container, err := runtime.Create(
+	container, _, err := runtime.Create(
 		&Config{
 			Image:     GetTestImage(runtime).ID,
 			Cmd:       []string{"/bin/top"},
@@ -881,7 +881,7 @@ func TestPostContainersWait(t *testing.T) {
 
 	srv := &Server{runtime: runtime}
 
-	container, err := runtime.Create(
+	container, _, err := runtime.Create(
 		&Config{
 			Image:     GetTestImage(runtime).ID,
 			Cmd:       []string{"/bin/sleep", "1"},
@@ -923,7 +923,7 @@ func TestPostContainersAttach(t *testing.T) {
 
 	srv := &Server{runtime: runtime}
 
-	container, err := runtime.Create(
+	container, _, err := runtime.Create(
 		&Config{
 			Image:     GetTestImage(runtime).ID,
 			Cmd:       []string{"/bin/cat"},
@@ -1012,7 +1012,7 @@ func TestPostContainersAttachStderr(t *testing.T) {
 
 	srv := &Server{runtime: runtime}
 
-	container, err := runtime.Create(
+	container, _, err := runtime.Create(
 		&Config{
 			Image:     GetTestImage(runtime).ID,
 			Cmd:       []string{"/bin/sh", "-c", "/bin/cat >&2"},
@@ -1104,7 +1104,7 @@ func TestDeleteContainers(t *testing.T) {
 
 	srv := &Server{runtime: runtime}
 
-	container, err := runtime.Create(&Config{
+	container, _, err := runtime.Create(&Config{
 		Image: GetTestImage(runtime).ID,
 		Cmd:   []string{"touch", "/test"},
 	})
@@ -1142,7 +1142,8 @@ func TestOptionsRoute(t *testing.T) {
 	runtime := mkRuntime(t)
 	defer nuke(runtime)
 
-	srv := &Server{runtime: runtime, enableCors: true}
+	runtime.config.EnableCors = true
+	srv := &Server{runtime: runtime}
 
 	r := httptest.NewRecorder()
 	router, err := createRouter(srv, false)
@@ -1165,7 +1166,8 @@ func TestGetEnabledCors(t *testing.T) {
 	runtime := mkRuntime(t)
 	defer nuke(runtime)
 
-	srv := &Server{runtime: runtime, enableCors: true}
+	runtime.config.EnableCors = true
+	srv := &Server{runtime: runtime}
 
 	r := httptest.NewRecorder()
 
@@ -1292,7 +1294,7 @@ func TestPostContainersCopy(t *testing.T) {
 	srv := &Server{runtime: runtime}
 
 	// Create a container and remove a file
-	container, err := runtime.Create(
+	container, _, err := runtime.Create(
 		&Config{
 			Image: GetTestImage(runtime).ID,
 			Cmd:   []string{"touch", "/test.txt"},

+ 6 - 3
buildfile.go

@@ -187,6 +187,9 @@ func (b *buildFile) CmdCmd(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, " ")
 	b.config.PortSpecs = append(ports, b.config.PortSpecs...)
 	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
 	// Create the container and start it
-	container, err := b.runtime.Create(b.config)
+	container, _, err := b.runtime.Create(b.config)
 	if err != nil {
 		return err
 	}
@@ -367,7 +370,7 @@ func (b *buildFile) run() (string, error) {
 	b.config.Image = b.image
 
 	// Create the container and start it
-	c, err := b.runtime.Create(b.config)
+	c, _, err := b.runtime.Create(b.config)
 	if err != nil {
 		return "", err
 	}
@@ -430,7 +433,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 {
 			return err
 		}

+ 94 - 14
commands.go

@@ -92,6 +92,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
 		{"kill", "Kill a running container"},
 		{"login", "Register or Login to the docker registry server"},
 		{"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"},
 		{"ps", "List containers"},
 		{"pull", "Pull an image or a repository from the docker registry server"},
@@ -504,7 +505,8 @@ func (cli *DockerCli) CmdStop(args ...string) error {
 	v.Set("t", strconv.Itoa(*nSeconds))
 
 	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 {
 			fmt.Fprintf(cli.err, "%s\n", err)
 		} else {
@@ -529,7 +531,8 @@ func (cli *DockerCli) CmdRestart(args ...string) error {
 	v.Set("t", strconv.Itoa(*nSeconds))
 
 	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 {
 			fmt.Fprintf(cli.err, "%s\n", err)
 		} else {
@@ -605,7 +608,8 @@ func (cli *DockerCli) CmdStart(args ...string) error {
 
 	var encounteredError error
 	for _, name := range cmd.Args() {
-		_, _, err := cli.call("POST", "/containers/"+name+"/start", nil)
+		encName := cleanName(name)
+		_, _, err := cli.call("POST", "/containers/"+encName+"/start", nil)
 		if err != nil {
 			if !*attach || !*openStdin {
 				fmt.Fprintf(cli.err, "%s\n", err)
@@ -811,6 +815,8 @@ func (cli *DockerCli) CmdHistory(args ...string) error {
 func (cli *DockerCli) CmdRm(args ...string) error {
 	cmd := Subcmd("rm", "[OPTIONS] CONTAINER [CONTAINER...]", "Remove one or more containers")
 	v := cmd.Bool("v", false, "Remove the volumes associated to the container")
+	link := cmd.Bool("link", false, "Remove the specified link and not the underlying container")
+
 	if err := cmd.Parse(args); err != nil {
 		return nil
 	}
@@ -822,8 +828,12 @@ func (cli *DockerCli) CmdRm(args ...string) error {
 	if *v {
 		val.Set("v", "1")
 	}
+	if *link {
+		val.Set("link", "1")
+	}
 	for _, name := range cmd.Args() {
-		_, _, err := cli.call("DELETE", "/containers/"+name+"?"+val.Encode(), nil)
+		encName := cleanName(name)
+		_, _, err := cli.call("DELETE", "/containers/"+encName+"?"+val.Encode(), nil)
 		if err != nil {
 			fmt.Fprintf(cli.err, "%s\n", err)
 		} else {
@@ -845,7 +855,8 @@ func (cli *DockerCli) CmdKill(args ...string) error {
 	}
 
 	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 {
 			fmt.Fprintf(cli.err, "%s\n", err)
 		} else {
@@ -1088,10 +1099,10 @@ func (cli *DockerCli) CmdImages(args ...string) error {
 func displayablePorts(ports []APIPort) string {
 	result := []string{}
 	for _, port := range ports {
-		if port.Type == "tcp" {
-			result = append(result, fmt.Sprintf("%d->%d", port.PublicPort, port.PrivatePort))
+		if port.IP == "" {
+			result = append(result, fmt.Sprintf("%d/%s", port.PublicPort, port.Type))
 		} else {
-			result = append(result, fmt.Sprintf("%d->%d/%s", port.PublicPort, port.PrivatePort, port.Type))
+			result = append(result, fmt.Sprintf("%s:%d->%d/%s", port.IP, port.PublicPort, port.PrivatePort, port.Type))
 		}
 	}
 	sort.Strings(result)
@@ -1144,7 +1155,7 @@ func (cli *DockerCli) CmdPs(args ...string) error {
 	}
 	w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
 	if !*quiet {
-		fmt.Fprint(w, "ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS")
+		fmt.Fprint(w, "ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS\tNAMES")
 		if *size {
 			fmt.Fprintln(w, "\tSIZE")
 		} else {
@@ -1153,11 +1164,16 @@ func (cli *DockerCli) CmdPs(args ...string) error {
 	}
 
 	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 *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 {
-				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 out.SizeRootFs > 0 {
@@ -1183,6 +1199,64 @@ func (cli *DockerCli) CmdPs(args ...string) error {
 	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 {
 	cmd := Subcmd("commit", "[OPTIONS] CONTAINER [REPOSITORY [TAG]]", "Create a new image from a container's changes")
 	flComment := cmd.String("m", "", "Commit message")
@@ -1300,8 +1374,9 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
 		cmd.Usage()
 		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, nil); err != nil {
+	if err := cli.hijack("POST", "/containers/"+name+"/attach?logs=1&stdout=1&stderr=1", false, nil, cli.out, cli.err, nil); err != nil {
 		return err
 	}
 	return nil
@@ -1318,8 +1393,9 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
 		cmd.Usage()
 		return nil
 	}
-
-	body, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil)
+	name := cmd.Arg(0)
+	name = cleanName(name)
+	body, _, err := cli.call("GET", "/containers/"+name+"/json", nil)
 	if err != nil {
 		return err
 	}
@@ -2028,6 +2104,10 @@ func getExitCode(cli *DockerCli, containerId string) (bool, int, error) {
 	return c.State.Running, 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 {
 	var (
 		isTerminal = false

+ 17 - 0
config.go

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

+ 184 - 45
container.go

@@ -59,6 +59,8 @@ type Container struct {
 	// Store rw/ro in a separate structure to preserve reverse-compatibility on-disk.
 	// Easier than migrating older container configs :)
 	VolumesRW map[string]bool
+
+	activeLinks map[string]*Link
 }
 
 type Config struct {
@@ -71,7 +73,8 @@ type Config struct {
 	AttachStdin     bool
 	AttachStdout    bool
 	AttachStderr    bool
-	PortSpecs       []string
+	PortSpecs       []string // Deprecated - Can be in the format of 8080/tcp
+	ExposedPorts    map[Port]struct{}
 	Tty             bool // Attach standard streams to a tty, including stdin if it is not closed.
 	OpenStdin       bool // Open stdin
 	StdinOnce       bool // If true, close stdin after the 1 attached client disconnects.
@@ -91,6 +94,8 @@ type HostConfig struct {
 	Binds           []string
 	ContainerIDFile string
 	LxcConf         []KeyValuePair
+	PortBindings    map[Port][]PortBinding
+	Links           []string
 }
 
 type BindMap struct {
@@ -113,6 +118,34 @@ type KeyValuePair struct {
 	Value string
 }
 
+type PortBinding struct {
+	HostIp   string
+	HostPort string
+}
+
+// 80/tcp
+type Port string
+
+func (p Port) Proto() string {
+	return strings.Split(string(p), "/")[1]
+}
+
+func (p Port) Port() string {
+	return strings.Split(string(p), "/")[0]
+}
+
+func (p Port) Int() int {
+	i, err := parsePort(p.Port())
+	if err != nil {
+		panic(err)
+	}
+	return i
+}
+
+func NewPort(proto, port string) Port {
+	return Port(fmt.Sprintf("%s/%s", port, proto))
+}
+
 func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, *flag.FlagSet, error) {
 	cmd := Subcmd("run", "[OPTIONS] IMAGE [COMMAND] [ARG...]", "Run a command in a new container")
 	if os.Getenv("TEST") != "" {
@@ -142,8 +175,11 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
 
 	flCpuShares := cmd.Int64("c", 0, "CPU shares (relative weight)")
 
-	var flPorts ListOpts
-	cmd.Var(&flPorts, "p", "Expose a container's port to the host (use 'docker port' to see the actual mapping)")
+	var flPublish ListOpts
+	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
 	cmd.Var(&flEnv, "e", "Set environment variables")
@@ -162,6 +198,9 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
 	var flLxcOpts ListOpts
 	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 {
 		return nil, nil, cmd, err
 	}
@@ -230,10 +269,28 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
 		hostname = parts[0]
 		domainname = parts[1]
 	}
+
+	ports, portBindings, err := parsePortSpecs(flPublish)
+	if err != nil {
+		return nil, nil, cmd, err
+	}
+
+	// Merge in exposed ports to the map of published ports
+	for _, e := range flExpose {
+		if strings.Contains(e, ":") {
+			return nil, nil, cmd, fmt.Errorf("Invalid port format for -expose: %s", e)
+		}
+		p := NewPort(splitProtoPort(e))
+		if _, exists := ports[p]; !exists {
+			ports[p] = struct{}{}
+		}
+	}
+
 	config := &Config{
-		Hostname:        hostname,
+		Hostname:        *flHostname,
 		Domainname:      domainname,
-		PortSpecs:       flPorts,
+		PortSpecs:       nil, // Deprecated
+		ExposedPorts:    ports,
 		User:            *flUser,
 		Tty:             *flTty,
 		NetworkDisabled: !*flNetwork,
@@ -253,10 +310,13 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
 		Privileged:      *flPrivileged,
 		WorkingDir:      *flWorkingDir,
 	}
+
 	hostConfig := &HostConfig{
 		Binds:           binds,
 		ContainerIDFile: *flContainerIDFile,
 		LxcConf:         lxcConf,
+		PortBindings:    portBindings,
+		Links:           flLinks,
 	}
 
 	if capabilities != nil && *flMemory > 0 && !capabilities.SwapLimit {
@@ -271,36 +331,38 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
 	return config, hostConfig, cmd, nil
 }
 
-type PortMapping map[string]string
+type PortMapping map[string]string // Deprecated
 
 type NetworkSettings struct {
 	IPAddress   string
 	IPPrefixLen int
 	Gateway     string
 	Bridge      string
-	PortMapping map[string]PortMapping
+	PortMapping map[string]PortMapping // Deprecated
+	Ports       map[Port][]PortBinding
 }
 
-// returns a more easy to process description of the port mapping defined in the settings
 func (settings *NetworkSettings) PortMappingAPI() []APIPort {
 	var mapping []APIPort
-	for private, public := range settings.PortMapping["Tcp"] {
-		pubint, _ := strconv.ParseInt(public, 0, 0)
-		privint, _ := strconv.ParseInt(private, 0, 0)
-		mapping = append(mapping, APIPort{
-			PrivatePort: privint,
-			PublicPort:  pubint,
-			Type:        "tcp",
-		})
-	}
-	for private, public := range settings.PortMapping["Udp"] {
-		pubint, _ := strconv.ParseInt(public, 0, 0)
-		privint, _ := strconv.ParseInt(private, 0, 0)
-		mapping = append(mapping, APIPort{
-			PrivatePort: privint,
-			PublicPort:  pubint,
-			Type:        "udp",
-		})
+	for port, bindings := range settings.Ports {
+		p, _ := parsePort(port.Port())
+		if len(bindings) == 0 {
+			mapping = append(mapping, APIPort{
+				PublicPort: int64(p),
+				Type:       port.Proto(),
+			})
+			continue
+		}
+		for _, binding := range bindings {
+			p, _ := parsePort(port.Port())
+			h, _ := parsePort(binding.HostPort)
+			mapping = append(mapping, APIPort{
+				PrivatePort: int64(p),
+				PublicPort:  int64(h),
+				Type:        port.Proto(),
+				IP:          binding.HostIp,
+			})
+		}
 	}
 	return mapping
 }
@@ -602,7 +664,7 @@ func (container *Container) Start(hostConfig *HostConfig) (err error) {
 	if container.runtime.networkManager.disabled {
 		container.Config.NetworkDisabled = true
 	} else {
-		if err := container.allocateNetwork(); err != nil {
+		if err := container.allocateNetwork(hostConfig); err != nil {
 			return err
 		}
 	}
@@ -792,6 +854,46 @@ func (container *Container) Start(hostConfig *HostConfig) (err error) {
 		"-e", "container=lxc",
 		"-e", "HOSTNAME="+container.Config.Hostname,
 	)
+
+	// Init any links between the parent and children
+	runtime := container.runtime
+
+	children, err := runtime.Children(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 != "" {
 		workingDir := path.Clean(container.Config.WorkingDir)
 		utils.Debugf("[working dir] working dir is %s", workingDir)
@@ -925,7 +1027,7 @@ func (container *Container) StderrPipe() (io.ReadCloser, error) {
 	return utils.NewBufReader(reader), nil
 }
 
-func (container *Container) allocateNetwork() error {
+func (container *Container) allocateNetwork(hostConfig *HostConfig) error {
 	if container.Config.NetworkDisabled {
 		return nil
 	}
@@ -952,36 +1054,59 @@ func (container *Container) allocateNetwork() error {
 		}
 	}
 
-	var portSpecs []string
+	if container.Config.PortSpecs != nil {
+		utils.Debugf("Migrating port mappings for container: %s", strings.Join(container.Config.PortSpecs, ", "))
+		if err := migratePortMappings(container.Config); err != nil {
+			return err
+		}
+		container.Config.PortSpecs = nil
+	}
+
+	portSpecs := make(map[Port]struct{})
+	bindings := make(map[Port][]PortBinding)
+
 	if !container.State.Ghost {
-		portSpecs = container.Config.PortSpecs
-	} else {
-		for backend, frontend := range container.NetworkSettings.PortMapping["Tcp"] {
-			portSpecs = append(portSpecs, fmt.Sprintf("%s:%s/tcp", frontend, backend))
+		if container.Config.ExposedPorts != nil {
+			portSpecs = container.Config.ExposedPorts
 		}
-		for backend, frontend := range container.NetworkSettings.PortMapping["Udp"] {
-			portSpecs = append(portSpecs, fmt.Sprintf("%s:%s/udp", frontend, backend))
+		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["Tcp"] = make(PortMapping)
-	container.NetworkSettings.PortMapping["Udp"] = make(PortMapping)
-	for _, spec := range portSpecs {
-		nat, err := iface.AllocatePort(spec)
-		if err != nil {
-			iface.Release()
-			return err
+	container.NetworkSettings.PortMapping = nil
+
+	for port := range portSpecs {
+		binding := bindings[port]
+		for i := 0; i < len(binding); i++ {
+			b := binding[i]
+			nat, err := iface.AllocatePort(port, b)
+			if err != nil {
+				iface.Release()
+				return err
+			}
+			utils.Debugf("Allocate port: %s:%s->%s", nat.Binding.HostIp, port, nat.Binding.HostPort)
+			binding[i] = nat.Binding
 		}
-		proto := strings.Title(nat.Proto)
-		backend, frontend := strconv.Itoa(nat.Backend), strconv.Itoa(nat.Frontend)
-		container.NetworkSettings.PortMapping[proto][backend] = frontend
+		bindings[port] = binding
 	}
+	container.SaveHostConfig(hostConfig)
+
+	container.NetworkSettings.Ports = bindings
 	container.network = iface
+
 	container.NetworkSettings.Bridge = container.runtime.networkManager.bridgeIface
 	container.NetworkSettings.IPAddress = iface.IPNet.IP.String()
 	container.NetworkSettings.IPPrefixLen, _ = iface.IPNet.Mask.Size()
 	container.NetworkSettings.Gateway = iface.Gateway.String()
+
 	return nil
 }
 
@@ -1064,6 +1189,14 @@ func (container *Container) monitor(hostConfig *HostConfig) {
 
 func (container *Container) cleanup() {
 	container.releaseNetwork()
+
+	// Disable all active links
+	if container.activeLinks != nil {
+		for _, link := range container.activeLinks {
+			link.Disable()
+		}
+	}
+
 	if container.Config.OpenStdin {
 		if err := container.stdin.Close(); err != nil {
 			utils.Errorf("%s: Error close stdin: %s", container.ID, err)
@@ -1345,3 +1478,9 @@ func (container *Container) Copy(resource string) (Archive, error) {
 	}
 	return TarFilter(basePath, Uncompressed, filter)
 }
+
+// Returns true if the container exposes a certain port
+func (container *Container) Exposes(p Port) bool {
+	_, exists := container.Config.ExposedPorts[p]
+	return exists
+}

+ 35 - 35
container_test.go

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

+ 34 - 16
docker/docker.go

@@ -7,6 +7,7 @@ import (
 	"github.com/dotcloud/docker/utils"
 	"io/ioutil"
 	"log"
+	"net"
 	"os"
 	"os/signal"
 	"strconv"
@@ -37,7 +38,11 @@ func main() {
 	flDns := flag.String("dns", "", "Set custom dns servers")
 	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")
+	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()
+
 	if *flVersion {
 		showVersion()
 		return
@@ -54,10 +59,9 @@ func main() {
 		}
 	}
 
+	bridge := docker.DefaultNetworkBridge
 	if *bridgeName != "" {
-		docker.NetworkBridgeIface = *bridgeName
-	} else {
-		docker.NetworkBridgeIface = docker.DefaultNetworkBridge
+		bridge = *bridgeName
 	}
 	if *flDebug {
 		os.Setenv("DEBUG", "1")
@@ -69,7 +73,25 @@ func main() {
 			flag.Usage()
 			return
 		}
-		if err := daemon(*pidfile, *flGraphPath, flHosts, *flAutoRestart, *flEnableCors, *flDns); err != nil {
+		var dns []string
+		if *flDns != "" {
+			dns = []string{*flDns}
+		}
+
+		ip := net.ParseIP(*flDefaultIp)
+
+		config := &docker.DaemonConfig{
+			Pidfile:        *pidfile,
+			GraphPath:      *flGraphPath,
+			AutoRestart:    *flAutoRestart,
+			EnableCors:     *flEnableCors,
+			Dns:            dns,
+			EnableIptables: *flEnableIptables,
+			BridgeIface:    bridge,
+			ProtoAddresses: flHosts,
+			DefaultIp:      ip,
+		}
+		if err := daemon(config); err != nil {
 			log.Fatal(err)
 		}
 	} else {
@@ -117,30 +139,26 @@ func removePidFile(pidfile string) {
 	}
 }
 
-func daemon(pidfile string, flGraphPath string, protoAddrs []string, autoRestart, enableCors bool, flDns string) error {
-	if err := createPidFile(pidfile); err != nil {
+func daemon(config *docker.DaemonConfig) error {
+	if err := createPidFile(config.Pidfile); err != nil {
 		log.Fatal(err)
 	}
-	defer removePidFile(pidfile)
+	defer removePidFile(config.Pidfile)
 
 	c := make(chan os.Signal, 1)
 	signal.Notify(c, os.Interrupt, os.Kill, os.Signal(syscall.SIGTERM))
 	go func() {
 		sig := <-c
 		log.Printf("Received signal '%v', exiting\n", sig)
-		removePidFile(pidfile)
+		removePidFile(config.Pidfile)
 		os.Exit(0)
 	}()
-	var dns []string
-	if flDns != "" {
-		dns = []string{flDns}
-	}
-	server, err := docker.NewServer(flGraphPath, autoRestart, enableCors, dns)
+	server, err := docker.NewServer(config)
 	if err != nil {
 		return err
 	}
-	chErrors := make(chan error, len(protoAddrs))
-	for _, protoAddr := range protoAddrs {
+	chErrors := make(chan error, len(config.ProtoAddresses))
+	for _, protoAddr := range config.ProtoAddresses {
 		protoAddrParts := strings.SplitN(protoAddr, "://", 2)
 		if protoAddrParts[0] == "unix" {
 			syscall.Unlink(protoAddrParts[1])
@@ -155,7 +173,7 @@ func daemon(pidfile string, flGraphPath string, protoAddrs []string, autoRestart
 			chErrors <- docker.ListenAndServe(protoAddrParts[0], protoAddrParts[1], server, true)
 		}()
 	}
-	for i := 0; i < len(protoAddrs); i += 1 {
+	for i := 0; i < len(config.ProtoAddresses); i += 1 {
 		err := <-chErrors
 		if err != nil {
 			return err

+ 90 - 7
docs/sources/commandline/cli.rst

@@ -96,8 +96,8 @@ Examples:
 
 .. _cli_build_examples:
 
-Examples
-~~~~~~~~
+Examples:
+~~~~~~~~~
 
 .. code-block:: bash
 
@@ -403,6 +403,33 @@ Insert file from github
 
     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:
 
 ``login``
@@ -430,7 +457,6 @@ Insert file from github
 ``logs``
 --------
 
-
 ::
 
     Usage: docker logs [OPTIONS] CONTAINER
@@ -510,6 +536,29 @@ Insert file from github
     Usage: docker rm [OPTIONS] CONTAINER
 
     Remove one or more containers
+        -link="": Remove the link instead of the actual container
+ 
+
+Examples:
+~~~~~~~~~
+
+.. code-block:: bash
+
+    $ docker rm /redis
+    /redis
+
+
+This will remove the container referenced under the link ``/redis``.
+
+
+.. code-block:: bash
+
+    $ docker rm -link /webapp/redis
+    /webapp/redis
+
+
+This will remove the underlying link between ``/webapp`` and the ``/redis`` containers removing all
+network communication.
 
 .. _cli_rmi:
 
@@ -533,7 +582,7 @@ Insert file from github
 
     Run a command in a new container
 
-      -a=map[]: Attach to stdin, stdout or stderr.
+      -a=map[]: Attach to stdin, stdout or stderr
       -c=0: CPU shares (relative weight)
       -cidfile="": Write the container ID to the file
       -d=false: Detached mode: Run container in the background, print new container id
@@ -549,14 +598,16 @@ Insert file from github
       -u="": Username or UID
       -dns=[]: Set custom dns servers for the container
       -v=[]: Create a bind mount with: [host-dir]:[container-dir]:[rw|ro]. If "container-dir" is missing, then docker creates a new volume.
-      -volumes-from="": Mount all volumes from the given container.
-      -entrypoint="": Overwrite the default entrypoint set by the image.
+      -volumes-from="": Mount all volumes from the given container
+      -entrypoint="": Overwrite the default entrypoint set by the image
       -w="": Working directory inside the container
       -lxc-conf=[]: Add custom lxc options -lxc-conf="lxc.cgroup.cpuset.cpus = 0,1"
       -sig-proxy=false: Proxify all received signal to the process (even in non-tty mode)
+      -expose=[]: Expose a port from the container without publishing it to your host
+      -link="": Add link to another container (containerid:alias)
 
 Examples
-~~~~~~~~
+--------
 
 .. code-block:: bash
 
@@ -604,6 +655,38 @@ working directory, by changing into the directory to the value
 returned by ``pwd``. So this combination executes the command
 using the container, but inside the current working directory.
 
+.. code-block:: bash
+
+    docker run -p 127.0.0.0::80 ubuntu bash
+
+This the ``-p`` flag now allows you to bind a port to a specific
+interface of the host machine.  In this example port ``80`` of the 
+container will have a dynamically allocated port bound to 127.0.0.1 
+of the host.
+
+.. code-block:: bash
+
+    docker run -p 127.0.0.1:80:80 ubuntu bash
+
+This will bind port ``80`` of the container to port ``80`` on 127.0.0.1 of your
+host machine.
+
+.. code-block:: bash
+
+    docker run -expose 80 ubuntu bash
+
+This will expose port ``80`` of the container for use within a link
+without publishing the port to the host system's interfaces.  
+
+.. code-block:: bash
+
+    docker run -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:
 
 ``search``

+ 2 - 1
docs/sources/examples/index.rst

@@ -1,6 +1,6 @@
 :title: Docker Examples
 :description: Examples on how to use Docker
-:keywords: docker, hello world, node, nodejs, python, couch, couchdb, redis, ssh, sshd, examples, postgresql
+:keywords: docker, hello world, node, nodejs, python, couch, couchdb, redis, ssh, sshd, examples, postgresql, link
 
 
 .. _example_list:
@@ -24,3 +24,4 @@ to more substantial services like you might find in production.
    postgresql_service
    mongodb
    running_riak_service
+   linking_into_redis

+ 146 - 0
docs/sources/examples/linking_into_redis.rst

@@ -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 - 0
gograph/MAINTAINERS

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

+ 455 - 0
gograph/gograph.go

@@ -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 - 0
gograph/gograph_test.go

@@ -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 - 0
gograph/sort.go

@@ -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 - 0
gograph/sort_test.go

@@ -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 - 0
gograph/utils.go

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

+ 2 - 1
hack/make.sh

@@ -45,7 +45,8 @@ if [ -n "$(git status --porcelain)" ]; then
 fi
 
 # Use these flags when compiling the tests and final binary
-LDFLAGS="-X main.GITCOMMIT $GITCOMMIT -X main.VERSION $VERSION -d -w"
+LDFLAGS='-X main.GITCOMMIT "'$GITCOMMIT'" -X main.VERSION "'$VERSION'" -w -linkmode external -extldflags "-lpthread -static -Wl,--unresolved-symbols=ignore-in-object-files"'
+BUILDFLAGS='-tags netgo'
 
 
 bundle() {

+ 3 - 3
hack/make/binary

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

+ 1 - 1
hack/make/test

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

+ 1 - 0
iptables/MAINTAINERS

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

+ 105 - 0
iptables/iptables.go

@@ -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 - 0
iptables/iptables_test.go

@@ -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 - 0
links.go

@@ -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 - 0
links_test.go

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

+ 115 - 163
network.go

@@ -4,6 +4,8 @@ import (
 	"encoding/binary"
 	"errors"
 	"fmt"
+	"github.com/dotcloud/docker/iptables"
+	"github.com/dotcloud/docker/proxy"
 	"github.com/dotcloud/docker/utils"
 	"log"
 	"net"
@@ -13,8 +15,6 @@ import (
 	"sync"
 )
 
-var NetworkBridgeIface string
-
 const (
 	DefaultNetworkBridge = "docker0"
 	DisableNetworkBridge = "none"
@@ -81,18 +81,6 @@ func ip(args ...string) (string, error) {
 	return string(output), nil
 }
 
-// Wrapper around the iptables command
-func iptables(args ...string) error {
-	path, err := exec.LookPath("iptables")
-	if err != nil {
-		return fmt.Errorf("command not found: iptables")
-	}
-	if err := exec.Command(path, args...).Run(); err != nil {
-		return fmt.Errorf("iptables failed: iptables %v", strings.Join(args, " "))
-	}
-	return nil
-}
-
 func checkRouteOverlaps(routes string, dockerNetwork *net.IPNet) error {
 	utils.Debugf("Routes:\n\n%s", routes)
 	for _, line := range strings.Split(routes, "\n") {
@@ -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`,
 // and attempts to configure it with an address which doesn't conflict with any other interface on the host.
 // If it can't find an address which doesn't conflict, it will return an error.
-func CreateBridgeIface(ifaceName string) error {
+func CreateBridgeIface(config *DaemonConfig) error {
 	addrs := []string{
 		// Here we don't follow the convention of using the 1st IP of the range for the gateway.
 		// This is to use the same gateway IPs as the /24 ranges, which predate the /16 ranges.
@@ -163,23 +151,29 @@ func CreateBridgeIface(ifaceName string) error {
 		}
 	}
 	if ifaceAddr == "" {
-		return fmt.Errorf("Could not find a free IP address range for interface '%s'. Please configure its address manually and run 'docker -b %s'", ifaceName, ifaceName)
+		return fmt.Errorf("Could not find a free IP address range for interface '%s'. Please configure its address manually and run 'docker -b %s'", config.BridgeIface, config.BridgeIface)
 	}
-	utils.Debugf("Creating bridge %s with network %s", ifaceName, ifaceAddr)
+	utils.Debugf("Creating bridge %s with network %s", config.BridgeIface, ifaceAddr)
 
-	if output, err := ip("link", "add", ifaceName, "type", "bridge"); err != nil {
+	if output, err := ip("link", "add", config.BridgeIface, "type", "bridge"); err != nil {
 		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)
 	}
-	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)
 	}
-	if err := iptables("-t", "nat", "-A", "POSTROUTING", "-s", ifaceAddr,
-		"!", "-d", ifaceAddr, "-j", "MASQUERADE"); err != nil {
-		return fmt.Errorf("Unable to enable network bridge NAT: %s", err)
+	if config.EnableIptables {
+		if err := iptables.Raw("-t", "nat", "-A", "POSTROUTING", "-s", ifaceAddr,
+			"!", "-d", ifaceAddr, "-j", "MASQUERADE"); err != nil {
+			return fmt.Errorf("Unable to enable network bridge NAT: %s", err)
+		}
+		// 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
 }
@@ -216,58 +210,27 @@ func getIfaceAddr(name string) (net.Addr, error) {
 // It keeps track of all mappings and is able to unmap at will
 type PortMapper struct {
 	tcpMapping map[int]*net.TCPAddr
-	tcpProxies map[int]Proxy
+	tcpProxies map[int]proxy.Proxy
 	udpMapping map[int]*net.UDPAddr
-	udpProxies map[int]Proxy
-}
-
-func (mapper *PortMapper) cleanup() error {
-	// Ignore errors - This could mean the chains were never set up
-	iptables("-t", "nat", "-D", "PREROUTING", "-m", "addrtype", "--dst-type", "LOCAL", "-j", "DOCKER")
-	iptables("-t", "nat", "-D", "OUTPUT", "-m", "addrtype", "--dst-type", "LOCAL", "!", "--dst", "127.0.0.0/8", "-j", "DOCKER")
-	iptables("-t", "nat", "-D", "OUTPUT", "-m", "addrtype", "--dst-type", "LOCAL", "-j", "DOCKER") // Created in versions <= 0.1.6
-	// Also cleanup rules created by older versions, or -X might fail.
-	iptables("-t", "nat", "-D", "PREROUTING", "-j", "DOCKER")
-	iptables("-t", "nat", "-D", "OUTPUT", "-j", "DOCKER")
-	iptables("-t", "nat", "-F", "DOCKER")
-	iptables("-t", "nat", "-X", "DOCKER")
-	mapper.tcpMapping = make(map[int]*net.TCPAddr)
-	mapper.tcpProxies = make(map[int]Proxy)
-	mapper.udpMapping = make(map[int]*net.UDPAddr)
-	mapper.udpProxies = make(map[int]Proxy)
-	return nil
-}
-
-func (mapper *PortMapper) setup() error {
-	if err := iptables("-t", "nat", "-N", "DOCKER"); err != nil {
-		return fmt.Errorf("Failed to create DOCKER chain: %s", err)
-	}
-	if err := iptables("-t", "nat", "-A", "PREROUTING", "-m", "addrtype", "--dst-type", "LOCAL", "-j", "DOCKER"); err != nil {
-		return fmt.Errorf("Failed to inject docker in PREROUTING chain: %s", err)
-	}
-	if err := iptables("-t", "nat", "-A", "OUTPUT", "-m", "addrtype", "--dst-type", "LOCAL", "!", "--dst", "127.0.0.0/8", "-j", "DOCKER"); err != nil {
-		return fmt.Errorf("Failed to inject docker in OUTPUT chain: %s", err)
-	}
-	return nil
-}
+	udpProxies map[int]proxy.Proxy
 
-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)))
+	iptables  *iptables.Chain
+	defaultIp net.IP
 }
 
-func (mapper *PortMapper) Map(port int, backendAddr net.Addr) error {
+func (mapper *PortMapper) Map(ip net.IP, port int, backendAddr net.Addr) error {
 	if _, isTCP := backendAddr.(*net.TCPAddr); isTCP {
 		backendPort := backendAddr.(*net.TCPAddr).Port
 		backendIP := backendAddr.(*net.TCPAddr).IP
-		if err := mapper.iptablesForward("-A", port, "tcp", backendIP.String(), backendPort); err != nil {
-			return err
+		if mapper.iptables != nil {
+			if err := mapper.iptables.Forward(iptables.Add, ip, port, "tcp", backendIP.String(), backendPort); err != nil {
+				return err
+			}
 		}
 		mapper.tcpMapping[port] = backendAddr.(*net.TCPAddr)
-		proxy, err := NewProxy(&net.TCPAddr{IP: net.IPv4(0, 0, 0, 0), Port: port}, backendAddr)
+		proxy, err := proxy.NewProxy(&net.TCPAddr{IP: ip, Port: port}, backendAddr)
 		if err != nil {
-			mapper.Unmap(port, "tcp")
+			mapper.Unmap(ip, port, "tcp")
 			return err
 		}
 		mapper.tcpProxies[port] = proxy
@@ -275,13 +238,15 @@ func (mapper *PortMapper) Map(port int, backendAddr net.Addr) error {
 	} else {
 		backendPort := backendAddr.(*net.UDPAddr).Port
 		backendIP := backendAddr.(*net.UDPAddr).IP
-		if err := mapper.iptablesForward("-A", port, "udp", backendIP.String(), backendPort); err != nil {
-			return err
+		if mapper.iptables != nil {
+			if err := mapper.iptables.Forward(iptables.Add, ip, port, "udp", backendIP.String(), backendPort); err != nil {
+				return err
+			}
 		}
 		mapper.udpMapping[port] = backendAddr.(*net.UDPAddr)
-		proxy, err := NewProxy(&net.UDPAddr{IP: net.IPv4(0, 0, 0, 0), Port: port}, backendAddr)
+		proxy, err := proxy.NewProxy(&net.UDPAddr{IP: ip, Port: port}, backendAddr)
 		if err != nil {
-			mapper.Unmap(port, "udp")
+			mapper.Unmap(ip, port, "udp")
 			return err
 		}
 		mapper.udpProxies[port] = proxy
@@ -290,7 +255,7 @@ func (mapper *PortMapper) Map(port int, backendAddr net.Addr) error {
 	return nil
 }
 
-func (mapper *PortMapper) Unmap(port int, proto string) error {
+func (mapper *PortMapper) Unmap(ip net.IP, port int, proto string) error {
 	if proto == "tcp" {
 		backendAddr, ok := mapper.tcpMapping[port]
 		if !ok {
@@ -300,8 +265,10 @@ func (mapper *PortMapper) Unmap(port int, proto string) error {
 			proxy.Close()
 			delete(mapper.tcpProxies, port)
 		}
-		if err := mapper.iptablesForward("-D", port, proto, backendAddr.IP.String(), backendAddr.Port); err != nil {
-			return err
+		if mapper.iptables != nil {
+			if err := mapper.iptables.Forward(iptables.Delete, ip, port, proto, backendAddr.IP.String(), backendAddr.Port); err != nil {
+				return err
+			}
 		}
 		delete(mapper.tcpMapping, port)
 	} else {
@@ -313,21 +280,37 @@ func (mapper *PortMapper) Unmap(port int, proto string) error {
 			proxy.Close()
 			delete(mapper.udpProxies, port)
 		}
-		if err := mapper.iptablesForward("-D", port, proto, backendAddr.IP.String(), backendAddr.Port); err != nil {
-			return err
+		if mapper.iptables != nil {
+			if err := mapper.iptables.Forward(iptables.Delete, ip, port, proto, backendAddr.IP.String(), backendAddr.Port); err != nil {
+				return err
+			}
 		}
 		delete(mapper.udpMapping, port)
 	}
 	return nil
 }
 
-func newPortMapper() (*PortMapper, error) {
-	mapper := &PortMapper{}
-	if err := mapper.cleanup(); err != nil {
+func newPortMapper(config *DaemonConfig) (*PortMapper, error) {
+	// We can always try removing the iptables
+	if err := iptables.RemoveExistingChain("DOCKER"); err != nil {
 		return nil, err
 	}
-	if err := mapper.setup(); err != nil {
-		return nil, err
+	var chain *iptables.Chain
+	if config.EnableIptables {
+		var err error
+		chain, err = iptables.NewChain("DOCKER", config.BridgeIface)
+		if err != nil {
+			return nil, fmt.Errorf("Failed to create DOCKER chain: %s", err)
+		}
+	}
+
+	mapper := &PortMapper{
+		tcpMapping: make(map[int]*net.TCPAddr),
+		tcpProxies: make(map[int]proxy.Proxy),
+		udpMapping: make(map[int]*net.UDPAddr),
+		udpProxies: make(map[int]proxy.Proxy),
+		iptables:   chain,
+		defaultIp:  config.DefaultIp,
 	}
 	return mapper, nil
 }
@@ -519,40 +502,56 @@ type NetworkInterface struct {
 	disabled bool
 }
 
-// Allocate an external TCP port and map it to the interface
-func (iface *NetworkInterface) AllocatePort(spec string) (*Nat, error) {
+// Allocate an external port and map it to the interface
+func (iface *NetworkInterface) AllocatePort(port Port, binding PortBinding) (*Nat, error) {
 
 	if iface.disabled {
 		return nil, fmt.Errorf("Trying to allocate port for interface %v, which is disabled", iface) // FIXME
 	}
 
-	nat, err := parseNat(spec)
+	ip := iface.manager.portMapper.defaultIp
+
+	if binding.HostIp != "" {
+		ip = net.ParseIP(binding.HostIp)
+	} else {
+		binding.HostIp = ip.String()
+	}
+
+	nat := &Nat{
+		Port:    port,
+		Binding: binding,
+	}
+
+	containerPort, err := parsePort(port.Port())
 	if err != nil {
 		return nil, err
 	}
 
-	if nat.Proto == "tcp" {
-		extPort, err := iface.manager.tcpPortAllocator.Acquire(nat.Frontend)
+	hostPort, _ := parsePort(nat.Binding.HostPort)
+
+	if nat.Port.Proto() == "tcp" {
+		extPort, err := iface.manager.tcpPortAllocator.Acquire(hostPort)
 		if err != nil {
 			return nil, err
 		}
-		backend := &net.TCPAddr{IP: iface.IPNet.IP, Port: nat.Backend}
-		if err := iface.manager.portMapper.Map(extPort, backend); err != nil {
+
+		backend := &net.TCPAddr{IP: iface.IPNet.IP, Port: containerPort}
+		if err := iface.manager.portMapper.Map(ip, extPort, backend); err != nil {
 			iface.manager.tcpPortAllocator.Release(extPort)
 			return nil, err
 		}
-		nat.Frontend = extPort
+		nat.Binding.HostPort = strconv.Itoa(extPort)
 	} else {
-		extPort, err := iface.manager.udpPortAllocator.Acquire(nat.Frontend)
+		extPort, err := iface.manager.udpPortAllocator.Acquire(hostPort)
 		if err != nil {
 			return nil, err
 		}
-		backend := &net.UDPAddr{IP: iface.IPNet.IP, Port: nat.Backend}
-		if err := iface.manager.portMapper.Map(extPort, backend); err != nil {
+		backend := &net.UDPAddr{IP: iface.IPNet.IP, Port: containerPort}
+		if err := iface.manager.portMapper.Map(ip, extPort, backend); err != nil {
 			iface.manager.udpPortAllocator.Release(extPort)
 			return nil, err
 		}
-		nat.Frontend = extPort
+		nat.Binding.HostPort = strconv.Itoa(extPort)
 	}
 	iface.extPorts = append(iface.extPorts, nat)
 
@@ -560,83 +559,37 @@ func (iface *NetworkInterface) AllocatePort(spec string) (*Nat, error) {
 }
 
 type Nat struct {
-	Proto    string
-	Frontend int
-	Backend  int
+	Port    Port
+	Binding PortBinding
 }
 
-func parseNat(spec string) (*Nat, error) {
-	var nat Nat
-
-	if strings.Contains(spec, "/") {
-		specParts := strings.Split(spec, "/")
-		if len(specParts) != 2 {
-			return nil, fmt.Errorf("Invalid port format.")
-		}
-		proto := specParts[1]
-		spec = specParts[0]
-		if proto != "tcp" && proto != "udp" {
-			return nil, fmt.Errorf("Invalid port format: unknown protocol %v.", proto)
-		}
-		nat.Proto = proto
-	} else {
-		nat.Proto = "tcp"
-	}
-
-	if strings.Contains(spec, ":") {
-		specParts := strings.Split(spec, ":")
-		if len(specParts) != 2 {
-			return nil, fmt.Errorf("Invalid port format.")
-		}
-		// If spec starts with ':', external and internal ports must be the same.
-		// This might fail if the requested external port is not available.
-		var sameFrontend bool
-		if len(specParts[0]) == 0 {
-			sameFrontend = true
-		} else {
-			front, err := strconv.ParseUint(specParts[0], 10, 16)
-			if err != nil {
-				return nil, err
-			}
-			nat.Frontend = int(front)
-		}
-		back, err := strconv.ParseUint(specParts[1], 10, 16)
-		if err != nil {
-			return nil, err
-		}
-		nat.Backend = int(back)
-		if sameFrontend {
-			nat.Frontend = nat.Backend
-		}
-	} else {
-		port, err := strconv.ParseUint(spec, 10, 16)
-		if err != nil {
-			return nil, err
-		}
-		nat.Backend = int(port)
-	}
-
-	return &nat, nil
+func (n *Nat) String() string {
+	return fmt.Sprintf("%s:%d:%d/%s", n.Binding.HostIp, n.Binding.HostPort, n.Port.Port(), n.Port.Proto())
 }
 
 // Release: Network cleanup - release all resources
 func (iface *NetworkInterface) Release() {
-
 	if iface.disabled {
 		return
 	}
 
 	for _, nat := range iface.extPorts {
-		utils.Debugf("Unmaping %v/%v", nat.Proto, nat.Frontend)
-		if err := iface.manager.portMapper.Unmap(nat.Frontend, nat.Proto); err != nil {
-			log.Printf("Unable to unmap port %v/%v: %v", nat.Proto, nat.Frontend, err)
+		hostPort, err := parsePort(nat.Binding.HostPort)
+		if err != nil {
+			log.Printf("Unable to get host port: %s", err)
+			continue
+		}
+		ip := net.ParseIP(nat.Binding.HostIp)
+		utils.Debugf("Unmaping %s/%s", nat.Port.Proto, nat.Binding.HostPort)
+		if err := iface.manager.portMapper.Unmap(ip, hostPort, nat.Port.Proto()); err != nil {
+			log.Printf("Unable to unmap port %s: %s", nat, err)
 		}
-		if nat.Proto == "tcp" {
-			if err := iface.manager.tcpPortAllocator.Release(nat.Frontend); err != nil {
-				log.Printf("Unable to release port tcp/%v: %v", nat.Frontend, err)
+		if nat.Port.Proto() == "tcp" {
+			if err := iface.manager.tcpPortAllocator.Release(hostPort); err != nil {
+				log.Printf("Unable to release port %s", nat)
 			}
-		} else if err := iface.manager.udpPortAllocator.Release(nat.Frontend); err != nil {
-			log.Printf("Unable to release port udp/%v: %v", nat.Frontend, err)
+		} else if err := iface.manager.udpPortAllocator.Release(hostPort); err != nil {
+			log.Printf("Unable to release port %s: %s", nat, err)
 		}
 	}
 
@@ -704,22 +657,21 @@ func (manager *NetworkManager) Close() error {
 	return err3
 }
 
-func newNetworkManager(bridgeIface string) (*NetworkManager, error) {
-
-	if bridgeIface == DisableNetworkBridge {
+func newNetworkManager(config *DaemonConfig) (*NetworkManager, error) {
+	if config.BridgeIface == DisableNetworkBridge {
 		manager := &NetworkManager{
 			disabled: true,
 		}
 		return manager, nil
 	}
 
-	addr, err := getIfaceAddr(bridgeIface)
+	addr, err := getIfaceAddr(config.BridgeIface)
 	if err != nil {
 		// If the iface is not found, try to create it
-		if err := CreateBridgeIface(bridgeIface); err != nil {
+		if err := CreateBridgeIface(config); err != nil {
 			return nil, err
 		}
-		addr, err = getIfaceAddr(bridgeIface)
+		addr, err = getIfaceAddr(config.BridgeIface)
 		if err != nil {
 			return nil, err
 		}
@@ -737,13 +689,13 @@ func newNetworkManager(bridgeIface string) (*NetworkManager, error) {
 		return nil, err
 	}
 
-	portMapper, err := newPortMapper()
+	portMapper, err := newPortMapper(config)
 	if err != nil {
 		return nil, err
 	}
 
 	manager := &NetworkManager{
-		bridgeIface:      bridgeIface,
+		bridgeIface:      config.BridgeIface,
 		bridgeNetwork:    network,
 		ipAllocator:      ipAllocator,
 		tcpPortAllocator: tcpPortAllocator,

+ 0 - 108
network_test.go

@@ -2,117 +2,9 @@ package docker
 
 import (
 	"net"
-	"os"
 	"testing"
 )
 
-func TestIptables(t *testing.T) {
-	if err := iptables("-L"); err != nil {
-		t.Fatal(err)
-	}
-	path := os.Getenv("PATH")
-	os.Setenv("PATH", "")
-	defer os.Setenv("PATH", path)
-	if err := iptables("-L"); err == nil {
-		t.Fatal("Not finding iptables in the PATH should cause an error")
-	}
-}
-
-func TestParseNat(t *testing.T) {
-	if nat, err := parseNat("4500"); err == nil {
-		if nat.Frontend != 0 || nat.Backend != 4500 || nat.Proto != "tcp" {
-			t.Errorf("-p 4500 should produce 0->4500/tcp, got %d->%d/%s",
-				nat.Frontend, nat.Backend, nat.Proto)
-		}
-	} else {
-		t.Fatal(err)
-	}
-
-	if nat, err := parseNat(":4501"); err == nil {
-		if nat.Frontend != 4501 || nat.Backend != 4501 || nat.Proto != "tcp" {
-			t.Errorf("-p :4501 should produce 4501->4501/tcp, got %d->%d/%s",
-				nat.Frontend, nat.Backend, nat.Proto)
-		}
-	} else {
-		t.Fatal(err)
-	}
-
-	if nat, err := parseNat("4502:4503"); err == nil {
-		if nat.Frontend != 4502 || nat.Backend != 4503 || nat.Proto != "tcp" {
-			t.Errorf("-p 4502:4503 should produce 4502->4503/tcp, got %d->%d/%s",
-				nat.Frontend, nat.Backend, nat.Proto)
-		}
-	} else {
-		t.Fatal(err)
-	}
-
-	if nat, err := parseNat("4502:4503/tcp"); err == nil {
-		if nat.Frontend != 4502 || nat.Backend != 4503 || nat.Proto != "tcp" {
-			t.Errorf("-p 4502:4503/tcp should produce 4502->4503/tcp, got %d->%d/%s",
-				nat.Frontend, nat.Backend, nat.Proto)
-		}
-	} else {
-		t.Fatal(err)
-	}
-
-	if nat, err := parseNat("4502:4503/udp"); err == nil {
-		if nat.Frontend != 4502 || nat.Backend != 4503 || nat.Proto != "udp" {
-			t.Errorf("-p 4502:4503/udp should produce 4502->4503/udp, got %d->%d/%s",
-				nat.Frontend, nat.Backend, nat.Proto)
-		}
-	} else {
-		t.Fatal(err)
-	}
-
-	if nat, err := parseNat(":4503/udp"); err == nil {
-		if nat.Frontend != 4503 || nat.Backend != 4503 || nat.Proto != "udp" {
-			t.Errorf("-p :4503/udp should produce 4503->4503/udp, got %d->%d/%s",
-				nat.Frontend, nat.Backend, nat.Proto)
-		}
-	} else {
-		t.Fatal(err)
-	}
-
-	if nat, err := parseNat(":4503/tcp"); err == nil {
-		if nat.Frontend != 4503 || nat.Backend != 4503 || nat.Proto != "tcp" {
-			t.Errorf("-p :4503/tcp should produce 4503->4503/tcp, got %d->%d/%s",
-				nat.Frontend, nat.Backend, nat.Proto)
-		}
-	} else {
-		t.Fatal(err)
-	}
-
-	if nat, err := parseNat("4503/tcp"); err == nil {
-		if nat.Frontend != 0 || nat.Backend != 4503 || nat.Proto != "tcp" {
-			t.Errorf("-p 4503/tcp should produce 0->4503/tcp, got %d->%d/%s",
-				nat.Frontend, nat.Backend, nat.Proto)
-		}
-	} else {
-		t.Fatal(err)
-	}
-
-	if nat, err := parseNat("4503/udp"); err == nil {
-		if nat.Frontend != 0 || nat.Backend != 4503 || nat.Proto != "udp" {
-			t.Errorf("-p 4503/udp should produce 0->4503/udp, got %d->%d/%s",
-				nat.Frontend, nat.Backend, nat.Proto)
-		}
-	} else {
-		t.Fatal(err)
-	}
-
-	if _, err := parseNat("4503/tcpgarbage"); err == nil {
-		t.Fatal(err)
-	}
-
-	if _, err := parseNat("4503/tcp/udp"); err == nil {
-		t.Fatal(err)
-	}
-
-	if _, err := parseNat("4503/"); err == nil {
-		t.Fatal(err)
-	}
-}
-
 func TestPortAllocation(t *testing.T) {
 	allocator, err := newPortAllocator()
 	if err != nil {

+ 1 - 0
proxy/MAINTAINERS

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

+ 1 - 1
network_proxy_test.go → proxy/network_proxy_test.go

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

+ 29 - 0
proxy/proxy.go

@@ -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 - 0
proxy/tcp_proxy.go

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

+ 1 - 115
network_proxy.go → proxy/udp_proxy.go

@@ -1,10 +1,8 @@
-package docker
+package proxy
 
 import (
 	"encoding/binary"
-	"fmt"
 	"github.com/dotcloud/docker/utils"
-	"io"
 	"log"
 	"net"
 	"sync"
@@ -17,107 +15,6 @@ const (
 	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
 // in a map:
 type connTrackKey struct {
@@ -253,14 +150,3 @@ func (proxy *UDPProxy) Close() {
 
 func (proxy *UDPProxy) FrontendAddr() net.Addr { return proxy.frontendAddr }
 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"))
-	}
-}

+ 167 - 43
runtime.go

@@ -3,6 +3,7 @@ package docker
 import (
 	"container/list"
 	"fmt"
+	"github.com/dotcloud/docker/gograph"
 	"github.com/dotcloud/docker/utils"
 	"io"
 	"io/ioutil"
@@ -24,7 +25,6 @@ type Capabilities struct {
 }
 
 type Runtime struct {
-	root           string
 	repository     string
 	containers     *list.List
 	networkManager *NetworkManager
@@ -32,10 +32,10 @@ type Runtime struct {
 	repositories   *TagStore
 	idIndex        *utils.TruncIndex
 	capabilities   *Capabilities
-	autoRestart    bool
 	volumes        *Graph
 	srv            *Server
-	Dns            []string
+	config         *DaemonConfig
+	containerGraph *gograph.Database
 }
 
 var sysInitPath string
@@ -66,10 +66,15 @@ func (runtime *Runtime) getContainerElement(id string) *list.Element {
 // 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.
 func (runtime *Runtime) Get(name string) *Container {
+	if c, _ := runtime.GetByName(name); c != nil {
+		return c
+	}
+
 	id, err := runtime.idIndex.Get(name)
 	if err != nil {
 		return nil
 	}
+
 	e := runtime.getContainerElement(id)
 	if e == nil {
 		return nil
@@ -87,10 +92,9 @@ func (runtime *Runtime) containerRoot(id string) string {
 	return path.Join(runtime.repository, id)
 }
 
-// Load reads the contents of a container from disk and registers
-// it with Register.
+// Load reads the contents of a container from disk
 // 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)}
 	if err := container.FromDisk(); err != nil {
 		return nil, err
@@ -101,9 +105,6 @@ func (runtime *Runtime) Load(id string) (*Container, error) {
 	if container.State.Running {
 		container.State.Ghost = true
 	}
-	if err := runtime.Register(container); err != nil {
-		return nil, err
-	}
 	return container, nil
 }
 
@@ -148,11 +149,11 @@ func (runtime *Runtime) Register(container *Container) error {
 		}
 		if !strings.Contains(string(output), "RUNNING") {
 			utils.Debugf("Container %s was supposed to be running be is not.", container.ID)
-			if runtime.autoRestart {
+			if runtime.config.AutoRestart {
 				utils.Debugf("Restarting")
 				container.State.Ghost = false
 				container.State.setStopped(0)
-				hostConfig := &HostConfig{}
+				hostConfig, _ := container.ReadHostConfig()
 				if err := container.Start(hostConfig); err != nil {
 					return err
 				}
@@ -172,9 +173,9 @@ func (runtime *Runtime) Register(container *Container) error {
 	if !container.State.Running {
 		close(container.waitLock)
 	} else if !nomonitor {
-		container.allocateNetwork()
-		// hostConfig isn't needed here and can be nil
-		go container.monitor(nil)
+		hostConfig, _ := container.ReadHostConfig()
+		container.allocateNetwork(hostConfig)
+		go container.monitor(hostConfig)
 	}
 	return nil
 }
@@ -202,6 +203,7 @@ func (runtime *Runtime) Destroy(container *Container) error {
 	if err := container.Stop(3); err != nil {
 		return err
 	}
+
 	if mounted, err := container.Mounted(); err != nil {
 		return err
 	} else if mounted {
@@ -209,6 +211,11 @@ func (runtime *Runtime) Destroy(container *Container) error {
 			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
 	runtime.idIndex.Delete(container.ID)
 	runtime.containers.Remove(element)
@@ -227,9 +234,10 @@ func (runtime *Runtime) restore() error {
 	if err != nil {
 		return err
 	}
+	containers := []*Container{}
 	for i, v := range dir {
 		id := v.Name()
-		container, err := runtime.Load(id)
+		container, err := runtime.load(id)
 		if i%21 == 0 && os.Getenv("DEBUG") == "" && os.Getenv("TEST") == "" {
 			fmt.Printf("\b%c", wheel[i%4])
 		}
@@ -238,10 +246,30 @@ func (runtime *Runtime) restore() error {
 			continue
 		}
 		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") == "" {
 		fmt.Printf("\bdone.\n")
 	}
+
 	return nil
 }
 
@@ -274,27 +302,45 @@ func (runtime *Runtime) UpdateCapabilities(quiet bool) {
 }
 
 // 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
 	img, err := runtime.repositories.LookupImage(config.Image)
 	if err != nil {
-		return nil, err
+		return nil, nil, err
 	}
 
+	warnings := []string{}
 	if img.Config != nil {
+		if img.Config.PortSpecs != nil && warnings != nil {
+			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, err
+			return nil, nil, err
 		}
+
 	}
 
 	if len(config.Entrypoint) != 0 && config.Cmd == nil {
 		config.Cmd = []string{}
 	} 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
 	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
 	// FIXME: the lxc template no longer needs to set a default hostname
 	if config.Hostname == "" {
@@ -328,36 +374,36 @@ func (runtime *Runtime) Create(config *Config) (*Container, error) {
 	// Step 1: create the container directory.
 	// This doubles as a barrier to avoid race conditions.
 	if err := os.Mkdir(container.root, 0700); err != nil {
-		return nil, err
+		return nil, nil, err
 	}
 
 	resolvConf, err := utils.GetResolvConf()
 	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
-		runtime.Dns = defaultDns
+		runtime.config.Dns = defaultDns
 	}
 
 	// 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
 		if len(config.Dns) > 0 {
 			dns = config.Dns
 		} else {
-			dns = runtime.Dns
+			dns = runtime.config.Dns
 		}
 		container.ResolvConfPath = path.Join(container.root, "resolv.conf")
 		f, err := os.Create(container.ResolvConfPath)
 		if err != nil {
-			return nil, err
+			return nil, nil, err
 		}
 		defer f.Close()
 		for _, dns := range dns {
 			if _, err := f.Write([]byte("nameserver " + dns + "\n")); err != nil {
-				return nil, err
+				return nil, nil, err
 			}
 		}
 	} else {
@@ -366,7 +412,7 @@ func (runtime *Runtime) Create(config *Config) (*Container, error) {
 
 	// Step 2: save the container json
 	if err := container.ToDisk(); err != nil {
-		return nil, err
+		return nil, nil, err
 	}
 
 	// Step 3: if hostname, build hostname and hosts files
@@ -396,9 +442,9 @@ ff02::2		ip6-allrouters
 
 	// Step 4: register the container
 	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.
@@ -428,13 +474,85 @@ func (runtime *Runtime) Commit(container *Container, repository, tag, comment, a
 	return img, nil
 }
 
+func (runtime *Runtime) GetByName(name string) (*Container, error) {
+	if id, err := runtime.idIndex.Get(name); err == nil {
+		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(flGraphPath string, autoRestart bool, dns []string) (*Runtime, error) {
-	runtime, err := NewRuntimeFromDirectory(flGraphPath, autoRestart)
+func NewRuntime(config *DaemonConfig) (*Runtime, error) {
+	runtime, err := NewRuntimeFromDirectory(config)
 	if err != nil {
 		return nil, err
 	}
-	runtime.Dns = dns
 
 	if k, err := utils.GetKernelVersion(); err != nil {
 		log.Printf("WARNING: %s\n", err)
@@ -447,34 +565,39 @@ func NewRuntime(flGraphPath string, autoRestart bool, dns []string) (*Runtime, e
 	return runtime, nil
 }
 
-func NewRuntimeFromDirectory(root string, autoRestart bool) (*Runtime, error) {
-	runtimeRepo := path.Join(root, "containers")
+func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) {
+	runtimeRepo := path.Join(config.GraphPath, "containers")
 
 	if err := os.MkdirAll(runtimeRepo, 0700); err != nil && !os.IsExist(err) {
 		return nil, err
 	}
 
-	g, err := NewGraph(path.Join(root, "graph"))
+	g, err := NewGraph(path.Join(config.GraphPath, "graph"))
 	if err != nil {
 		return nil, err
 	}
-	volumes, err := NewGraph(path.Join(root, "volumes"))
+	volumes, err := NewGraph(path.Join(config.GraphPath, "volumes"))
 	if err != nil {
 		return nil, err
 	}
-	repositories, err := NewTagStore(path.Join(root, "repositories"), g)
+	repositories, err := NewTagStore(path.Join(config.GraphPath, "repositories"), g)
 	if err != nil {
 		return nil, fmt.Errorf("Couldn't create Tag store: %s", err)
 	}
-	if NetworkBridgeIface == "" {
-		NetworkBridgeIface = DefaultNetworkBridge
+	if config.BridgeIface == "" {
+		config.BridgeIface = DefaultNetworkBridge
+	}
+	netManager, err := newNetworkManager(config)
+	if err != nil {
+		return nil, err
 	}
-	netManager, err := newNetworkManager(NetworkBridgeIface)
+
+	graph, err := gograph.NewDatabase(path.Join(config.GraphPath, "linkgraph.db"))
 	if err != nil {
 		return nil, err
 	}
+
 	runtime := &Runtime{
-		root:           root,
 		repository:     runtimeRepo,
 		containers:     list.New(),
 		networkManager: netManager,
@@ -482,8 +605,9 @@ func NewRuntimeFromDirectory(root string, autoRestart bool) (*Runtime, error) {
 		repositories:   repositories,
 		idIndex:        utils.NewTruncIndex(),
 		capabilities:   &Capabilities{},
-		autoRestart:    autoRestart,
 		volumes:        volumes,
+		config:         config,
+		containerGraph: graph,
 	}
 
 	if err := runtime.restore(); err != nil {

+ 306 - 34
runtime_test.go

@@ -43,7 +43,7 @@ func nuke(runtime *Runtime) error {
 	}
 	wg.Wait()
 	runtime.networkManager.Close()
-	return os.RemoveAll(runtime.root)
+	return os.RemoveAll(runtime.config.GraphPath)
 }
 
 func cleanup(runtime *Runtime) error {
@@ -85,8 +85,6 @@ func init() {
 		log.Fatal("docker tests need to be run as root")
 	}
 
-	NetworkBridgeIface = unitTestNetworkBridge
-
 	// Setup the base runtime, which will be duplicated for each test.
 	// (no tests are run directly in the base)
 	setupBaseImage()
@@ -98,7 +96,12 @@ func init() {
 
 
 func setupBaseImage() {
-	runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, false)
+	config := &DaemonConfig{
+		GraphPath:   unitTestStoreBase,
+		AutoRestart: false,
+		BridgeIface: unitTestNetworkBridge,
+	}
+	runtime, err := NewRuntimeFromDirectory(config)
 	if err != nil {
 		log.Fatalf("Unable to create a runtime for tests:", err)
 	}
@@ -106,7 +109,6 @@ func setupBaseImage() {
 	// Create the "Server"
 	srv := &Server{
 		runtime:     runtime,
-		enableCors:  false,
 		pullingPool: make(map[string]struct{}),
 		pushingPool: make(map[string]struct{}),
 	}
@@ -129,7 +131,6 @@ func spawnGlobalDaemon() {
 	globalRuntime = mkRuntime(log.New(os.Stderr, "", 0))
 	srv := &Server{
 		runtime:     globalRuntime,
-		enableCors:  false,
 		pullingPool: make(map[string]struct{}),
 		pushingPool: make(map[string]struct{}),
 	}
@@ -171,7 +172,7 @@ func TestRuntimeCreate(t *testing.T) {
 		t.Errorf("Expected 0 containers, %v found", len(runtime.List()))
 	}
 
-	container, err := runtime.Create(&Config{
+	container, _, err := runtime.Create(&Config{
 		Image: GetTestImage(runtime).ID,
 		Cmd:   []string{"ls", "-al"},
 	},
@@ -211,12 +212,12 @@ func TestRuntimeCreate(t *testing.T) {
 		t.Errorf("Exists() returned false for a newly created container")
 	}
 
-	// Make sure crete with bad parameters returns an error
-	if _, err = runtime.Create(&Config{Image: GetTestImage(runtime).ID}); err == nil {
+	// Make sure create with bad parameters returns an error
+	if _, _, err = runtime.Create(&Config{Image: GetTestImage(runtime).ID}); err == nil {
 		t.Fatal("Builder.Create should throw an error when Cmd is missing")
 	}
 
-	if _, err := runtime.Create(
+	if _, _, err := runtime.Create(
 		&Config{
 			Image: GetTestImage(runtime).ID,
 			Cmd:   []string{},
@@ -230,30 +231,19 @@ func TestRuntimeCreate(t *testing.T) {
 		Cmd:       []string{"/bin/ls"},
 		PortSpecs: []string{"80"},
 	}
-	container, err = runtime.Create(config)
+	container, _, err = runtime.Create(config)
 
-	image, err := runtime.Commit(container, "testrepo", "testtag", "", "", config)
+	_, err = runtime.Commit(container, "testrepo", "testtag", "", "", config)
 	if err != nil {
 		t.Error(err)
 	}
-
-	_, err = runtime.Create(
-		&Config{
-			Image:     image.ID,
-			PortSpecs: []string{"80000:80"},
-		},
-	)
-	if err == nil {
-		t.Fatal("Builder.Create should throw an error when PortSpecs is invalid")
-	}
-
 }
 
 func TestDestroy(t *testing.T) {
 	runtime := mkRuntime(t)
 	defer nuke(runtime)
 
-	container, err := runtime.Create(&Config{
+	container, _, err := runtime.Create(&Config{
 		Image: GetTestImage(runtime).ID,
 		Cmd:   []string{"ls", "-al"},
 	})
@@ -327,6 +317,7 @@ func startEchoServerContainer(t *testing.T, proto string) (*Runtime, *Container,
 		strPort   string
 		runtime   = mkRuntime(t)
 		port      = 5554
+		p	  Port
 	)
 
 	for {
@@ -340,22 +331,34 @@ func startEchoServerContainer(t *testing.T, proto string) (*Runtime, *Container,
 		} else {
 			t.Fatal(fmt.Errorf("Unknown protocol %v", proto))
 		}
-		container, err = runtime.Create(&Config{
-			Image:     GetTestImage(runtime).ID,
-			Cmd:       []string{"sh", "-c", cmd},
-			PortSpecs: []string{fmt.Sprintf("%s/%s", strPort, proto)},
+		ep := make(map[Port]struct{}, 1)
+		p = Port(fmt.Sprintf("%s/%s", strPort, proto))
+		ep[p] = struct{}{}
+
+		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 {
 			nuke(runtime)
 			t.Fatal(err)
 		}
+
+		if container != nil {
+			break
+		}
 		t.Logf("Port %v already in use, trying another one", strPort)
 	}
 
-	if err := container.Start(&HostConfig{}); err != nil {
+	hostConfig := &HostConfig{
+		PortBindings: make(map[Port][]PortBinding),
+	}
+	hostConfig.PortBindings[p] = []PortBinding{
+		{},
+	}
+	if err := container.Start(hostConfig); err != nil {
 		nuke(runtime)
 		t.Fatal(err)
 	}
@@ -369,7 +372,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
 	container.WaitTimeout(500 * time.Millisecond)
 
-	strPort = container.NetworkSettings.PortMapping[strings.Title(proto)][strPort]
+	strPort = container.NetworkSettings.Ports[p][0].HostPort
 	return runtime, container, strPort
 }
 
@@ -501,7 +504,8 @@ func TestRestore(t *testing.T) {
 
 	// Here are are simulating a docker restart - that is, reloading all containers
 	// from scratch
-	runtime2, err := NewRuntimeFromDirectory(runtime1.root, false)
+	runtime1.config.AutoRestart = false
+	runtime2, err := NewRuntimeFromDirectory(runtime1.config)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -528,3 +532,271 @@ func TestRestore(t *testing.T) {
 	}
 	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)
+		}
+	}
+}

+ 92 - 37
server.go

@@ -6,6 +6,7 @@ import (
 	"errors"
 	"fmt"
 	"github.com/dotcloud/docker/auth"
+	"github.com/dotcloud/docker/gograph"
 	"github.com/dotcloud/docker/registry"
 	"github.com/dotcloud/docker/utils"
 	"io"
@@ -114,7 +115,7 @@ func (srv *Server) ContainerExport(name string, out io.Writer) 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 {
 		return nil, err
 	}
@@ -151,7 +152,7 @@ func (srv *Server) ImageInsert(name, url, path string, out io.Writer, sf *utils.
 		return "", err
 	}
 
-	c, err := srv.runtime.Create(config)
+	c, _, err := srv.runtime.Create(config)
 	if err != nil {
 		return "", err
 	}
@@ -369,7 +370,7 @@ func (srv *Server) ContainerChanges(name string) ([]Change, error) {
 func (srv *Server) Containers(all, size bool, n int, since, before string) []APIContainers {
 	var foundBefore bool
 	var displayed int
-	retContainers := []APIContainers{}
+	out := []APIContainers{}
 
 	for _, container := range srv.runtime.List() {
 		if !container.State.Running && !all && n == -1 && since == "" && before == "" {
@@ -391,23 +392,35 @@ func (srv *Server) Containers(all, size bool, n int, since, before string) []API
 			break
 		}
 		displayed++
-
-		c := APIContainers{
-			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)
+		c := createAPIContainer(container, size, srv.runtime)
+		out = append(out, 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) {
 	container := srv.runtime.Get(name)
 	if container == nil {
@@ -646,7 +659,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 {
-	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 {
 		return err
 	}
@@ -855,7 +868,7 @@ func (srv *Server) ImagePush(localName string, out io.Writer, sf *utils.StreamFo
 
 	out = utils.NewWriteFlusher(out)
 	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 {
 		return err2
 	}
@@ -920,10 +933,9 @@ func (srv *Server) ImageImport(src, repo, tag string, in io.Reader, out io.Write
 	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 {
-		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 {
@@ -933,7 +945,7 @@ func (srv *Server) ContainerCreate(config *Config) (string, error) {
 	if config.Memory > 0 && !srv.runtime.capabilities.SwapLimit {
 		config.MemorySwap = -1
 	}
-	container, err := srv.runtime.Create(config)
+	container, buildWarnings, err := srv.runtime.Create(config)
 	if err != nil {
 		if srv.runtime.graph.IsNotExist(err) {
 
@@ -942,12 +954,12 @@ func (srv *Server) ContainerCreate(config *Config) (string, error) {
 				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))
-	return container.ShortID(), nil
+	return container.ShortID(), buildWarnings, nil
 }
 
 func (srv *Server) ContainerRestart(name string, t int) error {
@@ -962,7 +974,34 @@ func (srv *Server) ContainerRestart(name string, t int) error {
 	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.State.Running {
 			return fmt.Errorf("Impossible to remove a running container, please stop it first")
@@ -1162,14 +1201,32 @@ func (srv *Server) ImageGetCached(imgID string, config *Config) (*Image, error)
 }
 
 func (srv *Server) ContainerStart(name string, hostConfig *HostConfig) error {
-	if container := srv.runtime.Get(name); container != nil {
-		if err := container.Start(hostConfig); err != nil {
-			return fmt.Errorf("Error starting container %s: %s", name, err)
-		}
-		srv.LogEvent("start", container.ShortID(), srv.runtime.repositories.ImageName(container.Image))
-	} else {
+	runtime := srv.runtime
+	container := runtime.Get(name)
+	if container == nil {
 		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
 }
 
@@ -1321,17 +1378,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" {
 		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 {
 		return nil, err
 	}
 	srv := &Server{
 		runtime:     runtime,
-		enableCors:  enableCors,
 		pullingPool: make(map[string]struct{}),
 		pushingPool: make(map[string]struct{}),
 		events:      make([]utils.JSONMessage, 0, 64), //only keeps the 64 last events
@@ -1369,7 +1425,6 @@ func (srv *Server) LogEvent(action, id, from string) {
 type Server struct {
 	sync.Mutex
 	runtime     *Runtime
-	enableCors  bool
 	pullingPool map[string]struct{}
 	pushingPool map[string]struct{}
 	events      []utils.JSONMessage

+ 10 - 10
server_test.go

@@ -89,7 +89,7 @@ func TestCreateRm(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	id, err := srv.ContainerCreate(config)
+	id, _, err := srv.ContainerCreate(config)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -98,7 +98,7 @@ func TestCreateRm(t *testing.T) {
 		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)
 	}
 
@@ -119,7 +119,7 @@ func TestCreateRmVolumes(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	id, err := srv.ContainerCreate(config)
+	id, _, err := srv.ContainerCreate(config)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -138,7 +138,7 @@ func TestCreateRmVolumes(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	if err = srv.ContainerDestroy(id, true); err != nil {
+	if err = srv.ContainerDestroy(id, true, false); err != nil {
 		t.Fatal(err)
 	}
 
@@ -158,7 +158,7 @@ func TestCommit(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	id, err := srv.ContainerCreate(config)
+	id, _, err := srv.ContainerCreate(config)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -179,7 +179,7 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	id, err := srv.ContainerCreate(config)
+	id, _, err := srv.ContainerCreate(config)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -209,7 +209,7 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) {
 	}
 
 	// 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)
 	}
 
@@ -224,7 +224,7 @@ func TestRunWithTooLowMemoryLimit(t *testing.T) {
 	defer nuke(runtime)
 
 	// Try to create a container with a memory limit of 1 byte less than the minimum allowed limit.
-	if _, err := (*Server).ContainerCreate(&Server{runtime: runtime},
+	if _, _, err := (*Server).ContainerCreate(&Server{runtime: runtime},
 		&Config{
 			Image:     GetTestImage(runtime).ID,
 			Memory:    524287,
@@ -397,7 +397,7 @@ func TestRmi(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	containerID, err := srv.ContainerCreate(config)
+	containerID, _, err := srv.ContainerCreate(config)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -418,7 +418,7 @@ func TestRmi(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	containerID, err = srv.ContainerCreate(config)
+	containerID, _, err = srv.ContainerCreate(config)
 	if err != nil {
 		t.Fatal(err)
 	}

+ 69 - 0
sorter.go

@@ -34,3 +34,72 @@ func sortImagesByCreationAndTag(images []APIImages) {
 
 	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)
+}

+ 36 - 0
sorter_test.go

@@ -1,6 +1,7 @@
 package docker
 
 import (
+	"fmt"
 	"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.")
 	}
 }
+
+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()
+	}
+}

+ 134 - 19
utils.go

@@ -2,6 +2,8 @@ package docker
 
 import (
 	"fmt"
+	"github.com/dotcloud/docker/utils"
+	"strconv"
 	"strings"
 )
 
@@ -27,6 +29,7 @@ func CompareConfig(a, b *Config) bool {
 		len(a.Dns) != len(b.Dns) ||
 		len(a.Env) != len(b.Env) ||
 		len(a.PortSpecs) != len(b.PortSpecs) ||
+		len(a.ExposedPorts) != len(b.ExposedPorts) ||
 		len(a.Entrypoint) != len(b.Entrypoint) ||
 		len(a.Volumes) != len(b.Volumes) {
 		return false
@@ -52,6 +55,11 @@ func CompareConfig(a, b *Config) bool {
 			return false
 		}
 	}
+	for k := range a.ExposedPorts {
+		if _, exists := b.ExposedPorts[k]; !exists {
+			return false
+		}
+	}
 	for i := 0; i < len(a.Entrypoint); i++ {
 		if a.Entrypoint[i] != b.Entrypoint[i] {
 			return false
@@ -78,26 +86,38 @@ func MergeConfig(userConf, imageConf *Config) error {
 	if userConf.CpuShares == 0 {
 		userConf.CpuShares = imageConf.CpuShares
 	}
-	if userConf.PortSpecs == nil || len(userConf.PortSpecs) == 0 {
-		userConf.PortSpecs = imageConf.PortSpecs
-	} else {
-		for _, imagePortSpec := range imageConf.PortSpecs {
-			found := false
-			imageNat, err := parseNat(imagePortSpec)
-			if err != nil {
-				return err
-			}
-			for _, userPortSpec := range userConf.PortSpecs {
-				userNat, err := parseNat(userPortSpec)
-				if err != nil {
-					return err
-				}
-				if imageNat.Proto == userNat.Proto && imageNat.Backend == userNat.Backend {
-					found = true
-				}
+	if userConf.ExposedPorts == nil || len(userConf.ExposedPorts) == 0 {
+		userConf.ExposedPorts = imageConf.ExposedPorts
+	}
+
+	if userConf.PortSpecs != nil && len(userConf.PortSpecs) > 0 {
+		if userConf.ExposedPorts == nil {
+			userConf.ExposedPorts = make(map[Port]struct{})
+		}
+		ports, _, err := parsePortSpecs(userConf.PortSpecs)
+		if err != nil {
+			return err
+		}
+		for port := range ports {
+			if _, exists := userConf.ExposedPorts[port]; !exists {
+				userConf.ExposedPorts[port] = struct{}{}
 			}
-			if !found {
-				userConf.PortSpecs = append(userConf.PortSpecs, imagePortSpec)
+		}
+		userConf.PortSpecs = nil
+	}
+	if imageConf.PortSpecs != nil && len(imageConf.PortSpecs) > 0 {
+		utils.Debugf("Migrating image port specs to containter: %s", strings.Join(imageConf.PortSpecs, ", "))
+		if userConf.ExposedPorts == nil {
+			userConf.ExposedPorts = make(map[Port]struct{})
+		}
+
+		ports, _, err := parsePortSpecs(imageConf.PortSpecs)
+		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
 }
+
+// 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)
+}

+ 19 - 0
utils/utils.go

@@ -1044,3 +1044,22 @@ func IsClosedError(err error) bool {
 	 */
 	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
+}

+ 20 - 0
utils/utils_test.go

@@ -424,3 +424,23 @@ func TestDependencyGraph(t *testing.T) {
 		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()
+	}
+}

+ 135 - 47
utils_test.go

@@ -66,7 +66,11 @@ func newTestRuntime(prefix string) (runtime *Runtime, err error) {
 		return nil, err
 	}
 
-	runtime, err = NewRuntimeFromDirectory(root, false)
+	config := &DaemonConfig{
+		GraphPath:   root,
+		AutoRestart: false,
+	}
+	runtime, err = NewRuntimeFromDirectory(config)
 	if err != nil {
 		return nil, err
 	}
@@ -125,7 +129,7 @@ func mkContainer(r *Runtime, args []string, t *testing.T) (*Container, *HostConf
 	if config.Image == "_" {
 		config.Image = GetTestImage(r).ID
 	}
-	c, err := r.Create(config)
+	c, _, err := r.Create(config)
 	if err != nil {
 		return nil, nil, err
 	}
@@ -253,12 +257,12 @@ func TestMergeConfig(t *testing.T) {
 		}
 	}
 
-	if len(configUser.PortSpecs) != 3 {
-		t.Fatalf("Expected 3 portSpecs, 1111:1111, 3333:2222 and 3333:3333, found %d", len(configUser.PortSpecs))
+	if len(configUser.ExposedPorts) != 3 {
+		t.Fatalf("Expected 3 portSpecs, 1111, 2222 and 3333, found %d", len(configUser.PortSpecs))
 	}
-	for _, portSpecs := range configUser.PortSpecs {
-		if portSpecs != "1111:1111" && portSpecs != "3333:2222" && portSpecs != "3333:3333" {
-			t.Fatalf("Expected 1111:1111 or 3333:2222 or 3333:3333, found %s", portSpecs)
+	for portSpecs := range configUser.ExposedPorts {
+		if portSpecs.Port() != "1111" && portSpecs.Port() != "2222" && portSpecs.Port() != "3333" {
+			t.Fatalf("Expected 1111 or 2222 or 3333, found %s", portSpecs)
 		}
 	}
 	if len(configUser.Env) != 3 {
@@ -284,60 +288,144 @@ 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,
-	}
+func TestParseLxcConfOpt(t *testing.T) {
+	opts := []string{"lxc.utsname=docker", "lxc.utsname = docker "}
 
-	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,
+	for _, o := range opts {
+		k, v, err := parseLxcOpt(o)
+		if err != nil {
+			t.FailNow()
+		}
+		if k != "lxc.utsname" {
+			t.Fail()
+		}
+		if v != "docker" {
+			t.Fail()
+		}
 	}
+}
 
-	MergeConfig(configUser, configImage)
-
-	contains := func(a []string, expect string) bool {
-		for _, p := range a {
-			if p == expect {
-				return true
-			}
+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()
 		}
-		return false
 	}
+}
 
-	if !contains(configUser.PortSpecs, "2222") {
-		t.Logf("Expected '2222' Ports: %v", configUser.PortSpecs)
-		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 !contains(configUser.PortSpecs, "1111:3333") {
-		t.Logf("Expected '1111:3333' Ports: %v", configUser.PortSpecs)
-		t.Fail()
+	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 TestParseLxcConfOpt(t *testing.T) {
-	opts := []string{"lxc.utsname=docker", "lxc.utsname = docker "}
-
-	for _, o := range opts {
-		k, v, err := parseLxcOpt(o)
-		if err != nil {
+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 k != "lxc.utsname" {
+		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 v != "docker" {
+		if s.HostIp != "192.168.1.100" {
 			t.Fail()
 		}
 	}

+ 404 - 0
vendor/src/code.google.com/p/gosqlite/sqlite/sqlite.go

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

+ 498 - 0
vendor/src/code.google.com/p/gosqlite/sqlite3/driver.go

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