Conflicts:
api.go
builder_client.go
commands.go
... | ... |
@@ -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 |
|
... | ... |
@@ -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 |
+} |