Browse code

Merge pull request #938 from dotcloud/add_unix_socket-feature

* Runtime: Add unix socket and multiple -H

Guillaume J. Charmes authored on 2013/06/21 03:17:16
Showing 9 changed files
... ...
@@ -8,12 +8,16 @@ import (
8 8
 	"github.com/gorilla/mux"
9 9
 	"io"
10 10
 	"log"
11
+	"net"
11 12
 	"net/http"
13
+	"os"
12 14
 	"strconv"
13 15
 	"strings"
14 16
 )
15 17
 
16 18
 const APIVERSION = 1.2
19
+const DEFAULTHTTPHOST string = "127.0.0.1"
20
+const DEFAULTHTTPPORT int = 4243
17 21
 
18 22
 func hijackServer(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) {
19 23
 	conn, _, err := w.(http.Hijacker).Hijack()
... ...
@@ -848,12 +852,21 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
848 848
 	return r, nil
849 849
 }
850 850
 
851
-func ListenAndServe(addr string, srv *Server, logging bool) error {
852
-	log.Printf("Listening for HTTP on %s\n", addr)
851
+func ListenAndServe(proto, addr string, srv *Server, logging bool) error {
852
+	log.Printf("Listening for HTTP on %s (%s)\n", addr, proto)
853 853
 
854 854
 	r, err := createRouter(srv, logging)
855 855
 	if err != nil {
856 856
 		return err
857 857
 	}
858
-	return http.ListenAndServe(addr, r)
858
+	l, e := net.Listen(proto, addr)
859
+	if e != nil {
860
+		return e
861
+	}
862
+	//as the daemon is launched as root, change to permission of the socket to allow non-root to connect
863
+	if proto == "unix" {
864
+		os.Chmod(addr, 0777)
865
+	}
866
+	httpSrv := http.Server{Addr: addr, Handler: r}
867
+	return httpSrv.Serve(l)
859 868
 }
... ...
@@ -304,9 +304,9 @@ func (b *builderClient) Build(dockerfile, context io.Reader) (string, error) {
304 304
 	return "", fmt.Errorf("An error occured during the build\n")
305 305
 }
306 306
 
307
-func NewBuilderClient(addr string, port int) BuildFile {
307
+func NewBuilderClient(proto, addr string) BuildFile {
308 308
 	return &builderClient{
309
-		cli:           NewDockerCli(addr, port),
309
+		cli:           NewDockerCli(proto, addr),
310 310
 		config:        &Config{},
311 311
 		tmpContainers: make(map[string]struct{}),
312 312
 		tmpImages:     make(map[string]struct{}),
... ...
@@ -40,8 +40,8 @@ func (cli *DockerCli) getMethod(name string) (reflect.Method, bool) {
40 40
 	return reflect.TypeOf(cli).MethodByName(methodName)
41 41
 }
42 42
 
43
-func ParseCommands(addr string, port int, args ...string) error {
44
-	cli := NewDockerCli(addr, port)
43
+func ParseCommands(proto, addr string, args ...string) error {
44
+	cli := NewDockerCli(proto, addr)
45 45
 
46 46
 	if len(args) > 0 {
47 47
 		method, exists := cli.getMethod(args[0])
... ...
@@ -74,7 +74,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
74 74
 			return nil
75 75
 		}
76 76
 	}
77
-	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)
77
+	help := fmt.Sprintf("Usage: docker [OPTIONS] COMMAND [arg...]\n  -H=[tcp://%s:%d]: tcp://host:port to bind/connect to or unix://path/to/socker to use\n\nA self-sufficient runtime for linux containers.\n\nCommands:\n", DEFAULTHTTPHOST, DEFAULTHTTPPORT)
78 78
 	for _, command := range [][2]string{
79 79
 		{"attach", "Attach to a running container"},
80 80
 		{"build", "Build a container from a Dockerfile"},
... ...
@@ -197,7 +197,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
197 197
 	v := &url.Values{}
198 198
 	v.Set("t", *tag)
199 199
 	// Send the multipart request with correct content-type
200
-	req, err := http.NewRequest("POST", fmt.Sprintf("http://%s:%d%s?%s", cli.host, cli.port, "/build", v.Encode()), multipartBody)
200
+	req, err := http.NewRequest("POST", fmt.Sprintf("/v%s/build?%s", APIVERSION, v.Encode()), multipartBody)
201 201
 	if err != nil {
202 202
 		return err
203 203
 	}
... ...
@@ -206,8 +206,13 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
206 206
 		req.Header.Set("X-Docker-Context-Compression", compression.Flag())
207 207
 		fmt.Println("Uploading Context...")
208 208
 	}
209
-
210
-	resp, err := http.DefaultClient.Do(req)
209
+	dial, err := net.Dial(cli.proto, cli.addr)
210
+	if err != nil {
211
+		return err
212
+	}
213
+	clientconn := httputil.NewClientConn(dial, nil)
214
+	resp, err := clientconn.Do(req)
215
+	defer clientconn.Close()
211 216
 	if err != nil {
212 217
 		return err
213 218
 	}
... ...
@@ -1302,7 +1307,7 @@ func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int,
1302 1302
 		params = bytes.NewBuffer(buf)
1303 1303
 	}
1304 1304
 
1305
-	req, err := http.NewRequest(method, fmt.Sprintf("http://%s:%d/v%g%s", cli.host, cli.port, APIVERSION, path), params)
1305
+	req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), params)
1306 1306
 	if err != nil {
1307 1307
 		return nil, -1, err
1308 1308
 	}
... ...
@@ -1312,7 +1317,13 @@ func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int,
1312 1312
 	} else if method == "POST" {
1313 1313
 		req.Header.Set("Content-Type", "plain/text")
1314 1314
 	}
