Browse code

Merge branch 'master' into simpler-build-upload

Conflicts:
api.go
builder_client.go
commands.go

Solomon Hykes authored on 2013/06/21 06:19:09
Showing 17 changed files
... ...
@@ -1,5 +1,25 @@
1 1
 # Changelog
2 2
 
3
+## 0.4.3 (2013-06-19)
4
+ + Builder: ADD of a local file will detect tar archives and unpack them
5
+ * Runtime: Remove bsdtar dependency
6
+ * Runtime: Add unix socket and multiple -H support
7
+ * Runtime: Prevent rm of running containers
8
+ * Runtime: Use go1.1 cookiejar
9
+ * Builder: ADD improvements: use tar for copy + automatically unpack local archives
10
+ * Builder: ADD uses tar/untar for copies instead of calling 'cp -ar'
11
+ * Builder: nicer output for 'docker build'
12
+ * Builder: fixed the behavior of ADD to be (mostly) reverse-compatible, predictable and well-documented.
13
+ * Client: HumanReadable ProgressBar sizes in pull
14
+ * Client: Fix docker version's git commit output
15
+ * API: Send all tags on History API call
16
+ * API: Add tag lookup to history command. Fixes #882
17
+ - Runtime: Fix issue detaching from running TTY container
18
+ - Runtime: Forbid parralel push/pull for a single image/repo. Fixes #311
19
+ - Runtime: Fix race condition within Run command when attaching.
20
+ - Builder: fix a bug which caused builds to fail if ADD was the first command
21
+ - Documentation: fix missing command in irc bouncer example
22
+
3 23
 ## 0.4.2 (2013-06-17)
4 24
  - Packaging: Bumped version to work around an Ubuntu bug
5 25
 
... ...
@@ -74,6 +74,9 @@ endif
74 74
 test: all
75 75
 	@(cd $(DOCKER_DIR); sudo -E go test $(GO_OPTIONS))
76 76
 
77
+testall: all
78
+	@(cd $(DOCKER_DIR); sudo -E go test ./... $(GO_OPTIONS))
79
+
77 80
 fmt:
78 81
 	@gofmt -s -l -w .
79 82
 
... ...
@@ -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.3
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()
... ...
@@ -836,12 +840,21 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
836 836
 	return r, nil
837 837
 }
838 838
 
839
-func ListenAndServe(addr string, srv *Server, logging bool) error {
840
-	log.Printf("Listening for HTTP on %s\n", addr)
839
+func ListenAndServe(proto, addr string, srv *Server, logging bool) error {
840
+	log.Printf("Listening for HTTP on %s (%s)\n", addr, proto)
841 841
 
842 842
 	r, err := createRouter(srv, logging)
843 843
 	if err != nil {
844 844
 		return err
845 845
 	}
846
-	return http.ListenAndServe(addr, r)
846
+	l, e := net.Listen(proto, addr)
847
+	if e != nil {
848
+		return e
849
+	}
850
+	//as the daemon is launched as root, change to permission of the socket to allow non-root to connect
851
+	if proto == "unix" {
852
+		os.Chmod(addr, 0777)
853
+	}
854
+	httpSrv := http.Server{Addr: addr, Handler: r}
855
+	return httpSrv.Serve(l)
847 856
 }
... ...
@@ -82,7 +82,7 @@ func decodeAuth(authStr string) (*AuthConfig, error) {
82 82
 func LoadConfig(rootPath string) (*AuthConfig, error) {
83 83
 	confFile := path.Join(rootPath, CONFIGFILE)
84 84
 	if _, err := os.Stat(confFile); err != nil {
85
-		return &AuthConfig{rootPath:rootPath}, ErrConfigFileMissing
85
+		return &AuthConfig{rootPath: rootPath}, ErrConfigFileMissing
86 86
 	}
87 87
 	b, err := ioutil.ReadFile(confFile)
88 88
 	if err != nil {
... ...
@@ -10,8 +10,8 @@ import (
10 10
 
11 11
 func TestEncodeAuth(t *testing.T) {
12 12
 	newAuthConfig := &AuthConfig{Username: "ken", Password: "test", Email: "test@example.com"}
13
-	authStr := EncodeAuth(newAuthConfig)
14
-	decAuthConfig, err := DecodeAuth(authStr)
13
+	authStr := encodeAuth(newAuthConfig)
14
+	decAuthConfig, err := decodeAuth(authStr)
15 15
 	if err != nil {
16 16
 		t.Fatal(err)
17 17
 	}
... ...
@@ -30,7 +30,7 @@ func TestLogin(t *testing.T) {
30 30
 	os.Setenv("DOCKER_INDEX_URL", "https://indexstaging-docker.dotcloud.com")
31 31
 	defer os.Setenv("DOCKER_INDEX_URL", "")
32 32
 	authConfig := NewAuthConfig("unittester", "surlautrerivejetattendrai", "noise+unittester@dotcloud.com", "/tmp")
33
-	status, err := Login(authConfig)
33
+	status, err := Login(authConfig, false)
34 34
 	if err != nil {
35 35
 		t.Fatal(err)
36 36
 	}
... ...
@@ -50,7 +50,7 @@ func TestCreateAccount(t *testing.T) {
50 50
 	token := hex.EncodeToString(tokenBuffer)[:12]
51 51
 	username := "ut" + token
52 52
 	authConfig := NewAuthConfig(username, "test42", "docker-ut+"+token+"@example.com", "/tmp")
53
-	status, err := Login(authConfig)
53
+	status, err := Login(authConfig, false)
54 54
 	if err != nil {
55 55
 		t.Fatal(err)
56 56
 	}
... ...
@@ -60,7 +60,7 @@ func TestCreateAccount(t *testing.T) {
60 60
 		t.Fatalf("Expected status: \"%s\", found \"%s\" instead.", expectedStatus, status)
61 61
 	}
62 62
 
63
-	status, err = Login(authConfig)
63
+	status, err = Login(authConfig, false)
64 64
 	if err == nil {
65 65
 		t.Fatalf("Expected error but found nil instead")
66 66
 	}
... ...
@@ -28,7 +28,7 @@ import (
28 28
 	"unicode"
29 29
 )
30 30
 
31
-const VERSION = "0.4.2"
31
+const VERSION = "0.4.3"
32 32
 
33 33
 var (
34 34
 	GITCOMMIT string
... ...
@@ -39,8 +39,8 @@ func (cli *DockerCli) getMethod(name string) (reflect.Method, bool) {
39 39
 	return reflect.TypeOf(cli).MethodByName(methodName)
40 40
 }
41 41
 
42
-func ParseCommands(addr string, port int, args ...string) error {
43
-	cli := NewDockerCli(addr, port)
42
+func ParseCommands(proto, addr string, args ...string) error {
43
+	cli := NewDockerCli(proto, addr)
44 44
 
45 45
 	if len(args) > 0 {
46 46
 		method, exists := cli.getMethod(args[0])
... ...
@@ -73,7 +73,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
73 73
 			return nil
74 74
 		}
75 75
 	}
76
-	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)
76
+	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)
77 77
 	for _, command := range [][2]string{
78 78
 		{"attach", "Attach to a running container"},
79 79
 		{"build", "Build a container from a Dockerfile"},
... ...
@@ -1055,37 +1055,18 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
1055 1055
 		return fmt.Errorf("Impossible to attach to a stopped container, start it first")
1056 1056
 	}
1057 1057
 
1058
-	splitStderr := container.Config.Tty
1059
-
1060
-	connections := 1
1061
-	if splitStderr {
1062
-		connections += 1
1063
-	}
1064
-	chErrors := make(chan error, connections)
1065 1058
 	if container.Config.Tty {
1066 1059
 		cli.monitorTtySize(cmd.Arg(0))
1067 1060
 	}
1068
-	if splitStderr {
1069
-		go func() {
1070
-			chErrors <- cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?stream=1&stderr=1", false, nil, os.Stderr)
1071
-		}()
1072
-	}
1061
+
1073 1062
 	v := url.Values{}
1074 1063
 	v.Set("stream", "1")
1075 1064
 	v.Set("stdin", "1")
1076 1065
 	v.Set("stdout", "1")
1077
-	if !splitStderr {
1078
-		v.Set("stderr", "1")
1079
-	}
1080
-	go func() {
1081
-		chErrors <- cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), container.Config.Tty, os.Stdin, os.Stdout)
1082
-	}()
1083
-	for connections > 0 {
1084
-		err := <-chErrors
1085
-		if err != nil {
1086
-			return err
1087
-		}
1088
-		connections -= 1
1066
+	v.Set("stderr", "1")
1067
+
1068
+	if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), container.Config.Tty, os.Stdin, os.Stdout); err != nil {
1069
+		return err
1089 1070
 	}
1090 1071
 	return nil
1091 1072
 }
... ...
@@ -1311,7 +1292,7 @@ func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int,
1311 1311
 		params = bytes.NewBuffer(buf)
1312 1312
 	}
1313 1313
 
1314
-	req, err := http.NewRequest(method, fmt.Sprintf("http://%s:%d/v%g%s", cli.host, cli.port, APIVERSION, path), params)
1314
+	req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), params)
1315 1315
 	if err != nil {
1316 1316
 		return nil, -1, err
1317 1317
 	}
... ...
@@ -1321,7 +1302,13 @@ func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int,
1321 1321
 	} else if method == "POST" {
1322 1322
 		req.Header.Set("Content-Type", "plain/text")
1323 1323
 	}
1324
-	resp, err := http.DefaultClient.Do(req)
1324
+	dial, err := net.Dial(cli.proto, cli.addr)
1325
+	if err != nil {
1326
+		return nil, -1, err
1327
+	}
1328
+	clientconn := httputil.NewClientConn(dial, nil)
1329
+	resp, err := clientconn.Do(req)
1330
+	defer clientconn.Close()
1325 1331
 	if err != nil {
1326 1332
 		if strings.Contains(err.Error(), "connection refused") {
1327 1333
 			return nil, -1, fmt.Errorf("Can't connect to docker daemon. Is 'docker -d' running on this host?")
... ...
@@ -1346,7 +1333,7 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e
1346 1346
 	if (method == "POST" || method == "PUT") && in == nil {
1347 1347
 		in = bytes.NewReader([]byte{})
1348 1348
 	}
1349
-	req, err := http.NewRequest(method, fmt.Sprintf("http://%s:%d/v%g%s", cli.host, cli.port, APIVERSION, path), in)
1349
+	req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), in)
1350 1350
 	if err != nil {
1351 1351
 		return err
1352 1352
 	}
... ...
@@ -1354,7 +1341,13 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e
1354 1354
 	if method == "POST" {
1355 1355
 		req.Header.Set("Content-Type", "plain/text")
1356 1356
 	}
1357
-	resp, err := http.DefaultClient.Do(req)
1357
+	dial, err := net.Dial(cli.proto, cli.addr)
1358
+	if err != nil {
1359
+		return err
1360
+	}
1361
+	clientconn := httputil.NewClientConn(dial, nil)
1362
+	resp, err := clientconn.Do(req)
1363
+	defer clientconn.Close()
1358 1364
 	if err != nil {
1359 1365
 		if strings.Contains(err.Error(), "connection refused") {
1360 1366
 			return fmt.Errorf("Can't connect to docker daemon. Is 'docker -d' running on this host?")
... ...
@@ -1404,7 +1397,7 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in *os.Fi
1404 1404
 		return err
1405 1405
 	}
1406 1406
 	req.Header.Set("Content-Type", "plain/text")
1407
-	dial, err := net.Dial("tcp", fmt.Sprintf("%s:%d", cli.host, cli.port))
1407
+	dial, err := net.Dial(cli.proto, cli.addr)
1408 1408
 	if err != nil {
1409 1409
 		return err
1410 1410
 	}
... ...
@@ -1487,13 +1480,13 @@ func Subcmd(name, signature, description string) *flag.FlagSet {
1487 1487
 	return flags
1488 1488
 }
1489 1489
 
1490
-func NewDockerCli(addr string, port int) *DockerCli {
1490
+func NewDockerCli(proto, addr string) *DockerCli {
1491 1491
 	authConfig, _ := auth.LoadConfig(os.Getenv("HOME"))
1492
-	return &DockerCli{addr, port, authConfig}
1492
+	return &DockerCli{proto, addr, authConfig}
1493 1493
 }
1494 1494
 
1495 1495
 type DockerCli struct {
1496
-	host       string
1497
-	port       int
1496
+	proto      string
1497
+	addr       string
1498 1498
 	authConfig *auth.AuthConfig
1499 1499
 }
... ...
@@ -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,28 @@ 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
 }
... ...
@@ -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
 --------------------------------------
... ...
@@ -19,19 +19,14 @@ run	add-apt-repository "deb http://archive.ubuntu.com/ubuntu $(lsb_release -sc)
19 19
 run	add-apt-repository -y ppa:dotcloud/docker-golang/ubuntu
20 20
 run	apt-get update
21 21
 # Packages required to checkout, build and upload docker
22
-run	DEBIAN_FRONTEND=noninteractive apt-get install -y -q s3cmd
23
-run	DEBIAN_FRONTEND=noninteractive apt-get install -y -q curl
22
+run	DEBIAN_FRONTEND=noninteractive apt-get install -y -q s3cmd curl
24 23
 run	curl -s -o /go.tar.gz https://go.googlecode.com/files/go1.1.1.linux-amd64.tar.gz
25 24
 run	tar -C /usr/local -xzf /go.tar.gz
26 25
 run	echo "export PATH=/usr/local/go/bin:$PATH" > /.bashrc
27 26
 run	echo "export PATH=/usr/local/go/bin:$PATH" > /.bash_profile
28
-run	DEBIAN_FRONTEND=noninteractive apt-get install -y -q git
29
-run	DEBIAN_FRONTEND=noninteractive apt-get install -y -q build-essential
27
+run	DEBIAN_FRONTEND=noninteractive apt-get install -y -q git build-essential
30 28
 # Packages required to build an ubuntu package
31
-run	DEBIAN_FRONTEND=noninteractive apt-get install -y -q golang-stable
32
-run	DEBIAN_FRONTEND=noninteractive apt-get install -y -q debhelper
33
-run	DEBIAN_FRONTEND=noninteractive apt-get install -y -q autotools-dev
34
-run	apt-get install -y -q devscripts
29
+run	DEBIAN_FRONTEND=noninteractive apt-get install -y -q golang-stable debhelper autotools-dev devscripts
35 30
 # Copy dockerbuilder files into the container
36 31
 add	.       /src
37 32
 run	cp /src/dockerbuilder /usr/local/bin/ && chmod +x /usr/local/bin/dockerbuilder
... ...
@@ -2,11 +2,11 @@
2 2
 #
3 3
 # Dependencies:  debhelper autotools-dev devscripts golang-stable
4 4
 # Notes:
5
-# Use 'make ubuntu' to create the ubuntu package
6
-# GPG_KEY environment variable needs to contain a GPG private key for package to be signed
7
-# and uploaded to docker PPA.
8
-# If GPG_KEY is not defined, make ubuntu will create docker package and exit with
9
-# status code 2
5
+# Use 'make ubuntu' to create the ubuntu package and push it to stating PPA by
6
+# default. To push to production, set PUBLISH_PPA=1 before doing 'make ubuntu'
7
+# GPG_KEY environment variable needs to contain a GPG private key for package
8
+# to be signed and uploaded to docker PPA. If GPG_KEY is not defined,
9
+# make ubuntu will create docker package and exit with status code 2
10 10
 
11 11
 PKG_NAME=lxc-docker
12 12
 GITHUB_PATH=github.com/dotcloud/docker
... ...
@@ -52,9 +52,11 @@ ubuntu:
52 52
 	if /usr/bin/test "$${GPG_KEY}" == ""; then exit 2; fi
53 53
 	mkdir ${BUILD_SRC}
54 54
 	# Import gpg signing key
55
-	echo "$${GPG_KEY}" | gpg --allow-secret-key-import --import
55
+	echo "$${GPG_KEY}" | gpg --allow-secret-key-import --import || true
56 56
 	# Sign the package
57 57
 	cd ${BUILD_SRC}; dpkg-source -x ${BUILD_SRC}/../${PKG_NAME}_${VERSION}-1.dsc
58 58
 	cd ${BUILD_SRC}/${PKG_NAME}-${VERSION}; debuild -S -sa
59
-	cd ${BUILD_SRC};dput ppa:dotcloud/lxc-docker ${PKG_NAME}_${VERSION}-1_source.changes
59
+	# Upload to PPA
60
+	if [ "${PUBLISH_PPA}" = "1" ];  then cd ${BUILD_SRC};dput ppa:dotcloud/lxc-docker ${PKG_NAME}_${VERSION}-1_source.changes; fi
61
+	if [ "${PUBLISH_PPA}" != "1" ]; then cd ${BUILD_SRC};dput ppa:dotcloud/docker-staging ${PKG_NAME}_${VERSION}-1_source.changes; fi
60 62
 	rm -rf ${BUILD_SRC}
... ...
@@ -7,10 +7,10 @@ import (
7 7
 	"fmt"
8 8
 	"github.com/dotcloud/docker/auth"
9 9
 	"github.com/dotcloud/docker/utils"
10
-	"github.com/shin-/cookiejar"
11 10
 	"io"
12 11
 	"io/ioutil"
13 12
 	"net/http"
13
+	"net/http/cookiejar"
14 14
 	"net/url"
15 15
 	"strconv"
16 16
 	"strings"
... ...
@@ -156,7 +156,7 @@ func (r *Registry) GetRemoteTags(registries []string, repository string, token [
156 156
 	}
157 157
 	for _, host := range registries {
158 158
 		endpoint := fmt.Sprintf("https://%s/v1/repositories/%s/tags", host, repository)
159
-		req, err := http.NewRequest("GET", endpoint, nil)
159
+		req, err := r.opaqueRequest("GET", endpoint, nil)
160 160
 		if err != nil {
161 161
 			return nil, err
162 162
 		}
... ...
@@ -190,7 +190,7 @@ func (r *Registry) GetRemoteTags(registries []string, repository string, token [
190 190
 func (r *Registry) GetRepositoryData(remote string) (*RepositoryData, error) {
191 191
 	repositoryTarget := auth.IndexServerAddress() + "/repositories/" + remote + "/images"
192 192
 
193
-	req, err := http.NewRequest("GET", repositoryTarget, nil)
193
+	req, err := r.opaqueRequest("GET", repositoryTarget, nil)
194 194
 	if err != nil {
195 195
 		return nil, err
196 196
 	}
... ...
@@ -309,6 +309,15 @@ func (r *Registry) PushImageLayerRegistry(imgId string, layer io.Reader, registr
309 309
 	return nil
310 310
 }
311 311
 
312
+func (r *Registry) opaqueRequest(method, urlStr string, body io.Reader) (*http.Request, error) {
313
+	req, err := http.NewRequest(method, urlStr, body)
314
+	if err != nil {
315
+		return nil, err
316
+	}
317
+	req.URL.Opaque = strings.Replace(urlStr, req.URL.Scheme+":", "", 1)
318
+	return req, err
319
+}
320
+
312 321
 // push a tag on the registry.
313 322
 // Remote has the format '<user>/<repo>
314 323
 func (r *Registry) PushRegistryTag(remote, revision, tag, registry string, token []string) error {
... ...
@@ -316,7 +325,7 @@ func (r *Registry) PushRegistryTag(remote, revision, tag, registry string, token
316 316
 	revision = "\"" + revision + "\""
317 317
 	registry = "https://" + registry + "/v1"
318 318
 
319
-	req, err := http.NewRequest("PUT", registry+"/repositories/"+remote+"/tags/"+tag, strings.NewReader(revision))
319
+	req, err := r.opaqueRequest("PUT", registry+"/repositories/"+remote+"/tags/"+tag, strings.NewReader(revision))
320 320
 	if err != nil {
321 321
 		return err
322 322
 	}
... ...
@@ -346,7 +355,7 @@ func (r *Registry) PushImageJSONIndex(remote string, imgList []*ImgData, validat
346 346
 
347 347
 	utils.Debugf("Image list pushed to index:\n%s\n", imgListJSON)
348 348
 
349
-	req, err := http.NewRequest("PUT", auth.IndexServerAddress()+"/repositories/"+remote+"/"+suffix, bytes.NewReader(imgListJSON))
349
+	req, err := r.opaqueRequest("PUT", auth.IndexServerAddress()+"/repositories/"+remote+"/"+suffix, bytes.NewReader(imgListJSON))
350 350
 	if err != nil {
351 351
 		return nil, err
352 352
 	}
... ...
@@ -366,7 +375,7 @@ func (r *Registry) PushImageJSONIndex(remote string, imgList []*ImgData, validat
366 366
 	// Redirect if necessary
367 367
 	for res.StatusCode >= 300 && res.StatusCode < 400 {
368 368
 		utils.Debugf("Redirected to %s\n", res.Header.Get("Location"))
369
-		req, err = http.NewRequest("PUT", res.Header.Get("Location"), bytes.NewReader(imgListJSON))
369
+		req, err = r.opaqueRequest("PUT", res.Header.Get("Location"), bytes.NewReader(imgListJSON))
370 370
 		if err != nil {
371 371
 			return nil, err
372 372
 		}
... ...
@@ -444,11 +453,6 @@ func (r *Registry) SearchRepositories(term string) (*SearchResults, error) {
444 444
 	return result, err
445 445
 }
446 446
 
447
-func (r *Registry) ResetClient(authConfig *auth.AuthConfig) {
448
-	r.authConfig = authConfig
449
-	r.client.Jar = cookiejar.NewCookieJar()
450
-}
451
-
452 447
 func (r *Registry) GetAuthConfig(withPasswd bool) *auth.AuthConfig {
453 448
 	password := ""
454 449
 	if withPasswd {
... ...
@@ -484,18 +488,18 @@ type Registry struct {
484 484
 	authConfig *auth.AuthConfig
485 485
 }
486 486
 
487
-func NewRegistry(root string, authConfig *auth.AuthConfig) *Registry {
487
+func NewRegistry(root string, authConfig *auth.AuthConfig) (r *Registry, err error) {
488 488
 	httpTransport := &http.Transport{
489 489
 		DisableKeepAlives: true,
490 490
 		Proxy:             http.ProxyFromEnvironment,
491 491
 	}
492 492
 
493
-	r := &Registry{
493
+	r = &Registry{
494 494
 		authConfig: authConfig,
495 495
 		client: &http.Client{
496 496
 			Transport: httpTransport,
497 497
 		},
498 498
 	}
499
-	r.client.Jar = cookiejar.NewCookieJar()
500
-	return r
499
+	r.client.Jar, err = cookiejar.New(nil)
500
+	return r, err
501 501
 }
... ...
@@ -55,8 +55,11 @@ func (srv *Server) ContainerExport(name string, out io.Writer) error {
55 55
 }
56 56
 
57 57
 func (srv *Server) ImagesSearch(term string) ([]APISearch, error) {
58
-
59
-	results, err := registry.NewRegistry(srv.runtime.root, nil).SearchRepositories(term)
58
+	r, err := registry.NewRegistry(srv.runtime.root, nil)
59
+	if err != nil {
60
+		return nil, err
61
+	}
62
+	results, err := r.SearchRepositories(term)
60 63
 	if err != nil {
61 64
 		return nil, err
62 65
 	}
... ...
@@ -450,12 +453,15 @@ func (srv *Server) poolRemove(kind, key string) error {
450 450
 }
451 451
 
452 452
 func (srv *Server) ImagePull(name, tag, endpoint string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig) error {
453
+	r, err := registry.NewRegistry(srv.runtime.root, authConfig)
454
+	if err != nil {
455
+		return err
456
+	}
453 457
 	if err := srv.poolAdd("pull", name+":"+tag); err != nil {
454 458
 		return err
455 459
 	}
456 460
 	defer srv.poolRemove("pull", name+":"+tag)
457 461
 
458
-	r := registry.NewRegistry(srv.runtime.root, authConfig)
459 462
 	out = utils.NewWriteFlusher(out)
460 463
 	if endpoint != "" {
461 464
 		if err := srv.pullImage(r, out, name, endpoint, nil, sf); err != nil {
... ...
@@ -572,7 +578,7 @@ func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, name stri
572 572
 				// FIXME: Continue on error?
573 573
 				return err
574 574
 			}
575
-			out.Write(sf.FormatStatus("Pushing tags for rev [%s] on {%s}", elem.ID, ep+"/users/"+srvName+"/"+elem.Tag))
575
+			out.Write(sf.FormatStatus("Pushing tags for rev [%s] on {%s}", elem.ID, ep+"/repositories/"+srvName+"/tags/"+elem.Tag))
576 576
 			if err := r.PushRegistryTag(srvName, elem.ID, elem.Tag, ep, repoData.Tokens); err != nil {
577 577
 				return err
578 578
 			}
... ...
@@ -654,8 +660,10 @@ func (srv *Server) ImagePush(name, endpoint string, out io.Writer, sf *utils.Str
654 654
 
655 655
 	out = utils.NewWriteFlusher(out)
656 656
 	img, err := srv.runtime.graph.Get(name)
657
-	r := registry.NewRegistry(srv.runtime.root, authConfig)
658
-
657
+	r, err2 := registry.NewRegistry(srv.runtime.root, authConfig)
658
+	if err2 != nil {
659
+		return err2
660
+	}
659 661
 	if err != nil {
660 662
 		out.Write(sf.FormatStatus("The push refers to a repository [%s] (len: %d)", name, len(srv.runtime.repositories.Repositories[name])))
661 663
 		// If it fails, try to get the repository
... ...
@@ -751,6 +759,9 @@ func (srv *Server) ContainerRestart(name string, t int) error {
751 751
 
752 752
 func (srv *Server) ContainerDestroy(name string, removeVolume bool) error {
753 753
 	if container := srv.runtime.Get(name); container != nil {
754
+		if container.State.Running {
755
+			return fmt.Errorf("Impossible to remove a running container, please stop it first")
756
+		}
754 757
 		volumes := make(map[string]struct{})
755 758
 		// Store all the deleted containers volumes
756 759
 		for _, volumeId := range container.Volumes {
... ...
@@ -9,16 +9,16 @@ const (
9 9
 	getTermios = syscall.TIOCGETA
10 10
 	setTermios = syscall.TIOCSETA
11 11
 
12
-	ECHO    = 0x00000008
13
-	ONLCR   = 0x2
14
-	ISTRIP  = 0x20
15
-	INLCR   = 0x40
16
-	ISIG    = 0x80
17
-	IGNCR   = 0x80
18
-	ICANON  = 0x100
19
-	ICRNL   = 0x100
20
-	IXOFF   = 0x400
21
-	IXON    = 0x200
12
+	ECHO   = 0x00000008
13
+	ONLCR  = 0x2
14
+	ISTRIP = 0x20
15
+	INLCR  = 0x40
16
+	ISIG   = 0x80
17
+	IGNCR  = 0x80
18
+	ICANON = 0x100
19
+	ICRNL  = 0x100
20
+	IXOFF  = 0x400
21
+	IXON   = 0x200
22 22
 )
23 23
 
24 24
 type Termios struct {
... ...
@@ -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,28 @@ 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
+}
... ...
@@ -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
+}