mirror of
				https://github.com/moby/moby.git
				synced 2022-11-09 12:21:53 -05:00 
			
		
		
		
	Add links for container relationships and introspection
This commit is contained in:
		
							parent
							
								
									ff567f8729
								
							
						
					
					
						commit
						1cbdaebaa1
					
				
					 46 changed files with 4247 additions and 748 deletions
				
			
		
							
								
								
									
										12
									
								
								Dockerfile
									
										
									
									
									
								
							
							
						
						
									
										12
									
								
								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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										105
									
								
								api.go
									
										
									
									
									
								
							
							
						
						
									
										105
									
								
								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,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										36
									
								
								api_test.go
									
										
									
									
									
								
							
							
						
						
									
										36
									
								
								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"},
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										108
									
								
								commands.go
									
										
									
									
									
								
							
							
						
						
									
										108
									
								
								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
									
								
								config.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								config.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,17 @@
 | 
			
		|||
package docker
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"net"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type DaemonConfig struct {
 | 
			
		||||
	Pidfile        string
 | 
			
		||||
	GraphPath      string
 | 
			
		||||
	ProtoAddresses []string
 | 
			
		||||
	AutoRestart    bool
 | 
			
		||||
	EnableCors     bool
 | 
			
		||||
	Dns            []string
 | 
			
		||||
	EnableIptables bool
 | 
			
		||||
	BridgeIface    string
 | 
			
		||||
	DefaultIp      net.IP
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										231
									
								
								container.go
									
										
									
									
									
								
							
							
						
						
									
										231
									
								
								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.State.Ghost {
 | 
			
		||||
		portSpecs = container.Config.PortSpecs
 | 
			
		||||
	} else {
 | 
			
		||||
		for backend, frontend := range container.NetworkSettings.PortMapping["Tcp"] {
 | 
			
		||||
			portSpecs = append(portSpecs, fmt.Sprintf("%s:%s/tcp", frontend, backend))
 | 
			
		||||
	if container.Config.PortSpecs != nil {
 | 
			
		||||
		utils.Debugf("Migrating port mappings for container: %s", strings.Join(container.Config.PortSpecs, ", "))
 | 
			
		||||
		if err := migratePortMappings(container.Config); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		for backend, frontend := range container.NetworkSettings.PortMapping["Udp"] {
 | 
			
		||||
			portSpecs = append(portSpecs, fmt.Sprintf("%s:%s/udp", frontend, backend))
 | 
			
		||||
		container.Config.PortSpecs = nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	portSpecs := make(map[Port]struct{})
 | 
			
		||||
	bindings := make(map[Port][]PortBinding)
 | 
			
		||||
 | 
			
		||||
	if !container.State.Ghost {
 | 
			
		||||
		if container.Config.ExposedPorts != nil {
 | 
			
		||||
			portSpecs = container.Config.ExposedPorts
 | 
			
		||||
		}
 | 
			
		||||
		if hostConfig.PortBindings != nil {
 | 
			
		||||
			bindings = hostConfig.PortBindings
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		if container.NetworkSettings.Ports != nil {
 | 
			
		||||
			for port, binding := range container.NetworkSettings.Ports {
 | 
			
		||||
				portSpecs[port] = struct{}{}
 | 
			
		||||
				bindings[port] = binding
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	container.NetworkSettings.PortMapping = make(map[string]PortMapping)
 | 
			
		||||
	container.NetworkSettings.PortMapping["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
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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"},
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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``
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
									
								
								docs/sources/examples/linking_into_redis.rst
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										146
									
								
								docs/sources/examples/linking_into_redis.rst
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,146 @@
 | 
			
		|||
:title: Linking to an Redis container
 | 
			
		||||
:description: Running redis linked into your web app
 | 
			
		||||
:keywords: docker, example, networking, redis, link
 | 
			
		||||
 | 
			
		||||
.. _linking_redis:
 | 
			
		||||
 | 
			
		||||
Linking Redis
 | 
			
		||||
=============
 | 
			
		||||
 | 
			
		||||
.. include:: example_header.inc
 | 
			
		||||
 | 
			
		||||
Building a redis container to link as a child of our web application.
 | 
			
		||||
 | 
			
		||||
Building the redis container
 | 
			
		||||
----------------------------
 | 
			
		||||
 | 
			
		||||
We will use a pre-build version of redis from the index under 
 | 
			
		||||
the name ``crosbymichael/redis``.  If you are interested in the 
 | 
			
		||||
Dockerfile that was used to build this container here it is.
 | 
			
		||||
 | 
			
		||||
.. code-block:: bash
 | 
			
		||||
    
 | 
			
		||||
    # Build redis from source
 | 
			
		||||
    # Make sure you have the redis source code checked out in
 | 
			
		||||
    # the same directory as this Dockerfile
 | 
			
		||||
    FROM ubuntu
 | 
			
		||||
 | 
			
		||||
    RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list
 | 
			
		||||
    RUN apt-get update
 | 
			
		||||
    RUN apt-get upgrade -y
 | 
			
		||||
 | 
			
		||||
    RUN apt-get install -y gcc make g++ build-essential libc6-dev tcl
 | 
			
		||||
 | 
			
		||||
    ADD . /redis
 | 
			
		||||
 | 
			
		||||
    RUN (cd /redis && make)
 | 
			
		||||
    RUN (cd /redis && make test)
 | 
			
		||||
 | 
			
		||||
    RUN mkdir -p /redis-data
 | 
			
		||||
    VOLUME ["/redis-data"]
 | 
			
		||||
    EXPOSE 6379
 | 
			
		||||
 | 
			
		||||
    ENTRYPOINT ["/redis/src/redis-server"]
 | 
			
		||||
    CMD ["--dir", "/redis-data"]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
We need to ``EXPOSE`` the default port of 6379 so that our link knows what ports 
 | 
			
		||||
to connect to our redis container on.  If you do not expose any ports for the
 | 
			
		||||
image then docker will not be able to establish the link between containers.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Run the redis container
 | 
			
		||||
-----------------------
 | 
			
		||||
 | 
			
		||||
.. code-block:: bash
 | 
			
		||||
    
 | 
			
		||||
    docker run -d -e PASSWORD=docker crosbymichael/redis --requirepass=docker
 | 
			
		||||
 
 | 
			
		||||
This will run our redis container using the default port of 6379 and using
 | 
			
		||||
as password to secure our service. Next we will link the redis container to 
 | 
			
		||||
a new name using ``docker link`` and ``docker ls``.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Linking an existing container
 | 
			
		||||
-----------------------------
 | 
			
		||||
 | 
			
		||||
.. code-block:: bash
 | 
			
		||||
 | 
			
		||||
    docker ls
 | 
			
		||||
 | 
			
		||||
    NAME                                                                      ID                                                                 IMAGE
 | 
			
		||||
    /39588b6a45100ef5b328b2c302ea085624f29e6cbab70f88be04793af02cec89         39588b6a45100ef5b328b2c302ea085624f29e6cbab70f88be04793af02cec89   crosbymichael/redis:latest
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Docker will automatically create an initial link with the container's id but
 | 
			
		||||
because the is long and not very user friendly we can link the container with
 | 
			
		||||
a new name.
 | 
			
		||||
 | 
			
		||||
.. code-block:: bash
 | 
			
		||||
 | 
			
		||||
    docker link /39588b6a45100ef5b328b2c302ea085624f29e6cbab70f88be04793af02cec89 /redis
 | 
			
		||||
 | 
			
		||||
    docker ls 
 | 
			
		||||
 | 
			
		||||
    NAME                                                                      ID                                                                 IMAGE
 | 
			
		||||
    /redis                                                                    39588b6a45100ef5b328b2c302ea085624f29e6cbab70f88be04793af02cec89   crosbymichael/redis:latest
 | 
			
		||||
    /39588b6a45100ef5b328b2c302ea085624f29e6cbab70f88be04793af02cec89         39588b6a45100ef5b328b2c302ea085624f29e6cbab70f88be04793af02cec89   crosbymichael/redis:latest
 | 
			
		||||
 | 
			
		||||
Now we can reference our running redis service using the friendly name ``/redis``.  
 | 
			
		||||
We can issue all the commands that you would expect; start, stop, attach, using the new name.
 | 
			
		||||
 | 
			
		||||
Linking redis as a child
 | 
			
		||||
------------------------
 | 
			
		||||
 | 
			
		||||
Next we can start a new web application that has a dependency on redis and apply a link 
 | 
			
		||||
to connect both containers.  If you noticed when running our redis service we did not use
 | 
			
		||||
the ``-p`` option to publish the redis port to the host system.  Redis exposed port 6379
 | 
			
		||||
but we did not publish the port.  This allows docker to prevent all network traffic to
 | 
			
		||||
the redis container except when explicitly specified within a link.  This is a big win
 | 
			
		||||
for security.  
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Now lets start our web application with a link into redis.
 | 
			
		||||
 | 
			
		||||
.. code-block:: bash
 | 
			
		||||
   
 | 
			
		||||
    docker run -t -i -link /redis:db ubuntu bash
 | 
			
		||||
 | 
			
		||||
    root@4c01db0b339c:/# env
 | 
			
		||||
 | 
			
		||||
    HOSTNAME=4c01db0b339c
 | 
			
		||||
    DB_NAME=/4c01db0b339cf19958731255a796ee072040a652f51652a4ade190ab8c27006f/db
 | 
			
		||||
    TERM=xterm
 | 
			
		||||
    DB_PORT=tcp://172.17.0.8:6379
 | 
			
		||||
    DB_PORT_6379_TCP=tcp://172.17.0.8:6379
 | 
			
		||||
    PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
 | 
			
		||||
    PWD=/
 | 
			
		||||
    DB_ENV_PASSWORD=dockerpass
 | 
			
		||||
    SHLVL=1
 | 
			
		||||
    HOME=/
 | 
			
		||||
    container=lxc
 | 
			
		||||
    _=/usr/bin/env
 | 
			
		||||
    root@4c01db0b339c:/#
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
When we inspect the environment of the linked container we can see a few extra environment 
 | 
			
		||||
variables have been added.  When you specified ``-link /redis:db`` you are telling docker
 | 
			
		||||
to link the container named ``/redis`` into this new container with the alias ``db``.  
 | 
			
		||||
Environment variables are prefixed with the alias so that the parent container can access
 | 
			
		||||
network and environment information from the child.
 | 
			
		||||
 | 
			
		||||
.. code-block:: bash
 | 
			
		||||
 | 
			
		||||
    # The name of the child container
 | 
			
		||||
    DB_NAME=/4c01db0b339cf19958731255a796ee072040a652f51652a4ade190ab8c27006f/db
 | 
			
		||||
    # The default protocol, ip, and port of the service running in the container
 | 
			
		||||
    DB_PORT=tcp://172.17.0.8:6379
 | 
			
		||||
    # A specific protocol, ip, and port of various services
 | 
			
		||||
    DB_PORT_6379_TCP=tcp://172.17.0.8:6379
 | 
			
		||||
    # Get environment variables of the container 
 | 
			
		||||
    DB_ENV_PASSWORD=dockerpass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Accessing the network information along with the environment of the child container allows
 | 
			
		||||
us to easily connect to the redis service on the specific ip and port and use the password
 | 
			
		||||
specified in the environment.  
 | 
			
		||||
							
								
								
									
										1
									
								
								gograph/MAINTAINERS
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								gograph/MAINTAINERS
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
Michael Crosby <michael@crosbymichael.com> (@crosbymichael)
 | 
			
		||||
							
								
								
									
										455
									
								
								gograph/gograph.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										455
									
								
								gograph/gograph.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,455 @@
 | 
			
		|||
package gograph
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	_ "code.google.com/p/gosqlite/sqlite3"
 | 
			
		||||
	"database/sql"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	createEntityTable = `
 | 
			
		||||
    CREATE TABLE IF NOT EXISTS entity (
 | 
			
		||||
        id text NOT NULL PRIMARY KEY
 | 
			
		||||
    );`
 | 
			
		||||
 | 
			
		||||
	createEdgeTable = `
 | 
			
		||||
    CREATE TABLE IF NOT EXISTS edge (
 | 
			
		||||
        "entity_id" text NOT NULL,
 | 
			
		||||
        "parent_id" text NULL,
 | 
			
		||||
        "name" text NOT NULL,
 | 
			
		||||
        CONSTRAINT "parent_fk" FOREIGN KEY ("parent_id") REFERENCES "entity" ("id"),
 | 
			
		||||
        CONSTRAINT "entity_fk" FOREIGN KEY ("entity_id") REFERENCES "entity" ("id")
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
    CREATE UNIQUE INDEX "name_parent_ix" ON "edge" (parent_id, name);
 | 
			
		||||
    `
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Entity with a unique id
 | 
			
		||||
type Entity struct {
 | 
			
		||||
	id string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// An Edge connects two entities together
 | 
			
		||||
type Edge struct {
 | 
			
		||||
	EntityID string
 | 
			
		||||
	Name     string
 | 
			
		||||
	ParentID string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Entities map[string]*Entity
 | 
			
		||||
type Edges []*Edge
 | 
			
		||||
 | 
			
		||||
type WalkFunc func(fullPath string, entity *Entity) error
 | 
			
		||||
 | 
			
		||||
// Graph database for storing entities and their relationships
 | 
			
		||||
type Database struct {
 | 
			
		||||
	dbPath string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Create a new graph database initialized with a root entity
 | 
			
		||||
func NewDatabase(dbPath string) (*Database, error) {
 | 
			
		||||
	db := &Database{dbPath}
 | 
			
		||||
	if _, err := os.Stat(dbPath); err == nil {
 | 
			
		||||
		return db, nil
 | 
			
		||||
	}
 | 
			
		||||
	conn, err := db.openConn()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	defer conn.Close()
 | 
			
		||||
 | 
			
		||||
	if _, err := conn.Exec(createEntityTable); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	if _, err := conn.Exec(createEdgeTable); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	rollback := func() {
 | 
			
		||||
		conn.Exec("ROLLBACK")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Create root entities
 | 
			
		||||
	if _, err := conn.Exec("BEGIN"); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	if _, err := conn.Exec("INSERT INTO entity (id) VALUES (?);", "0"); err != nil {
 | 
			
		||||
		rollback()
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, err := conn.Exec("INSERT INTO edge (entity_id, name) VALUES(?,?);", "0", "/"); err != nil {
 | 
			
		||||
		rollback()
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, err := conn.Exec("COMMIT"); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return db, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Set the entity id for a given path
 | 
			
		||||
func (db *Database) Set(fullPath, id string) (*Entity, error) {
 | 
			
		||||
	conn, err := db.openConn()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	defer conn.Close()
 | 
			
		||||
	rollback := func() {
 | 
			
		||||
		conn.Exec("ROLLBACK")
 | 
			
		||||
	}
 | 
			
		||||
	if _, err := conn.Exec("BEGIN"); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	var entityId string
 | 
			
		||||
	if err := conn.QueryRow("SELECT id FROM entity WHERE id = ?;", id).Scan(&entityId); err != nil {
 | 
			
		||||
		if err == sql.ErrNoRows {
 | 
			
		||||
			if _, err := conn.Exec("INSERT INTO entity (id) VALUES(?);", id); err != nil {
 | 
			
		||||
				rollback()
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			rollback()
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	e := &Entity{id}
 | 
			
		||||
 | 
			
		||||
	parentPath, name := splitPath(fullPath)
 | 
			
		||||
	if err := db.setEdge(conn, parentPath, name, e); err != nil {
 | 
			
		||||
		rollback()
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, err := conn.Exec("COMMIT"); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return e, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *Database) setEdge(conn *sql.DB, parentPath, name string, e *Entity) error {
 | 
			
		||||
	parent, err := db.get(conn, parentPath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if parent.id == e.id {
 | 
			
		||||
		return fmt.Errorf("Cannot set self as child")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, err := conn.Exec("INSERT INTO edge (parent_id, name, entity_id) VALUES (?,?,?);", parent.id, name, e.id); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Return the root "/" entity for the database
 | 
			
		||||
func (db *Database) RootEntity() *Entity {
 | 
			
		||||
	return &Entity{
 | 
			
		||||
		id: "0",
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Return the entity for a given path
 | 
			
		||||
func (db *Database) Get(name string) *Entity {
 | 
			
		||||
	conn, err := db.openConn()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	e, err := db.get(conn, name)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	return e
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *Database) get(conn *sql.DB, name string) (*Entity, error) {
 | 
			
		||||
	e := db.RootEntity()
 | 
			
		||||
	// We always know the root name so return it if
 | 
			
		||||
	// it is requested
 | 
			
		||||
	if name == "/" {
 | 
			
		||||
		return e, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	parts := split(name)
 | 
			
		||||
	for i := 1; i < len(parts); i++ {
 | 
			
		||||
		p := parts[i]
 | 
			
		||||
 | 
			
		||||
		next := db.child(conn, e, p)
 | 
			
		||||
		if next == nil {
 | 
			
		||||
			return nil, fmt.Errorf("Cannot find child")
 | 
			
		||||
		}
 | 
			
		||||
		e = next
 | 
			
		||||
	}
 | 
			
		||||
	return e, nil
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// List all entities by from the name
 | 
			
		||||
// The key will be the full path of the entity
 | 
			
		||||
func (db *Database) List(name string, depth int) Entities {
 | 
			
		||||
	out := Entities{}
 | 
			
		||||
	conn, err := db.openConn()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return out
 | 
			
		||||
	}
 | 
			
		||||
	defer conn.Close()
 | 
			
		||||
 | 
			
		||||
	for c := range db.children(conn, name, depth) {
 | 
			
		||||
		out[c.FullPath] = c.Entity
 | 
			
		||||
	}
 | 
			
		||||
	return out
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *Database) Walk(name string, walkFunc WalkFunc, depth int) error {
 | 
			
		||||
	conn, err := db.openConn()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	defer conn.Close()
 | 
			
		||||
 | 
			
		||||
	for c := range db.children(conn, name, depth) {
 | 
			
		||||
		if err := walkFunc(c.FullPath, c.Entity); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Return the refrence count for a specified id
 | 
			
		||||
func (db *Database) Refs(id string) int {
 | 
			
		||||
	conn, err := db.openConn()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return -1
 | 
			
		||||
	}
 | 
			
		||||
	defer conn.Close()
 | 
			
		||||
 | 
			
		||||
	var count int
 | 
			
		||||
	if err := conn.QueryRow("SELECT COUNT(*) FROM edge WHERE entity_id = ?;", id).Scan(&count); err != nil {
 | 
			
		||||
		return 0
 | 
			
		||||
	}
 | 
			
		||||
	return count
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Return all the id's path references
 | 
			
		||||
func (db *Database) RefPaths(id string) Edges {
 | 
			
		||||
	refs := Edges{}
 | 
			
		||||
	conn, err := db.openConn()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return refs
 | 
			
		||||
	}
 | 
			
		||||
	defer conn.Close()
 | 
			
		||||
 | 
			
		||||
	rows, err := conn.Query("SELECT name, parent_id FROM edge WHERE entity_id = ?;", id)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return refs
 | 
			
		||||
	}
 | 
			
		||||
	defer rows.Close()
 | 
			
		||||
 | 
			
		||||
	for rows.Next() {
 | 
			
		||||
		var name string
 | 
			
		||||
		var parentId string
 | 
			
		||||
		if err := rows.Scan(&name, &parentId); err != nil {
 | 
			
		||||
			return refs
 | 
			
		||||
		}
 | 
			
		||||
		refs = append(refs, &Edge{
 | 
			
		||||
			EntityID: id,
 | 
			
		||||
			Name:     name,
 | 
			
		||||
			ParentID: parentId,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	return refs
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Delete the reference to an entity at a given path
 | 
			
		||||
func (db *Database) Delete(name string) error {
 | 
			
		||||
	if name == "/" {
 | 
			
		||||
		return fmt.Errorf("Cannot delete root entity")
 | 
			
		||||
	}
 | 
			
		||||
	conn, err := db.openConn()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	defer conn.Close()
 | 
			
		||||
 | 
			
		||||
	parentPath, n := splitPath(name)
 | 
			
		||||
	parent, err := db.get(conn, parentPath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, err := conn.Exec("DELETE FROM edge WHERE parent_id = ? AND name = ?;", parent.id, n); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Remove the entity with the specified id
 | 
			
		||||
// Walk the graph to make sure all references to the entity
 | 
			
		||||
// are removed and return the number of references removed
 | 
			
		||||
func (db *Database) Purge(id string) (int, error) {
 | 
			
		||||
	conn, err := db.openConn()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return -1, err
 | 
			
		||||
	}
 | 
			
		||||
	defer conn.Close()
 | 
			
		||||
 | 
			
		||||
	rollback := func() {
 | 
			
		||||
		conn.Exec("ROLLBACK")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, err := conn.Exec("BEGIN"); err != nil {
 | 
			
		||||
		return -1, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Delete all edges
 | 
			
		||||
	rows, err := conn.Exec("DELETE FROM edge WHERE entity_id = ?;", id)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		rollback()
 | 
			
		||||
		return -1, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	changes, err := rows.RowsAffected()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return -1, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Delete entity
 | 
			
		||||
	if _, err := conn.Exec("DELETE FROM entity where id = ?;", id); err != nil {
 | 
			
		||||
		rollback()
 | 
			
		||||
		return -1, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, err := conn.Exec("COMMIT"); err != nil {
 | 
			
		||||
		return -1, err
 | 
			
		||||
	}
 | 
			
		||||
	return int(changes), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Rename an edge for a given path
 | 
			
		||||
func (db *Database) Rename(currentName, newName string) error {
 | 
			
		||||
	parentPath, name := splitPath(currentName)
 | 
			
		||||
	newParentPath, newEdgeName := splitPath(newName)
 | 
			
		||||
 | 
			
		||||
	if parentPath != newParentPath {
 | 
			
		||||
		return fmt.Errorf("Cannot rename when root paths do not match %s != %s", parentPath, newParentPath)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	conn, err := db.openConn()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	defer conn.Close()
 | 
			
		||||
 | 
			
		||||
	parent, err := db.get(conn, parentPath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	rows, err := conn.Exec("UPDATE edge SET name = ? WHERE parent_id = ? AND name = ?;", newEdgeName, parent.id, name)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	i, err := rows.RowsAffected()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if i == 0 {
 | 
			
		||||
		return fmt.Errorf("Cannot locate edge for %s %s", parent.id, name)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type WalkMeta struct {
 | 
			
		||||
	Parent   *Entity
 | 
			
		||||
	Entity   *Entity
 | 
			
		||||
	FullPath string
 | 
			
		||||
	Edge     *Edge
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *Database) children(conn *sql.DB, name string, depth int) <-chan WalkMeta {
 | 
			
		||||
	out := make(chan WalkMeta)
 | 
			
		||||
	e, err := db.get(conn, name)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		close(out)
 | 
			
		||||
		return out
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	go func() {
 | 
			
		||||
		rows, err := conn.Query("SELECT entity_id, name FROM edge where parent_id = ?;", e.id)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			close(out)
 | 
			
		||||
		}
 | 
			
		||||
		defer rows.Close()
 | 
			
		||||
 | 
			
		||||
		for rows.Next() {
 | 
			
		||||
			var entityId, entityName string
 | 
			
		||||
			if err := rows.Scan(&entityId, &entityName); err != nil {
 | 
			
		||||
				// Log error
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			child := &Entity{entityId}
 | 
			
		||||
			edge := &Edge{
 | 
			
		||||
				ParentID: e.id,
 | 
			
		||||
				Name:     entityName,
 | 
			
		||||
				EntityID: child.id,
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			meta := WalkMeta{
 | 
			
		||||
				Parent:   e,
 | 
			
		||||
				Entity:   child,
 | 
			
		||||
				FullPath: path.Join(name, edge.Name),
 | 
			
		||||
				Edge:     edge,
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			out <- meta
 | 
			
		||||
			if depth == 0 {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			nDepth := depth
 | 
			
		||||
			if depth != -1 {
 | 
			
		||||
				nDepth -= 1
 | 
			
		||||
			}
 | 
			
		||||
			sc := db.children(conn, meta.FullPath, nDepth)
 | 
			
		||||
			for c := range sc {
 | 
			
		||||
				out <- c
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		close(out)
 | 
			
		||||
	}()
 | 
			
		||||
	return out
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Return the entity based on the parent path and name
 | 
			
		||||
func (db *Database) child(conn *sql.DB, parent *Entity, name string) *Entity {
 | 
			
		||||
	var id string
 | 
			
		||||
	if err := conn.QueryRow("SELECT entity_id FROM edge WHERE parent_id = ? AND name = ?;", parent.id, name).Scan(&id); err != nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	return &Entity{id}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *Database) openConn() (*sql.DB, error) {
 | 
			
		||||
	return sql.Open("sqlite3", db.dbPath)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Return the id used to reference this entity
 | 
			
		||||
func (e *Entity) ID() string {
 | 
			
		||||
	return e.id
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Return the paths sorted by depth
 | 
			
		||||
func (e Entities) Paths() []string {
 | 
			
		||||
	out := make([]string, len(e))
 | 
			
		||||
	var i int
 | 
			
		||||
	for k := range e {
 | 
			
		||||
		out[i] = k
 | 
			
		||||
		i++
 | 
			
		||||
	}
 | 
			
		||||
	sortByDepth(out)
 | 
			
		||||
 | 
			
		||||
	return out
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										452
									
								
								gograph/gograph_test.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										452
									
								
								gograph/gograph_test.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,452 @@
 | 
			
		|||
package gograph
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"os"
 | 
			
		||||
	"path"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func newTestDb(t *testing.T) *Database {
 | 
			
		||||
	db, err := NewDatabase(path.Join(os.TempDir(), "sqlite.db"))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	return db
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func destroyTestDb(db *Database) {
 | 
			
		||||
	os.Remove(db.dbPath)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestNewDatabase(t *testing.T) {
 | 
			
		||||
	db := newTestDb(t)
 | 
			
		||||
	if db == nil {
 | 
			
		||||
		t.Fatal("Datbase should not be nil")
 | 
			
		||||
	}
 | 
			
		||||
	defer destroyTestDb(db)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestCreateRootEnity(t *testing.T) {
 | 
			
		||||
	db := newTestDb(t)
 | 
			
		||||
	defer destroyTestDb(db)
 | 
			
		||||
	root := db.RootEntity()
 | 
			
		||||
	if root == nil {
 | 
			
		||||
		t.Fatal("Root entity should not be nil")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetRootEntity(t *testing.T) {
 | 
			
		||||
	db := newTestDb(t)
 | 
			
		||||
	defer destroyTestDb(db)
 | 
			
		||||
 | 
			
		||||
	e := db.Get("/")
 | 
			
		||||
	if e == nil {
 | 
			
		||||
		t.Fatal("Entity should not be nil")
 | 
			
		||||
	}
 | 
			
		||||
	if e.ID() != "0" {
 | 
			
		||||
		t.Fatalf("Enity id should be 0, got %s", e.ID())
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSetEntityWithDifferentName(t *testing.T) {
 | 
			
		||||
	db := newTestDb(t)
 | 
			
		||||
	defer destroyTestDb(db)
 | 
			
		||||
 | 
			
		||||
	db.Set("/test", "1")
 | 
			
		||||
	if _, err := db.Set("/other", "1"); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestCreateChild(t *testing.T) {
 | 
			
		||||
	db := newTestDb(t)
 | 
			
		||||
	defer destroyTestDb(db)
 | 
			
		||||
 | 
			
		||||
	child, err := db.Set("/db", "1")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if child == nil {
 | 
			
		||||
		t.Fatal("Child should not be nil")
 | 
			
		||||
	}
 | 
			
		||||
	if child.ID() != "1" {
 | 
			
		||||
		t.Fail()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestListAllRootChildren(t *testing.T) {
 | 
			
		||||
	db := newTestDb(t)
 | 
			
		||||
	defer destroyTestDb(db)
 | 
			
		||||
 | 
			
		||||
	for i := 1; i < 6; i++ {
 | 
			
		||||
		a := strconv.Itoa(i)
 | 
			
		||||
		if _, err := db.Set("/"+a, a); err != nil {
 | 
			
		||||
			t.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	entries := db.List("/", -1)
 | 
			
		||||
	if len(entries) != 5 {
 | 
			
		||||
		t.Fatalf("Expect 5 entries for / got %d", len(entries))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestListAllSubChildren(t *testing.T) {
 | 
			
		||||
	db := newTestDb(t)
 | 
			
		||||
	defer destroyTestDb(db)
 | 
			
		||||
 | 
			
		||||
	_, err := db.Set("/webapp", "1")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	child2, err := db.Set("/db", "2")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	child4, err := db.Set("/logs", "4")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if _, err := db.Set("/db/logs", child4.ID()); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	child3, err := db.Set("/sentry", "3")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	entries := db.List("/webapp", 1)
 | 
			
		||||
	if len(entries) != 3 {
 | 
			
		||||
		t.Fatalf("Expect 3 entries for / got %d", len(entries))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	entries = db.List("/webapp", 0)
 | 
			
		||||
	if len(entries) != 2 {
 | 
			
		||||
		t.Fatalf("Expect 2 entries for / got %d", len(entries))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestAddSelfAsChild(t *testing.T) {
 | 
			
		||||
	db := newTestDb(t)
 | 
			
		||||
	defer destroyTestDb(db)
 | 
			
		||||
 | 
			
		||||
	child, err := db.Set("/test", "1")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if _, err := db.Set("/test/other", child.ID()); err == nil {
 | 
			
		||||
		t.Fatal("Error should not be nil")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestAddChildToNonExistantRoot(t *testing.T) {
 | 
			
		||||
	db := newTestDb(t)
 | 
			
		||||
	defer destroyTestDb(db)
 | 
			
		||||
 | 
			
		||||
	if _, err := db.Set("/myapp", "1"); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, err := db.Set("/myapp/proxy/db", "2"); err == nil {
 | 
			
		||||
		t.Fatal("Error should not be nil")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestWalkAll(t *testing.T) {
 | 
			
		||||
	db := newTestDb(t)
 | 
			
		||||
	defer destroyTestDb(db)
 | 
			
		||||
	_, err := db.Set("/webapp", "1")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	child2, err := db.Set("/db", "2")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	child4, err := db.Set("/db/logs", "4")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if _, err := db.Set("/webapp/logs", child4.ID()); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	child3, err := db.Set("/sentry", "3")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	child5, err := db.Set("/gograph", "5")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if _, err := db.Set("/webapp/same-ref-diff-name", child5.ID()); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := db.Walk("/", func(p string, e *Entity) error {
 | 
			
		||||
		t.Logf("Path: %s Entity: %s", p, e.ID())
 | 
			
		||||
		return nil
 | 
			
		||||
	}, -1); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetEntityByPath(t *testing.T) {
 | 
			
		||||
	db := newTestDb(t)
 | 
			
		||||
	defer destroyTestDb(db)
 | 
			
		||||
	_, err := db.Set("/webapp", "1")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	child2, err := db.Set("/db", "2")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	child4, err := db.Set("/logs", "4")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if _, err := db.Set("/db/logs", child4.ID()); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	child3, err := db.Set("/sentry", "3")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	child5, err := db.Set("/gograph", "5")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if _, err := db.Set("/webapp/same-ref-diff-name", child5.ID()); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	entity := db.Get("/webapp/db/logs")
 | 
			
		||||
	if entity == nil {
 | 
			
		||||
		t.Fatal("Entity should not be nil")
 | 
			
		||||
	}
 | 
			
		||||
	if entity.ID() != "4" {
 | 
			
		||||
		t.Fatalf("Expected to get entity with id 4, got %s", entity.ID())
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestEnitiesPaths(t *testing.T) {
 | 
			
		||||
	db := newTestDb(t)
 | 
			
		||||
	defer destroyTestDb(db)
 | 
			
		||||
	_, err := db.Set("/webapp", "1")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	child2, err := db.Set("/db", "2")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	child4, err := db.Set("/logs", "4")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if _, err := db.Set("/db/logs", child4.ID()); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	child3, err := db.Set("/sentry", "3")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	child5, err := db.Set("/gograph", "5")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if _, err := db.Set("/webapp/same-ref-diff-name", child5.ID()); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	out := db.List("/", -1)
 | 
			
		||||
	for _, p := range out.Paths() {
 | 
			
		||||
		t.Log(p)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDeleteRootEntity(t *testing.T) {
 | 
			
		||||
	db := newTestDb(t)
 | 
			
		||||
	defer destroyTestDb(db)
 | 
			
		||||
 | 
			
		||||
	if err := db.Delete("/"); err == nil {
 | 
			
		||||
		t.Fatal("Error should not be nil")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDeleteEntity(t *testing.T) {
 | 
			
		||||
	db := newTestDb(t)
 | 
			
		||||
	defer destroyTestDb(db)
 | 
			
		||||
	_, err := db.Set("/webapp", "1")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	child2, err := db.Set("/db", "2")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	child4, err := db.Set("/logs", "4")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if _, err := db.Set("/db/logs", child4.ID()); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	child3, err := db.Set("/sentry", "3")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	child5, err := db.Set("/gograph", "5")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if _, err := db.Set("/webapp/same-ref-diff-name", child5.ID()); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := db.Delete("/webapp/sentry"); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	entity := db.Get("/webapp/sentry")
 | 
			
		||||
	if entity != nil {
 | 
			
		||||
		t.Fatal("Entity /webapp/sentry should be nil")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestCountRefs(t *testing.T) {
 | 
			
		||||
	db := newTestDb(t)
 | 
			
		||||
	defer destroyTestDb(db)
 | 
			
		||||
 | 
			
		||||
	db.Set("/webapp", "1")
 | 
			
		||||
 | 
			
		||||
	if db.Refs("1") != 1 {
 | 
			
		||||
		t.Fatal("Expect reference count to be 1")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	db.Set("/db", "2")
 | 
			
		||||
	db.Set("/webapp/db", "2")
 | 
			
		||||
	if db.Refs("2") != 2 {
 | 
			
		||||
		t.Fatal("Expect reference count to be 2")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestPurgeId(t *testing.T) {
 | 
			
		||||
	db := newTestDb(t)
 | 
			
		||||
	defer destroyTestDb(db)
 | 
			
		||||
 | 
			
		||||
	db.Set("/webapp", "1")
 | 
			
		||||
 | 
			
		||||
	if db.Refs("1") != 1 {
 | 
			
		||||
		t.Fatal("Expect reference count to be 1")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	db.Set("/db", "2")
 | 
			
		||||
	db.Set("/webapp/db", "2")
 | 
			
		||||
 | 
			
		||||
	count, err := db.Purge("2")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if count != 2 {
 | 
			
		||||
		t.Fatal("Expected 2 references to be removed")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestRename(t *testing.T) {
 | 
			
		||||
	db := newTestDb(t)
 | 
			
		||||
	defer destroyTestDb(db)
 | 
			
		||||
 | 
			
		||||
	db.Set("/webapp", "1")
 | 
			
		||||
 | 
			
		||||
	if db.Refs("1") != 1 {
 | 
			
		||||
		t.Fatal("Expect reference count to be 1")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	db.Set("/db", "2")
 | 
			
		||||
	db.Set("/webapp/db", "2")
 | 
			
		||||
 | 
			
		||||
	if db.Get("/webapp/db") == nil {
 | 
			
		||||
		t.Fatal("Cannot find entity at path /webapp/db")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := db.Rename("/webapp/db", "/webapp/newdb"); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if db.Get("/webapp/db") != nil {
 | 
			
		||||
		t.Fatal("Entity should not exist at /webapp/db")
 | 
			
		||||
	}
 | 
			
		||||
	if db.Get("/webapp/newdb") == nil {
 | 
			
		||||
		t.Fatal("Cannot find entity at path /webapp/newdb")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestCreateMultipleNames(t *testing.T) {
 | 
			
		||||
	db := newTestDb(t)
 | 
			
		||||
	defer destroyTestDb(db)
 | 
			
		||||
 | 
			
		||||
	db.Set("/db", "1")
 | 
			
		||||
	if _, err := db.Set("/myapp", "1"); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	db.Walk("/", func(p string, e *Entity) error {
 | 
			
		||||
		t.Logf("%s\n", p)
 | 
			
		||||
		return nil
 | 
			
		||||
	}, -1)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestRefPaths(t *testing.T) {
 | 
			
		||||
	db := newTestDb(t)
 | 
			
		||||
	defer destroyTestDb(db)
 | 
			
		||||
 | 
			
		||||
	db.Set("/webapp", "1")
 | 
			
		||||
 | 
			
		||||
	db.Set("/db", "2")
 | 
			
		||||
	db.Set("/webapp/db", "2")
 | 
			
		||||
 | 
			
		||||
	refs := db.RefPaths("2")
 | 
			
		||||
	if len(refs) != 2 {
 | 
			
		||||
		t.Fatalf("Expected reference count to be 2, got %d", len(refs))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										27
									
								
								gograph/sort.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								gograph/sort.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,27 @@
 | 
			
		|||
package gograph
 | 
			
		||||
 | 
			
		||||
import "sort"
 | 
			
		||||
 | 
			
		||||
type pathSorter struct {
 | 
			
		||||
	paths []string
 | 
			
		||||
	by    func(i, j string) bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func sortByDepth(paths []string) {
 | 
			
		||||
	s := &pathSorter{paths, func(i, j string) bool {
 | 
			
		||||
		return pathDepth(i) > pathDepth(j)
 | 
			
		||||
	}}
 | 
			
		||||
	sort.Sort(s)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *pathSorter) Len() int {
 | 
			
		||||
	return len(s.paths)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *pathSorter) Swap(i, j int) {
 | 
			
		||||
	s.paths[i], s.paths[j] = s.paths[j], s.paths[i]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *pathSorter) Less(i, j int) bool {
 | 
			
		||||
	return s.by(s.paths[i], s.paths[j])
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										29
									
								
								gograph/sort_test.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								gograph/sort_test.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,29 @@
 | 
			
		|||
package gograph
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestSort(t *testing.T) {
 | 
			
		||||
	paths := []string{
 | 
			
		||||
		"/",
 | 
			
		||||
		"/myreallylongname",
 | 
			
		||||
		"/app/db",
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sortByDepth(paths)
 | 
			
		||||
 | 
			
		||||
	if len(paths) != 3 {
 | 
			
		||||
		t.Fatalf("Expected 3 parts got %d", len(paths))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if paths[0] != "/app/db" {
 | 
			
		||||
		t.Fatalf("Expected /app/db got %s", paths[0])
 | 
			
		||||
	}
 | 
			
		||||
	if paths[1] != "/myreallylongname" {
 | 
			
		||||
		t.Fatalf("Expected /myreallylongname got %s", paths[1])
 | 
			
		||||
	}
 | 
			
		||||
	if paths[2] != "/" {
 | 
			
		||||
		t.Fatalf("Expected / got %s", paths[2])
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										32
									
								
								gograph/utils.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								gograph/utils.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,32 @@
 | 
			
		|||
package gograph
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"path"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Split p on /
 | 
			
		||||
func split(p string) []string {
 | 
			
		||||
	return strings.Split(p, "/")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Returns the depth or number of / in a given path
 | 
			
		||||
func pathDepth(p string) int {
 | 
			
		||||
	parts := split(p)
 | 
			
		||||
	if len(parts) == 2 && parts[1] == "" {
 | 
			
		||||
		return 1
 | 
			
		||||
	}
 | 
			
		||||
	return len(parts)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func splitPath(p string) (parent, name string) {
 | 
			
		||||
	if p[0] != '/' {
 | 
			
		||||
		p = "/" + p
 | 
			
		||||
	}
 | 
			
		||||
	parent, name = path.Split(p)
 | 
			
		||||
	l := len(parent)
 | 
			
		||||
	if parent[l-1] == '/' {
 | 
			
		||||
		parent = parent[:l-1]
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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() {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
									
								
								iptables/MAINTAINERS
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								iptables/MAINTAINERS
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
Michael Crosby <michael@crosbymichael.com> (@crosbymichael)
 | 
			
		||||
							
								
								
									
										105
									
								
								iptables/iptables.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								iptables/iptables.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,105 @@
 | 
			
		|||
package iptables
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net"
 | 
			
		||||
	"os/exec"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Action string
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	Add    Action = "-A"
 | 
			
		||||
	Delete Action = "-D"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	ErrIptablesNotFound = errors.New("Iptables not found")
 | 
			
		||||
	nat                 = []string{"-t", "nat"}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Chain struct {
 | 
			
		||||
	Name   string
 | 
			
		||||
	Bridge string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewChain(name, bridge string) (*Chain, error) {
 | 
			
		||||
	if err := Raw("-t", "nat", "-N", name); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	chain := &Chain{
 | 
			
		||||
		Name:   name,
 | 
			
		||||
		Bridge: bridge,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := chain.Prerouting(Add, "-m", "addrtype", "--dst-type", "LOCAL"); err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("Failed to inject docker in PREROUTING chain: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := chain.Output(Add, "-m", "addrtype", "--dst-type", "LOCAL", "!", "--dst", "127.0.0.0/8"); err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("Failed to inject docker in OUTPUT chain: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	return chain, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func RemoveExistingChain(name string) error {
 | 
			
		||||
	chain := &Chain{
 | 
			
		||||
		Name: name,
 | 
			
		||||
	}
 | 
			
		||||
	return chain.Remove()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *Chain) Forward(action Action, ip net.IP, port int, proto, dest_addr string, dest_port int) error {
 | 
			
		||||
	return Raw("-t", "nat", fmt.Sprint(action), c.Name,
 | 
			
		||||
		"-p", proto,
 | 
			
		||||
		"-d", ip.String(),
 | 
			
		||||
		"--dport", strconv.Itoa(port),
 | 
			
		||||
		"!", "-i", c.Bridge,
 | 
			
		||||
		"-j", "DNAT",
 | 
			
		||||
		"--to-destination", net.JoinHostPort(dest_addr, strconv.Itoa(dest_port)))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *Chain) Prerouting(action Action, args ...string) error {
 | 
			
		||||
	a := append(nat, fmt.Sprint(action), "PREROUTING")
 | 
			
		||||
	if len(args) > 0 {
 | 
			
		||||
		a = append(a, args...)
 | 
			
		||||
	}
 | 
			
		||||
	return Raw(append(a, "-j", c.Name)...)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *Chain) Output(action Action, args ...string) error {
 | 
			
		||||
	a := append(nat, fmt.Sprint(action), "OUTPUT")
 | 
			
		||||
	if len(args) > 0 {
 | 
			
		||||
		a = append(a, args...)
 | 
			
		||||
	}
 | 
			
		||||
	return Raw(append(a, "-j", c.Name)...)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *Chain) Remove() error {
 | 
			
		||||
	// Ignore errors - This could mean the chains were never set up
 | 
			
		||||
	c.Prerouting(Delete, "-m", "addrtype", "--dst-type", "LOCAL")
 | 
			
		||||
	c.Output(Delete, "-m", "addrtype", "--dst-type", "LOCAL", "!", "--dst", "127.0.0.0/8")
 | 
			
		||||
	c.Output(Delete, "-m", "addrtype", "--dst-type", "LOCAL") // Created in versions <= 0.1.6
 | 
			
		||||
 | 
			
		||||
	c.Prerouting(Delete)
 | 
			
		||||
	c.Output(Delete)
 | 
			
		||||
 | 
			
		||||
	Raw("-t", "nat", "-F", c.Name)
 | 
			
		||||
	Raw("-t", "nat", "-X", c.Name)
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Raw(args ...string) error {
 | 
			
		||||
	path, err := exec.LookPath("iptables")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return ErrIptablesNotFound
 | 
			
		||||
	}
 | 
			
		||||
	if err := exec.Command(path, args...).Run(); err != nil {
 | 
			
		||||
		return fmt.Errorf("iptables failed: iptables %v", strings.Join(args, " "))
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										18
									
								
								iptables/iptables_test.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								iptables/iptables_test.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,18 @@
 | 
			
		|||
package iptables
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"os"
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestIptables(t *testing.T) {
 | 
			
		||||
	if err := Raw("-L"); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	path := os.Getenv("PATH")
 | 
			
		||||
	os.Setenv("PATH", "")
 | 
			
		||||
	defer os.Setenv("PATH", path)
 | 
			
		||||
	if err := Raw("-L"); err == nil {
 | 
			
		||||
		t.Fatal("Not finding iptables in the PATH should cause an error")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										141
									
								
								links.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										141
									
								
								links.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,141 @@
 | 
			
		|||
package docker
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"github.com/dotcloud/docker/iptables"
 | 
			
		||||
	"path"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Link struct {
 | 
			
		||||
	ParentIP         string
 | 
			
		||||
	ChildIP          string
 | 
			
		||||
	Name             string
 | 
			
		||||
	BridgeInterface  string
 | 
			
		||||
	ChildEnvironment []string
 | 
			
		||||
	Ports            []Port
 | 
			
		||||
	IsEnabled        bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewLink(parent, child *Container, name, bridgeInterface string) (*Link, error) {
 | 
			
		||||
	if parent.ID == child.ID {
 | 
			
		||||
		return nil, fmt.Errorf("Cannot link to self: %s == %s", parent.ID, child.ID)
 | 
			
		||||
	}
 | 
			
		||||
	if !child.State.Running {
 | 
			
		||||
		return nil, fmt.Errorf("Cannot link to a non running container: %s AS %s", child.ID, name)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ports := make([]Port, len(child.Config.ExposedPorts))
 | 
			
		||||
	var i int
 | 
			
		||||
	for p := range child.Config.ExposedPorts {
 | 
			
		||||
		ports[i] = p
 | 
			
		||||
		i++
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	l := &Link{
 | 
			
		||||
		BridgeInterface:  bridgeInterface,
 | 
			
		||||
		Name:             name,
 | 
			
		||||
		ChildIP:          child.NetworkSettings.IPAddress,
 | 
			
		||||
		ParentIP:         parent.NetworkSettings.IPAddress,
 | 
			
		||||
		ChildEnvironment: child.Config.Env,
 | 
			
		||||
		Ports:            ports,
 | 
			
		||||
	}
 | 
			
		||||
	return l, nil
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *Link) Alias() string {
 | 
			
		||||
	_, alias := path.Split(l.Name)
 | 
			
		||||
	return alias
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *Link) ToEnv() []string {
 | 
			
		||||
	env := []string{}
 | 
			
		||||
	alias := strings.ToUpper(l.Alias())
 | 
			
		||||
 | 
			
		||||
	if p := l.getDefaultPort(); p != nil {
 | 
			
		||||
		env = append(env, fmt.Sprintf("%s_PORT=%s://%s:%s", alias, p.Proto(), l.ChildIP, p.Port()))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Load exposed ports into the environment
 | 
			
		||||
	for _, p := range l.Ports {
 | 
			
		||||
		env = append(env, fmt.Sprintf("%s_PORT_%s_%s=%s://%s:%s", alias, p.Port(), strings.ToUpper(p.Proto()), p.Proto(), l.ChildIP, p.Port()))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Load the linked container's name into the environment
 | 
			
		||||
	env = append(env, fmt.Sprintf("%s_NAME=%s", alias, l.Name))
 | 
			
		||||
 | 
			
		||||
	if l.ChildEnvironment != nil {
 | 
			
		||||
		for _, v := range l.ChildEnvironment {
 | 
			
		||||
			parts := strings.Split(v, "=")
 | 
			
		||||
			if len(parts) != 2 {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			// Ignore a few variables that are added during docker build
 | 
			
		||||
			if parts[0] == "HOME" || parts[0] == "PATH" {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			env = append(env, fmt.Sprintf("%s_ENV_%s=%s", alias, parts[0], parts[1]))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return env
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Default port rules
 | 
			
		||||
func (l *Link) getDefaultPort() *Port {
 | 
			
		||||
	var p Port
 | 
			
		||||
	i := len(l.Ports)
 | 
			
		||||
 | 
			
		||||
	if i == 0 {
 | 
			
		||||
		return nil
 | 
			
		||||
	} else if i > 1 {
 | 
			
		||||
		sortPorts(l.Ports, func(ip, jp Port) bool {
 | 
			
		||||
			// If the two ports have the same number, tcp takes priority
 | 
			
		||||
			// Sort in desc order
 | 
			
		||||
			return ip.Int() < jp.Int() || (ip.Int() == jp.Int() && strings.ToLower(ip.Proto()) == "tcp")
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	p = l.Ports[0]
 | 
			
		||||
	return &p
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *Link) Enable() error {
 | 
			
		||||
	if err := l.toggle("-I", false); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	l.IsEnabled = true
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *Link) Disable() {
 | 
			
		||||
	// We do not care about errors here because the link may not
 | 
			
		||||
	// exist in iptables
 | 
			
		||||
	l.toggle("-D", true)
 | 
			
		||||
 | 
			
		||||
	l.IsEnabled = false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *Link) toggle(action string, ignoreErrors bool) error {
 | 
			
		||||
	for _, p := range l.Ports {
 | 
			
		||||
		if err := iptables.Raw(action, "FORWARD",
 | 
			
		||||
			"-i", l.BridgeInterface, "-o", l.BridgeInterface,
 | 
			
		||||
			"-p", p.Proto(),
 | 
			
		||||
			"-s", l.ParentIP,
 | 
			
		||||
			"--dport", p.Port(),
 | 
			
		||||
			"-d", l.ChildIP,
 | 
			
		||||
			"-j", "ACCEPT"); !ignoreErrors && err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := iptables.Raw(action, "FORWARD",
 | 
			
		||||
			"-i", l.BridgeInterface, "-o", l.BridgeInterface,
 | 
			
		||||
			"-p", p.Proto(),
 | 
			
		||||
			"-s", l.ChildIP,
 | 
			
		||||
			"--sport", p.Port(),
 | 
			
		||||
			"-d", l.ParentIP,
 | 
			
		||||
			"-j", "ACCEPT"); !ignoreErrors && err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										104
									
								
								links_test.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								links_test.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,104 @@
 | 
			
		|||
package docker
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func newMockLinkContainer(id string, ip string) *Container {
 | 
			
		||||
	return &Container{
 | 
			
		||||
		Config: &Config{},
 | 
			
		||||
		ID:     id,
 | 
			
		||||
		NetworkSettings: &NetworkSettings{
 | 
			
		||||
			IPAddress: ip,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestLinkNew(t *testing.T) {
 | 
			
		||||
	toID := GenerateID()
 | 
			
		||||
	fromID := GenerateID()
 | 
			
		||||
 | 
			
		||||
	from := newMockLinkContainer(fromID, "172.0.17.2")
 | 
			
		||||
	from.Config.Env = []string{}
 | 
			
		||||
	from.State = State{Running: true}
 | 
			
		||||
	ports := make(map[Port]struct{})
 | 
			
		||||
 | 
			
		||||
	ports[Port("6379/tcp")] = struct{}{}
 | 
			
		||||
 | 
			
		||||
	from.Config.ExposedPorts = ports
 | 
			
		||||
 | 
			
		||||
	to := newMockLinkContainer(toID, "172.0.17.3")
 | 
			
		||||
 | 
			
		||||
	link, err := NewLink(to, from, "/db/docker", "172.0.17.1")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if link == nil {
 | 
			
		||||
		t.FailNow()
 | 
			
		||||
	}
 | 
			
		||||
	if link.Name != "/db/docker" {
 | 
			
		||||
		t.Fail()
 | 
			
		||||
	}
 | 
			
		||||
	if link.Alias() != "docker" {
 | 
			
		||||
		t.Fail()
 | 
			
		||||
	}
 | 
			
		||||
	if link.ParentIP != "172.0.17.3" {
 | 
			
		||||
		t.Fail()
 | 
			
		||||
	}
 | 
			
		||||
	if link.ChildIP != "172.0.17.2" {
 | 
			
		||||
		t.Fail()
 | 
			
		||||
	}
 | 
			
		||||
	if link.BridgeInterface != "172.0.17.1" {
 | 
			
		||||
		t.Fail()
 | 
			
		||||
	}
 | 
			
		||||
	for _, p := range link.Ports {
 | 
			
		||||
		if p != Port("6379/tcp") {
 | 
			
		||||
			t.Fail()
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestLinkEnv(t *testing.T) {
 | 
			
		||||
	toID := GenerateID()
 | 
			
		||||
	fromID := GenerateID()
 | 
			
		||||
 | 
			
		||||
	from := newMockLinkContainer(fromID, "172.0.17.2")
 | 
			
		||||
	from.Config.Env = []string{"PASSWORD=gordon"}
 | 
			
		||||
	from.State = State{Running: true}
 | 
			
		||||
	ports := make(map[Port]struct{})
 | 
			
		||||
 | 
			
		||||
	ports[Port("6379/tcp")] = struct{}{}
 | 
			
		||||
 | 
			
		||||
	from.Config.ExposedPorts = ports
 | 
			
		||||
 | 
			
		||||
	to := newMockLinkContainer(toID, "172.0.17.3")
 | 
			
		||||
 | 
			
		||||
	link, err := NewLink(to, from, "/db/docker", "172.0.17.1")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	rawEnv := link.ToEnv()
 | 
			
		||||
	env := make(map[string]string, len(rawEnv))
 | 
			
		||||
	for _, e := range rawEnv {
 | 
			
		||||
		parts := strings.Split(e, "=")
 | 
			
		||||
		if len(parts) != 2 {
 | 
			
		||||
			t.FailNow()
 | 
			
		||||
		}
 | 
			
		||||
		env[parts[0]] = parts[1]
 | 
			
		||||
	}
 | 
			
		||||
	if env["DOCKER_PORT"] != "tcp://172.0.17.2:6379" {
 | 
			
		||||
		t.Fatalf("Expected tcp://172.0.17.2:6379, got %s", env["DOCKER_PORT"])
 | 
			
		||||
	}
 | 
			
		||||
	if env["DOCKER_PORT_6379_TCP"] != "tcp://172.0.17.2:6379" {
 | 
			
		||||
		t.Fatalf("Expected tcp://172.0.17.2:6379, got %s", env["DOCKER_PORT_6379_TCP"])
 | 
			
		||||
	}
 | 
			
		||||
	if env["DOCKER_NAME"] != "/db/docker" {
 | 
			
		||||
		t.Fatalf("Expected /db/docker, got %s", env["DOCKER_NAME"])
 | 
			
		||||
	}
 | 
			
		||||
	if env["DOCKER_ENV_PASSWORD"] != "gordon" {
 | 
			
		||||
		t.Fatalf("Expected gordon, got %s", env["DOCKER_ENV_PASSWORD"])
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										280
									
								
								network.go
									
										
									
									
									
								
							
							
						
						
									
										280
									
								
								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
 | 
			
		||||
	udpProxies map[int]proxy.Proxy
 | 
			
		||||
 | 
			
		||||
	iptables  *iptables.Chain
 | 
			
		||||
	defaultIp net.IP
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (mapper *PortMapper) cleanup() error {
 | 
			
		||||
	// Ignore errors - This could mean the chains were never set up
 | 
			
		||||
	iptables("-t", "nat", "-D", "PREROUTING", "-m", "addrtype", "--dst-type", "LOCAL", "-j", "DOCKER")
 | 
			
		||||
	iptables("-t", "nat", "-D", "OUTPUT", "-m", "addrtype", "--dst-type", "LOCAL", "!", "--dst", "127.0.0.0/8", "-j", "DOCKER")
 | 
			
		||||
	iptables("-t", "nat", "-D", "OUTPUT", "-m", "addrtype", "--dst-type", "LOCAL", "-j", "DOCKER") // Created in versions <= 0.1.6
 | 
			
		||||
	// Also cleanup rules created by older versions, or -X might fail.
 | 
			
		||||
	iptables("-t", "nat", "-D", "PREROUTING", "-j", "DOCKER")
 | 
			
		||||
	iptables("-t", "nat", "-D", "OUTPUT", "-j", "DOCKER")
 | 
			
		||||
	iptables("-t", "nat", "-F", "DOCKER")
 | 
			
		||||
	iptables("-t", "nat", "-X", "DOCKER")
 | 
			
		||||
	mapper.tcpMapping = make(map[int]*net.TCPAddr)
 | 
			
		||||
	mapper.tcpProxies = make(map[int]Proxy)
 | 
			
		||||
	mapper.udpMapping = make(map[int]*net.UDPAddr)
 | 
			
		||||
	mapper.udpProxies = make(map[int]Proxy)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (mapper *PortMapper) setup() error {
 | 
			
		||||
	if err := iptables("-t", "nat", "-N", "DOCKER"); err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to create DOCKER chain: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := iptables("-t", "nat", "-A", "PREROUTING", "-m", "addrtype", "--dst-type", "LOCAL", "-j", "DOCKER"); err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to inject docker in PREROUTING chain: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := iptables("-t", "nat", "-A", "OUTPUT", "-m", "addrtype", "--dst-type", "LOCAL", "!", "--dst", "127.0.0.0/8", "-j", "DOCKER"); err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to inject docker in OUTPUT chain: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (mapper *PortMapper) iptablesForward(rule string, port int, proto string, dest_addr string, dest_port int) error {
 | 
			
		||||
	return iptables("-t", "nat", rule, "DOCKER", "-p", proto, "--dport", strconv.Itoa(port),
 | 
			
		||||
		"!", "-i", NetworkBridgeIface,
 | 
			
		||||
		"-j", "DNAT", "--to-destination", net.JoinHostPort(dest_addr, strconv.Itoa(dest_port)))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (mapper *PortMapper) Map(port int, backendAddr net.Addr) error {
 | 
			
		||||
func (mapper *PortMapper) Map(ip net.IP, port int, backendAddr net.Addr) error {
 | 
			
		||||
	if _, isTCP := backendAddr.(*net.TCPAddr); isTCP {
 | 
			
		||||
		backendPort := backendAddr.(*net.TCPAddr).Port
 | 
			
		||||
		backendIP := backendAddr.(*net.TCPAddr).IP
 | 
			
		||||
		if err := mapper.iptablesForward("-A", port, "tcp", backendIP.String(), backendPort); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		if mapper.iptables != nil {
 | 
			
		||||
			if err := mapper.iptables.Forward(iptables.Add, ip, port, "tcp", backendIP.String(), backendPort); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		mapper.tcpMapping[port] = backendAddr.(*net.TCPAddr)
 | 
			
		||||
		proxy, err := NewProxy(&net.TCPAddr{IP: net.IPv4(0, 0, 0, 0), Port: port}, backendAddr)
 | 
			
		||||
		proxy, err := proxy.NewProxy(&net.TCPAddr{IP: ip, Port: port}, backendAddr)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			mapper.Unmap(port, "tcp")
 | 
			
		||||
			mapper.Unmap(ip, port, "tcp")
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		mapper.tcpProxies[port] = proxy
 | 
			
		||||
| 
						 | 
				
			
			@ -275,13 +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
 | 
			
		||||
		}
 | 
			
		||||
		if nat.Proto == "tcp" {
 | 
			
		||||
			if err := iface.manager.tcpPortAllocator.Release(nat.Frontend); err != nil {
 | 
			
		||||
				log.Printf("Unable to release port tcp/%v: %v", nat.Frontend, err)
 | 
			
		||||
		ip := net.ParseIP(nat.Binding.HostIp)
 | 
			
		||||
		utils.Debugf("Unmaping %s/%s", nat.Port.Proto, nat.Binding.HostPort)
 | 
			
		||||
		if err := iface.manager.portMapper.Unmap(ip, hostPort, nat.Port.Proto()); err != nil {
 | 
			
		||||
			log.Printf("Unable to unmap port %s: %s", nat, err)
 | 
			
		||||
		}
 | 
			
		||||
		if nat.Port.Proto() == "tcp" {
 | 
			
		||||
			if err := iface.manager.tcpPortAllocator.Release(hostPort); err != nil {
 | 
			
		||||
				log.Printf("Unable to release port %s", nat)
 | 
			
		||||
			}
 | 
			
		||||
		} else if err := iface.manager.udpPortAllocator.Release(nat.Frontend); err != nil {
 | 
			
		||||
			log.Printf("Unable to release port udp/%v: %v", nat.Frontend, err)
 | 
			
		||||
		} else if err := iface.manager.udpPortAllocator.Release(hostPort); err != nil {
 | 
			
		||||
			log.Printf("Unable to release port %s: %s", nat, err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -704,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,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										108
									
								
								network_test.go
									
										
									
									
									
								
							
							
						
						
									
										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
									
								
								proxy/MAINTAINERS
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								proxy/MAINTAINERS
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
Michael Crosby <michael@crosbymichael.com> (@crosbymichael)
 | 
			
		||||
| 
						 | 
				
			
			@ -1,4 +1,4 @@
 | 
			
		|||
package docker
 | 
			
		||||
package proxy
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
							
								
								
									
										29
									
								
								proxy/proxy.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								proxy/proxy.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,29 @@
 | 
			
		|||
package proxy
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Proxy interface {
 | 
			
		||||
	// Start forwarding traffic back and forth the front and back-end
 | 
			
		||||
	// addresses.
 | 
			
		||||
	Run()
 | 
			
		||||
	// Stop forwarding traffic and close both ends of the Proxy.
 | 
			
		||||
	Close()
 | 
			
		||||
	// Return the address on which the proxy is listening.
 | 
			
		||||
	FrontendAddr() net.Addr
 | 
			
		||||
	// Return the proxied address.
 | 
			
		||||
	BackendAddr() net.Addr
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewProxy(frontendAddr, backendAddr net.Addr) (Proxy, error) {
 | 
			
		||||
	switch frontendAddr.(type) {
 | 
			
		||||
	case *net.UDPAddr:
 | 
			
		||||
		return NewUDPProxy(frontendAddr.(*net.UDPAddr), backendAddr.(*net.UDPAddr))
 | 
			
		||||
	case *net.TCPAddr:
 | 
			
		||||
		return NewTCPProxy(frontendAddr.(*net.TCPAddr), backendAddr.(*net.TCPAddr))
 | 
			
		||||
	default:
 | 
			
		||||
		panic(fmt.Errorf("Unsupported protocol"))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										93
									
								
								proxy/tcp_proxy.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								proxy/tcp_proxy.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,93 @@
 | 
			
		|||
package proxy
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/dotcloud/docker/utils"
 | 
			
		||||
	"io"
 | 
			
		||||
	"log"
 | 
			
		||||
	"net"
 | 
			
		||||
	"syscall"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type TCPProxy struct {
 | 
			
		||||
	listener     *net.TCPListener
 | 
			
		||||
	frontendAddr *net.TCPAddr
 | 
			
		||||
	backendAddr  *net.TCPAddr
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewTCPProxy(frontendAddr, backendAddr *net.TCPAddr) (*TCPProxy, error) {
 | 
			
		||||
	listener, err := net.ListenTCP("tcp", frontendAddr)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	// If the port in frontendAddr was 0 then ListenTCP will have a picked
 | 
			
		||||
	// a port to listen on, hence the call to Addr to get that actual port:
 | 
			
		||||
	return &TCPProxy{
 | 
			
		||||
		listener:     listener,
 | 
			
		||||
		frontendAddr: listener.Addr().(*net.TCPAddr),
 | 
			
		||||
		backendAddr:  backendAddr,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (proxy *TCPProxy) clientLoop(client *net.TCPConn, quit chan bool) {
 | 
			
		||||
	backend, err := net.DialTCP("tcp", nil, proxy.backendAddr)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Printf("Can't forward traffic to backend tcp/%v: %v\n", proxy.backendAddr, err.Error())
 | 
			
		||||
		client.Close()
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	event := make(chan int64)
 | 
			
		||||
	var broker = func(to, from *net.TCPConn) {
 | 
			
		||||
		written, err := io.Copy(to, from)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			// If the socket we are writing to is shutdown with
 | 
			
		||||
			// SHUT_WR, forward it to the other end of the pipe:
 | 
			
		||||
			if err, ok := err.(*net.OpError); ok && err.Err == syscall.EPIPE {
 | 
			
		||||
				from.CloseWrite()
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		to.CloseRead()
 | 
			
		||||
		event <- written
 | 
			
		||||
	}
 | 
			
		||||
	utils.Debugf("Forwarding traffic between tcp/%v and tcp/%v", client.RemoteAddr(), backend.RemoteAddr())
 | 
			
		||||
	go broker(client, backend)
 | 
			
		||||
	go broker(backend, client)
 | 
			
		||||
 | 
			
		||||
	var transferred int64 = 0
 | 
			
		||||
	for i := 0; i < 2; i++ {
 | 
			
		||||
		select {
 | 
			
		||||
		case written := <-event:
 | 
			
		||||
			transferred += written
 | 
			
		||||
		case <-quit:
 | 
			
		||||
			// Interrupt the two brokers and "join" them.
 | 
			
		||||
			client.Close()
 | 
			
		||||
			backend.Close()
 | 
			
		||||
			for ; i < 2; i++ {
 | 
			
		||||
				transferred += <-event
 | 
			
		||||
			}
 | 
			
		||||
			goto done
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	client.Close()
 | 
			
		||||
	backend.Close()
 | 
			
		||||
done:
 | 
			
		||||
	utils.Debugf("%v bytes transferred between tcp/%v and tcp/%v", transferred, client.RemoteAddr(), backend.RemoteAddr())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (proxy *TCPProxy) Run() {
 | 
			
		||||
	quit := make(chan bool)
 | 
			
		||||
	defer close(quit)
 | 
			
		||||
	utils.Debugf("Starting proxy on tcp/%v for tcp/%v", proxy.frontendAddr, proxy.backendAddr)
 | 
			
		||||
	for {
 | 
			
		||||
		client, err := proxy.listener.Accept()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			utils.Debugf("Stopping proxy on tcp/%v for tcp/%v (%v)", proxy.frontendAddr, proxy.backendAddr, err.Error())
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		go proxy.clientLoop(client.(*net.TCPConn), quit)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (proxy *TCPProxy) Close()                 { proxy.listener.Close() }
 | 
			
		||||
func (proxy *TCPProxy) FrontendAddr() net.Addr { return proxy.frontendAddr }
 | 
			
		||||
func (proxy *TCPProxy) BackendAddr() net.Addr  { return proxy.backendAddr }
 | 
			
		||||
| 
						 | 
				
			
			@ -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"))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										214
									
								
								runtime.go
									
										
									
									
									
								
							
							
						
						
									
										214
									
								
								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 err := MergeConfig(config, img.Config); err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		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, 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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FIXME: harmonize with NewGraph()
 | 
			
		||||
func NewRuntime(flGraphPath string, autoRestart bool, dns []string) (*Runtime, error) {
 | 
			
		||||
	runtime, err := NewRuntimeFromDirectory(flGraphPath, autoRestart)
 | 
			
		||||
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(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(NetworkBridgeIface)
 | 
			
		||||
	netManager, err := newNetworkManager(config)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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 {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										340
									
								
								runtime_test.go
									
										
									
									
									
								
							
							
						
						
									
										340
									
								
								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)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										129
									
								
								server.go
									
										
									
									
									
								
							
							
						
						
									
										129
									
								
								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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
									
								
								sorter.go
									
										
									
									
									
								
							
							
						
						
									
										69
									
								
								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)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										153
									
								
								utils.go
									
										
									
									
									
								
							
							
						
						
									
										153
									
								
								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
 | 
			
		||||
	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{}{}
 | 
			
		||||
			}
 | 
			
		||||
			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 !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)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										186
									
								
								utils_test.go
									
										
									
									
									
								
							
							
						
						
									
										186
									
								
								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,48 +288,6 @@ func TestMergeConfig(t *testing.T) {
 | 
			
		|||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestMergeConfigPublicPortNotHonored(t *testing.T) {
 | 
			
		||||
	volumesImage := make(map[string]struct{})
 | 
			
		||||
	volumesImage["/test1"] = struct{}{}
 | 
			
		||||
	volumesImage["/test2"] = struct{}{}
 | 
			
		||||
	configImage := &Config{
 | 
			
		||||
		Dns:       []string{"1.1.1.1", "2.2.2.2"},
 | 
			
		||||
		PortSpecs: []string{"1111", "2222"},
 | 
			
		||||
		Env:       []string{"VAR1=1", "VAR2=2"},
 | 
			
		||||
		Volumes:   volumesImage,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	volumesUser := make(map[string]struct{})
 | 
			
		||||
	volumesUser["/test3"] = struct{}{}
 | 
			
		||||
	configUser := &Config{
 | 
			
		||||
		Dns:       []string{"3.3.3.3"},
 | 
			
		||||
		PortSpecs: []string{"1111:3333"},
 | 
			
		||||
		Env:       []string{"VAR2=3", "VAR3=3"},
 | 
			
		||||
		Volumes:   volumesUser,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	MergeConfig(configUser, configImage)
 | 
			
		||||
 | 
			
		||||
	contains := func(a []string, expect string) bool {
 | 
			
		||||
		for _, p := range a {
 | 
			
		||||
			if p == expect {
 | 
			
		||||
				return true
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !contains(configUser.PortSpecs, "2222") {
 | 
			
		||||
		t.Logf("Expected '2222' Ports: %v", configUser.PortSpecs)
 | 
			
		||||
		t.Fail()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !contains(configUser.PortSpecs, "1111:3333") {
 | 
			
		||||
		t.Logf("Expected '1111:3333' Ports: %v", configUser.PortSpecs)
 | 
			
		||||
		t.Fail()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestParseLxcConfOpt(t *testing.T) {
 | 
			
		||||
	opts := []string{"lxc.utsname=docker", "lxc.utsname = docker "}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -342,3 +304,129 @@ func TestParseLxcConfOpt(t *testing.T) {
 | 
			
		|||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestParseNetworkOptsPrivateOnly(t *testing.T) {
 | 
			
		||||
	ports, bindings, err := parsePortSpecs([]string{"192.168.1.100::80"})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if len(ports) != 1 {
 | 
			
		||||
		t.Logf("Expected 1 got %d", len(ports))
 | 
			
		||||
		t.FailNow()
 | 
			
		||||
	}
 | 
			
		||||
	if len(bindings) != 1 {
 | 
			
		||||
		t.Logf("Expected 1 got %d", len(bindings))
 | 
			
		||||
		t.FailNow()
 | 
			
		||||
	}
 | 
			
		||||
	for k := range ports {
 | 
			
		||||
		if k.Proto() != "tcp" {
 | 
			
		||||
			t.Logf("Expected tcp got %s", k.Proto())
 | 
			
		||||
			t.Fail()
 | 
			
		||||
		}
 | 
			
		||||
		if k.Port() != "80" {
 | 
			
		||||
			t.Logf("Expected 80 got %s", k.Port())
 | 
			
		||||
			t.Fail()
 | 
			
		||||
		}
 | 
			
		||||
		b, exists := bindings[k]
 | 
			
		||||
		if !exists {
 | 
			
		||||
			t.Log("Binding does not exist")
 | 
			
		||||
			t.FailNow()
 | 
			
		||||
		}
 | 
			
		||||
		if len(b) != 1 {
 | 
			
		||||
			t.Logf("Expected 1 got %d", len(b))
 | 
			
		||||
			t.FailNow()
 | 
			
		||||
		}
 | 
			
		||||
		s := b[0]
 | 
			
		||||
		if s.HostPort != "" {
 | 
			
		||||
			t.Logf("Expected \"\" got %s", s.HostPort)
 | 
			
		||||
			t.Fail()
 | 
			
		||||
		}
 | 
			
		||||
		if s.HostIp != "192.168.1.100" {
 | 
			
		||||
			t.Fail()
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestParseNetworkOptsPublic(t *testing.T) {
 | 
			
		||||
	ports, bindings, err := parsePortSpecs([]string{"192.168.1.100:8080:80"})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if len(ports) != 1 {
 | 
			
		||||
		t.Logf("Expected 1 got %d", len(ports))
 | 
			
		||||
		t.FailNow()
 | 
			
		||||
	}
 | 
			
		||||
	if len(bindings) != 1 {
 | 
			
		||||
		t.Logf("Expected 1 got %d", len(bindings))
 | 
			
		||||
		t.FailNow()
 | 
			
		||||
	}
 | 
			
		||||
	for k := range ports {
 | 
			
		||||
		if k.Proto() != "tcp" {
 | 
			
		||||
			t.Logf("Expected tcp got %s", k.Proto())
 | 
			
		||||
			t.Fail()
 | 
			
		||||
		}
 | 
			
		||||
		if k.Port() != "80" {
 | 
			
		||||
			t.Logf("Expected 80 got %s", k.Port())
 | 
			
		||||
			t.Fail()
 | 
			
		||||
		}
 | 
			
		||||
		b, exists := bindings[k]
 | 
			
		||||
		if !exists {
 | 
			
		||||
			t.Log("Binding does not exist")
 | 
			
		||||
			t.FailNow()
 | 
			
		||||
		}
 | 
			
		||||
		if len(b) != 1 {
 | 
			
		||||
			t.Logf("Expected 1 got %d", len(b))
 | 
			
		||||
			t.FailNow()
 | 
			
		||||
		}
 | 
			
		||||
		s := b[0]
 | 
			
		||||
		if s.HostPort != "8080" {
 | 
			
		||||
			t.Logf("Expected 8080 got %s", s.HostPort)
 | 
			
		||||
			t.Fail()
 | 
			
		||||
		}
 | 
			
		||||
		if s.HostIp != "192.168.1.100" {
 | 
			
		||||
			t.Fail()
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestParseNetworkOptsUdp(t *testing.T) {
 | 
			
		||||
	ports, bindings, err := parsePortSpecs([]string{"192.168.1.100::6000/udp"})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if len(ports) != 1 {
 | 
			
		||||
		t.Logf("Expected 1 got %d", len(ports))
 | 
			
		||||
		t.FailNow()
 | 
			
		||||
	}
 | 
			
		||||
	if len(bindings) != 1 {
 | 
			
		||||
		t.Logf("Expected 1 got %d", len(bindings))
 | 
			
		||||
		t.FailNow()
 | 
			
		||||
	}
 | 
			
		||||
	for k := range ports {
 | 
			
		||||
		if k.Proto() != "udp" {
 | 
			
		||||
			t.Logf("Expected udp got %s", k.Proto())
 | 
			
		||||
			t.Fail()
 | 
			
		||||
		}
 | 
			
		||||
		if k.Port() != "6000" {
 | 
			
		||||
			t.Logf("Expected 6000 got %s", k.Port())
 | 
			
		||||
			t.Fail()
 | 
			
		||||
		}
 | 
			
		||||
		b, exists := bindings[k]
 | 
			
		||||
		if !exists {
 | 
			
		||||
			t.Log("Binding does not exist")
 | 
			
		||||
			t.FailNow()
 | 
			
		||||
		}
 | 
			
		||||
		if len(b) != 1 {
 | 
			
		||||
			t.Logf("Expected 1 got %d", len(b))
 | 
			
		||||
			t.FailNow()
 | 
			
		||||
		}
 | 
			
		||||
		s := b[0]
 | 
			
		||||
		if s.HostPort != "" {
 | 
			
		||||
			t.Logf("Expected \"\" got %s", s.HostPort)
 | 
			
		||||
			t.Fail()
 | 
			
		||||
		}
 | 
			
		||||
		if s.HostIp != "192.168.1.100" {
 | 
			
		||||
			t.Fail()
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										404
									
								
								vendor/src/code.google.com/p/gosqlite/sqlite/sqlite.go
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										404
									
								
								vendor/src/code.google.com/p/gosqlite/sqlite/sqlite.go
									
										
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,404 @@
 | 
			
		|||
// Copyright 2010 The Go Authors. All rights reserved.
 | 
			
		||||
// Use of this source code is governed by a BSD-style
 | 
			
		||||
// license that can be found in the LICENSE file.
 | 
			
		||||
 | 
			
		||||
// Package sqlite provides access to the SQLite library, version 3.
 | 
			
		||||
package sqlite
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
#cgo LDFLAGS: -lsqlite3
 | 
			
		||||
 | 
			
		||||
#include <sqlite3.h>
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
 | 
			
		||||
// These wrappers are necessary because SQLITE_TRANSIENT
 | 
			
		||||
// is a pointer constant, and cgo doesn't translate them correctly.
 | 
			
		||||
// The definition in sqlite3.h is:
 | 
			
		||||
//
 | 
			
		||||
// typedef void (*sqlite3_destructor_type)(void*);
 | 
			
		||||
// #define SQLITE_STATIC      ((sqlite3_destructor_type)0)
 | 
			
		||||
// #define SQLITE_TRANSIENT   ((sqlite3_destructor_type)-1)
 | 
			
		||||
 | 
			
		||||
static int my_bind_text(sqlite3_stmt *stmt, int n, char *p, int np) {
 | 
			
		||||
	return sqlite3_bind_text(stmt, n, p, np, SQLITE_TRANSIENT);
 | 
			
		||||
}
 | 
			
		||||
static int my_bind_blob(sqlite3_stmt *stmt, int n, void *p, int np) {
 | 
			
		||||
	return sqlite3_bind_blob(stmt, n, p, np, SQLITE_TRANSIENT);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
*/
 | 
			
		||||
import "C"
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"time"
 | 
			
		||||
	"unsafe"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Errno int
 | 
			
		||||
 | 
			
		||||
func (e Errno) Error() string {
 | 
			
		||||
	s := errText[e]
 | 
			
		||||
	if s == "" {
 | 
			
		||||
		return fmt.Sprintf("errno %d", int(e))
 | 
			
		||||
	}
 | 
			
		||||
	return s
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	ErrError      error = Errno(1)   //    /* SQL error or missing database */
 | 
			
		||||
	ErrInternal   error = Errno(2)   //    /* Internal logic error in SQLite */
 | 
			
		||||
	ErrPerm       error = Errno(3)   //    /* Access permission denied */
 | 
			
		||||
	ErrAbort      error = Errno(4)   //    /* Callback routine requested an abort */
 | 
			
		||||
	ErrBusy       error = Errno(5)   //    /* The database file is locked */
 | 
			
		||||
	ErrLocked     error = Errno(6)   //    /* A table in the database is locked */
 | 
			
		||||
	ErrNoMem      error = Errno(7)   //    /* A malloc() failed */
 | 
			
		||||
	ErrReadOnly   error = Errno(8)   //    /* Attempt to write a readonly database */
 | 
			
		||||
	ErrInterrupt  error = Errno(9)   //    /* Operation terminated by sqlite3_interrupt()*/
 | 
			
		||||
	ErrIOErr      error = Errno(10)  //    /* Some kind of disk I/O error occurred */
 | 
			
		||||
	ErrCorrupt    error = Errno(11)  //    /* The database disk image is malformed */
 | 
			
		||||
	ErrFull       error = Errno(13)  //    /* Insertion failed because database is full */
 | 
			
		||||
	ErrCantOpen   error = Errno(14)  //    /* Unable to open the database file */
 | 
			
		||||
	ErrEmpty      error = Errno(16)  //    /* Database is empty */
 | 
			
		||||
	ErrSchema     error = Errno(17)  //    /* The database schema changed */
 | 
			
		||||
	ErrTooBig     error = Errno(18)  //    /* String or BLOB exceeds size limit */
 | 
			
		||||
	ErrConstraint error = Errno(19)  //    /* Abort due to constraint violation */
 | 
			
		||||
	ErrMismatch   error = Errno(20)  //    /* Data type mismatch */
 | 
			
		||||
	ErrMisuse     error = Errno(21)  //    /* Library used incorrectly */
 | 
			
		||||
	ErrNolfs      error = Errno(22)  //    /* Uses OS features not supported on host */
 | 
			
		||||
	ErrAuth       error = Errno(23)  //    /* Authorization denied */
 | 
			
		||||
	ErrFormat     error = Errno(24)  //    /* Auxiliary database format error */
 | 
			
		||||
	ErrRange      error = Errno(25)  //    /* 2nd parameter to sqlite3_bind out of range */
 | 
			
		||||
	ErrNotDB      error = Errno(26)  //    /* File opened that is not a database file */
 | 
			
		||||
	Row                 = Errno(100) //   /* sqlite3_step() has another row ready */
 | 
			
		||||
	Done                = Errno(101) //   /* sqlite3_step() has finished executing */
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var errText = map[Errno]string{
 | 
			
		||||
	1:   "SQL error or missing database",
 | 
			
		||||
	2:   "Internal logic error in SQLite",
 | 
			
		||||
	3:   "Access permission denied",
 | 
			
		||||
	4:   "Callback routine requested an abort",
 | 
			
		||||
	5:   "The database file is locked",
 | 
			
		||||
	6:   "A table in the database is locked",
 | 
			
		||||
	7:   "A malloc() failed",
 | 
			
		||||
	8:   "Attempt to write a readonly database",
 | 
			
		||||
	9:   "Operation terminated by sqlite3_interrupt()*/",
 | 
			
		||||
	10:  "Some kind of disk I/O error occurred",
 | 
			
		||||
	11:  "The database disk image is malformed",
 | 
			
		||||
	12:  "NOT USED. Table or record not found",
 | 
			
		||||
	13:  "Insertion failed because database is full",
 | 
			
		||||
	14:  "Unable to open the database file",
 | 
			
		||||
	15:  "NOT USED. Database lock protocol error",
 | 
			
		||||
	16:  "Database is empty",
 | 
			
		||||
	17:  "The database schema changed",
 | 
			
		||||
	18:  "String or BLOB exceeds size limit",
 | 
			
		||||
	19:  "Abort due to constraint violation",
 | 
			
		||||
	20:  "Data type mismatch",
 | 
			
		||||
	21:  "Library used incorrectly",
 | 
			
		||||
	22:  "Uses OS features not supported on host",
 | 
			
		||||
	23:  "Authorization denied",
 | 
			
		||||
	24:  "Auxiliary database format error",
 | 
			
		||||
	25:  "2nd parameter to sqlite3_bind out of range",
 | 
			
		||||
	26:  "File opened that is not a database file",
 | 
			
		||||
	100: "sqlite3_step() has another row ready",
 | 
			
		||||
	101: "sqlite3_step() has finished executing",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *Conn) error(rv C.int) error {
 | 
			
		||||
	if c == nil || c.db == nil {
 | 
			
		||||
		return errors.New("nil sqlite database")
 | 
			
		||||
	}
 | 
			
		||||
	if rv == 0 {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	if rv == 21 { // misuse
 | 
			
		||||
		return Errno(rv)
 | 
			
		||||
	}
 | 
			
		||||
	return errors.New(Errno(rv).Error() + ": " + C.GoString(C.sqlite3_errmsg(c.db)))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Conn struct {
 | 
			
		||||
	db *C.sqlite3
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Version() string {
 | 
			
		||||
	p := C.sqlite3_libversion()
 | 
			
		||||
	return C.GoString(p)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Open(filename string) (*Conn, error) {
 | 
			
		||||
	if C.sqlite3_threadsafe() == 0 {
 | 
			
		||||
		return nil, errors.New("sqlite library was not compiled for thread-safe operation")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var db *C.sqlite3
 | 
			
		||||
	name := C.CString(filename)
 | 
			
		||||
	defer C.free(unsafe.Pointer(name))
 | 
			
		||||
	rv := C.sqlite3_open_v2(name, &db,
 | 
			
		||||
		C.SQLITE_OPEN_FULLMUTEX|
 | 
			
		||||
			C.SQLITE_OPEN_READWRITE|
 | 
			
		||||
			C.SQLITE_OPEN_CREATE,
 | 
			
		||||
		nil)
 | 
			
		||||
	if rv != 0 {
 | 
			
		||||
		return nil, Errno(rv)
 | 
			
		||||
	}
 | 
			
		||||
	if db == nil {
 | 
			
		||||
		return nil, errors.New("sqlite succeeded without returning a database")
 | 
			
		||||
	}
 | 
			
		||||
	return &Conn{db}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewBackup(dst *Conn, dstTable string, src *Conn, srcTable string) (*Backup, error) {
 | 
			
		||||
	dname := C.CString(dstTable)
 | 
			
		||||
	sname := C.CString(srcTable)
 | 
			
		||||
	defer C.free(unsafe.Pointer(dname))
 | 
			
		||||
	defer C.free(unsafe.Pointer(sname))
 | 
			
		||||
 | 
			
		||||
	sb := C.sqlite3_backup_init(dst.db, dname, src.db, sname)
 | 
			
		||||
	if sb == nil {
 | 
			
		||||
		return nil, dst.error(C.sqlite3_errcode(dst.db))
 | 
			
		||||
	}
 | 
			
		||||
	return &Backup{sb, dst, src}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Backup struct {
 | 
			
		||||
	sb       *C.sqlite3_backup
 | 
			
		||||
	dst, src *Conn
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Backup) Step(npage int) error {
 | 
			
		||||
	rv := C.sqlite3_backup_step(b.sb, C.int(npage))
 | 
			
		||||
	if rv == 0 || Errno(rv) == ErrBusy || Errno(rv) == ErrLocked {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	return Errno(rv)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type BackupStatus struct {
 | 
			
		||||
	Remaining int
 | 
			
		||||
	PageCount int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Backup) Status() BackupStatus {
 | 
			
		||||
	return BackupStatus{int(C.sqlite3_backup_remaining(b.sb)), int(C.sqlite3_backup_pagecount(b.sb))}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Backup) Run(npage int, period time.Duration, c chan<- BackupStatus) error {
 | 
			
		||||
	var err error
 | 
			
		||||
	for {
 | 
			
		||||
		err = b.Step(npage)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		if c != nil {
 | 
			
		||||
			c <- b.Status()
 | 
			
		||||
		}
 | 
			
		||||
		time.Sleep(period)
 | 
			
		||||
	}
 | 
			
		||||
	return b.dst.error(C.sqlite3_errcode(b.dst.db))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Backup) Close() error {
 | 
			
		||||
	if b.sb == nil {
 | 
			
		||||
		return errors.New("backup already closed")
 | 
			
		||||
	}
 | 
			
		||||
	C.sqlite3_backup_finish(b.sb)
 | 
			
		||||
	b.sb = nil
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *Conn) BusyTimeout(ms int) error {
 | 
			
		||||
	rv := C.sqlite3_busy_timeout(c.db, C.int(ms))
 | 
			
		||||
	if rv == 0 {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	return Errno(rv)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *Conn) Exec(cmd string, args ...interface{}) error {
 | 
			
		||||
	s, err := c.Prepare(cmd)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	defer s.Finalize()
 | 
			
		||||
	err = s.Exec(args...)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	rv := C.sqlite3_step(s.stmt)
 | 
			
		||||
	if Errno(rv) != Done {
 | 
			
		||||
		return c.error(rv)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Stmt struct {
 | 
			
		||||
	c    *Conn
 | 
			
		||||
	stmt *C.sqlite3_stmt
 | 
			
		||||
	err  error
 | 
			
		||||
	t0   time.Time
 | 
			
		||||
	sql  string
 | 
			
		||||
	args string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *Conn) Prepare(cmd string) (*Stmt, error) {
 | 
			
		||||
	if c == nil || c.db == nil {
 | 
			
		||||
		return nil, errors.New("nil sqlite database")
 | 
			
		||||
	}
 | 
			
		||||
	cmdstr := C.CString(cmd)
 | 
			
		||||
	defer C.free(unsafe.Pointer(cmdstr))
 | 
			
		||||
	var stmt *C.sqlite3_stmt
 | 
			
		||||
	var tail *C.char
 | 
			
		||||
	rv := C.sqlite3_prepare_v2(c.db, cmdstr, C.int(len(cmd)+1), &stmt, &tail)
 | 
			
		||||
	if rv != 0 {
 | 
			
		||||
		return nil, c.error(rv)
 | 
			
		||||
	}
 | 
			
		||||
	return &Stmt{c: c, stmt: stmt, sql: cmd, t0: time.Now()}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *Stmt) Exec(args ...interface{}) error {
 | 
			
		||||
	s.args = fmt.Sprintf(" %v", []interface{}(args))
 | 
			
		||||
	rv := C.sqlite3_reset(s.stmt)
 | 
			
		||||
	if rv != 0 {
 | 
			
		||||
		return s.c.error(rv)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	n := int(C.sqlite3_bind_parameter_count(s.stmt))
 | 
			
		||||
	if n != len(args) {
 | 
			
		||||
		return errors.New(fmt.Sprintf("incorrect argument count for Stmt.Exec: have %d want %d", len(args), n))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i, v := range args {
 | 
			
		||||
		var str string
 | 
			
		||||
		switch v := v.(type) {
 | 
			
		||||
		case []byte:
 | 
			
		||||
			var p *byte
 | 
			
		||||
			if len(v) > 0 {
 | 
			
		||||
				p = &v[0]
 | 
			
		||||
			}
 | 
			
		||||
			if rv := C.my_bind_blob(s.stmt, C.int(i+1), unsafe.Pointer(p), C.int(len(v))); rv != 0 {
 | 
			
		||||
				return s.c.error(rv)
 | 
			
		||||
			}
 | 
			
		||||
			continue
 | 
			
		||||
 | 
			
		||||
		case bool:
 | 
			
		||||
			if v {
 | 
			
		||||
				str = "1"
 | 
			
		||||
			} else {
 | 
			
		||||
				str = "0"
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		default:
 | 
			
		||||
			str = fmt.Sprint(v)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		cstr := C.CString(str)
 | 
			
		||||
		rv := C.my_bind_text(s.stmt, C.int(i+1), cstr, C.int(len(str)))
 | 
			
		||||
		C.free(unsafe.Pointer(cstr))
 | 
			
		||||
		if rv != 0 {
 | 
			
		||||
			return s.c.error(rv)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *Stmt) Error() error {
 | 
			
		||||
	return s.err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *Stmt) Next() bool {
 | 
			
		||||
	rv := C.sqlite3_step(s.stmt)
 | 
			
		||||
	err := Errno(rv)
 | 
			
		||||
	if err == Row {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	if err != Done {
 | 
			
		||||
		s.err = s.c.error(rv)
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *Stmt) Reset() error {
 | 
			
		||||
	C.sqlite3_reset(s.stmt)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *Stmt) Scan(args ...interface{}) error {
 | 
			
		||||
	n := int(C.sqlite3_column_count(s.stmt))
 | 
			
		||||
	if n != len(args) {
 | 
			
		||||
		return errors.New(fmt.Sprintf("incorrect argument count for Stmt.Scan: have %d want %d", len(args), n))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i, v := range args {
 | 
			
		||||
		n := C.sqlite3_column_bytes(s.stmt, C.int(i))
 | 
			
		||||
		p := C.sqlite3_column_blob(s.stmt, C.int(i))
 | 
			
		||||
		if p == nil && n > 0 {
 | 
			
		||||
			return errors.New("got nil blob")
 | 
			
		||||
		}
 | 
			
		||||
		var data []byte
 | 
			
		||||
		if n > 0 {
 | 
			
		||||
			data = (*[1 << 30]byte)(unsafe.Pointer(p))[0:n]
 | 
			
		||||
		}
 | 
			
		||||
		switch v := v.(type) {
 | 
			
		||||
		case *[]byte:
 | 
			
		||||
			*v = data
 | 
			
		||||
		case *string:
 | 
			
		||||
			*v = string(data)
 | 
			
		||||
		case *bool:
 | 
			
		||||
			*v = string(data) == "1"
 | 
			
		||||
		case *int:
 | 
			
		||||
			x, err := strconv.Atoi(string(data))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return errors.New("arg " + strconv.Itoa(i) + " as int: " + err.Error())
 | 
			
		||||
			}
 | 
			
		||||
			*v = x
 | 
			
		||||
		case *int64:
 | 
			
		||||
			x, err := strconv.ParseInt(string(data), 10, 64)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return errors.New("arg " + strconv.Itoa(i) + " as int64: " + err.Error())
 | 
			
		||||
			}
 | 
			
		||||
			*v = x
 | 
			
		||||
		case *float64:
 | 
			
		||||
			x, err := strconv.ParseFloat(string(data), 64)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return errors.New("arg " + strconv.Itoa(i) + " as float64: " + err.Error())
 | 
			
		||||
			}
 | 
			
		||||
			*v = x
 | 
			
		||||
		default:
 | 
			
		||||
			return errors.New("unsupported type in Scan: " + reflect.TypeOf(v).String())
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *Stmt) SQL() string {
 | 
			
		||||
	return s.sql + s.args
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *Stmt) Nanoseconds() int64 {
 | 
			
		||||
	return time.Now().Sub(s.t0).Nanoseconds()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *Stmt) Finalize() error {
 | 
			
		||||
	rv := C.sqlite3_finalize(s.stmt)
 | 
			
		||||
	if rv != 0 {
 | 
			
		||||
		return s.c.error(rv)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *Conn) Close() error {
 | 
			
		||||
	if c == nil || c.db == nil {
 | 
			
		||||
		return errors.New("nil sqlite database")
 | 
			
		||||
	}
 | 
			
		||||
	rv := C.sqlite3_close(c.db)
 | 
			
		||||
	if rv != 0 {
 | 
			
		||||
		return c.error(rv)
 | 
			
		||||
	}
 | 
			
		||||
	c.db = nil
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										498
									
								
								vendor/src/code.google.com/p/gosqlite/sqlite3/driver.go
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										498
									
								
								vendor/src/code.google.com/p/gosqlite/sqlite3/driver.go
									
										
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,498 @@
 | 
			
		|||
// Copyright 2010 The Go Authors. All rights reserved.
 | 
			
		||||
// Use of this source code is governed by a BSD-style
 | 
			
		||||
// license that can be found in the LICENSE file.
 | 
			
		||||
 | 
			
		||||
// Package sqlite3 provides access to the SQLite library, version 3.
 | 
			
		||||
//
 | 
			
		||||
// The package has no exported API.
 | 
			
		||||
// It registers a driver for the standard Go database/sql package.
 | 
			
		||||
//
 | 
			
		||||
//	import _ "code.google.com/p/gosqlite/sqlite3"
 | 
			
		||||
//
 | 
			
		||||
// (For an alternate, earlier API, see the code.google.com/p/gosqlite/sqlite package.)
 | 
			
		||||
package sqlite
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
#cgo LDFLAGS: -lsqlite3
 | 
			
		||||
 | 
			
		||||
#include <sqlite3.h>
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
 | 
			
		||||
// These wrappers are necessary because SQLITE_TRANSIENT
 | 
			
		||||
// is a pointer constant, and cgo doesn't translate them correctly.
 | 
			
		||||
// The definition in sqlite3.h is:
 | 
			
		||||
//
 | 
			
		||||
// typedef void (*sqlite3_destructor_type)(void*);
 | 
			
		||||
// #define SQLITE_STATIC      ((sqlite3_destructor_type)0)
 | 
			
		||||
// #define SQLITE_TRANSIENT   ((sqlite3_destructor_type)-1)
 | 
			
		||||
 | 
			
		||||
static int my_bind_text(sqlite3_stmt *stmt, int n, char *p, int np) {
 | 
			
		||||
	return sqlite3_bind_text(stmt, n, p, np, SQLITE_TRANSIENT);
 | 
			
		||||
}
 | 
			
		||||
static int my_bind_blob(sqlite3_stmt *stmt, int n, void *p, int np) {
 | 
			
		||||
	return sqlite3_bind_blob(stmt, n, p, np, SQLITE_TRANSIENT);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
*/
 | 
			
		||||
import "C"
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"database/sql"
 | 
			
		||||
	"database/sql/driver"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
	"unsafe"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	sql.Register("sqlite3", impl{})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type errno int
 | 
			
		||||
 | 
			
		||||
func (e errno) Error() string {
 | 
			
		||||
	s := errText[e]
 | 
			
		||||
	if s == "" {
 | 
			
		||||
		return fmt.Sprintf("errno %d", int(e))
 | 
			
		||||
	}
 | 
			
		||||
	return s
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	errError      error = errno(1)   //    /* SQL error or missing database */
 | 
			
		||||
	errInternal   error = errno(2)   //    /* Internal logic error in SQLite */
 | 
			
		||||
	errPerm       error = errno(3)   //    /* Access permission denied */
 | 
			
		||||
	errAbort      error = errno(4)   //    /* Callback routine requested an abort */
 | 
			
		||||
	errBusy       error = errno(5)   //    /* The database file is locked */
 | 
			
		||||
	errLocked     error = errno(6)   //    /* A table in the database is locked */
 | 
			
		||||
	errNoMem      error = errno(7)   //    /* A malloc() failed */
 | 
			
		||||
	errReadOnly   error = errno(8)   //    /* Attempt to write a readonly database */
 | 
			
		||||
	errInterrupt  error = errno(9)   //    /* Operation terminated by sqlite3_interrupt()*/
 | 
			
		||||
	errIOErr      error = errno(10)  //    /* Some kind of disk I/O error occurred */
 | 
			
		||||
	errCorrupt    error = errno(11)  //    /* The database disk image is malformed */
 | 
			
		||||
	errFull       error = errno(13)  //    /* Insertion failed because database is full */
 | 
			
		||||
	errCantOpen   error = errno(14)  //    /* Unable to open the database file */
 | 
			
		||||
	errEmpty      error = errno(16)  //    /* Database is empty */
 | 
			
		||||
	errSchema     error = errno(17)  //    /* The database schema changed */
 | 
			
		||||
	errTooBig     error = errno(18)  //    /* String or BLOB exceeds size limit */
 | 
			
		||||
	errConstraint error = errno(19)  //    /* Abort due to constraint violation */
 | 
			
		||||
	errMismatch   error = errno(20)  //    /* Data type mismatch */
 | 
			
		||||
	errMisuse     error = errno(21)  //    /* Library used incorrectly */
 | 
			
		||||
	errNolfs      error = errno(22)  //    /* Uses OS features not supported on host */
 | 
			
		||||
	errAuth       error = errno(23)  //    /* Authorization denied */
 | 
			
		||||
	errFormat     error = errno(24)  //    /* Auxiliary database format error */
 | 
			
		||||
	errRange      error = errno(25)  //    /* 2nd parameter to sqlite3_bind out of range */
 | 
			
		||||
	errNotDB      error = errno(26)  //    /* File opened that is not a database file */
 | 
			
		||||
	stepRow             = errno(100) //   /* sqlite3_step() has another row ready */
 | 
			
		||||
	stepDone            = errno(101) //   /* sqlite3_step() has finished executing */
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var errText = map[errno]string{
 | 
			
		||||
	1:   "SQL error or missing database",
 | 
			
		||||
	2:   "Internal logic error in SQLite",
 | 
			
		||||
	3:   "Access permission denied",
 | 
			
		||||
	4:   "Callback routine requested an abort",
 | 
			
		||||
	5:   "The database file is locked",
 | 
			
		||||
	6:   "A table in the database is locked",
 | 
			
		||||
	7:   "A malloc() failed",
 | 
			
		||||
	8:   "Attempt to write a readonly database",
 | 
			
		||||
	9:   "Operation terminated by sqlite3_interrupt()*/",
 | 
			
		||||
	10:  "Some kind of disk I/O error occurred",
 | 
			
		||||
	11:  "The database disk image is malformed",
 | 
			
		||||
	12:  "NOT USED. Table or record not found",
 | 
			
		||||
	13:  "Insertion failed because database is full",
 | 
			
		||||
	14:  "Unable to open the database file",
 | 
			
		||||
	15:  "NOT USED. Database lock protocol error",
 | 
			
		||||
	16:  "Database is empty",
 | 
			
		||||
	17:  "The database schema changed",
 | 
			
		||||
	18:  "String or BLOB exceeds size limit",
 | 
			
		||||
	19:  "Abort due to constraint violation",
 | 
			
		||||
	20:  "Data type mismatch",
 | 
			
		||||
	21:  "Library used incorrectly",
 | 
			
		||||
	22:  "Uses OS features not supported on host",
 | 
			
		||||
	23:  "Authorization denied",
 | 
			
		||||
	24:  "Auxiliary database format error",
 | 
			
		||||
	25:  "2nd parameter to sqlite3_bind out of range",
 | 
			
		||||
	26:  "File opened that is not a database file",
 | 
			
		||||
	100: "sqlite3_step() has another row ready",
 | 
			
		||||
	101: "sqlite3_step() has finished executing",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type impl struct{}
 | 
			
		||||
 | 
			
		||||
func (impl) Open(name string) (driver.Conn, error) {
 | 
			
		||||
	if C.sqlite3_threadsafe() == 0 {
 | 
			
		||||
		return nil, errors.New("sqlite library was not compiled for thread-safe operation")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var db *C.sqlite3
 | 
			
		||||
	cname := C.CString(name)
 | 
			
		||||
	defer C.free(unsafe.Pointer(cname))
 | 
			
		||||
	rv := C.sqlite3_open_v2(cname, &db,
 | 
			
		||||
		C.SQLITE_OPEN_FULLMUTEX|
 | 
			
		||||
			C.SQLITE_OPEN_READWRITE|
 | 
			
		||||
			C.SQLITE_OPEN_CREATE,
 | 
			
		||||
		nil)
 | 
			
		||||
	if rv != 0 {
 | 
			
		||||
		return nil, errno(rv)
 | 
			
		||||
	}
 | 
			
		||||
	if db == nil {
 | 
			
		||||
		return nil, errors.New("sqlite succeeded without returning a database")
 | 
			
		||||
	}
 | 
			
		||||
	return &conn{db: db}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type conn struct {
 | 
			
		||||
	db     *C.sqlite3
 | 
			
		||||
	closed bool
 | 
			
		||||
	tx     bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *conn) error(rv C.int) error {
 | 
			
		||||
	if rv == 0 {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	if rv == 21 || c.closed {
 | 
			
		||||
		return errno(rv)
 | 
			
		||||
	}
 | 
			
		||||
	return errors.New(errno(rv).Error() + ": " + C.GoString(C.sqlite3_errmsg(c.db)))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *conn) Prepare(cmd string) (driver.Stmt, error) {
 | 
			
		||||
	if c.closed {
 | 
			
		||||
		panic("database/sql/driver: misuse of sqlite driver: Prepare after Close")
 | 
			
		||||
	}
 | 
			
		||||
	cmdstr := C.CString(cmd)
 | 
			
		||||
	defer C.free(unsafe.Pointer(cmdstr))
 | 
			
		||||
	var s *C.sqlite3_stmt
 | 
			
		||||
	var tail *C.char
 | 
			
		||||
	rv := C.sqlite3_prepare_v2(c.db, cmdstr, C.int(len(cmd)+1), &s, &tail)
 | 
			
		||||
	if rv != 0 {
 | 
			
		||||
		return nil, c.error(rv)
 | 
			
		||||
	}
 | 
			
		||||
	return &stmt{c: c, stmt: s, sql: cmd, t0: time.Now()}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *conn) Close() error {
 | 
			
		||||
	if c.closed {
 | 
			
		||||
		panic("database/sql/driver: misuse of sqlite driver: multiple Close")
 | 
			
		||||
	}
 | 
			
		||||
	c.closed = true
 | 
			
		||||
	rv := C.sqlite3_close(c.db)
 | 
			
		||||
	c.db = nil
 | 
			
		||||
	return c.error(rv)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *conn) exec(cmd string) error {
 | 
			
		||||
	cstring := C.CString(cmd)
 | 
			
		||||
	defer C.free(unsafe.Pointer(cstring))
 | 
			
		||||
	rv := C.sqlite3_exec(c.db, cstring, nil, nil, nil)
 | 
			
		||||
	return c.error(rv)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *conn) Begin() (driver.Tx, error) {
 | 
			
		||||
	if c.tx {
 | 
			
		||||
		panic("database/sql/driver: misuse of sqlite driver: multiple Tx")
 | 
			
		||||
	}
 | 
			
		||||
	if err := c.exec("BEGIN TRANSACTION"); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	c.tx = true
 | 
			
		||||
	return &tx{c}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type tx struct {
 | 
			
		||||
	c *conn
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t *tx) Commit() error {
 | 
			
		||||
	if t.c == nil || !t.c.tx {
 | 
			
		||||
		panic("database/sql/driver: misuse of sqlite driver: extra Commit")
 | 
			
		||||
	}
 | 
			
		||||
	t.c.tx = false
 | 
			
		||||
	err := t.c.exec("COMMIT TRANSACTION")
 | 
			
		||||
	t.c = nil
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t *tx) Rollback() error {
 | 
			
		||||
	if t.c == nil || !t.c.tx {
 | 
			
		||||
		panic("database/sql/driver: misuse of sqlite driver: extra Rollback")
 | 
			
		||||
	}
 | 
			
		||||
	t.c.tx = false
 | 
			
		||||
	err := t.c.exec("ROLLBACK")
 | 
			
		||||
	t.c = nil
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type stmt struct {
 | 
			
		||||
	c        *conn
 | 
			
		||||
	stmt     *C.sqlite3_stmt
 | 
			
		||||
	err      error
 | 
			
		||||
	t0       time.Time
 | 
			
		||||
	sql      string
 | 
			
		||||
	args     string
 | 
			
		||||
	closed   bool
 | 
			
		||||
	rows     bool
 | 
			
		||||
	colnames []string
 | 
			
		||||
	coltypes []string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *stmt) Close() error {
 | 
			
		||||
	if s.rows {
 | 
			
		||||
		panic("database/sql/driver: misuse of sqlite driver: Close with active Rows")
 | 
			
		||||
	}
 | 
			
		||||
	if s.closed {
 | 
			
		||||
		panic("database/sql/driver: misuse of sqlite driver: double Close of Stmt")
 | 
			
		||||
	}
 | 
			
		||||
	s.closed = true
 | 
			
		||||
	rv := C.sqlite3_finalize(s.stmt)
 | 
			
		||||
	if rv != 0 {
 | 
			
		||||
		return s.c.error(rv)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *stmt) NumInput() int {
 | 
			
		||||
	if s.closed {
 | 
			
		||||
		panic("database/sql/driver: misuse of sqlite driver: NumInput after Close")
 | 
			
		||||
	}
 | 
			
		||||
	return int(C.sqlite3_bind_parameter_count(s.stmt))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *stmt) reset() error {
 | 
			
		||||
	return s.c.error(C.sqlite3_reset(s.stmt))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *stmt) start(args []driver.Value) error {
 | 
			
		||||
	if err := s.reset(); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	n := int(C.sqlite3_bind_parameter_count(s.stmt))
 | 
			
		||||
	if n != len(args) {
 | 
			
		||||
		return fmt.Errorf("incorrect argument count for command: have %d want %d", len(args), n)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i, v := range args {
 | 
			
		||||
		var str string
 | 
			
		||||
		switch v := v.(type) {
 | 
			
		||||
		case nil:
 | 
			
		||||
			if rv := C.sqlite3_bind_null(s.stmt, C.int(i+1)); rv != 0 {
 | 
			
		||||
				return s.c.error(rv)
 | 
			
		||||
			}
 | 
			
		||||
			continue
 | 
			
		||||
 | 
			
		||||
		case float64:
 | 
			
		||||
			if rv := C.sqlite3_bind_double(s.stmt, C.int(i+1), C.double(v)); rv != 0 {
 | 
			
		||||
				return s.c.error(rv)
 | 
			
		||||
			}
 | 
			
		||||
			continue
 | 
			
		||||
 | 
			
		||||
		case int64:
 | 
			
		||||
			if rv := C.sqlite3_bind_int64(s.stmt, C.int(i+1), C.sqlite3_int64(v)); rv != 0 {
 | 
			
		||||
				return s.c.error(rv)
 | 
			
		||||
			}
 | 
			
		||||
			continue
 | 
			
		||||
 | 
			
		||||
		case []byte:
 | 
			
		||||
			var p *byte
 | 
			
		||||
			if len(v) > 0 {
 | 
			
		||||
				p = &v[0]
 | 
			
		||||
			}
 | 
			
		||||
			if rv := C.my_bind_blob(s.stmt, C.int(i+1), unsafe.Pointer(p), C.int(len(v))); rv != 0 {
 | 
			
		||||
				return s.c.error(rv)
 | 
			
		||||
			}
 | 
			
		||||
			continue
 | 
			
		||||
 | 
			
		||||
		case bool:
 | 
			
		||||
			var vi int64
 | 
			
		||||
			if v {
 | 
			
		||||
				vi = 1
 | 
			
		||||
			}
 | 
			
		||||
			if rv := C.sqlite3_bind_int64(s.stmt, C.int(i+1), C.sqlite3_int64(vi)); rv != 0 {
 | 
			
		||||
				return s.c.error(rv)
 | 
			
		||||
			}
 | 
			
		||||
			continue
 | 
			
		||||
 | 
			
		||||
		case time.Time:
 | 
			
		||||
			str = v.UTC().Format(timefmt[0])
 | 
			
		||||
 | 
			
		||||
		case string:
 | 
			
		||||
			str = v
 | 
			
		||||
 | 
			
		||||
		default:
 | 
			
		||||
			str = fmt.Sprint(v)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		cstr := C.CString(str)
 | 
			
		||||
		rv := C.my_bind_text(s.stmt, C.int(i+1), cstr, C.int(len(str)))
 | 
			
		||||
		C.free(unsafe.Pointer(cstr))
 | 
			
		||||
		if rv != 0 {
 | 
			
		||||
			return s.c.error(rv)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *stmt) Exec(args []driver.Value) (driver.Result, error) {
 | 
			
		||||
	if s.closed {
 | 
			
		||||
		panic("database/sql/driver: misuse of sqlite driver: Exec after Close")
 | 
			
		||||
	}
 | 
			
		||||
	if s.rows {
 | 
			
		||||
		panic("database/sql/driver: misuse of sqlite driver: Exec with active Rows")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err := s.start(args)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	rv := C.sqlite3_step(s.stmt)
 | 
			
		||||
	if errno(rv) != stepDone {
 | 
			
		||||
		if rv == 0 {
 | 
			
		||||
			rv = 21 // errMisuse
 | 
			
		||||
		}
 | 
			
		||||
		return nil, s.c.error(rv)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	id := int64(C.sqlite3_last_insert_rowid(s.c.db))
 | 
			
		||||
	rows := int64(C.sqlite3_changes(s.c.db))
 | 
			
		||||
	return &result{id, rows}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *stmt) Query(args []driver.Value) (driver.Rows, error) {
 | 
			
		||||
	if s.closed {
 | 
			
		||||
		panic("database/sql/driver: misuse of sqlite driver: Query after Close")
 | 
			
		||||
	}
 | 
			
		||||
	if s.rows {
 | 
			
		||||
		panic("database/sql/driver: misuse of sqlite driver: Query with active Rows")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err := s.start(args)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	s.rows = true
 | 
			
		||||
	if s.colnames == nil {
 | 
			
		||||
		n := int64(C.sqlite3_column_count(s.stmt))
 | 
			
		||||
		s.colnames = make([]string, n)
 | 
			
		||||
		s.coltypes = make([]string, n)
 | 
			
		||||
		for i := range s.colnames {
 | 
			
		||||
			s.colnames[i] = C.GoString(C.sqlite3_column_name(s.stmt, C.int(i)))
 | 
			
		||||
			s.coltypes[i] = strings.ToLower(C.GoString(C.sqlite3_column_decltype(s.stmt, C.int(i))))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return &rows{s}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type rows struct {
 | 
			
		||||
	s *stmt
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *rows) Columns() []string {
 | 
			
		||||
	if r.s == nil {
 | 
			
		||||
		panic("database/sql/driver: misuse of sqlite driver: Columns of closed Rows")
 | 
			
		||||
	}
 | 
			
		||||
	return r.s.colnames
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const maxslice = 1<<31 - 1
 | 
			
		||||
 | 
			
		||||
var timefmt = []string{
 | 
			
		||||
	"2006-01-02 15:04:05.999999999",
 | 
			
		||||
	"2006-01-02T15:04:05.999999999",
 | 
			
		||||
	"2006-01-02 15:04:05",
 | 
			
		||||
	"2006-01-02T15:04:05",
 | 
			
		||||
	"2006-01-02 15:04",
 | 
			
		||||
	"2006-01-02T15:04",
 | 
			
		||||
	"2006-01-02",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *rows) Next(dst []driver.Value) error {
 | 
			
		||||
	if r.s == nil {
 | 
			
		||||
		panic("database/sql/driver: misuse of sqlite driver: Next of closed Rows")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	rv := C.sqlite3_step(r.s.stmt)
 | 
			
		||||
	if errno(rv) != stepRow {
 | 
			
		||||
		if errno(rv) == stepDone {
 | 
			
		||||
			return io.EOF
 | 
			
		||||
		}
 | 
			
		||||
		if rv == 0 {
 | 
			
		||||
			rv = 21
 | 
			
		||||
		}
 | 
			
		||||
		return r.s.c.error(rv)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i := range dst {
 | 
			
		||||
		switch typ := C.sqlite3_column_type(r.s.stmt, C.int(i)); typ {
 | 
			
		||||
		default:
 | 
			
		||||
			return fmt.Errorf("unexpected sqlite3 column type %d", typ)
 | 
			
		||||
		case C.SQLITE_INTEGER:
 | 
			
		||||
			val := int64(C.sqlite3_column_int64(r.s.stmt, C.int(i)))
 | 
			
		||||
			switch r.s.coltypes[i] {
 | 
			
		||||
			case "timestamp", "datetime":
 | 
			
		||||
				dst[i] = time.Unix(val, 0).UTC()
 | 
			
		||||
			case "boolean":
 | 
			
		||||
				dst[i] = val > 0
 | 
			
		||||
			default:
 | 
			
		||||
				dst[i] = val
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		case C.SQLITE_FLOAT:
 | 
			
		||||
			dst[i] = float64(C.sqlite3_column_double(r.s.stmt, C.int(i)))
 | 
			
		||||
 | 
			
		||||
		case C.SQLITE_BLOB, C.SQLITE_TEXT:
 | 
			
		||||
			n := int(C.sqlite3_column_bytes(r.s.stmt, C.int(i)))
 | 
			
		||||
			var b []byte
 | 
			
		||||
			if n > 0 {
 | 
			
		||||
				p := C.sqlite3_column_blob(r.s.stmt, C.int(i))
 | 
			
		||||
				b = (*[maxslice]byte)(unsafe.Pointer(p))[:n]
 | 
			
		||||
			}
 | 
			
		||||
			dst[i] = b
 | 
			
		||||
			switch r.s.coltypes[i] {
 | 
			
		||||
			case "timestamp", "datetime":
 | 
			
		||||
				dst[i] = time.Time{}
 | 
			
		||||
				s := string(b)
 | 
			
		||||
				for _, f := range timefmt {
 | 
			
		||||
					if t, err := time.Parse(f, s); err == nil {
 | 
			
		||||
						dst[i] = t
 | 
			
		||||
						break
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		case C.SQLITE_NULL:
 | 
			
		||||
			dst[i] = nil
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *rows) Close() error {
 | 
			
		||||
	if r.s == nil {
 | 
			
		||||
		panic("database/sql/driver: misuse of sqlite driver: Close of closed Rows")
 | 
			
		||||
	}
 | 
			
		||||
	r.s.rows = false
 | 
			
		||||
	r.s = nil
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type result struct {
 | 
			
		||||
	id   int64
 | 
			
		||||
	rows int64
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *result) LastInsertId() (int64, error) {
 | 
			
		||||
	return r.id, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *result) RowsAffected() (int64, error) {
 | 
			
		||||
	return r.rows, nil
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue