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…
Reference in a new issue