1315
-	resp, err := http.DefaultClient.Do(req)
1315
+	dial, err := net.Dial(cli.proto, cli.addr)
1316
+	if err != nil {
1317
+		return nil, -1, err
1318
+	}
1319
+	clientconn := httputil.NewClientConn(dial, nil)
1320
+	resp, err := clientconn.Do(req)
1321
+	defer clientconn.Close()
1316 1322
 	if err != nil {
1317 1323
 		if strings.Contains(err.Error(), "connection refused") {
1318 1324
 			return nil, -1, fmt.Errorf("Can't connect to docker daemon. Is 'docker -d' running on this host?")
... ...
@@ -1337,7 +1348,7 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e
1337 1337
 	if (method == "POST" || method == "PUT") && in == nil {
1338 1338
 		in = bytes.NewReader([]byte{})
1339 1339
 	}
1340
-	req, err := http.NewRequest(method, fmt.Sprintf("http://%s:%d/v%g%s", cli.host, cli.port, APIVERSION, path), in)
1340
+	req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), in)
1341 1341
 	if err != nil {
1342 1342
 		return err
1343 1343
 	}
... ...
@@ -1345,7 +1356,13 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e
1345 1345
 	if method == "POST" {
1346 1346
 		req.Header.Set("Content-Type", "plain/text")
1347 1347
 	}
