From 3adf9ce04ef1632f82f7bd1585c135bb141aa450 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 18 Jun 2013 18:59:56 +0000 Subject: [PATCH] add basic support for unix sockets --- api.go | 19 +++++++++-- builder_client.go | 4 +-- commands.go | 47 +++++++++++++++++--------- docker/docker.go | 84 ++++++++++++++++++++++++++++++++--------------- utils/utils.go | 2 ++ 5 files changed, 110 insertions(+), 46 deletions(-) diff --git a/api.go b/api.go index e870ca7723..0b89fcf323 100644 --- a/api.go +++ b/api.go @@ -8,12 +8,16 @@ import ( "github.com/gorilla/mux" "io" "log" + "net" "net/http" + "os" "strconv" "strings" ) const APIVERSION = 1.2 +const DEFAULTHTTPHOST string = "127.0.0.1" +const DEFAULTHTTPPORT int = 4243 func hijackServer(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) { conn, _, err := w.(http.Hijacker).Hijack() @@ -848,12 +852,21 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) { return r, nil } -func ListenAndServe(addr string, srv *Server, logging bool) error { - log.Printf("Listening for HTTP on %s\n", addr) +func ListenAndServe(proto, addr string, srv *Server, logging bool) error { + log.Printf("Listening for HTTP on %s (%s)\n", addr, proto) r, err := createRouter(srv, logging) if err != nil { return err } - return http.ListenAndServe(addr, r) + l, e := net.Listen(proto, addr) + if e != nil { + return e + } + //as the daemon is launched as root, change to permission of the socket to allow non-root to connect + if proto == "unix" { + os.Chmod(addr, 0777) + } + httpSrv := http.Server{Addr: addr, Handler: r} + return httpSrv.Serve(l) } diff --git a/builder_client.go b/builder_client.go index dc9528ff41..d11e7fc995 100644 --- a/builder_client.go +++ b/builder_client.go @@ -304,9 +304,9 @@ func (b *builderClient) Build(dockerfile, context io.Reader) (string, error) { return "", fmt.Errorf("An error occured during the build\n") } -func NewBuilderClient(addr string, port int) BuildFile { +func NewBuilderClient(proto, addr string) BuildFile { return &builderClient{ - cli: NewDockerCli(addr, port), + cli: NewDockerCli(proto, addr), config: &Config{}, tmpContainers: make(map[string]struct{}), tmpImages: make(map[string]struct{}), diff --git a/commands.go b/commands.go index cf23a8d65e..2c33485ecb 100644 --- a/commands.go +++ b/commands.go @@ -40,8 +40,8 @@ func (cli *DockerCli) getMethod(name string) (reflect.Method, bool) { return reflect.TypeOf(cli).MethodByName(methodName) } -func ParseCommands(addr string, port int, args ...string) error { - cli := NewDockerCli(addr, port) +func ParseCommands(proto, addr string, args ...string) error { + cli := NewDockerCli(proto, addr) if len(args) > 0 { method, exists := cli.getMethod(args[0]) @@ -74,7 +74,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error { return nil } } - help := fmt.Sprintf("Usage: docker [OPTIONS] COMMAND [arg...]\n -H=\"%s:%d\": Host:port to bind/connect to\n\nA self-sufficient runtime for linux containers.\n\nCommands:\n", cli.host, cli.port) + help := fmt.Sprintf("Usage: docker [OPTIONS] COMMAND [arg...]\n -H=\"%s:%d\": Host:port to bind/connect to\n\nA self-sufficient runtime for linux containers.\n\nCommands:\n", DEFAULTHTTPHOST, DEFAULTHTTPPORT) for _, command := range [][2]string{ {"attach", "Attach to a running container"}, {"build", "Build a container from a Dockerfile"}, @@ -197,7 +197,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error { v := &url.Values{} v.Set("t", *tag) // Send the multipart request with correct content-type - req, err := http.NewRequest("POST", fmt.Sprintf("http://%s:%d%s?%s", cli.host, cli.port, "/build", v.Encode()), multipartBody) + req, err := http.NewRequest("POST", fmt.Sprintf("/v%s/build?%s", APIVERSION, v.Encode()), multipartBody) if err != nil { return err } @@ -206,8 +206,13 @@ func (cli *DockerCli) CmdBuild(args ...string) error { req.Header.Set("X-Docker-Context-Compression", compression.Flag()) fmt.Println("Uploading Context...") } - - resp, err := http.DefaultClient.Do(req) + dial, err := net.Dial(cli.proto, cli.addr) + if err != nil { + return err + } + clientconn := httputil.NewClientConn(dial, nil) + resp, err := clientconn.Do(req) + defer clientconn.Close() if err != nil { return err } @@ -1339,7 +1344,7 @@ func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int, params = bytes.NewBuffer(buf) } - req, err := http.NewRequest(method, fmt.Sprintf("http://%s:%d/v%g%s", cli.host, cli.port, APIVERSION, path), params) + req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), params) if err != nil { return nil, -1, err } @@ -1349,7 +1354,13 @@ func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int, } else if method == "POST" { req.Header.Set("Content-Type", "plain/text") } - resp, err := http.DefaultClient.Do(req) + dial, err := net.Dial(cli.proto, cli.addr) + if err != nil { + return nil, -1, err + } + clientconn := httputil.NewClientConn(dial, nil) + resp, err := clientconn.Do(req) + defer clientconn.Close() if err != nil { if strings.Contains(err.Error(), "connection refused") { return nil, -1, fmt.Errorf("Can't connect to docker daemon. Is 'docker -d' running on this host?") @@ -1374,7 +1385,7 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e if (method == "POST" || method == "PUT") && in == nil { in = bytes.NewReader([]byte{}) } - req, err := http.NewRequest(method, fmt.Sprintf("http://%s:%d/v%g%s", cli.host, cli.port, APIVERSION, path), in) + req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), in) if err != nil { return err } @@ -1382,7 +1393,13 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e if method == "POST" { req.Header.Set("Content-Type", "plain/text") } - resp, err := http.DefaultClient.Do(req) + dial, err := net.Dial(cli.proto, cli.addr) + if err != nil { + return err + } + clientconn := httputil.NewClientConn(dial, nil) + resp, err := clientconn.Do(req) + defer clientconn.Close() if err != nil { if strings.Contains(err.Error(), "connection refused") { return fmt.Errorf("Can't connect to docker daemon. Is 'docker -d' running on this host?") @@ -1432,7 +1449,7 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in *os.Fi return err } req.Header.Set("Content-Type", "plain/text") - dial, err := net.Dial("tcp", fmt.Sprintf("%s:%d", cli.host, cli.port)) + dial, err := net.Dial(cli.proto, cli.addr) if err != nil { return err } @@ -1515,13 +1532,13 @@ func Subcmd(name, signature, description string) *flag.FlagSet { return flags } -func NewDockerCli(addr string, port int) *DockerCli { +func NewDockerCli(proto, addr string) *DockerCli { authConfig, _ := auth.LoadConfig(os.Getenv("HOME")) - return &DockerCli{addr, port, authConfig} + return &DockerCli{proto, addr, authConfig} } type DockerCli struct { - host string - port int + proto string + addr string authConfig *auth.AuthConfig } diff --git a/docker/docker.go b/docker/docker.go index 2e23999ad8..85c78c89da 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -24,15 +24,13 @@ func main() { docker.SysInit() return } - host := "127.0.0.1" - port := 4243 // FIXME: Switch d and D ? (to be more sshd like) flDaemon := flag.Bool("d", false, "Daemon mode") flDebug := flag.Bool("D", false, "Debug mode") flAutoRestart := flag.Bool("r", false, "Restart previously running containers") bridgeName := flag.String("b", "", "Attach containers to a pre-existing network bridge") pidfile := flag.String("p", "/var/run/docker.pid", "File containing process PID") - flHost := flag.String("H", fmt.Sprintf("%s:%d", host, port), "Host:port to bind/connect to") + flHost := flag.String("H", fmt.Sprintf("%s:%d", docker.DEFAULTHTTPHOST, docker.DEFAULTHTTPPORT), "Host:port to bind/connect to") flEnableCors := flag.Bool("api-enable-cors", false, "Enable CORS requests in the remote api.") flDns := flag.String("dns", "", "Set custom dns servers") flag.Parse() @@ -42,21 +40,8 @@ func main() { docker.NetworkBridgeIface = docker.DefaultNetworkBridge } - if strings.Contains(*flHost, ":") { - hostParts := strings.Split(*flHost, ":") - if len(hostParts) != 2 { - log.Fatal("Invalid bind address format.") - os.Exit(-1) - } - if hostParts[0] != "" { - host = hostParts[0] - } - if p, err := strconv.Atoi(hostParts[1]); err == nil { - port = p - } - } else { - host = *flHost - } + protoAddr := parseHost(*flHost) + protoAddrs := []string{protoAddr} if *flDebug { os.Setenv("DEBUG", "1") @@ -67,12 +52,13 @@ func main() { flag.Usage() return } - if err := daemon(*pidfile, host, port, *flAutoRestart, *flEnableCors, *flDns); err != nil { + if err := daemon(*pidfile, protoAddrs, *flAutoRestart, *flEnableCors, *flDns); err != nil { log.Fatal(err) os.Exit(-1) } } else { - if err := docker.ParseCommands(host, port, flag.Args()...); err != nil { + protoAddrParts := strings.SplitN(protoAddrs[0], "://", 2) + if err := docker.ParseCommands(protoAddrParts[0], protoAddrParts[1], flag.Args()...); err != nil { log.Fatal(err) os.Exit(-1) } @@ -106,10 +92,7 @@ func removePidFile(pidfile string) { } } -func daemon(pidfile, addr string, port int, autoRestart, enableCors bool, flDns string) error { - if addr != "127.0.0.1" { - log.Println("/!\\ DON'T BIND ON ANOTHER IP ADDRESS THAN 127.0.0.1 IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\") - } +func daemon(pidfile string, protoAddrs []string, autoRestart, enableCors bool, flDns string) error { if err := createPidFile(pidfile); err != nil { log.Fatal(err) } @@ -131,6 +114,55 @@ func daemon(pidfile, addr string, port int, autoRestart, enableCors bool, flDns if err != nil { return err } - - return docker.ListenAndServe(fmt.Sprintf("%s:%d", addr, port), server, true) + chErrors := make(chan error, len(protoAddrs)) + for _, protoAddr := range protoAddrs { + protoAddrParts := strings.SplitN(protoAddr, "://", 2) + if protoAddrParts[0] == "unix" { + syscall.Unlink(protoAddrParts[1]); + } else if protoAddrParts[0] == "tcp" { + if !strings.HasPrefix(protoAddrParts[1], "127.0.0.1") { + log.Println("/!\\ DON'T BIND ON ANOTHER IP ADDRESS THAN 127.0.0.1 IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\") + } + } else { + log.Fatal("Invalid protocol format.") + os.Exit(-1) + } + go func() { + chErrors <- docker.ListenAndServe(protoAddrParts[0], protoAddrParts[1], server, true) + }() + } + for i :=0 ; i < len(protoAddrs); i+=1 { + err := <-chErrors + if err != nil { + return err + } + } + return nil +} + +func parseHost(addr string) string { + if strings.HasPrefix(addr, "unix://") { + return addr + } + host := docker.DEFAULTHTTPHOST + port := docker.DEFAULTHTTPPORT + if strings.HasPrefix(addr, "tcp://") { + addr = strings.TrimPrefix(addr, "tcp://") + } + if strings.Contains(addr, ":") { + hostParts := strings.Split(addr, ":") + if len(hostParts) != 2 { + log.Fatal("Invalid bind address format.") + os.Exit(-1) + } + if hostParts[0] != "" { + host = hostParts[0] + } + if p, err := strconv.Atoi(hostParts[1]); err == nil { + port = p + } + } else { + host = addr + } + return fmt.Sprintf("tcp://%s:%d", host, port) } diff --git a/utils/utils.go b/utils/utils.go index b77c1ea053..da848c45bc 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -652,3 +652,5 @@ func CheckLocalDns() bool { } return false } + +