1348
-	resp, err := http.DefaultClient.Do(req)
1348
+	dial, err := net.Dial(cli.proto, cli.addr)
1349
+	if err != nil {
1350
+		return err
1351
+	}
1352
+	clientconn := httputil.NewClientConn(dial, nil)
1353
+	resp, err := clientconn.Do(req)
1354
+	defer clientconn.Close()
1349 1355
 	if err != nil {
1350 1356
 		if strings.Contains(err.Error(), "connection refused") {
1351 1357
 			return fmt.Errorf("Can't connect to docker daemon. Is 'docker -d' running on this host?")
... ...
@@ -1395,7 +1412,7 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in *os.Fi
1395 1395
 		return err
1396 1396
 	}
1397 1397
 	req.Header.Set("Content-Type", "plain/text")
1398
-	dial, err := net.Dial("tcp", fmt.Sprintf("%s:%d", cli.host, cli.port))
1398
+	dial, err := net.Dial(cli.proto, cli.addr)
1399 1399
 	if err != nil {
1400 1400
 		return err
1401 1401
 	}
... ...
@@ -1478,13 +1495,13 @@ func Subcmd(name, signature, description string) *flag.FlagSet {
1478 1478
 	return flags
1479 1479
 }
1480 1480
 
1481
-func NewDockerCli(addr string, port int) *DockerCli {
1481
+func NewDockerCli(proto, addr string) *DockerCli {
1482 1482
 	authConfig, _ := auth.LoadConfig(os.Getenv("HOME"))
1483
-	return &DockerCli{addr, port, authConfig}
1483
+	return &DockerCli{proto, addr, authConfig}
1484 1484
 }
1485 1485
 
1486 1486
 type DockerCli struct {
1487
-	host       string
1488
-	port       int
1487
+	proto      string
1488
+	addr       string
1489 1489
 	authConfig *auth.AuthConfig
1490 1490
 }
... ...
@@ -24,40 +24,29 @@ func main() {
24 24
 		docker.SysInit()
25 25
 		return
26 26
 	}
27
-	host := "127.0.0.1"
28
-	port := 4243
29 27
 	// FIXME: Switch d and D ? (to be more sshd like)
30 28
 	flDaemon := flag.Bool("d", false, "Daemon mode")
31 29
 	flDebug := flag.Bool("D", false, "Debug mode")
32 30
 	flAutoRestart := flag.Bool("r", false, "Restart previously running containers")
33 31
 	bridgeName := flag.String("b", "", "Attach containers to a pre-existing network bridge")
34 32
 	pidfile := flag.String("p", "/var/run/docker.pid", "File containing process PID")
35
-	flHost := flag.String("H", fmt.Sprintf("%s:%d", host, port), "Host:port to bind/connect to")
36 33
 	flEnableCors := flag.Bool("api-enable-cors", false, "Enable CORS requests in the remote api.")
37 34
 	flDns := flag.String("dns", "", "Set custom dns servers")
35
+	flHosts := docker.ListOpts{fmt.Sprintf("tcp://%s:%d", docker.DEFAULTHTTPHOST, docker.DEFAULTHTTPPORT)}
36
+	flag.Var(&flHosts, "H", "tcp://host:port to bind/connect to or unix://path/to/socket to use")
38 37
 	flag.Parse()
38
+	if len(flHosts) > 1 {
39
+		flHosts = flHosts[1:len(flHosts)] //trick to display a nice defaul value in the usage
40
+	}
41
+	for i, flHost := range flHosts {
42
+		flHosts[i] = utils.ParseHost(docker.DEFAULTHTTPHOST, docker.DEFAULTHTTPPORT, flHost)
43
+	}
44
+
39 45
 	if *bridgeName != "" {
40 46
 		docker.NetworkBridgeIface = *bridgeName
41 47
 	} else {
42 48
 		docker.NetworkBridgeIface = docker.DefaultNetworkBridge
43 49
 	}
44
-
45
-	if strings.Contains(*flHost, ":") {
46
-		hostParts := strings.Split(*flHost, ":")
47
-		if len(hostParts) != 2 {
48
-			log.Fatal("Invalid bind address format.")
49
-			os.Exit(-1)
50
-		}
51
-		if hostParts[0] != "" {
52
-			host = hostParts[0]
53
-		}
54
-		if p, err := strconv.Atoi(hostParts[1]); err == nil {
55
-			port = p
56
-		}
57
-	} else {
58
-		host = *flHost
59
-	}
60
-
61 50
 	if *flDebug {
62 51
 		os.Setenv("DEBUG", "1")
63 52
 	}
... ...
@@ -67,12 +56,17 @@ func main() {
67 67
 			flag.Usage()
68 68
 			return
69 69
 		}
70
-		if err := daemon(*pidfile, host, port, *flAutoRestart, *flEnableCors, *flDns); err != nil {
70
+		if err := daemon(*pidfile, flHosts, *flAutoRestart, *flEnableCors, *flDns); err != nil {
71 71
 			log.Fatal(err)
72 72
 			os.Exit(-1)
73 73
 		}
74 74
 	} else {
75
-		if err := docker.ParseCommands(host, port, flag.Args()...); err != nil {
75
+		if len(flHosts) > 1 {
76
+			log.Fatal("Please specify only one -H")
77
+			return
78
+		}
79
+		protoAddrParts := strings.SplitN(flHosts[0], "://", 2)
80
+		if err := docker.ParseCommands(protoAddrParts[0], protoAddrParts[1], flag.Args()...); err != nil {
76 81
 			log.Fatal(err)
77 82
 			os.Exit(-1)
78 83
 		}
... ...
@@ -106,10 +100,7 @@ func removePidFile(pidfile string) {
106 106
 	}
107 107
 }
108 108
 
109
-func daemon(pidfile, addr string, port int, autoRestart, enableCors bool, flDns string) error {
110
-	if addr != "127.0.0.1" {
111
-		log.Println("/!\\ DON'T BIND ON ANOTHER IP ADDRESS THAN 127.0.0.1 IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\")
112
-	}
109
+func daemon(pidfile string, protoAddrs []string, autoRestart, enableCors bool, flDns string) error {
113 110
 	if err := createPidFile(pidfile); err != nil {
114 111
 		log.Fatal(err)
115 112
 	}
... ...
@@ -131,6 +122,29 @@ func daemon(pidfile, addr string, port int, autoRestart, enableCors bool, flDns
131 131
 	if err != nil {
132 132
 		return err
133 133
 	}
134
-
135
-	return docker.ListenAndServe(fmt.Sprintf("%s:%d", addr, port), server, true)
134
+	chErrors := make(chan error, len(protoAddrs))
135
+	for _, protoAddr := range protoAddrs {
136
+		protoAddrParts := strings.SplitN(protoAddr, "://", 2)
137
+		if protoAddrParts[0] == "unix" {
138
+			syscall.Unlink(protoAddrParts[1]);
139
+		} else if protoAddrParts[0] == "tcp" {
140
+			if !strings.HasPrefix(protoAddrParts[1], "127.0.0.1") {
141
+				log.Println("/!\\ DON'T BIND ON ANOTHER IP ADDRESS THAN 127.0.0.1 IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\")
142
+			}
143
+		} else {
144
+			log.Fatal("Invalid protocol format.")
145
+			os.Exit(-1)
146
+		}
147
+		go func() {
148
+			chErrors <- docker.ListenAndServe(protoAddrParts[0], protoAddrParts[1], server, true)
149
+		}()
150
+	}
151
+	for i :=0 ; i < len(protoAddrs); i+=1 {
152
+		err := <-chErrors
153
+		if err != nil {
154
+			return err
155
+		}
156
+	}
157
+	return nil
136 158
 }
159
+
... ...
@@ -1027,5 +1027,5 @@ In this version of the API, /attach, uses hijacking to transport stdin, stdout a
1027 1027
 
1028 1028
 To enable cross origin requests to the remote api add the flag "-api-enable-cors" when running docker in daemon mode.
1029 1029
     
1030
-    docker -d -H="192.168.1.9:4243" -api-enable-cors
1030
+    docker -d -H="tcp://192.168.1.9:4243" -api-enable-cors
1031 1031
 
... ...
@@ -15,7 +15,7 @@ To list available commands, either run ``docker`` with no parameters or execute
15 15
 
16 16
   $ docker
17 17
     Usage: docker [OPTIONS] COMMAND [arg...]
18
-      -H="127.0.0.1:4243": Host:port to bind/connect to
18
+      -H=[tcp://127.0.0.1:4243]: tcp://host:port to bind/connect to or unix://path/to/socket to use
19 19
 
20 20
     A self-sufficient runtime for linux containers.
21 21
 
... ...
@@ -33,11 +33,20 @@ Running an interactive shell
33 33
   # allocate a tty, attach stdin and stdout
34 34
   docker run -i -t base /bin/bash
35 35
 
36
-Bind Docker to another host/port
36
+Bind Docker to another host/port or a unix socket
37
+-------------------------------------------------
37 38
 
38
-If you want Docker to listen to another port and bind to another ip
39
-use -host and -port on both deamon and client
39
+With -H it is possible to make the Docker daemon to listen on a specific ip and port. By default, it will listen on 127.0.0.1:4243 to allow only local connections but you can set it to 0.0.0.0:4243 or a specific host ip to give access to everybody.
40
+
41
+Similarly, the Docker client can use -H to connect to a custom port.
42
+
43
+-H accepts host and port assignment in the following format: tcp://[host][:port] or unix://path
44
+For example:
45
+
46
+* tcp://host -> tcp connection on host:4243
47
+* tcp://host:port -> tcp connection on host:port
48
+* tcp://:port -> tcp connection on 127.0.0.1:port
49
+* unix://path/to/socket -> unix socket located at path/to/socket
40 50
 
41 51
 .. code-block:: bash
42 52
 
... ...
@@ -46,6 +55,17 @@ use -host and -port on both deamon and client
46 46
    # Download a base image
47 47
    docker -H :5555 pull base
48 48
 
49
+You can use multiple -H, for example, if you want to listen
50
+on both tcp and a unix socket
51
+
52
+.. code-block:: bash
53
+
54
+   # Run docker in daemon mode
55
+   sudo <path to>/docker -H tcp://127.0.0.1:4243 -H unix:///var/run/docker.sock
56
+   # Download a base image
57
+   docker pull base
58
+   # OR
59
+   docker -H unix:///var/run/docker.sock pull base
49 60
 
50 61
 Starting a long-running worker process
51 62
 --------------------------------------
... ...
@@ -10,6 +10,7 @@ import (
10 10
 	"index/suffixarray"
11 11
 	"io"
12 12
 	"io/ioutil"
13
+	"log"
13 14
 	"net/http"
14 15
 	"os"
15 16
 	"os/exec"
... ...
@@ -652,3 +653,30 @@ func CheckLocalDns() bool {
652 652
 	}
653 653
 	return false
654 654
 }
655
+
656
+func ParseHost(host string, port int, addr string) string {
657
+	if strings.HasPrefix(addr, "unix://") {
658
+		return addr
659
+	}
660
+	if strings.HasPrefix(addr, "tcp://") {
661
+		addr = strings.TrimPrefix(addr, "tcp://")
662
+	}
663
+	if strings.Contains(addr, ":") {
664
+		hostParts := strings.Split(addr, ":")
665
+		if len(hostParts) != 2 {
666
+			log.Fatal("Invalid bind address format.")
667
+			os.Exit(-1)
668
+		}
669
+		if hostParts[0] != "" {
670
+			host = hostParts[0]
671
+		}
672
+		if p, err := strconv.Atoi(hostParts[1]); err == nil {
673
+			port = p
674
+		}
675
+	} else {
676
+		host = addr
677
+	}
678
+	return fmt.Sprintf("tcp://%s:%d", host, port)
679
+}
680
+
681
+
... ...
@@ -274,3 +274,21 @@ func TestHumanSize(t *testing.T) {
274 274
 		t.Errorf("1024 -> expected 1.024 kB, got %s", size1024)
275 275
 	}
276 276
 }
277
+
278
+func TestParseHost(t *testing.T) {
279
+	if addr := ParseHost("127.0.0.1", 4243, "0.0.0.0"); addr != "tcp://0.0.0.0:4243" {
280
+		t.Errorf("0.0.0.0 -> expected tcp://0.0.0.0:4243, got %s", addr)
281
+	}
282
+	if addr := ParseHost("127.0.0.1", 4243, "0.0.0.1:5555"); addr != "tcp://0.0.0.1:5555" {
283
+		t.Errorf("0.0.0.1:5555 -> expected tcp://0.0.0.1:5555, got %s", addr)
284
+	}
285
+	if addr := ParseHost("127.0.0.1", 4243, ":6666"); addr != "tcp://127.0.0.1:6666" {
286
+		t.Errorf(":6666 -> expected tcp://127.0.0.1:6666, got %s", addr)
287
+	}
288
+	if addr := ParseHost("127.0.0.1", 4243, "tcp://:7777"); addr != "tcp://127.0.0.1:7777" {
289
+		t.Errorf("tcp://:7777 -> expected tcp://127.0.0.1:7777, got %s", addr)
290
+	}
291
+	if addr := ParseHost("127.0.0.1", 4243, "unix:///var/run/docker.sock"); addr != "unix:///var/run/docker.sock" {
292
+		t.Errorf("unix:///var/run/docker.sock -> expected unix:///var/run/docker.sock, got %s", addr)
293
+	}
294
+}