Compare commits
212 Commits
00166d05d9
...
v1.6.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7c8fca2ddb | ||
|
|
376188dcd3 | ||
|
|
dc610864aa | ||
|
|
97cd073598 | ||
|
|
d5ebb60bdd | ||
|
|
83c5131acd | ||
|
|
b6a9dc399b | ||
|
|
614a9690e7 | ||
|
|
545b440a80 | ||
|
|
3162024e28 | ||
|
|
769acfec29 | ||
|
|
47496519da | ||
|
|
fdd21bf032 | ||
|
|
d928dad8c8 | ||
|
|
82366ce059 | ||
|
|
6410c3c066 | ||
|
|
9231dc9cc0 | ||
|
|
6a3f37386b | ||
|
|
d9a0c05208 | ||
|
|
24cb9df189 | ||
|
|
c51cd3298c | ||
|
|
10affa8018 | ||
|
|
ce27fa2716 | ||
|
|
8d83409e85 | ||
|
|
3a73b6a2bf | ||
|
|
f99269882f | ||
|
|
568a9703ac | ||
|
|
faaeb5162d | ||
|
|
b5613baac2 | ||
|
|
c956efcd52 | ||
|
|
5455864187 | ||
|
|
ceb72fab34 | ||
|
|
c6ea062a26 | ||
|
|
0e045ab50c | ||
|
|
eeb05fc081 | ||
|
|
e1381ae328 | ||
|
|
45ad064150 | ||
|
|
72e14a1566 | ||
|
|
7d7bec86c9 | ||
|
|
a39d49d676 | ||
|
|
5bf15a013b | ||
|
|
9461967eec | ||
|
|
3be7d11cee | ||
|
|
d9910b8fd8 | ||
|
|
f115c32f6b | ||
|
|
57939badc3 | ||
|
|
51ee02d478 | ||
|
|
c92860748c | ||
|
|
f582f9717f | ||
|
|
ebcb36a8d2 | ||
|
|
e6e8f2d717 | ||
|
|
317a510261 | ||
|
|
5d3a080178 | ||
|
|
542c84c2d2 | ||
|
|
f1df74d09d | ||
|
|
4ddbc7a62f | ||
|
|
f72b2c02b8 | ||
|
|
af9dab70f8 | ||
|
|
10425e83f2 | ||
|
|
9c528dca85 | ||
|
|
cb2c25ad2d | ||
|
|
962dec81ec | ||
|
|
1eae925a3d | ||
|
|
3ce2cc8ee7 | ||
|
|
054acc4bee | ||
|
|
63cb03a55b | ||
|
|
49b6f23696 | ||
|
|
299ae6a2e6 | ||
|
|
97b521bf10 | ||
|
|
7f5937d46c | ||
|
|
b6166b9496 | ||
|
|
b596d025f5 | ||
|
|
ca32446950 | ||
|
|
5328d6d620 | ||
|
|
d0023242ab | ||
|
|
3ff002aa1a | ||
|
|
ea9b357be2 | ||
|
|
bf1829459f | ||
|
|
4f744ca781 | ||
|
|
7dab04383b | ||
|
|
8a003c8134 | ||
|
|
208178c799 | ||
|
|
03b36f3451 | ||
|
|
7758553239 | ||
|
|
10fb5ce6d0 | ||
|
|
0959aec1a9 | ||
|
|
773f74eb71 | ||
|
|
7070d9255a | ||
|
|
2cb4b7f65c | ||
|
|
2d80652d8a | ||
|
|
81b4691406 | ||
|
|
4bae33ef9f | ||
|
|
a8a31eff10 | ||
|
|
68a8fd5c4e | ||
|
|
8387c5ab65 | ||
|
|
69498943c3 | ||
|
|
1aeb78c2ae | ||
|
|
331d37f35d | ||
|
|
edf3bf7f33 | ||
|
|
9ee8dca246 | ||
|
|
aa98bb6c13 | ||
|
|
2aba3c69f9 | ||
|
|
71a44c769e | ||
|
|
d8381fad2b | ||
|
|
be379580d0 | ||
|
|
7ea8513479 | ||
|
|
3b2fe01c78 | ||
|
|
e8afc22b1f | ||
|
|
4e407e6b77 | ||
|
|
23f1c2ea9e | ||
|
|
788047cafb | ||
|
|
0c0e7b1b60 | ||
|
|
09d41529a0 | ||
|
|
cb288fefee | ||
|
|
f7636796c5 | ||
|
|
cb5af83444 | ||
|
|
96feaf1920 | ||
|
|
1f03944950 | ||
|
|
6060eedf9c | ||
|
|
d217da854a | ||
|
|
d74d6d981b | ||
|
|
0205ac33d2 | ||
|
|
dbb9d47bdc | ||
|
|
ddd1d081d7 | ||
|
|
d6ac36d929 | ||
|
|
715b94f664 | ||
|
|
16baca9277 | ||
|
|
627f8a6cd5 | ||
|
|
a8a7df203a | ||
|
|
580cbcefd3 | ||
|
|
d9c5ce6e97 | ||
|
|
0fe9b95415 | ||
|
|
41d0e4293e | ||
|
|
26fe640da1 | ||
|
|
198ca26969 | ||
|
|
d5365f6fc4 | ||
|
|
5f7e814ee7 | ||
|
|
a84aca0985 | ||
|
|
68ec22876a | ||
|
|
0dcc3559e9 | ||
|
|
d4c731ecd6 | ||
|
|
2dba4e1386 | ||
|
|
06a7f471e0 | ||
|
|
4683d01691 | ||
|
|
6020a06399 | ||
|
|
cc0bfccdf4 | ||
|
|
0c18ec62f3 | ||
|
|
a9825c9bd8 | ||
|
|
908be50c44 | ||
|
|
2a82dba34d | ||
|
|
13fd2a908c | ||
|
|
464891aaf8 | ||
|
|
9974663ed7 | ||
|
|
76269e5c9d | ||
|
|
1121d7c4fd | ||
|
|
7e197575a2 | ||
|
|
3dc3059d94 | ||
|
|
7b6de74c9a | ||
|
|
cad8adacb8 | ||
|
|
6226deeaf4 | ||
|
|
3ec19f56cf | ||
|
|
48c71787ed | ||
|
|
604731a930 | ||
|
|
e8650e01f8 | ||
|
|
817d04d992 | ||
|
|
cdff91a01c | ||
|
|
6f26bd0e16 | ||
|
|
3c090db4e9 | ||
|
|
b7c3fdfd0d | ||
|
|
aa682a845b | ||
|
|
218d0dcc9d | ||
|
|
510d8f8634 | ||
|
|
b65600f6b6 | ||
|
|
79dcea718c | ||
|
|
072b09c45d | ||
|
|
c2d9837745 | ||
|
|
fa5dfbb18b | ||
|
|
6532a075f3 | ||
|
|
3b4a4bf809 | ||
|
|
4602909566 | ||
|
|
588f350b61 | ||
|
|
6e5ff509b2 | ||
|
|
61d341c2ca | ||
|
|
b996d379a1 | ||
|
|
b0935ea730 | ||
|
|
96fe13b49b | ||
|
|
12ccde442a | ||
|
|
4262cfe41f | ||
|
|
ddc2e25546 | ||
|
|
6646cff646 | ||
|
|
ac8fd856c0 | ||
|
|
48754d673c | ||
|
|
723684525a | ||
|
|
32aceadbe6 | ||
|
|
c67d3e159c | ||
|
|
a080e2add7 | ||
|
|
24d81b0ddb | ||
|
|
08f2fad40b | ||
|
|
f91fbe39ce | ||
|
|
018ab080bb | ||
|
|
fe94ecb2c1 | ||
|
|
7b2e67036f | ||
|
|
e130faea1b | ||
|
|
38f09de334 | ||
|
|
f9ba68ddfb | ||
|
|
16913455bd | ||
|
|
32f189cd08 | ||
|
|
526ca42282 | ||
|
|
b98b42d843 | ||
|
|
7bf03dd132 | ||
|
|
034aa3b2c4 | ||
|
|
6da1e01e6c |
37
CHANGELOG.md
@@ -1,5 +1,42 @@
|
||||
# Changelog
|
||||
|
||||
## 1.6.2 (2015-05-13)
|
||||
|
||||
#### Runtime
|
||||
- Revert change prohibiting mounting into /sys
|
||||
|
||||
## 1.6.1 (2015-05-07)
|
||||
|
||||
#### Security
|
||||
- Fix read/write /proc paths (CVE-2015-3630)
|
||||
- Prohibit VOLUME /proc and VOLUME / (CVE-2015-3631)
|
||||
- Fix opening of file-descriptor 1 (CVE-2015-3627)
|
||||
- Fix symlink traversal on container respawn allowing local privilege escalation (CVE-2015-3629)
|
||||
- Prohibit mount of /sys
|
||||
|
||||
#### Runtime
|
||||
- Update Apparmor policy to not allow mounts
|
||||
|
||||
## 1.6.0 (2015-04-07)
|
||||
|
||||
#### Builder
|
||||
+ Building images from an image ID
|
||||
+ build containers with resource constraints, ie `docker build --cpu-shares=100 --memory=1024m...`
|
||||
+ `commit --change` to apply specified Dockerfile instructions while committing the image
|
||||
+ `import --change` to apply specified Dockerfile instructions while importing the image
|
||||
+ basic build cancellation
|
||||
|
||||
#### Client
|
||||
+ Windows Support
|
||||
|
||||
#### Runtime
|
||||
+ Container and image Labels
|
||||
+ `--cgroup-parent` for specifying a parent cgroup to place container cgroup within
|
||||
+ Logging drivers, `json-file`, `syslog`, or `none`
|
||||
+ Pulling images by ID
|
||||
+ `--ulimit` to set the ulimit on a container
|
||||
+ `--default-ulimit` option on the daemon which applies to all created containers (and overwritten by `--ulimit` on run)
|
||||
|
||||
## 1.5.0 (2015-02-10)
|
||||
|
||||
#### Builder
|
||||
|
||||
@@ -441,7 +441,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
|
||||
authconfig.ServerAddress = serverAddress
|
||||
cli.configFile.Configs[serverAddress] = authconfig
|
||||
|
||||
stream, statusCode, err := cli.call("POST", "/auth", cli.configFile.Configs[serverAddress], false)
|
||||
stream, statusCode, err := cli.call("POST", "/auth", cli.configFile.Configs[serverAddress], nil)
|
||||
if statusCode == 401 {
|
||||
delete(cli.configFile.Configs, serverAddress)
|
||||
registry.SaveConfig(cli.configFile)
|
||||
@@ -527,7 +527,7 @@ func (cli *DockerCli) CmdVersion(args ...string) error {
|
||||
}
|
||||
fmt.Fprintf(cli.out, "OS/Arch (client): %s/%s\n", runtime.GOOS, runtime.GOARCH)
|
||||
|
||||
body, _, err := readBody(cli.call("GET", "/version", nil, false))
|
||||
body, _, err := readBody(cli.call("GET", "/version", nil, nil))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -559,7 +559,7 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
|
||||
cmd.Require(flag.Exact, 0)
|
||||
utils.ParseFlags(cmd, args, false)
|
||||
|
||||
body, _, err := readBody(cli.call("GET", "/info", nil, false))
|
||||
body, _, err := readBody(cli.call("GET", "/info", nil, nil))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -696,7 +696,7 @@ func (cli *DockerCli) CmdStop(args ...string) error {
|
||||
|
||||
var encounteredError error
|
||||
for _, name := range cmd.Args() {
|
||||
_, _, err := readBody(cli.call("POST", "/containers/"+name+"/stop?"+v.Encode(), nil, false))
|
||||
_, _, err := readBody(cli.call("POST", "/containers/"+name+"/stop?"+v.Encode(), nil, nil))
|
||||
if err != nil {
|
||||
fmt.Fprintf(cli.err, "%s\n", err)
|
||||
encounteredError = fmt.Errorf("Error: failed to stop one or more containers")
|
||||
@@ -719,7 +719,7 @@ func (cli *DockerCli) CmdRestart(args ...string) error {
|
||||
|
||||
var encounteredError error
|
||||
for _, name := range cmd.Args() {
|
||||
_, _, err := readBody(cli.call("POST", "/containers/"+name+"/restart?"+v.Encode(), nil, false))
|
||||
_, _, err := readBody(cli.call("POST", "/containers/"+name+"/restart?"+v.Encode(), nil, nil))
|
||||
if err != nil {
|
||||
fmt.Fprintf(cli.err, "%s\n", err)
|
||||
encounteredError = fmt.Errorf("Error: failed to restart one or more containers")
|
||||
@@ -748,7 +748,7 @@ func (cli *DockerCli) forwardAllSignals(cid string) chan os.Signal {
|
||||
if sig == "" {
|
||||
log.Errorf("Unsupported signal: %v. Discarding.", s)
|
||||
}
|
||||
if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/kill?signal=%s", cid, sig), nil, false)); err != nil {
|
||||
if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/kill?signal=%s", cid, sig), nil, nil)); err != nil {
|
||||
log.Debugf("Error sending signal: %s", err)
|
||||
}
|
||||
}
|
||||
@@ -769,24 +769,12 @@ func (cli *DockerCli) CmdStart(args ...string) error {
|
||||
cmd.Require(flag.Min, 1)
|
||||
utils.ParseFlags(cmd, args, true)
|
||||
|
||||
hijacked := make(chan io.Closer)
|
||||
// Block the return until the chan gets closed
|
||||
defer func() {
|
||||
log.Debugf("CmdStart() returned, defer waiting for hijack to finish.")
|
||||
if _, ok := <-hijacked; ok {
|
||||
log.Errorf("Hijack did not finish (chan still open)")
|
||||
}
|
||||
if *openStdin || *attach {
|
||||
cli.in.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
if *attach || *openStdin {
|
||||
if cmd.NArg() > 1 {
|
||||
return fmt.Errorf("You cannot start and attach multiple containers at once.")
|
||||
}
|
||||
|
||||
stream, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, false)
|
||||
stream, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -816,29 +804,37 @@ func (cli *DockerCli) CmdStart(args ...string) error {
|
||||
v.Set("stdout", "1")
|
||||
v.Set("stderr", "1")
|
||||
|
||||
hijacked := make(chan io.Closer)
|
||||
// Block the return until the chan gets closed
|
||||
defer func() {
|
||||
log.Debugf("CmdStart() returned, defer waiting for hijack to finish.")
|
||||
if _, ok := <-hijacked; ok {
|
||||
log.Errorf("Hijack did not finish (chan still open)")
|
||||
}
|
||||
cli.in.Close()
|
||||
}()
|
||||
cErr = promise.Go(func() error {
|
||||
return cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), tty, in, cli.out, cli.err, hijacked, nil)
|
||||
})
|
||||
} else {
|
||||
close(hijacked)
|
||||
|
||||
// Acknowledge the hijack before starting
|
||||
select {
|
||||
case closer := <-hijacked:
|
||||
// Make sure that the hijack gets closed when returning (results
|
||||
// in closing the hijack chan and freeing server's goroutines)
|
||||
if closer != nil {
|
||||
defer closer.Close()
|
||||
}
|
||||
case err := <-cErr:
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Acknowledge the hijack before starting
|
||||
select {
|
||||
case closer := <-hijacked:
|
||||
// Make sure that the hijack gets closed when returning (results
|
||||
// in closing the hijack chan and freeing server's goroutines)
|
||||
if closer != nil {
|
||||
defer closer.Close()
|
||||
}
|
||||
case err := <-cErr:
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
var encounteredError error
|
||||
for _, name := range cmd.Args() {
|
||||
_, _, err := readBody(cli.call("POST", "/containers/"+name+"/start", nil, false))
|
||||
_, _, err := readBody(cli.call("POST", "/containers/"+name+"/start", nil, nil))
|
||||
if err != nil {
|
||||
if !*attach && !*openStdin {
|
||||
// attach and openStdin is false means it could be starting multiple containers
|
||||
@@ -886,7 +882,7 @@ func (cli *DockerCli) CmdUnpause(args ...string) error {
|
||||
|
||||
var encounteredError error
|
||||
for _, name := range cmd.Args() {
|
||||
if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/unpause", name), nil, false)); err != nil {
|
||||
if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/unpause", name), nil, nil)); err != nil {
|
||||
fmt.Fprintf(cli.err, "%s\n", err)
|
||||
encounteredError = fmt.Errorf("Error: failed to unpause container named %s", name)
|
||||
} else {
|
||||
@@ -903,7 +899,7 @@ func (cli *DockerCli) CmdPause(args ...string) error {
|
||||
|
||||
var encounteredError error
|
||||
for _, name := range cmd.Args() {
|
||||
if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/pause", name), nil, false)); err != nil {
|
||||
if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/pause", name), nil, nil)); err != nil {
|
||||
fmt.Fprintf(cli.err, "%s\n", err)
|
||||
encounteredError = fmt.Errorf("Error: failed to pause container named %s", name)
|
||||
} else {
|
||||
@@ -926,7 +922,7 @@ func (cli *DockerCli) CmdRename(args ...string) error {
|
||||
old_name := cmd.Arg(0)
|
||||
new_name := cmd.Arg(1)
|
||||
|
||||
if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/rename?name=%s", old_name, new_name), nil, false)); err != nil {
|
||||
if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/rename?name=%s", old_name, new_name), nil, nil)); err != nil {
|
||||
fmt.Fprintf(cli.err, "%s\n", err)
|
||||
return fmt.Errorf("Error: failed to rename container named %s", old_name)
|
||||
}
|
||||
@@ -955,7 +951,7 @@ func (cli *DockerCli) CmdInspect(args ...string) error {
|
||||
status := 0
|
||||
|
||||
for _, name := range cmd.Args() {
|
||||
obj, _, err := readBody(cli.call("GET", "/containers/"+name+"/json", nil, false))
|
||||
obj, _, err := readBody(cli.call("GET", "/containers/"+name+"/json", nil, nil))
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "Too many") {
|
||||
fmt.Fprintf(cli.err, "Error: %v", err)
|
||||
@@ -963,7 +959,7 @@ func (cli *DockerCli) CmdInspect(args ...string) error {
|
||||
continue
|
||||
}
|
||||
|
||||
obj, _, err = readBody(cli.call("GET", "/images/"+name+"/json", nil, false))
|
||||
obj, _, err = readBody(cli.call("GET", "/images/"+name+"/json", nil, nil))
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "No such") {
|
||||
fmt.Fprintf(cli.err, "Error: No such image or container: %s\n", name)
|
||||
@@ -1026,7 +1022,7 @@ func (cli *DockerCli) CmdTop(args ...string) error {
|
||||
val.Set("ps_args", strings.Join(cmd.Args()[1:], " "))
|
||||
}
|
||||
|
||||
stream, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/top?"+val.Encode(), nil, false)
|
||||
stream, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/top?"+val.Encode(), nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1052,7 +1048,7 @@ func (cli *DockerCli) CmdPort(args ...string) error {
|
||||
cmd.Require(flag.Min, 1)
|
||||
utils.ParseFlags(cmd, args, true)
|
||||
|
||||
stream, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, false)
|
||||
stream, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1117,7 +1113,7 @@ func (cli *DockerCli) CmdRmi(args ...string) error {
|
||||
|
||||
var encounteredError error
|
||||
for _, name := range cmd.Args() {
|
||||
body, _, err := readBody(cli.call("DELETE", "/images/"+name+"?"+v.Encode(), nil, false))
|
||||
body, _, err := readBody(cli.call("DELETE", "/images/"+name+"?"+v.Encode(), nil, nil))
|
||||
if err != nil {
|
||||
fmt.Fprintf(cli.err, "%s\n", err)
|
||||
encounteredError = fmt.Errorf("Error: failed to remove one or more images")
|
||||
@@ -1148,7 +1144,7 @@ func (cli *DockerCli) CmdHistory(args ...string) error {
|
||||
|
||||
utils.ParseFlags(cmd, args, true)
|
||||
|
||||
body, _, err := readBody(cli.call("GET", "/images/"+cmd.Arg(0)+"/history", nil, false))
|
||||
body, _, err := readBody(cli.call("GET", "/images/"+cmd.Arg(0)+"/history", nil, nil))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1215,7 +1211,7 @@ func (cli *DockerCli) CmdRm(args ...string) error {
|
||||
|
||||
var encounteredError error
|
||||
for _, name := range cmd.Args() {
|
||||
_, _, err := readBody(cli.call("DELETE", "/containers/"+name+"?"+val.Encode(), nil, false))
|
||||
_, _, err := readBody(cli.call("DELETE", "/containers/"+name+"?"+val.Encode(), nil, nil))
|
||||
if err != nil {
|
||||
fmt.Fprintf(cli.err, "%s\n", err)
|
||||
encounteredError = fmt.Errorf("Error: failed to remove one or more containers")
|
||||
@@ -1236,7 +1232,7 @@ func (cli *DockerCli) CmdKill(args ...string) error {
|
||||
|
||||
var encounteredError error
|
||||
for _, name := range cmd.Args() {
|
||||
if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/kill?signal=%s", name, *signal), nil, false)); err != nil {
|
||||
if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/kill?signal=%s", name, *signal), nil, nil)); err != nil {
|
||||
fmt.Fprintf(cli.err, "%s\n", err)
|
||||
encounteredError = fmt.Errorf("Error: failed to kill one or more containers")
|
||||
} else {
|
||||
@@ -1321,32 +1317,8 @@ func (cli *DockerCli) CmdPush(args ...string) error {
|
||||
v := url.Values{}
|
||||
v.Set("tag", tag)
|
||||
|
||||
push := func(authConfig registry.AuthConfig) error {
|
||||
buf, err := json.Marshal(authConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
registryAuthHeader := []string{
|
||||
base64.URLEncoding.EncodeToString(buf),
|
||||
}
|
||||
|
||||
return cli.stream("POST", "/images/"+remote+"/push?"+v.Encode(), nil, cli.out, map[string][]string{
|
||||
"X-Registry-Auth": registryAuthHeader,
|
||||
})
|
||||
}
|
||||
|
||||
if err := push(authConfig); err != nil {
|
||||
if strings.Contains(err.Error(), "Status 401") {
|
||||
fmt.Fprintln(cli.out, "\nPlease login prior to push:")
|
||||
if err := cli.CmdLogin(repoInfo.Index.GetAuthConfigKey()); err != nil {
|
||||
return err
|
||||
}
|
||||
authConfig := cli.configFile.ResolveAuthConfig(repoInfo.Index)
|
||||
return push(authConfig)
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
_, _, err = cli.clientRequestAttemptLogin("POST", "/images/"+remote+"/push?"+v.Encode(), nil, cli.out, repoInfo.Index, "push")
|
||||
return err
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdPull(args ...string) error {
|
||||
@@ -1379,36 +1351,8 @@ func (cli *DockerCli) CmdPull(args ...string) error {
|
||||
|
||||
cli.LoadConfigFile()
|
||||
|
||||
// Resolve the Auth config relevant for this server
|
||||
authConfig := cli.configFile.ResolveAuthConfig(repoInfo.Index)
|
||||
|
||||
pull := func(authConfig registry.AuthConfig) error {
|
||||
buf, err := json.Marshal(authConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
registryAuthHeader := []string{
|
||||
base64.URLEncoding.EncodeToString(buf),
|
||||
}
|
||||
|
||||
return cli.stream("POST", "/images/create?"+v.Encode(), nil, cli.out, map[string][]string{
|
||||
"X-Registry-Auth": registryAuthHeader,
|
||||
})
|
||||
}
|
||||
|
||||
if err := pull(authConfig); err != nil {
|
||||
if strings.Contains(err.Error(), "Status 401") {
|
||||
fmt.Fprintln(cli.out, "\nPlease login prior to pull:")
|
||||
if err := cli.CmdLogin(repoInfo.Index.GetAuthConfigKey()); err != nil {
|
||||
return err
|
||||
}
|
||||
authConfig := cli.configFile.ResolveAuthConfig(repoInfo.Index)
|
||||
return pull(authConfig)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
_, _, err = cli.clientRequestAttemptLogin("POST", "/images/create?"+v.Encode(), nil, cli.out, repoInfo.Index, "pull")
|
||||
return err
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdImages(args ...string) error {
|
||||
@@ -1452,7 +1396,7 @@ func (cli *DockerCli) CmdImages(args ...string) error {
|
||||
v.Set("filters", filterJson)
|
||||
}
|
||||
|
||||
body, _, err := readBody(cli.call("GET", "/images/json?"+v.Encode(), nil, false))
|
||||
body, _, err := readBody(cli.call("GET", "/images/json?"+v.Encode(), nil, nil))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1530,7 +1474,7 @@ func (cli *DockerCli) CmdImages(args ...string) error {
|
||||
v.Set("all", "1")
|
||||
}
|
||||
|
||||
body, _, err := readBody(cli.call("GET", "/images/json?"+v.Encode(), nil, false))
|
||||
body, _, err := readBody(cli.call("GET", "/images/json?"+v.Encode(), nil, nil))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -1728,7 +1672,7 @@ func (cli *DockerCli) CmdPs(args ...string) error {
|
||||
v.Set("filters", filterJson)
|
||||
}
|
||||
|
||||
body, _, err := readBody(cli.call("GET", "/containers/json?"+v.Encode(), nil, false))
|
||||
body, _, err := readBody(cli.call("GET", "/containers/json?"+v.Encode(), nil, nil))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1869,7 +1813,7 @@ func (cli *DockerCli) CmdCommit(args ...string) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
stream, _, err := cli.call("POST", "/commit?"+v.Encode(), config, false)
|
||||
stream, _, err := cli.call("POST", "/commit?"+v.Encode(), config, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1980,7 +1924,7 @@ func (cli *DockerCli) CmdDiff(args ...string) error {
|
||||
|
||||
utils.ParseFlags(cmd, args, true)
|
||||
|
||||
body, _, err := readBody(cli.call("GET", "/containers/"+cmd.Arg(0)+"/changes", nil, false))
|
||||
body, _, err := readBody(cli.call("GET", "/containers/"+cmd.Arg(0)+"/changes", nil, nil))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -2018,7 +1962,7 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
|
||||
|
||||
name := cmd.Arg(0)
|
||||
|
||||
stream, _, err := cli.call("GET", "/containers/"+name+"/json", nil, false)
|
||||
stream, _, err := cli.call("GET", "/containers/"+name+"/json", nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -2059,7 +2003,7 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
|
||||
utils.ParseFlags(cmd, args, true)
|
||||
name := cmd.Arg(0)
|
||||
|
||||
stream, _, err := cli.call("GET", "/containers/"+name+"/json", nil, false)
|
||||
stream, _, err := cli.call("GET", "/containers/"+name+"/json", nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -2130,16 +2074,27 @@ func (cli *DockerCli) CmdSearch(args ...string) error {
|
||||
|
||||
utils.ParseFlags(cmd, args, true)
|
||||
|
||||
name := cmd.Arg(0)
|
||||
v := url.Values{}
|
||||
v.Set("term", cmd.Arg(0))
|
||||
|
||||
body, _, err := readBody(cli.call("GET", "/images/search?"+v.Encode(), nil, true))
|
||||
v.Set("term", name)
|
||||
|
||||
// Resolve the Repository name from fqn to hostname + name
|
||||
taglessRemote, _ := parsers.ParseRepositoryTag(name)
|
||||
repoInfo, err := registry.ParseRepositoryInfo(taglessRemote)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cli.LoadConfigFile()
|
||||
|
||||
body, statusCode, errReq := cli.clientRequestAttemptLogin("GET", "/images/search?"+v.Encode(), nil, nil, repoInfo.Index, "search")
|
||||
rawBody, _, err := readBody(body, statusCode, errReq)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
outs := engine.NewTable("star_count", 0)
|
||||
if _, err := outs.ReadListFrom(body); err != nil {
|
||||
if _, err := outs.ReadListFrom(rawBody); err != nil {
|
||||
return err
|
||||
}
|
||||
w := tabwriter.NewWriter(cli.out, 10, 1, 3, ' ', 0)
|
||||
@@ -2194,7 +2149,7 @@ func (cli *DockerCli) CmdTag(args ...string) error {
|
||||
v.Set("force", "1")
|
||||
}
|
||||
|
||||
if _, _, err := readBody(cli.call("POST", "/images/"+cmd.Arg(0)+"/tag?"+v.Encode(), nil, false)); err != nil {
|
||||
if _, _, err := readBody(cli.call("POST", "/images/"+cmd.Arg(0)+"/tag?"+v.Encode(), nil, nil)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@@ -2296,7 +2251,7 @@ func (cli *DockerCli) createContainer(config *runconfig.Config, hostConfig *runc
|
||||
}
|
||||
|
||||
//create the container
|
||||
stream, statusCode, err := cli.call("POST", "/containers/create?"+containerValues.Encode(), mergedConfig, false)
|
||||
stream, statusCode, err := cli.call("POST", "/containers/create?"+containerValues.Encode(), mergedConfig, nil)
|
||||
//if image not found try to pull it
|
||||
if statusCode == 404 {
|
||||
repo, tag := parsers.ParseRepositoryTag(config.Image)
|
||||
@@ -2310,7 +2265,7 @@ func (cli *DockerCli) createContainer(config *runconfig.Config, hostConfig *runc
|
||||
return nil, err
|
||||
}
|
||||
// Retry
|
||||
if stream, _, err = cli.call("POST", "/containers/create?"+containerValues.Encode(), mergedConfig, false); err != nil {
|
||||
if stream, _, err = cli.call("POST", "/containers/create?"+containerValues.Encode(), mergedConfig, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if err != nil {
|
||||
@@ -2500,7 +2455,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
||||
}
|
||||
|
||||
//start the container
|
||||
if _, _, err = readBody(cli.call("POST", "/containers/"+createResponse.ID+"/start", nil, false)); err != nil {
|
||||
if _, _, err = readBody(cli.call("POST", "/containers/"+createResponse.ID+"/start", nil, nil)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -2530,13 +2485,13 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
||||
if *flAutoRemove {
|
||||
// Autoremove: wait for the container to finish, retrieve
|
||||
// the exit code and remove the container
|
||||
if _, _, err := readBody(cli.call("POST", "/containers/"+createResponse.ID+"/wait", nil, false)); err != nil {
|
||||
if _, _, err := readBody(cli.call("POST", "/containers/"+createResponse.ID+"/wait", nil, nil)); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, status, err = getExitCode(cli, createResponse.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, _, err := readBody(cli.call("DELETE", "/containers/"+createResponse.ID+"?v=1", nil, false)); err != nil {
|
||||
if _, _, err := readBody(cli.call("DELETE", "/containers/"+createResponse.ID+"?v=1", nil, nil)); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
@@ -2576,7 +2531,7 @@ func (cli *DockerCli) CmdCp(args ...string) error {
|
||||
copyData.Set("Resource", info[1])
|
||||
copyData.Set("HostPath", cmd.Arg(1))
|
||||
|
||||
stream, statusCode, err := cli.call("POST", "/containers/"+info[0]+"/copy", copyData, false)
|
||||
stream, statusCode, err := cli.call("POST", "/containers/"+info[0]+"/copy", copyData, nil)
|
||||
if stream != nil {
|
||||
defer stream.Close()
|
||||
}
|
||||
@@ -2671,7 +2626,7 @@ func (cli *DockerCli) CmdExec(args ...string) error {
|
||||
return &utils.StatusError{StatusCode: 1}
|
||||
}
|
||||
|
||||
stream, _, err := cli.call("POST", "/containers/"+execConfig.Container+"/exec", execConfig, false)
|
||||
stream, _, err := cli.call("POST", "/containers/"+execConfig.Container+"/exec", execConfig, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -2693,7 +2648,7 @@ func (cli *DockerCli) CmdExec(args ...string) error {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if _, _, err := readBody(cli.call("POST", "/exec/"+execID+"/start", execConfig, false)); err != nil {
|
||||
if _, _, err := readBody(cli.call("POST", "/exec/"+execID+"/start", execConfig, nil)); err != nil {
|
||||
return err
|
||||
}
|
||||
// For now don't print this - wait for when we support exec wait()
|
||||
@@ -2785,7 +2740,7 @@ type containerStats struct {
|
||||
}
|
||||
|
||||
func (s *containerStats) Collect(cli *DockerCli) {
|
||||
stream, _, err := cli.call("GET", "/containers/"+s.Name+"/stats", nil, false)
|
||||
stream, _, err := cli.call("GET", "/containers/"+s.Name+"/stats", nil, nil)
|
||||
if err != nil {
|
||||
s.err = err
|
||||
return
|
||||
|
||||
@@ -12,8 +12,10 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
gosignal "os/signal"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/api"
|
||||
@@ -54,124 +56,144 @@ func (cli *DockerCli) encodeData(data interface{}) (*bytes.Buffer, error) {
|
||||
return params, nil
|
||||
}
|
||||
|
||||
func (cli *DockerCli) call(method, path string, data interface{}, passAuthInfo bool) (io.ReadCloser, int, error) {
|
||||
params, err := cli.encodeData(data)
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), params)
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
if passAuthInfo {
|
||||
cli.LoadConfigFile()
|
||||
// Resolve the Auth config relevant for this server
|
||||
authConfig := cli.configFile.Configs[registry.IndexServerAddress()]
|
||||
getHeaders := func(authConfig registry.AuthConfig) (map[string][]string, error) {
|
||||
buf, err := json.Marshal(authConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
registryAuthHeader := []string{
|
||||
base64.URLEncoding.EncodeToString(buf),
|
||||
}
|
||||
return map[string][]string{"X-Registry-Auth": registryAuthHeader}, nil
|
||||
}
|
||||
if headers, err := getHeaders(authConfig); err == nil && headers != nil {
|
||||
for k, v := range headers {
|
||||
req.Header[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION)
|
||||
req.URL.Host = cli.addr
|
||||
req.URL.Scheme = cli.scheme
|
||||
if data != nil {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
} else if method == "POST" {
|
||||
req.Header.Set("Content-Type", "text/plain")
|
||||
}
|
||||
resp, err := cli.HTTPClient().Do(req)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "connection refused") {
|
||||
return nil, -1, ErrConnectionRefused
|
||||
}
|
||||
|
||||
if cli.tlsConfig == nil {
|
||||
return nil, -1, fmt.Errorf("%v. Are you trying to connect to a TLS-enabled daemon without TLS?", err)
|
||||
}
|
||||
return nil, -1, fmt.Errorf("An error occurred trying to connect: %v", err)
|
||||
|
||||
}
|
||||
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 400 {
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
if len(body) == 0 {
|
||||
return nil, resp.StatusCode, fmt.Errorf("Error: request returned %s for API route and version %s, check if the server supports the requested API version", http.StatusText(resp.StatusCode), req.URL)
|
||||
}
|
||||
return nil, resp.StatusCode, fmt.Errorf("Error response from daemon: %s", bytes.TrimSpace(body))
|
||||
}
|
||||
|
||||
return resp.Body, resp.StatusCode, nil
|
||||
}
|
||||
|
||||
func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer, headers map[string][]string) error {
|
||||
return cli.streamHelper(method, path, true, in, out, nil, headers)
|
||||
}
|
||||
|
||||
func (cli *DockerCli) streamHelper(method, path string, setRawTerminal bool, in io.Reader, stdout, stderr io.Writer, headers map[string][]string) error {
|
||||
if (method == "POST" || method == "PUT") && in == nil {
|
||||
func (cli *DockerCli) clientRequest(method, path string, in io.Reader, headers map[string][]string) (io.ReadCloser, string, int, error) {
|
||||
expectedPayload := (method == "POST" || method == "PUT")
|
||||
if expectedPayload && in == nil {
|
||||
in = bytes.NewReader([]byte{})
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), in)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, "", -1, err
|
||||
}
|
||||
req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION)
|
||||
req.URL.Host = cli.addr
|
||||
req.URL.Scheme = cli.scheme
|
||||
if method == "POST" {
|
||||
req.Header.Set("Content-Type", "text/plain")
|
||||
}
|
||||
|
||||
if headers != nil {
|
||||
for k, v := range headers {
|
||||
req.Header[k] = v
|
||||
}
|
||||
}
|
||||
if expectedPayload && req.Header.Get("Content-Type") == "" {
|
||||
req.Header.Set("Content-Type", "text/plain")
|
||||
}
|
||||
|
||||
resp, err := cli.HTTPClient().Do(req)
|
||||
statusCode := -1
|
||||
if resp != nil {
|
||||
statusCode = resp.StatusCode
|
||||
}
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "connection refused") {
|
||||
return fmt.Errorf("Cannot connect to the Docker daemon. Is 'docker -d' running on this host?")
|
||||
return nil, "", statusCode, ErrConnectionRefused
|
||||
}
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 400 {
|
||||
if cli.tlsConfig == nil {
|
||||
return nil, "", statusCode, fmt.Errorf("%v. Are you trying to connect to a TLS-enabled daemon without TLS?", err)
|
||||
}
|
||||
|
||||
return nil, "", statusCode, fmt.Errorf("An error occurred trying to connect: %v", err)
|
||||
}
|
||||
|
||||
if statusCode < 200 || statusCode >= 400 {
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, "", statusCode, err
|
||||
}
|
||||
if len(body) == 0 {
|
||||
return fmt.Errorf("Error :%s", http.StatusText(resp.StatusCode))
|
||||
return nil, "", statusCode, fmt.Errorf("Error: request returned %s for API route and version %s, check if the server supports the requested API version", http.StatusText(statusCode), req.URL)
|
||||
}
|
||||
return fmt.Errorf("Error: %s", bytes.TrimSpace(body))
|
||||
return nil, "", statusCode, fmt.Errorf("Error response from daemon: %s", bytes.TrimSpace(body))
|
||||
}
|
||||
|
||||
if api.MatchesContentType(resp.Header.Get("Content-Type"), "application/json") {
|
||||
return utils.DisplayJSONMessagesStream(resp.Body, stdout, cli.outFd, cli.isTerminalOut)
|
||||
return resp.Body, resp.Header.Get("Content-Type"), statusCode, nil
|
||||
}
|
||||
|
||||
func (cli *DockerCli) clientRequestAttemptLogin(method, path string, in io.Reader, out io.Writer, index *registry.IndexInfo, cmdName string) (io.ReadCloser, int, error) {
|
||||
cmdAttempt := func(authConfig registry.AuthConfig) (io.ReadCloser, int, error) {
|
||||
buf, err := json.Marshal(authConfig)
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
registryAuthHeader := []string{
|
||||
base64.URLEncoding.EncodeToString(buf),
|
||||
}
|
||||
|
||||
// begin the request
|
||||
body, contentType, statusCode, err := cli.clientRequest(method, path, in, map[string][]string{
|
||||
"X-Registry-Auth": registryAuthHeader,
|
||||
})
|
||||
if err == nil && out != nil {
|
||||
// If we are streaming output, complete the stream since
|
||||
// errors may not appear until later.
|
||||
err = cli.streamBody(body, contentType, true, out, nil)
|
||||
}
|
||||
if err != nil {
|
||||
// Since errors in a stream appear after status 200 has been written,
|
||||
// we may need to change the status code.
|
||||
if strings.Contains(err.Error(), "Authentication is required") ||
|
||||
strings.Contains(err.Error(), "Status 401") ||
|
||||
strings.Contains(err.Error(), "status code 401") {
|
||||
statusCode = http.StatusUnauthorized
|
||||
}
|
||||
}
|
||||
return body, statusCode, err
|
||||
}
|
||||
|
||||
// Resolve the Auth config relevant for this server
|
||||
authConfig := cli.configFile.ResolveAuthConfig(index)
|
||||
body, statusCode, err := cmdAttempt(authConfig)
|
||||
if statusCode == http.StatusUnauthorized {
|
||||
fmt.Fprintf(cli.out, "\nPlease login prior to %s:\n", cmdName)
|
||||
if err = cli.CmdLogin(index.GetAuthConfigKey()); err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
authConfig = cli.configFile.ResolveAuthConfig(index)
|
||||
return cmdAttempt(authConfig)
|
||||
}
|
||||
return body, statusCode, err
|
||||
}
|
||||
|
||||
func (cli *DockerCli) call(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, int, error) {
|
||||
params, err := cli.encodeData(data)
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
|
||||
if data != nil {
|
||||
if headers == nil {
|
||||
headers = make(map[string][]string)
|
||||
}
|
||||
headers["Content-Type"] = []string{"application/json"}
|
||||
}
|
||||
|
||||
body, _, statusCode, err := cli.clientRequest(method, path, params, headers)
|
||||
return body, statusCode, err
|
||||
}
|
||||
func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer, headers map[string][]string) error {
|
||||
return cli.streamHelper(method, path, true, in, out, nil, headers)
|
||||
}
|
||||
|
||||
func (cli *DockerCli) streamHelper(method, path string, setRawTerminal bool, in io.Reader, stdout, stderr io.Writer, headers map[string][]string) error {
|
||||
body, contentType, _, err := cli.clientRequest(method, path, in, headers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return cli.streamBody(body, contentType, setRawTerminal, stdout, stderr)
|
||||
}
|
||||
|
||||
func (cli *DockerCli) streamBody(body io.ReadCloser, contentType string, setRawTerminal bool, stdout, stderr io.Writer) error {
|
||||
defer body.Close()
|
||||
|
||||
if api.MatchesContentType(contentType, "application/json") {
|
||||
return utils.DisplayJSONMessagesStream(body, stdout, cli.outFd, cli.isTerminalOut)
|
||||
}
|
||||
if stdout != nil || stderr != nil {
|
||||
// When TTY is ON, use regular copy
|
||||
var err error
|
||||
if setRawTerminal {
|
||||
_, err = io.Copy(stdout, resp.Body)
|
||||
_, err = io.Copy(stdout, body)
|
||||
} else {
|
||||
_, err = stdcopy.StdCopy(stdout, stderr, resp.Body)
|
||||
_, err = stdcopy.StdCopy(stdout, stderr, body)
|
||||
}
|
||||
log.Debugf("[stream] End of stdout")
|
||||
return err
|
||||
@@ -195,13 +217,13 @@ func (cli *DockerCli) resizeTty(id string, isExec bool) {
|
||||
path = "/exec/" + id + "/resize?"
|
||||
}
|
||||
|
||||
if _, _, err := readBody(cli.call("POST", path+v.Encode(), nil, false)); err != nil {
|
||||
if _, _, err := readBody(cli.call("POST", path+v.Encode(), nil, nil)); err != nil {
|
||||
log.Debugf("Error resize: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func waitForExit(cli *DockerCli, containerId string) (int, error) {
|
||||
stream, _, err := cli.call("POST", "/containers/"+containerId+"/wait", nil, false)
|
||||
func waitForExit(cli *DockerCli, containerID string) (int, error) {
|
||||
stream, _, err := cli.call("POST", "/containers/"+containerID+"/wait", nil, nil)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
@@ -215,8 +237,8 @@ func waitForExit(cli *DockerCli, containerId string) (int, error) {
|
||||
|
||||
// getExitCode perform an inspect on the container. It returns
|
||||
// the running state and the exit code.
|
||||
func getExitCode(cli *DockerCli, containerId string) (bool, int, error) {
|
||||
stream, _, err := cli.call("GET", "/containers/"+containerId+"/json", nil, false)
|
||||
func getExitCode(cli *DockerCli, containerID string) (bool, int, error) {
|
||||
stream, _, err := cli.call("GET", "/containers/"+containerID+"/json", nil, nil)
|
||||
if err != nil {
|
||||
// If we can't connect, then the daemon probably died.
|
||||
if err != ErrConnectionRefused {
|
||||
@@ -236,8 +258,8 @@ func getExitCode(cli *DockerCli, containerId string) (bool, int, error) {
|
||||
|
||||
// getExecExitCode perform an inspect on the exec command. It returns
|
||||
// the running state and the exit code.
|
||||
func getExecExitCode(cli *DockerCli, execId string) (bool, int, error) {
|
||||
stream, _, err := cli.call("GET", "/exec/"+execId+"/json", nil, false)
|
||||
func getExecExitCode(cli *DockerCli, execID string) (bool, int, error) {
|
||||
stream, _, err := cli.call("GET", "/exec/"+execID+"/json", nil, nil)
|
||||
if err != nil {
|
||||
// If we can't connect, then the daemon probably died.
|
||||
if err != ErrConnectionRefused {
|
||||
@@ -257,13 +279,29 @@ func getExecExitCode(cli *DockerCli, execId string) (bool, int, error) {
|
||||
func (cli *DockerCli) monitorTtySize(id string, isExec bool) error {
|
||||
cli.resizeTty(id, isExec)
|
||||
|
||||
sigchan := make(chan os.Signal, 1)
|
||||
gosignal.Notify(sigchan, signal.SIGWINCH)
|
||||
go func() {
|
||||
for _ = range sigchan {
|
||||
cli.resizeTty(id, isExec)
|
||||
}
|
||||
}()
|
||||
if runtime.GOOS == "windows" {
|
||||
go func() {
|
||||
prevH, prevW := cli.getTtySize()
|
||||
for {
|
||||
time.Sleep(time.Millisecond * 250)
|
||||
h, w := cli.getTtySize()
|
||||
|
||||
if prevW != w || prevH != h {
|
||||
cli.resizeTty(id, isExec)
|
||||
}
|
||||
prevH = h
|
||||
prevW = w
|
||||
}
|
||||
}()
|
||||
} else {
|
||||
sigchan := make(chan os.Signal, 1)
|
||||
gosignal.Notify(sigchan, signal.SIGWINCH)
|
||||
go func() {
|
||||
for _ = range sigchan {
|
||||
cli.resizeTty(id, isExec)
|
||||
}
|
||||
}()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ import (
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/api"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/daemon/networkdriver/portallocator"
|
||||
"github.com/docker/docker/daemon/networkdriver/bridge"
|
||||
"github.com/docker/docker/engine"
|
||||
"github.com/docker/docker/pkg/listenbuffer"
|
||||
"github.com/docker/docker/pkg/parsers"
|
||||
@@ -38,7 +38,7 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
activationLock chan struct{}
|
||||
activationLock chan struct{} = make(chan struct{})
|
||||
)
|
||||
|
||||
type HttpServer struct {
|
||||
@@ -1527,7 +1527,7 @@ func allocateDaemonPort(addr string) error {
|
||||
}
|
||||
|
||||
for _, hostIP := range hostIPs {
|
||||
if _, err := portallocator.RequestPort(hostIP, "tcp", intPort); err != nil {
|
||||
if _, err := bridge.RequestPort(hostIP, "tcp", intPort); err != nil {
|
||||
return fmt.Errorf("failed to allocate daemon listening port %d (err: %v)", intPort, err)
|
||||
}
|
||||
}
|
||||
@@ -1578,7 +1578,6 @@ func ServeApi(job *engine.Job) engine.Status {
|
||||
protoAddrs = job.Args
|
||||
chErrors = make(chan error, len(protoAddrs))
|
||||
)
|
||||
activationLock = make(chan struct{})
|
||||
|
||||
for _, protoAddr := range protoAddrs {
|
||||
protoAddrParts := strings.SplitN(protoAddr, "://", 2)
|
||||
|
||||
@@ -95,7 +95,9 @@ func AcceptConnections(job *engine.Job) engine.Status {
|
||||
go systemd.SdNotify("READY=1")
|
||||
|
||||
// close the lock so the listeners start accepting connections
|
||||
if activationLock != nil {
|
||||
select {
|
||||
case <-activationLock:
|
||||
default:
|
||||
close(activationLock)
|
||||
}
|
||||
|
||||
|
||||
@@ -312,7 +312,11 @@ func (b *Builder) dispatch(stepN int, ast *parser.Node) error {
|
||||
var str string
|
||||
str = ast.Value
|
||||
if _, ok := replaceEnvAllowed[cmd]; ok {
|
||||
str = b.replaceEnv(ast.Value)
|
||||
var err error
|
||||
str, err = ProcessWord(ast.Value, b.Config.Env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
strList[i+l] = str
|
||||
msgList[i] = ast.Value
|
||||
|
||||
@@ -90,7 +90,7 @@ func parseNameVal(rest string, key string) (*Node, map[string]bool, error) {
|
||||
if blankOK || len(word) > 0 {
|
||||
words = append(words, word)
|
||||
|
||||
// Look for = and if no there assume
|
||||
// Look for = and if not there assume
|
||||
// we're doing the old stuff and
|
||||
// just read the rest of the line
|
||||
if !strings.Contains(word, "=") {
|
||||
@@ -107,12 +107,15 @@ func parseNameVal(rest string, key string) (*Node, map[string]bool, error) {
|
||||
quote = ch
|
||||
blankOK = true
|
||||
phase = inQuote
|
||||
continue
|
||||
}
|
||||
if ch == '\\' {
|
||||
if pos+1 == len(rest) {
|
||||
continue // just skip \ at end
|
||||
}
|
||||
// If we're not quoted and we see a \, then always just
|
||||
// add \ plus the char to the word, even if the char
|
||||
// is a quote.
|
||||
word += string(ch)
|
||||
pos++
|
||||
ch = rune(rest[pos])
|
||||
}
|
||||
@@ -122,15 +125,17 @@ func parseNameVal(rest string, key string) (*Node, map[string]bool, error) {
|
||||
if phase == inQuote {
|
||||
if ch == quote {
|
||||
phase = inWord
|
||||
continue
|
||||
}
|
||||
if ch == '\\' {
|
||||
// \ is special except for ' quotes - can't escape anything for '
|
||||
if ch == '\\' && quote != '\'' {
|
||||
if pos+1 == len(rest) {
|
||||
phase = inWord
|
||||
continue // just skip \ at end
|
||||
}
|
||||
pos++
|
||||
ch = rune(rest[pos])
|
||||
nextCh := rune(rest[pos])
|
||||
word += string(ch)
|
||||
ch = nextCh
|
||||
}
|
||||
word += string(ch)
|
||||
}
|
||||
|
||||
8
builder/parser/testfiles/env/Dockerfile
vendored
@@ -7,6 +7,14 @@ ENV name=value\ value2
|
||||
ENV name="value'quote space'value2"
|
||||
ENV name='value"double quote"value2'
|
||||
ENV name=value\ value2 name2=value2\ value3
|
||||
ENV name="a\"b"
|
||||
ENV name="a\'b"
|
||||
ENV name='a\'b'
|
||||
ENV name='a\'b''
|
||||
ENV name='a\"b'
|
||||
ENV name="''"
|
||||
# don't put anything after the next line - it must be the last line of the
|
||||
# Dockerfile and it must end with \
|
||||
ENV name=value \
|
||||
name1=value1 \
|
||||
name2="value2a \
|
||||
|
||||
18
builder/parser/testfiles/env/result
vendored
@@ -2,9 +2,15 @@
|
||||
(env "name" "value")
|
||||
(env "name" "value")
|
||||
(env "name" "value" "name2" "value2")
|
||||
(env "name" "value value1")
|
||||
(env "name" "value value2")
|
||||
(env "name" "value'quote space'value2")
|
||||
(env "name" "value\"double quote\"value2")
|
||||
(env "name" "value value2" "name2" "value2 value3")
|
||||
(env "name" "value" "name1" "value1" "name2" "value2a value2b" "name3" "value3an\"value3b\"" "name4" "value4a\\nvalue4b")
|
||||
(env "name" "\"value value1\"")
|
||||
(env "name" "value\\ value2")
|
||||
(env "name" "\"value'quote space'value2\"")
|
||||
(env "name" "'value\"double quote\"value2'")
|
||||
(env "name" "value\\ value2" "name2" "value2\\ value3")
|
||||
(env "name" "\"a\\\"b\"")
|
||||
(env "name" "\"a\\'b\"")
|
||||
(env "name" "'a\\'b'")
|
||||
(env "name" "'a\\'b''")
|
||||
(env "name" "'a\\\"b'")
|
||||
(env "name" "\"''\"")
|
||||
(env "name" "value" "name1" "value1" "name2" "\"value2a value2b\"" "name3" "\"value3a\\n\\\"value3b\\\"\"" "name4" "\"value4a\\\\nvalue4b\"")
|
||||
|
||||
209
builder/shell_parser.go
Normal file
@@ -0,0 +1,209 @@
|
||||
package builder
|
||||
|
||||
// This will take a single word and an array of env variables and
|
||||
// process all quotes (" and ') as well as $xxx and ${xxx} env variable
|
||||
// tokens. Tries to mimic bash shell process.
|
||||
// It doesn't support all flavors of ${xx:...} formats but new ones can
|
||||
// be added by adding code to the "special ${} format processing" section
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
type shellWord struct {
|
||||
word string
|
||||
envs []string
|
||||
pos int
|
||||
}
|
||||
|
||||
func ProcessWord(word string, env []string) (string, error) {
|
||||
sw := &shellWord{
|
||||
word: word,
|
||||
envs: env,
|
||||
pos: 0,
|
||||
}
|
||||
return sw.process()
|
||||
}
|
||||
|
||||
func (sw *shellWord) process() (string, error) {
|
||||
return sw.processStopOn('\000')
|
||||
}
|
||||
|
||||
// Process the word, starting at 'pos', and stop when we get to the
|
||||
// end of the word or the 'stopChar' character
|
||||
func (sw *shellWord) processStopOn(stopChar rune) (string, error) {
|
||||
var result string
|
||||
var charFuncMapping = map[rune]func() (string, error){
|
||||
'\'': sw.processSingleQuote,
|
||||
'"': sw.processDoubleQuote,
|
||||
'$': sw.processDollar,
|
||||
}
|
||||
|
||||
for sw.pos < len(sw.word) {
|
||||
ch := sw.peek()
|
||||
if stopChar != '\000' && ch == stopChar {
|
||||
sw.next()
|
||||
break
|
||||
}
|
||||
if fn, ok := charFuncMapping[ch]; ok {
|
||||
// Call special processing func for certain chars
|
||||
tmp, err := fn()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
result += tmp
|
||||
} else {
|
||||
// Not special, just add it to the result
|
||||
ch = sw.next()
|
||||
if ch == '\\' {
|
||||
// '\' escapes, except end of line
|
||||
ch = sw.next()
|
||||
if ch == '\000' {
|
||||
continue
|
||||
}
|
||||
}
|
||||
result += string(ch)
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (sw *shellWord) peek() rune {
|
||||
if sw.pos == len(sw.word) {
|
||||
return '\000'
|
||||
}
|
||||
return rune(sw.word[sw.pos])
|
||||
}
|
||||
|
||||
func (sw *shellWord) next() rune {
|
||||
if sw.pos == len(sw.word) {
|
||||
return '\000'
|
||||
}
|
||||
ch := rune(sw.word[sw.pos])
|
||||
sw.pos++
|
||||
return ch
|
||||
}
|
||||
|
||||
func (sw *shellWord) processSingleQuote() (string, error) {
|
||||
// All chars between single quotes are taken as-is
|
||||
// Note, you can't escape '
|
||||
var result string
|
||||
|
||||
sw.next()
|
||||
|
||||
for {
|
||||
ch := sw.next()
|
||||
if ch == '\000' || ch == '\'' {
|
||||
break
|
||||
}
|
||||
result += string(ch)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (sw *shellWord) processDoubleQuote() (string, error) {
|
||||
// All chars up to the next " are taken as-is, even ', except any $ chars
|
||||
// But you can escape " with a \
|
||||
var result string
|
||||
|
||||
sw.next()
|
||||
|
||||
for sw.pos < len(sw.word) {
|
||||
ch := sw.peek()
|
||||
if ch == '"' {
|
||||
sw.next()
|
||||
break
|
||||
}
|
||||
if ch == '$' {
|
||||
tmp, err := sw.processDollar()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
result += tmp
|
||||
} else {
|
||||
ch = sw.next()
|
||||
if ch == '\\' {
|
||||
chNext := sw.peek()
|
||||
|
||||
if chNext == '\000' {
|
||||
// Ignore \ at end of word
|
||||
continue
|
||||
}
|
||||
|
||||
if chNext == '"' || chNext == '$' {
|
||||
// \" and \$ can be escaped, all other \'s are left as-is
|
||||
ch = sw.next()
|
||||
}
|
||||
}
|
||||
result += string(ch)
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (sw *shellWord) processDollar() (string, error) {
|
||||
sw.next()
|
||||
ch := sw.peek()
|
||||
if ch == '{' {
|
||||
sw.next()
|
||||
name := sw.processName()
|
||||
ch = sw.peek()
|
||||
if ch == '}' {
|
||||
// Normal ${xx} case
|
||||
sw.next()
|
||||
return sw.getEnv(name), nil
|
||||
}
|
||||
return "", fmt.Errorf("Unsupported ${} substitution: %s", sw.word)
|
||||
} else {
|
||||
// $xxx case
|
||||
name := sw.processName()
|
||||
if name == "" {
|
||||
return "$", nil
|
||||
}
|
||||
return sw.getEnv(name), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (sw *shellWord) processName() string {
|
||||
// Read in a name (alphanumeric or _)
|
||||
// If it starts with a numeric then just return $#
|
||||
var name string
|
||||
|
||||
for sw.pos < len(sw.word) {
|
||||
ch := sw.peek()
|
||||
if len(name) == 0 && unicode.IsDigit(ch) {
|
||||
ch = sw.next()
|
||||
return string(ch)
|
||||
}
|
||||
if !unicode.IsLetter(ch) && !unicode.IsDigit(ch) && ch != '_' {
|
||||
break
|
||||
}
|
||||
ch = sw.next()
|
||||
name += string(ch)
|
||||
}
|
||||
|
||||
return name
|
||||
}
|
||||
|
||||
func (sw *shellWord) getEnv(name string) string {
|
||||
for _, env := range sw.envs {
|
||||
i := strings.Index(env, "=")
|
||||
if i < 0 {
|
||||
if name == env {
|
||||
// Should probably never get here, but just in case treat
|
||||
// it like "var" and "var=" are the same
|
||||
return ""
|
||||
}
|
||||
continue
|
||||
}
|
||||
if name != env[:i] {
|
||||
continue
|
||||
}
|
||||
return env[i+1:]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
51
builder/shell_parser_test.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestShellParser(t *testing.T) {
|
||||
file, err := os.Open("words")
|
||||
if err != nil {
|
||||
t.Fatalf("Can't open 'words': %s", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
envs := []string{"PWD=/home", "SHELL=bash"}
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
|
||||
// Trim comments and blank lines
|
||||
i := strings.Index(line, "#")
|
||||
if i >= 0 {
|
||||
line = line[:i]
|
||||
}
|
||||
line = strings.TrimSpace(line)
|
||||
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
words := strings.Split(line, "|")
|
||||
if len(words) != 2 {
|
||||
t.Fatalf("Error in 'words' - should be 2 words:%q", words)
|
||||
}
|
||||
|
||||
words[0] = strings.TrimSpace(words[0])
|
||||
words[1] = strings.TrimSpace(words[1])
|
||||
|
||||
newWord, err := ProcessWord(words[0], envs)
|
||||
|
||||
if err != nil {
|
||||
newWord = "error"
|
||||
}
|
||||
|
||||
if newWord != words[1] {
|
||||
t.Fatalf("Error. Src: %s Calc: %s Expected: %s", words[0], newWord, words[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,50 +1,9 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
// `\\\\+|[^\\]|\b|\A` - match any number of "\\" (ie, properly-escaped backslashes), or a single non-backslash character, or a word boundary, or beginning-of-line
|
||||
// `\$` - match literal $
|
||||
// `[[:alnum:]_]+` - match things like `$SOME_VAR`
|
||||
// `{[[:alnum:]_]+}` - match things like `${SOME_VAR}`
|
||||
tokenEnvInterpolation = regexp.MustCompile(`(\\|\\\\+|[^\\]|\b|\A)\$([[:alnum:]_]+|{[[:alnum:]_]+})`)
|
||||
// this intentionally punts on more exotic interpolations like ${SOME_VAR%suffix} and lets the shell handle those directly
|
||||
)
|
||||
|
||||
// handle environment replacement. Used in dispatcher.
|
||||
func (b *Builder) replaceEnv(str string) string {
|
||||
for _, match := range tokenEnvInterpolation.FindAllString(str, -1) {
|
||||
idx := strings.Index(match, "\\$")
|
||||
if idx != -1 {
|
||||
if idx+2 >= len(match) {
|
||||
str = strings.Replace(str, match, "\\$", -1)
|
||||
continue
|
||||
}
|
||||
|
||||
prefix := match[:idx]
|
||||
stripped := match[idx+2:]
|
||||
str = strings.Replace(str, match, prefix+"$"+stripped, -1)
|
||||
continue
|
||||
}
|
||||
|
||||
match = match[strings.Index(match, "$"):]
|
||||
matchKey := strings.Trim(match, "${}")
|
||||
|
||||
for _, keyval := range b.Config.Env {
|
||||
tmp := strings.SplitN(keyval, "=", 2)
|
||||
if tmp[0] == matchKey {
|
||||
str = strings.Replace(str, match, tmp[1], -1)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return str
|
||||
}
|
||||
|
||||
func handleJsonArgs(args []string, attributes map[string]bool) []string {
|
||||
if len(args) == 0 {
|
||||
return []string{}
|
||||
|
||||
43
builder/words
Normal file
@@ -0,0 +1,43 @@
|
||||
hello | hello
|
||||
he'll'o | hello
|
||||
he'llo | hello
|
||||
he\'llo | he'llo
|
||||
he\\'llo | he\llo
|
||||
abc\tdef | abctdef
|
||||
"abc\tdef" | abc\tdef
|
||||
'abc\tdef' | abc\tdef
|
||||
hello\ | hello
|
||||
hello\\ | hello\
|
||||
"hello | hello
|
||||
"hello\" | hello"
|
||||
"hel'lo" | hel'lo
|
||||
'hello | hello
|
||||
'hello\' | hello\
|
||||
"''" | ''
|
||||
$. | $.
|
||||
$1 |
|
||||
he$1x | hex
|
||||
he$.x | he$.x
|
||||
he$pwd. | he.
|
||||
he$PWD | he/home
|
||||
he\$PWD | he$PWD
|
||||
he\\$PWD | he\/home
|
||||
he\${} | he${}
|
||||
he\${}xx | he${}xx
|
||||
he${} | he
|
||||
he${}xx | hexx
|
||||
he${hi} | he
|
||||
he${hi}xx | hexx
|
||||
he${PWD} | he/home
|
||||
he${.} | error
|
||||
'he${XX}' | he${XX}
|
||||
"he${PWD}" | he/home
|
||||
"he'$PWD'" | he'/home'
|
||||
"$PWD" | /home
|
||||
'$PWD' | $PWD
|
||||
'\$PWD' | \$PWD
|
||||
'"hello"' | "hello"
|
||||
he\$PWD | he$PWD
|
||||
"he\$PWD" | he$PWD
|
||||
'he\$PWD' | he\$PWD
|
||||
he${PWD | error
|
||||
@@ -58,6 +58,18 @@ __docker_containers_unpauseable() {
|
||||
__docker_containers_all '.State.Paused'
|
||||
}
|
||||
|
||||
__docker_container_names() {
|
||||
local containers=( $(__docker_q ps -aq --no-trunc) )
|
||||
local names=( $(__docker_q inspect --format '{{.Name}}' "${containers[@]}") )
|
||||
names=( "${names[@]#/}" ) # trim off the leading "/" from the container names
|
||||
COMPREPLY=( $(compgen -W "${names[*]}" -- "$cur") )
|
||||
}
|
||||
|
||||
__docker_container_ids() {
|
||||
local containers=( $(__docker_q ps -aq) )
|
||||
COMPREPLY=( $(compgen -W "${containers[*]}" -- "$cur") )
|
||||
}
|
||||
|
||||
__docker_image_repos() {
|
||||
local repos="$(__docker_q images | awk 'NR>1 && $1 != "<none>" { print $1 }')"
|
||||
COMPREPLY=( $(compgen -W "$repos" -- "$cur") )
|
||||
@@ -325,7 +337,7 @@ _docker_cp() {
|
||||
(( counter++ ))
|
||||
|
||||
if [ $cword -eq $counter ]; then
|
||||
_filedir
|
||||
_filedir -d
|
||||
return
|
||||
fi
|
||||
;;
|
||||
@@ -437,7 +449,10 @@ _docker_history() {
|
||||
_docker_images() {
|
||||
case "$prev" in
|
||||
--filter|-f)
|
||||
COMPREPLY=( $( compgen -W "dangling=true" -- "$cur" ) )
|
||||
COMPREPLY=( $( compgen -W "dangling=true label=" -- "$cur" ) )
|
||||
if [ "$COMPREPLY" = "label=" ]; then
|
||||
compopt -o nospace
|
||||
fi
|
||||
return
|
||||
;;
|
||||
esac
|
||||
@@ -447,17 +462,20 @@ _docker_images() {
|
||||
COMPREPLY=( $( compgen -W "true false" -- "${cur#=}" ) )
|
||||
return
|
||||
;;
|
||||
*label=*)
|
||||
return
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "--all -a --filter -f --help --no-trunc --quiet -q" -- "$cur" ) )
|
||||
;;
|
||||
=)
|
||||
return
|
||||
;;
|
||||
*)
|
||||
local counter=$(__docker_pos_first_nonflag)
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_image_repos
|
||||
fi
|
||||
__docker_image_repos
|
||||
;;
|
||||
esac
|
||||
}
|
||||
@@ -616,7 +634,7 @@ _docker_ps() {
|
||||
__docker_containers_all
|
||||
;;
|
||||
--filter|-f)
|
||||
COMPREPLY=( $( compgen -S = -W "exited status" -- "$cur" ) )
|
||||
COMPREPLY=( $( compgen -S = -W "exited id label name status" -- "$cur" ) )
|
||||
compopt -o nospace
|
||||
return
|
||||
;;
|
||||
@@ -626,6 +644,16 @@ _docker_ps() {
|
||||
esac
|
||||
|
||||
case "${words[$cword-2]}$prev=" in
|
||||
*id=*)
|
||||
cur="${cur#=}"
|
||||
__docker_container_ids
|
||||
return
|
||||
;;
|
||||
*name=*)
|
||||
cur="${cur#=}"
|
||||
__docker_container_names
|
||||
return
|
||||
;;
|
||||
*status=*)
|
||||
COMPREPLY=( $( compgen -W "exited paused restarting running" -- "${cur#=}" ) )
|
||||
return
|
||||
@@ -734,6 +762,7 @@ _docker_run() {
|
||||
--attach -a
|
||||
--cap-add
|
||||
--cap-drop
|
||||
--cgroup-parent
|
||||
--cidfile
|
||||
--cpuset
|
||||
--cpu-shares -c
|
||||
@@ -746,7 +775,10 @@ _docker_run() {
|
||||
--expose
|
||||
--hostname -h
|
||||
--ipc
|
||||
--label -l
|
||||
--label-file
|
||||
--link
|
||||
--log-driver
|
||||
--lxc-conf
|
||||
--mac-address
|
||||
--memory -m
|
||||
@@ -798,7 +830,7 @@ _docker_run() {
|
||||
__docker_capabilities
|
||||
return
|
||||
;;
|
||||
--cidfile|--env-file)
|
||||
--cidfile|--env-file|--label-file)
|
||||
_filedir
|
||||
return
|
||||
;;
|
||||
@@ -850,6 +882,10 @@ _docker_run() {
|
||||
esac
|
||||
return
|
||||
;;
|
||||
--log-driver)
|
||||
COMPREPLY=( $( compgen -W "json-file syslog none" -- "$cur") )
|
||||
return
|
||||
;;
|
||||
--net)
|
||||
case "$cur" in
|
||||
container:*)
|
||||
|
||||
@@ -49,7 +49,7 @@ post-start script
|
||||
fi
|
||||
if ! printf "%s" "$DOCKER_OPTS" | grep -qE -e '-H|--host'; then
|
||||
while ! [ -e /var/run/docker.sock ]; do
|
||||
initctl status $UPSTART_JOB | grep -q "stop/" && exit 1
|
||||
initctl status $UPSTART_JOB | grep -qE "(stop|respawn)/" && exit 1
|
||||
echo "Waiting for /var/run/docker.sock"
|
||||
sleep 0.1
|
||||
done
|
||||
|
||||
@@ -83,7 +83,7 @@ func (config *Config) InstallFlags() {
|
||||
opts.LabelListVar(&config.Labels, []string{"-label"}, "Set key=value labels to the daemon")
|
||||
config.Ulimits = make(map[string]*ulimit.Ulimit)
|
||||
opts.UlimitMapVar(config.Ulimits, []string{"-default-ulimit"}, "Set default ulimits for containers")
|
||||
flag.StringVar(&config.LogConfig.Type, []string{"-log-driver"}, "json-file", "Containers logging driver(json-file/none)")
|
||||
flag.StringVar(&config.LogConfig.Type, []string{"-log-driver"}, "json-file", "Containers logging driver")
|
||||
}
|
||||
|
||||
func getDefaultNetworkMtu() int {
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"github.com/docker/docker/daemon/execdriver"
|
||||
"github.com/docker/docker/daemon/logger"
|
||||
"github.com/docker/docker/daemon/logger/jsonfilelog"
|
||||
"github.com/docker/docker/daemon/logger/syslog"
|
||||
"github.com/docker/docker/engine"
|
||||
"github.com/docker/docker/image"
|
||||
"github.com/docker/docker/links"
|
||||
@@ -359,6 +360,10 @@ func (container *Container) Start() (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if container.removalInProgress || container.Dead {
|
||||
return fmt.Errorf("Container is marked for removal and cannot be started.")
|
||||
}
|
||||
|
||||
// if we encounter an error during start we need to ensure that any other
|
||||
// setup has been cleaned up properly
|
||||
defer func() {
|
||||
@@ -1374,12 +1379,19 @@ func (container *Container) startLogging() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
container.LogPath = pth
|
||||
|
||||
dl, err := jsonfilelog.New(pth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
l = dl
|
||||
case "syslog":
|
||||
dl, err := syslog.New(container.ID[:12])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
l = dl
|
||||
case "none":
|
||||
return nil
|
||||
default:
|
||||
|
||||
@@ -25,7 +25,6 @@ import (
|
||||
"github.com/docker/docker/daemon/graphdriver"
|
||||
_ "github.com/docker/docker/daemon/graphdriver/vfs"
|
||||
_ "github.com/docker/docker/daemon/networkdriver/bridge"
|
||||
"github.com/docker/docker/daemon/networkdriver/portallocator"
|
||||
"github.com/docker/docker/engine"
|
||||
"github.com/docker/docker/graph"
|
||||
"github.com/docker/docker/image"
|
||||
@@ -288,19 +287,8 @@ func (daemon *Daemon) register(container *Container, updateSuffixarray bool) err
|
||||
if err := container.ToDisk(); err != nil {
|
||||
log.Debugf("saving stopped state to disk %s", err)
|
||||
}
|
||||
|
||||
info := daemon.execDriver.Info(container.ID)
|
||||
if !info.IsRunning() {
|
||||
log.Debugf("Container %s was supposed to be running but is not.", container.ID)
|
||||
|
||||
log.Debugf("Marking as stopped")
|
||||
|
||||
container.SetStopped(&execdriver.ExitStatus{ExitCode: -127})
|
||||
if err := container.ToDisk(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -827,12 +815,6 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine) (*Daemon, error)
|
||||
}
|
||||
config.DisableNetwork = config.BridgeIface == disableNetworkBridge
|
||||
|
||||
// register portallocator release on shutdown
|
||||
eng.OnShutdown(func() {
|
||||
if err := portallocator.ReleaseAll(); err != nil {
|
||||
log.Errorf("portallocator.ReleaseAll(): %s", err)
|
||||
}
|
||||
})
|
||||
// Claim the pidfile first, to avoid any and all unexpected race conditions.
|
||||
// Some of the init doesn't need a pidfile lock - but let's not try to be smart.
|
||||
if config.Pidfile != "" {
|
||||
@@ -1012,7 +994,8 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine) (*Daemon, error)
|
||||
}
|
||||
|
||||
sysInfo := sysinfo.New(false)
|
||||
ed, err := execdrivers.NewDriver(config.ExecDriver, config.Root, sysInitPath, sysInfo)
|
||||
const runDir = "/var/run/docker"
|
||||
ed, err := execdrivers.NewDriver(config.ExecDriver, runDir, config.Root, sysInitPath, sysInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -61,8 +61,15 @@ func (daemon *Daemon) ContainerRm(job *engine.Job) engine.Status {
|
||||
return job.Errorf("Conflict, You cannot remove a running container. Stop the container before attempting removal or use -f")
|
||||
}
|
||||
}
|
||||
if err := daemon.Rm(container); err != nil {
|
||||
return job.Errorf("Cannot destroy container %s: %s", name, err)
|
||||
|
||||
if forceRemove {
|
||||
if err := daemon.ForceRm(container); err != nil {
|
||||
log.Errorf("Cannot destroy container %s: %v", name, err)
|
||||
}
|
||||
} else {
|
||||
if err := daemon.Rm(container); err != nil {
|
||||
return job.Errorf("Cannot destroy container %s: %v", name, err)
|
||||
}
|
||||
}
|
||||
container.LogEvent("destroy")
|
||||
if removeVolume {
|
||||
@@ -81,8 +88,16 @@ func (daemon *Daemon) DeleteVolumes(volumeIDs map[string]struct{}) {
|
||||
}
|
||||
}
|
||||
|
||||
func (daemon *Daemon) Rm(container *Container) (err error) {
|
||||
return daemon.commonRm(container, false)
|
||||
}
|
||||
|
||||
func (daemon *Daemon) ForceRm(container *Container) (err error) {
|
||||
return daemon.commonRm(container, true)
|
||||
}
|
||||
|
||||
// Destroy unregisters a container from the daemon and cleanly removes its contents from the filesystem.
|
||||
func (daemon *Daemon) Rm(container *Container) error {
|
||||
func (daemon *Daemon) commonRm(container *Container, forceRemove bool) (err error) {
|
||||
if container == nil {
|
||||
return fmt.Errorf("The given container is <nil>")
|
||||
}
|
||||
@@ -92,19 +107,40 @@ func (daemon *Daemon) Rm(container *Container) error {
|
||||
return fmt.Errorf("Container %v not found - maybe it was already destroyed?", container.ID)
|
||||
}
|
||||
|
||||
if err := container.Stop(3); err != nil {
|
||||
// Container state RemovalInProgress should be used to avoid races.
|
||||
if err = container.SetRemovalInProgress(); err != nil {
|
||||
return fmt.Errorf("Failed to set container state to RemovalInProgress: %s", err)
|
||||
}
|
||||
|
||||
defer container.ResetRemovalInProgress()
|
||||
|
||||
if err = container.Stop(3); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Deregister the container before removing its directory, to avoid race conditions
|
||||
daemon.idIndex.Delete(container.ID)
|
||||
daemon.containers.Delete(container.ID)
|
||||
// Mark container dead. We don't want anybody to be restarting it.
|
||||
container.SetDead()
|
||||
|
||||
// Save container state to disk. So that if error happens before
|
||||
// container meta file got removed from disk, then a restart of
|
||||
// docker should not make a dead container alive.
|
||||
container.ToDisk()
|
||||
|
||||
// If force removal is required, delete container from various
|
||||
// indexes even if removal failed.
|
||||
defer func() {
|
||||
if err != nil && forceRemove {
|
||||
daemon.idIndex.Delete(container.ID)
|
||||
daemon.containers.Delete(container.ID)
|
||||
}
|
||||
}()
|
||||
|
||||
container.derefVolumes()
|
||||
if _, err := daemon.containerGraph.Purge(container.ID); err != nil {
|
||||
log.Debugf("Unable to remove container from link graph: %s", err)
|
||||
}
|
||||
|
||||
if err := daemon.driver.Remove(container.ID); err != nil {
|
||||
if err = daemon.driver.Remove(container.ID); err != nil {
|
||||
return fmt.Errorf("Driver %s failed to remove root filesystem %s: %s", daemon.driver, container.ID, err)
|
||||
}
|
||||
|
||||
@@ -113,15 +149,17 @@ func (daemon *Daemon) Rm(container *Container) error {
|
||||
return fmt.Errorf("Driver %s failed to remove init filesystem %s: %s", daemon.driver, initID, err)
|
||||
}
|
||||
|
||||
if err := os.RemoveAll(container.root); err != nil {
|
||||
if err = os.RemoveAll(container.root); err != nil {
|
||||
return fmt.Errorf("Unable to remove filesystem for %v: %v", container.ID, err)
|
||||
}
|
||||
|
||||
if err := daemon.execDriver.Clean(container.ID); err != nil {
|
||||
if err = daemon.execDriver.Clean(container.ID); err != nil {
|
||||
return fmt.Errorf("Unable to remove execdriver data for %s: %s", container.ID, err)
|
||||
}
|
||||
|
||||
selinuxFreeLxcContexts(container.ProcessLabel)
|
||||
daemon.idIndex.Delete(container.ID)
|
||||
daemon.containers.Delete(container.ID)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -10,13 +10,13 @@ import (
|
||||
"github.com/docker/docker/pkg/sysinfo"
|
||||
)
|
||||
|
||||
func NewDriver(name, root, initPath string, sysInfo *sysinfo.SysInfo) (execdriver.Driver, error) {
|
||||
func NewDriver(name, root, libPath, initPath string, sysInfo *sysinfo.SysInfo) (execdriver.Driver, error) {
|
||||
switch name {
|
||||
case "lxc":
|
||||
// we want to give the lxc driver the full docker root because it needs
|
||||
// to access and write config and template files in /var/lib/docker/containers/*
|
||||
// to be backwards compatible
|
||||
return lxc.NewDriver(root, initPath, sysInfo.AppArmor)
|
||||
return lxc.NewDriver(root, libPath, initPath, sysInfo.AppArmor)
|
||||
case "native":
|
||||
return native.NewDriver(path.Join(root, "execdriver", "native"), initPath)
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"github.com/docker/docker/daemon/execdriver"
|
||||
sysinfo "github.com/docker/docker/pkg/system"
|
||||
"github.com/docker/docker/pkg/term"
|
||||
"github.com/docker/docker/pkg/version"
|
||||
"github.com/docker/docker/utils"
|
||||
"github.com/docker/libcontainer"
|
||||
"github.com/docker/libcontainer/cgroups"
|
||||
@@ -35,6 +36,7 @@ var ErrExec = errors.New("Unsupported: Exec is not supported by the lxc driver")
|
||||
|
||||
type driver struct {
|
||||
root string // root path for the driver to use
|
||||
libPath string
|
||||
initPath string
|
||||
apparmor bool
|
||||
sharedRoot bool
|
||||
@@ -48,7 +50,10 @@ type activeContainer struct {
|
||||
cmd *exec.Cmd
|
||||
}
|
||||
|
||||
func NewDriver(root, initPath string, apparmor bool) (*driver, error) {
|
||||
func NewDriver(root, libPath, initPath string, apparmor bool) (*driver, error) {
|
||||
if err := os.MkdirAll(root, 0700); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// setup unconfined symlink
|
||||
if err := linkLxcStart(root); err != nil {
|
||||
return nil, err
|
||||
@@ -60,6 +65,7 @@ func NewDriver(root, initPath string, apparmor bool) (*driver, error) {
|
||||
return &driver{
|
||||
apparmor: apparmor,
|
||||
root: root,
|
||||
libPath: libPath,
|
||||
initPath: initPath,
|
||||
sharedRoot: rootIsShared(),
|
||||
activeContainers: make(map[string]*activeContainer),
|
||||
@@ -115,6 +121,13 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba
|
||||
"-n", c.ID,
|
||||
"-f", configPath,
|
||||
}
|
||||
|
||||
// From lxc>=1.1 the default behavior is to daemonize containers after start
|
||||
lxcVersion := version.Version(d.version())
|
||||
if lxcVersion.GreaterThanOrEqualTo(version.Version("1.1")) {
|
||||
params = append(params, "-F")
|
||||
}
|
||||
|
||||
if c.Network.ContainerID != "" {
|
||||
params = append(params,
|
||||
"--share-net", c.Network.ContainerID,
|
||||
@@ -658,7 +671,7 @@ func rootIsShared() bool {
|
||||
}
|
||||
|
||||
func (d *driver) containerDir(containerId string) string {
|
||||
return path.Join(d.root, "containers", containerId)
|
||||
return path.Join(d.libPath, "containers", containerId)
|
||||
}
|
||||
|
||||
func (d *driver) generateLXCConfig(c *execdriver.Command) (string, error) {
|
||||
@@ -688,7 +701,7 @@ func (d *driver) generateEnvConfig(c *execdriver.Command) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p := path.Join(d.root, "containers", c.ID, "config.env")
|
||||
p := path.Join(d.libPath, "containers", c.ID, "config.env")
|
||||
c.Mounts = append(c.Mounts, execdriver.Mount{
|
||||
Source: p,
|
||||
Destination: "/.dockerenv",
|
||||
@@ -780,5 +793,8 @@ func (d *driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessCo
|
||||
}
|
||||
|
||||
func (d *driver) Stats(id string) (*execdriver.ResourceStats, error) {
|
||||
if _, ok := d.activeContainers[id]; !ok {
|
||||
return nil, fmt.Errorf("%s is not a key in active containers", id)
|
||||
}
|
||||
return execdriver.Stats(d.containerDir(id), d.activeContainers[id].container.Cgroups.Memory, d.machineMemory)
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ func TestLXCConfig(t *testing.T) {
|
||||
cpu = cpuMin + rand.Intn(cpuMax-cpuMin)
|
||||
)
|
||||
|
||||
driver, err := NewDriver(root, "", false)
|
||||
driver, err := NewDriver(root, root, "", false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -76,7 +76,7 @@ func TestCustomLxcConfig(t *testing.T) {
|
||||
|
||||
os.MkdirAll(path.Join(root, "containers", "1"), 0777)
|
||||
|
||||
driver, err := NewDriver(root, "", false)
|
||||
driver, err := NewDriver(root, root, "", false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -194,7 +194,7 @@ func TestCustomLxcConfigMounts(t *testing.T) {
|
||||
}
|
||||
os.MkdirAll(path.Join(root, "containers", "1"), 0777)
|
||||
|
||||
driver, err := NewDriver(root, "", false)
|
||||
driver, err := NewDriver(root, root, "", false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -248,7 +248,7 @@ func TestCustomLxcConfigMisc(t *testing.T) {
|
||||
}
|
||||
defer os.RemoveAll(root)
|
||||
os.MkdirAll(path.Join(root, "containers", "1"), 0777)
|
||||
driver, err := NewDriver(root, "", true)
|
||||
driver, err := NewDriver(root, root, "", true)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -313,7 +313,7 @@ func TestCustomLxcConfigMiscOverride(t *testing.T) {
|
||||
}
|
||||
defer os.RemoveAll(root)
|
||||
os.MkdirAll(path.Join(root, "containers", "1"), 0777)
|
||||
driver, err := NewDriver(root, "", false)
|
||||
driver, err := NewDriver(root, root, "", false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -6,12 +6,10 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/docker/docker/daemon/execdriver"
|
||||
"github.com/docker/docker/pkg/symlink"
|
||||
"github.com/docker/libcontainer/apparmor"
|
||||
"github.com/docker/libcontainer/configs"
|
||||
"github.com/docker/libcontainer/devices"
|
||||
@@ -228,10 +226,6 @@ func (d *driver) setupMounts(container *configs.Config, c *execdriver.Command) e
|
||||
container.Mounts = defaultMounts
|
||||
|
||||
for _, m := range c.Mounts {
|
||||
dest, err := symlink.FollowSymlinkInScope(filepath.Join(c.Rootfs, m.Destination), c.Rootfs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
flags := syscall.MS_BIND | syscall.MS_REC
|
||||
if !m.Writable {
|
||||
flags |= syscall.MS_RDONLY
|
||||
@@ -239,10 +233,9 @@ func (d *driver) setupMounts(container *configs.Config, c *execdriver.Command) e
|
||||
if m.Slave {
|
||||
flags |= syscall.MS_SLAVE
|
||||
}
|
||||
|
||||
container.Mounts = append(container.Mounts, &configs.Mount{
|
||||
Source: m.Source,
|
||||
Destination: dest,
|
||||
Destination: m.Destination,
|
||||
Device: "bind",
|
||||
Flags: flags,
|
||||
})
|
||||
|
||||
@@ -64,7 +64,6 @@ func NewDriver(root, initPath string) (*driver, error) {
|
||||
root,
|
||||
cgm,
|
||||
libcontainer.InitPath(reexec.Self(), DriverName),
|
||||
libcontainer.TmpfsRoot,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -157,32 +156,68 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba
|
||||
startCallback(&c.ProcessConfig, pid)
|
||||
}
|
||||
|
||||
oomKillNotification, err := cont.NotifyOOM()
|
||||
if err != nil {
|
||||
oomKillNotification = nil
|
||||
log.Warnf("Your kernel does not support OOM notifications: %s", err)
|
||||
}
|
||||
oom := notifyOnOOM(cont)
|
||||
waitF := p.Wait
|
||||
if nss := cont.Config().Namespaces; nss.Contains(configs.NEWPID) {
|
||||
if nss := cont.Config().Namespaces; !nss.Contains(configs.NEWPID) {
|
||||
// we need such hack for tracking processes with inerited fds,
|
||||
// because cmd.Wait() waiting for all streams to be copied
|
||||
waitF = waitInPIDHost(p, cont)
|
||||
}
|
||||
ps, err := waitF()
|
||||
if err != nil {
|
||||
if err, ok := err.(*exec.ExitError); !ok {
|
||||
execErr, ok := err.(*exec.ExitError)
|
||||
if !ok {
|
||||
return execdriver.ExitStatus{ExitCode: -1}, err
|
||||
} else {
|
||||
ps = err.ProcessState
|
||||
}
|
||||
ps = execErr.ProcessState
|
||||
}
|
||||
cont.Destroy()
|
||||
|
||||
_, oomKill := <-oomKillNotification
|
||||
|
||||
_, oomKill := <-oom
|
||||
return execdriver.ExitStatus{ExitCode: utils.ExitStatus(ps.Sys().(syscall.WaitStatus)), OOMKilled: oomKill}, nil
|
||||
}
|
||||
|
||||
// notifyOnOOM returns a channel that signals if the container received an OOM notification
|
||||
// for any process. If it is unable to subscribe to OOM notifications then a closed
|
||||
// channel is returned as it will be non-blocking and return the correct result when read.
|
||||
func notifyOnOOM(container libcontainer.Container) <-chan struct{} {
|
||||
oom, err := container.NotifyOOM()
|
||||
if err != nil {
|
||||
log.Warnf("Your kernel does not support OOM notifications: %s", err)
|
||||
c := make(chan struct{})
|
||||
close(c)
|
||||
return c
|
||||
}
|
||||
return oom
|
||||
}
|
||||
|
||||
func killCgroupProcs(c libcontainer.Container) {
|
||||
var procs []*os.Process
|
||||
if err := c.Pause(); err != nil {
|
||||
log.Warn(err)
|
||||
}
|
||||
pids, err := c.Processes()
|
||||
if err != nil {
|
||||
// don't care about childs if we can't get them, this is mostly because cgroup already deleted
|
||||
log.Warnf("Failed to get processes from container %s: %v", c.ID(), err)
|
||||
}
|
||||
for _, pid := range pids {
|
||||
if p, err := os.FindProcess(pid); err == nil {
|
||||
procs = append(procs, p)
|
||||
if err := p.Kill(); err != nil {
|
||||
log.Warn(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := c.Resume(); err != nil {
|
||||
log.Warn(err)
|
||||
}
|
||||
for _, p := range procs {
|
||||
if _, err := p.Wait(); err != nil {
|
||||
log.Warn(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func waitInPIDHost(p *libcontainer.Process, c libcontainer.Container) func() (*os.ProcessState, error) {
|
||||
return func() (*os.ProcessState, error) {
|
||||
pid, err := p.Pid()
|
||||
@@ -193,26 +228,13 @@ func waitInPIDHost(p *libcontainer.Process, c libcontainer.Container) func() (*o
|
||||
process, err := os.FindProcess(pid)
|
||||
s, err := process.Wait()
|
||||
if err != nil {
|
||||
if err, ok := err.(*exec.ExitError); !ok {
|
||||
execErr, ok := err.(*exec.ExitError)
|
||||
if !ok {
|
||||
return s, err
|
||||
} else {
|
||||
s = err.ProcessState
|
||||
}
|
||||
s = execErr.ProcessState
|
||||
}
|
||||
processes, err := c.Processes()
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
|
||||
for _, pid := range processes {
|
||||
process, err := os.FindProcess(pid)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to kill process: %d", pid)
|
||||
continue
|
||||
}
|
||||
process.Kill()
|
||||
}
|
||||
|
||||
killCgroupProcs(c)
|
||||
p.Wait()
|
||||
return s, err
|
||||
}
|
||||
@@ -248,29 +270,25 @@ func (d *driver) Unpause(c *execdriver.Command) error {
|
||||
|
||||
func (d *driver) Terminate(c *execdriver.Command) error {
|
||||
defer d.cleanContainer(c.ID)
|
||||
// lets check the start time for the process
|
||||
active := d.activeContainers[c.ID]
|
||||
if active == nil {
|
||||
return fmt.Errorf("active container for %s does not exist", c.ID)
|
||||
container, err := d.factory.Load(c.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
state, err := active.State()
|
||||
defer container.Destroy()
|
||||
state, err := container.State()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pid := state.InitProcessPid
|
||||
|
||||
currentStartTime, err := system.GetProcessStartTime(pid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if state.InitProcessStartTime == currentStartTime {
|
||||
err = syscall.Kill(pid, 9)
|
||||
syscall.Wait4(pid, nil, 0, nil)
|
||||
}
|
||||
|
||||
return err
|
||||
|
||||
}
|
||||
|
||||
func (d *driver) Info(id string) execdriver.Info {
|
||||
|
||||
@@ -82,9 +82,16 @@ func New() *configs.Config {
|
||||
},
|
||||
MaskPaths: []string{
|
||||
"/proc/kcore",
|
||||
"/proc/latency_stats",
|
||||
"/proc/timer_stats",
|
||||
},
|
||||
ReadonlyPaths: []string{
|
||||
"/proc/sys", "/proc/sysrq-trigger", "/proc/irq", "/proc/bus",
|
||||
"/proc/asound",
|
||||
"/proc/bus",
|
||||
"/proc/fs",
|
||||
"/proc/irq",
|
||||
"/proc/sys",
|
||||
"/proc/sysrq-trigger",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ package aufs
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
@@ -47,6 +48,9 @@ var (
|
||||
graphdriver.FsMagicAufs,
|
||||
}
|
||||
backingFs = "<unknown>"
|
||||
|
||||
enableDirpermLock sync.Once
|
||||
enableDirperm bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -152,6 +156,7 @@ func (a *Driver) Status() [][2]string {
|
||||
{"Root Dir", a.rootPath()},
|
||||
{"Backing Filesystem", backingFs},
|
||||
{"Dirs", fmt.Sprintf("%d", len(ids))},
|
||||
{"Dirperm1 Supported", fmt.Sprintf("%v", useDirperm())},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -422,7 +427,11 @@ func (a *Driver) aufsMount(ro []string, rw, target, mountLabel string) (err erro
|
||||
// Mount options are clipped to page size(4096 bytes). If there are more
|
||||
// layers then these are remounted individually using append.
|
||||
|
||||
b := make([]byte, syscall.Getpagesize()-len(mountLabel)-54) // room for xino & mountLabel
|
||||
offset := 54
|
||||
if useDirperm() {
|
||||
offset += len("dirperm1")
|
||||
}
|
||||
b := make([]byte, syscall.Getpagesize()-len(mountLabel)-offset) // room for xino & mountLabel
|
||||
bp := copy(b, fmt.Sprintf("br:%s=rw", rw))
|
||||
|
||||
firstMount := true
|
||||
@@ -446,7 +455,11 @@ func (a *Driver) aufsMount(ro []string, rw, target, mountLabel string) (err erro
|
||||
}
|
||||
|
||||
if firstMount {
|
||||
data := label.FormatMountLabel(fmt.Sprintf("%s,dio,xino=/dev/shm/aufs.xino", string(b[:bp])), mountLabel)
|
||||
opts := "dio,xino=/dev/shm/aufs.xino"
|
||||
if useDirperm() {
|
||||
opts += ",dirperm1"
|
||||
}
|
||||
data := label.FormatMountLabel(fmt.Sprintf("%s,%s", string(b[:bp]), opts), mountLabel)
|
||||
if err = mount("none", target, "aufs", 0, data); err != nil {
|
||||
return
|
||||
}
|
||||
@@ -460,3 +473,33 @@ func (a *Driver) aufsMount(ro []string, rw, target, mountLabel string) (err erro
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// useDirperm checks dirperm1 mount option can be used with the current
|
||||
// version of aufs.
|
||||
func useDirperm() bool {
|
||||
enableDirpermLock.Do(func() {
|
||||
base, err := ioutil.TempDir("", "docker-aufs-base")
|
||||
if err != nil {
|
||||
log.Errorf("error checking dirperm1: %v", err)
|
||||
return
|
||||
}
|
||||
defer os.RemoveAll(base)
|
||||
|
||||
union, err := ioutil.TempDir("", "docker-aufs-union")
|
||||
if err != nil {
|
||||
log.Errorf("error checking dirperm1: %v", err)
|
||||
return
|
||||
}
|
||||
defer os.RemoveAll(union)
|
||||
|
||||
opts := fmt.Sprintf("br:%s,dirperm1,xino=/dev/shm/aufs.xino", base)
|
||||
if err := mount("none", union, "aufs", 0, opts); err != nil {
|
||||
return
|
||||
}
|
||||
enableDirperm = true
|
||||
if err := Unmount(union); err != nil {
|
||||
log.Errorf("error checking dirperm1: failed to unmount %v", err)
|
||||
}
|
||||
})
|
||||
return enableDirperm
|
||||
}
|
||||
|
||||
@@ -5,20 +5,22 @@ package btrfs
|
||||
/*
|
||||
#include <btrfs/version.h>
|
||||
|
||||
// because around version 3.16, they did not define lib version yet
|
||||
int my_btrfs_lib_version() {
|
||||
#ifdef BTRFS_LIB_VERSION
|
||||
return BTRFS_LIB_VERSION;
|
||||
#else
|
||||
return -1;
|
||||
// around version 3.16, they did not define lib version yet
|
||||
#ifndef BTRFS_LIB_VERSION
|
||||
#define BTRFS_LIB_VERSION -1
|
||||
#endif
|
||||
|
||||
// upstream had removed it, but now it will be coming back
|
||||
#ifndef BTRFS_BUILD_VERSION
|
||||
#define BTRFS_BUILD_VERSION "-"
|
||||
#endif
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
func BtrfsBuildVersion() string {
|
||||
return string(C.BTRFS_BUILD_VERSION)
|
||||
}
|
||||
|
||||
func BtrfsLibVersion() int {
|
||||
return int(C.BTRFS_LIB_VERSION)
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ package btrfs
|
||||
func BtrfsBuildVersion() string {
|
||||
return "-"
|
||||
}
|
||||
|
||||
func BtrfsLibVersion() int {
|
||||
return -1
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// +build linux
|
||||
// +build linux,!btrfs_noversion
|
||||
|
||||
package btrfs
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBuildVersion(t *testing.T) {
|
||||
if len(BtrfsBuildVersion()) == 0 {
|
||||
t.Errorf("expected output from btrfs build version, but got empty string")
|
||||
func TestLibVersion(t *testing.T) {
|
||||
if BtrfsLibVersion() <= 0 {
|
||||
t.Errorf("expected output from btrfs lib version > 0")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,9 +39,8 @@ var (
|
||||
"aufs",
|
||||
"btrfs",
|
||||
"devicemapper",
|
||||
"vfs",
|
||||
// experimental, has to be enabled manually for now
|
||||
"overlay",
|
||||
"vfs",
|
||||
}
|
||||
|
||||
ErrNotSupported = errors.New("driver not supported")
|
||||
|
||||
45
daemon/logger/syslog/syslog.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package syslog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/syslog"
|
||||
"os"
|
||||
"path"
|
||||
"sync"
|
||||
|
||||
"github.com/docker/docker/daemon/logger"
|
||||
)
|
||||
|
||||
type Syslog struct {
|
||||
writer *syslog.Writer
|
||||
tag string
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func New(tag string) (logger.Logger, error) {
|
||||
log, err := syslog.New(syslog.LOG_DAEMON, fmt.Sprintf("%s/%s", path.Base(os.Args[0]), tag))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Syslog{
|
||||
writer: log,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Syslog) Log(msg *logger.Message) error {
|
||||
if msg.Source == "stderr" {
|
||||
return s.writer.Err(string(msg.Line))
|
||||
}
|
||||
return s.writer.Info(string(msg.Line))
|
||||
}
|
||||
|
||||
func (s *Syslog) Close() error {
|
||||
if s.writer != nil {
|
||||
return s.writer.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Syslog) Name() string {
|
||||
return "Syslog"
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
@@ -77,11 +78,19 @@ var (
|
||||
bridgeIPv4Network *net.IPNet
|
||||
bridgeIPv6Addr net.IP
|
||||
globalIPv6Network *net.IPNet
|
||||
portMapper *portmapper.PortMapper
|
||||
once sync.Once
|
||||
|
||||
defaultBindingIP = net.ParseIP("0.0.0.0")
|
||||
currentInterfaces = ifaces{c: make(map[string]*networkInterface)}
|
||||
)
|
||||
|
||||
func initPortMapper() {
|
||||
once.Do(func() {
|
||||
portMapper = portmapper.New()
|
||||
})
|
||||
}
|
||||
|
||||
func InitDriver(job *engine.Job) engine.Status {
|
||||
var (
|
||||
networkv4 *net.IPNet
|
||||
@@ -99,6 +108,14 @@ func InitDriver(job *engine.Job) engine.Status {
|
||||
fixedCIDRv6 = job.Getenv("FixedCIDRv6")
|
||||
)
|
||||
|
||||
// try to modprobe bridge first
|
||||
// see gh#12177
|
||||
if out, err := exec.Command("modprobe", "-va", "bridge", "nf_nat").Output(); err != nil {
|
||||
log.Warnf("Running modprobe bridge nf_nat failed with message: %s, error: %v", out, err)
|
||||
}
|
||||
|
||||
initPortMapper()
|
||||
|
||||
if defaultIP := job.Getenv("DefaultBindingIP"); defaultIP != "" {
|
||||
defaultBindingIP = net.ParseIP(defaultIP)
|
||||
}
|
||||
@@ -234,7 +251,7 @@ func InitDriver(job *engine.Job) engine.Status {
|
||||
if err != nil {
|
||||
return job.Error(err)
|
||||
}
|
||||
portmapper.SetIptablesChain(chain)
|
||||
portMapper.SetIptablesChain(chain)
|
||||
}
|
||||
|
||||
bridgeIPv4Network = networkv4
|
||||
@@ -349,6 +366,11 @@ func setupIPTables(addr net.Addr, icc, ipmasq bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func RequestPort(ip net.IP, proto string, port int) (int, error) {
|
||||
initPortMapper()
|
||||
return portMapper.Allocator.RequestPort(ip, proto, port)
|
||||
}
|
||||
|
||||
// configureBridge attempts to create and configure a network bridge interface named `bridgeIface` on the host
|
||||
// If bridgeIP is empty, it will try to find a non-conflicting IP from the Docker-specified private ranges
|
||||
// If the bridge `bridgeIface` already exists, it will only perform the IP address association with the existing
|
||||
@@ -586,7 +608,7 @@ func Release(job *engine.Job) engine.Status {
|
||||
}
|
||||
|
||||
for _, nat := range containerInterface.PortMappings {
|
||||
if err := portmapper.Unmap(nat); err != nil {
|
||||
if err := portMapper.Unmap(nat); err != nil {
|
||||
log.Infof("Unable to unmap port %s: %s", nat, err)
|
||||
}
|
||||
}
|
||||
@@ -643,7 +665,7 @@ func AllocatePort(job *engine.Job) engine.Status {
|
||||
|
||||
var host net.Addr
|
||||
for i := 0; i < MaxAllocatedPortAttempts; i++ {
|
||||
if host, err = portmapper.Map(container, ip, hostPort); err == nil {
|
||||
if host, err = portMapper.Map(container, ip, hostPort); err == nil {
|
||||
break
|
||||
}
|
||||
// There is no point in immediately retrying to map an explicitly
|
||||
|
||||
@@ -16,44 +16,12 @@ const (
|
||||
DefaultPortRangeEnd = 65535
|
||||
)
|
||||
|
||||
var (
|
||||
beginPortRange = DefaultPortRangeStart
|
||||
endPortRange = DefaultPortRangeEnd
|
||||
)
|
||||
|
||||
type portMap struct {
|
||||
p map[int]struct{}
|
||||
last int
|
||||
}
|
||||
|
||||
func newPortMap() *portMap {
|
||||
return &portMap{
|
||||
p: map[int]struct{}{},
|
||||
last: endPortRange,
|
||||
}
|
||||
}
|
||||
|
||||
type protoMap map[string]*portMap
|
||||
|
||||
func newProtoMap() protoMap {
|
||||
return protoMap{
|
||||
"tcp": newPortMap(),
|
||||
"udp": newPortMap(),
|
||||
}
|
||||
}
|
||||
|
||||
type ipMapping map[string]protoMap
|
||||
|
||||
var (
|
||||
ErrAllPortsAllocated = errors.New("all ports are allocated")
|
||||
ErrUnknownProtocol = errors.New("unknown protocol")
|
||||
)
|
||||
|
||||
var (
|
||||
mutex sync.Mutex
|
||||
|
||||
defaultIP = net.ParseIP("0.0.0.0")
|
||||
globalMap = ipMapping{}
|
||||
defaultIP = net.ParseIP("0.0.0.0")
|
||||
)
|
||||
|
||||
type ErrPortAlreadyAllocated struct {
|
||||
@@ -68,31 +36,6 @@ func NewErrPortAlreadyAllocated(ip string, port int) ErrPortAlreadyAllocated {
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
const portRangeKernelParam = "/proc/sys/net/ipv4/ip_local_port_range"
|
||||
|
||||
file, err := os.Open(portRangeKernelParam)
|
||||
if err != nil {
|
||||
log.Warnf("Failed to read %s kernel parameter: %v", portRangeKernelParam, err)
|
||||
return
|
||||
}
|
||||
var start, end int
|
||||
n, err := fmt.Fscanf(bufio.NewReader(file), "%d\t%d", &start, &end)
|
||||
if n != 2 || err != nil {
|
||||
if err == nil {
|
||||
err = fmt.Errorf("unexpected count of parsed numbers (%d)", n)
|
||||
}
|
||||
log.Errorf("Failed to parse port range from %s: %v", portRangeKernelParam, err)
|
||||
return
|
||||
}
|
||||
beginPortRange = start
|
||||
endPortRange = end
|
||||
}
|
||||
|
||||
func PortRange() (int, int) {
|
||||
return beginPortRange, endPortRange
|
||||
}
|
||||
|
||||
func (e ErrPortAlreadyAllocated) IP() string {
|
||||
return e.ip
|
||||
}
|
||||
@@ -109,12 +52,57 @@ func (e ErrPortAlreadyAllocated) Error() string {
|
||||
return fmt.Sprintf("Bind for %s:%d failed: port is already allocated", e.ip, e.port)
|
||||
}
|
||||
|
||||
type (
|
||||
PortAllocator struct {
|
||||
mutex sync.Mutex
|
||||
ipMap ipMapping
|
||||
Begin int
|
||||
End int
|
||||
}
|
||||
portMap struct {
|
||||
p map[int]struct{}
|
||||
begin, end int
|
||||
last int
|
||||
}
|
||||
protoMap map[string]*portMap
|
||||
)
|
||||
|
||||
func New() *PortAllocator {
|
||||
start, end, err := getDynamicPortRange()
|
||||
if err != nil {
|
||||
log.Warn(err)
|
||||
start, end = DefaultPortRangeStart, DefaultPortRangeEnd
|
||||
}
|
||||
return &PortAllocator{
|
||||
ipMap: ipMapping{},
|
||||
Begin: start,
|
||||
End: end,
|
||||
}
|
||||
}
|
||||
|
||||
func getDynamicPortRange() (start int, end int, err error) {
|
||||
const portRangeKernelParam = "/proc/sys/net/ipv4/ip_local_port_range"
|
||||
portRangeFallback := fmt.Sprintf("using fallback port range %d-%d", DefaultPortRangeStart, DefaultPortRangeEnd)
|
||||
file, err := os.Open(portRangeKernelParam)
|
||||
if err != nil {
|
||||
return 0, 0, fmt.Errorf("port allocator - %s due to error: %v", portRangeFallback, err)
|
||||
}
|
||||
n, err := fmt.Fscanf(bufio.NewReader(file), "%d\t%d", &start, &end)
|
||||
if n != 2 || err != nil {
|
||||
if err == nil {
|
||||
err = fmt.Errorf("unexpected count of parsed numbers (%d)", n)
|
||||
}
|
||||
return 0, 0, fmt.Errorf("port allocator - failed to parse system ephemeral port range from %s - %s: %v", portRangeKernelParam, portRangeFallback, err)
|
||||
}
|
||||
return start, end, nil
|
||||
}
|
||||
|
||||
// RequestPort requests new port from global ports pool for specified ip and proto.
|
||||
// If port is 0 it returns first free port. Otherwise it cheks port availability
|
||||
// in pool and return that port or error if port is already busy.
|
||||
func RequestPort(ip net.IP, proto string, port int) (int, error) {
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
func (p *PortAllocator) RequestPort(ip net.IP, proto string, port int) (int, error) {
|
||||
p.mutex.Lock()
|
||||
defer p.mutex.Unlock()
|
||||
|
||||
if proto != "tcp" && proto != "udp" {
|
||||
return 0, ErrUnknownProtocol
|
||||
@@ -124,10 +112,14 @@ func RequestPort(ip net.IP, proto string, port int) (int, error) {
|
||||
ip = defaultIP
|
||||
}
|
||||
ipstr := ip.String()
|
||||
protomap, ok := globalMap[ipstr]
|
||||
protomap, ok := p.ipMap[ipstr]
|
||||
if !ok {
|
||||
protomap = newProtoMap()
|
||||
globalMap[ipstr] = protomap
|
||||
protomap = protoMap{
|
||||
"tcp": p.newPortMap(),
|
||||
"udp": p.newPortMap(),
|
||||
}
|
||||
|
||||
p.ipMap[ipstr] = protomap
|
||||
}
|
||||
mapping := protomap[proto]
|
||||
if port > 0 {
|
||||
@@ -146,14 +138,14 @@ func RequestPort(ip net.IP, proto string, port int) (int, error) {
|
||||
}
|
||||
|
||||
// ReleasePort releases port from global ports pool for specified ip and proto.
|
||||
func ReleasePort(ip net.IP, proto string, port int) error {
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
func (p *PortAllocator) ReleasePort(ip net.IP, proto string, port int) error {
|
||||
p.mutex.Lock()
|
||||
defer p.mutex.Unlock()
|
||||
|
||||
if ip == nil {
|
||||
ip = defaultIP
|
||||
}
|
||||
protomap, ok := globalMap[ip.String()]
|
||||
protomap, ok := p.ipMap[ip.String()]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
@@ -161,20 +153,29 @@ func ReleasePort(ip net.IP, proto string, port int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *PortAllocator) newPortMap() *portMap {
|
||||
return &portMap{
|
||||
p: map[int]struct{}{},
|
||||
begin: p.Begin,
|
||||
end: p.End,
|
||||
last: p.End,
|
||||
}
|
||||
}
|
||||
|
||||
// ReleaseAll releases all ports for all ips.
|
||||
func ReleaseAll() error {
|
||||
mutex.Lock()
|
||||
globalMap = ipMapping{}
|
||||
mutex.Unlock()
|
||||
func (p *PortAllocator) ReleaseAll() error {
|
||||
p.mutex.Lock()
|
||||
p.ipMap = ipMapping{}
|
||||
p.mutex.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pm *portMap) findPort() (int, error) {
|
||||
port := pm.last
|
||||
for i := 0; i <= endPortRange-beginPortRange; i++ {
|
||||
for i := 0; i <= pm.end-pm.begin; i++ {
|
||||
port++
|
||||
if port > endPortRange {
|
||||
port = beginPortRange
|
||||
if port > pm.end {
|
||||
port = pm.begin
|
||||
}
|
||||
|
||||
if _, ok := pm.p[port]; !ok {
|
||||
|
||||
@@ -5,32 +5,23 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func init() {
|
||||
beginPortRange = DefaultPortRangeStart
|
||||
endPortRange = DefaultPortRangeEnd
|
||||
}
|
||||
|
||||
func reset() {
|
||||
ReleaseAll()
|
||||
}
|
||||
|
||||
func TestRequestNewPort(t *testing.T) {
|
||||
defer reset()
|
||||
p := New()
|
||||
|
||||
port, err := RequestPort(defaultIP, "tcp", 0)
|
||||
port, err := p.RequestPort(defaultIP, "tcp", 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if expected := beginPortRange; port != expected {
|
||||
if expected := p.Begin; port != expected {
|
||||
t.Fatalf("Expected port %d got %d", expected, port)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequestSpecificPort(t *testing.T) {
|
||||
defer reset()
|
||||
p := New()
|
||||
|
||||
port, err := RequestPort(defaultIP, "tcp", 5000)
|
||||
port, err := p.RequestPort(defaultIP, "tcp", 5000)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -40,9 +31,9 @@ func TestRequestSpecificPort(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestReleasePort(t *testing.T) {
|
||||
defer reset()
|
||||
p := New()
|
||||
|
||||
port, err := RequestPort(defaultIP, "tcp", 5000)
|
||||
port, err := p.RequestPort(defaultIP, "tcp", 5000)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -50,15 +41,15 @@ func TestReleasePort(t *testing.T) {
|
||||
t.Fatalf("Expected port 5000 got %d", port)
|
||||
}
|
||||
|
||||
if err := ReleasePort(defaultIP, "tcp", 5000); err != nil {
|
||||
if err := p.ReleasePort(defaultIP, "tcp", 5000); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReuseReleasedPort(t *testing.T) {
|
||||
defer reset()
|
||||
p := New()
|
||||
|
||||
port, err := RequestPort(defaultIP, "tcp", 5000)
|
||||
port, err := p.RequestPort(defaultIP, "tcp", 5000)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -66,20 +57,20 @@ func TestReuseReleasedPort(t *testing.T) {
|
||||
t.Fatalf("Expected port 5000 got %d", port)
|
||||
}
|
||||
|
||||
if err := ReleasePort(defaultIP, "tcp", 5000); err != nil {
|
||||
if err := p.ReleasePort(defaultIP, "tcp", 5000); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
port, err = RequestPort(defaultIP, "tcp", 5000)
|
||||
port, err = p.RequestPort(defaultIP, "tcp", 5000)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReleaseUnreadledPort(t *testing.T) {
|
||||
defer reset()
|
||||
p := New()
|
||||
|
||||
port, err := RequestPort(defaultIP, "tcp", 5000)
|
||||
port, err := p.RequestPort(defaultIP, "tcp", 5000)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -87,7 +78,7 @@ func TestReleaseUnreadledPort(t *testing.T) {
|
||||
t.Fatalf("Expected port 5000 got %d", port)
|
||||
}
|
||||
|
||||
port, err = RequestPort(defaultIP, "tcp", 5000)
|
||||
port, err = p.RequestPort(defaultIP, "tcp", 5000)
|
||||
|
||||
switch err.(type) {
|
||||
case ErrPortAlreadyAllocated:
|
||||
@@ -97,42 +88,40 @@ func TestReleaseUnreadledPort(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUnknowProtocol(t *testing.T) {
|
||||
defer reset()
|
||||
|
||||
if _, err := RequestPort(defaultIP, "tcpp", 0); err != ErrUnknownProtocol {
|
||||
if _, err := New().RequestPort(defaultIP, "tcpp", 0); err != ErrUnknownProtocol {
|
||||
t.Fatalf("Expected error %s got %s", ErrUnknownProtocol, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllocateAllPorts(t *testing.T) {
|
||||
defer reset()
|
||||
p := New()
|
||||
|
||||
for i := 0; i <= endPortRange-beginPortRange; i++ {
|
||||
port, err := RequestPort(defaultIP, "tcp", 0)
|
||||
for i := 0; i <= p.End-p.Begin; i++ {
|
||||
port, err := p.RequestPort(defaultIP, "tcp", 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if expected := beginPortRange + i; port != expected {
|
||||
if expected := p.Begin + i; port != expected {
|
||||
t.Fatalf("Expected port %d got %d", expected, port)
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := RequestPort(defaultIP, "tcp", 0); err != ErrAllPortsAllocated {
|
||||
if _, err := p.RequestPort(defaultIP, "tcp", 0); err != ErrAllPortsAllocated {
|
||||
t.Fatalf("Expected error %s got %s", ErrAllPortsAllocated, err)
|
||||
}
|
||||
|
||||
_, err := RequestPort(defaultIP, "udp", 0)
|
||||
_, err := p.RequestPort(defaultIP, "udp", 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// release a port in the middle and ensure we get another tcp port
|
||||
port := beginPortRange + 5
|
||||
if err := ReleasePort(defaultIP, "tcp", port); err != nil {
|
||||
port := p.Begin + 5
|
||||
if err := p.ReleasePort(defaultIP, "tcp", port); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
newPort, err := RequestPort(defaultIP, "tcp", 0)
|
||||
newPort, err := p.RequestPort(defaultIP, "tcp", 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -142,10 +131,10 @@ func TestAllocateAllPorts(t *testing.T) {
|
||||
|
||||
// now pm.last == newPort, release it so that it's the only free port of
|
||||
// the range, and ensure we get it back
|
||||
if err := ReleasePort(defaultIP, "tcp", newPort); err != nil {
|
||||
if err := p.ReleasePort(defaultIP, "tcp", newPort); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
port, err = RequestPort(defaultIP, "tcp", 0)
|
||||
port, err = p.RequestPort(defaultIP, "tcp", 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -155,34 +144,34 @@ func TestAllocateAllPorts(t *testing.T) {
|
||||
}
|
||||
|
||||
func BenchmarkAllocatePorts(b *testing.B) {
|
||||
defer reset()
|
||||
p := New()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
for i := 0; i <= endPortRange-beginPortRange; i++ {
|
||||
port, err := RequestPort(defaultIP, "tcp", 0)
|
||||
for i := 0; i <= p.End-p.Begin; i++ {
|
||||
port, err := p.RequestPort(defaultIP, "tcp", 0)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
if expected := beginPortRange + i; port != expected {
|
||||
if expected := p.Begin + i; port != expected {
|
||||
b.Fatalf("Expected port %d got %d", expected, port)
|
||||
}
|
||||
}
|
||||
reset()
|
||||
p.ReleaseAll()
|
||||
}
|
||||
}
|
||||
|
||||
func TestPortAllocation(t *testing.T) {
|
||||
defer reset()
|
||||
p := New()
|
||||
|
||||
ip := net.ParseIP("192.168.0.1")
|
||||
ip2 := net.ParseIP("192.168.0.2")
|
||||
if port, err := RequestPort(ip, "tcp", 80); err != nil {
|
||||
if port, err := p.RequestPort(ip, "tcp", 80); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if port != 80 {
|
||||
t.Fatalf("Acquire(80) should return 80, not %d", port)
|
||||
}
|
||||
port, err := RequestPort(ip, "tcp", 0)
|
||||
port, err := p.RequestPort(ip, "tcp", 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -190,41 +179,41 @@ func TestPortAllocation(t *testing.T) {
|
||||
t.Fatalf("Acquire(0) should return a non-zero port")
|
||||
}
|
||||
|
||||
if _, err := RequestPort(ip, "tcp", port); err == nil {
|
||||
if _, err := p.RequestPort(ip, "tcp", port); err == nil {
|
||||
t.Fatalf("Acquiring a port already in use should return an error")
|
||||
}
|
||||
|
||||
if newPort, err := RequestPort(ip, "tcp", 0); err != nil {
|
||||
if newPort, err := p.RequestPort(ip, "tcp", 0); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if newPort == port {
|
||||
t.Fatalf("Acquire(0) allocated the same port twice: %d", port)
|
||||
}
|
||||
|
||||
if _, err := RequestPort(ip, "tcp", 80); err == nil {
|
||||
if _, err := p.RequestPort(ip, "tcp", 80); err == nil {
|
||||
t.Fatalf("Acquiring a port already in use should return an error")
|
||||
}
|
||||
if _, err := RequestPort(ip2, "tcp", 80); err != nil {
|
||||
if _, err := p.RequestPort(ip2, "tcp", 80); err != nil {
|
||||
t.Fatalf("It should be possible to allocate the same port on a different interface")
|
||||
}
|
||||
if _, err := RequestPort(ip2, "tcp", 80); err == nil {
|
||||
if _, err := p.RequestPort(ip2, "tcp", 80); err == nil {
|
||||
t.Fatalf("Acquiring a port already in use should return an error")
|
||||
}
|
||||
if err := ReleasePort(ip, "tcp", 80); err != nil {
|
||||
if err := p.ReleasePort(ip, "tcp", 80); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := RequestPort(ip, "tcp", 80); err != nil {
|
||||
if _, err := p.RequestPort(ip, "tcp", 80); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
port, err = RequestPort(ip, "tcp", 0)
|
||||
port, err = p.RequestPort(ip, "tcp", 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
port2, err := RequestPort(ip, "tcp", port+1)
|
||||
port2, err := p.RequestPort(ip, "tcp", port+1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
port3, err := RequestPort(ip, "tcp", 0)
|
||||
port3, err := p.RequestPort(ip, "tcp", 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -234,17 +223,17 @@ func TestPortAllocation(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestNoDuplicateBPR(t *testing.T) {
|
||||
defer reset()
|
||||
p := New()
|
||||
|
||||
if port, err := RequestPort(defaultIP, "tcp", beginPortRange); err != nil {
|
||||
if port, err := p.RequestPort(defaultIP, "tcp", p.Begin); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if port != beginPortRange {
|
||||
t.Fatalf("Expected port %d got %d", beginPortRange, port)
|
||||
} else if port != p.Begin {
|
||||
t.Fatalf("Expected port %d got %d", p.Begin, port)
|
||||
}
|
||||
|
||||
if port, err := RequestPort(defaultIP, "tcp", 0); err != nil {
|
||||
if port, err := p.RequestPort(defaultIP, "tcp", 0); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if port == beginPortRange {
|
||||
} else if port == p.Begin {
|
||||
t.Fatalf("Acquire(0) allocated the same port twice: %d", port)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,15 +18,7 @@ type mapping struct {
|
||||
container net.Addr
|
||||
}
|
||||
|
||||
var (
|
||||
chain *iptables.Chain
|
||||
lock sync.Mutex
|
||||
|
||||
// udp:ip:port
|
||||
currentMappings = make(map[string]*mapping)
|
||||
|
||||
NewProxy = NewProxyCommand
|
||||
)
|
||||
var NewProxy = NewProxyCommand
|
||||
|
||||
var (
|
||||
ErrUnknownBackendAddressType = errors.New("unknown container address type not supported")
|
||||
@@ -34,13 +26,34 @@ var (
|
||||
ErrPortNotMapped = errors.New("port is not mapped")
|
||||
)
|
||||
|
||||
func SetIptablesChain(c *iptables.Chain) {
|
||||
chain = c
|
||||
type PortMapper struct {
|
||||
chain *iptables.Chain
|
||||
|
||||
// udp:ip:port
|
||||
currentMappings map[string]*mapping
|
||||
lock sync.Mutex
|
||||
|
||||
Allocator *portallocator.PortAllocator
|
||||
}
|
||||
|
||||
func Map(container net.Addr, hostIP net.IP, hostPort int) (host net.Addr, err error) {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
func New() *PortMapper {
|
||||
return NewWithPortAllocator(portallocator.New())
|
||||
}
|
||||
|
||||
func NewWithPortAllocator(allocator *portallocator.PortAllocator) *PortMapper {
|
||||
return &PortMapper{
|
||||
currentMappings: make(map[string]*mapping),
|
||||
Allocator: allocator,
|
||||
}
|
||||
}
|
||||
|
||||
func (pm *PortMapper) SetIptablesChain(c *iptables.Chain) {
|
||||
pm.chain = c
|
||||
}
|
||||
|
||||
func (pm *PortMapper) Map(container net.Addr, hostIP net.IP, hostPort int) (host net.Addr, err error) {
|
||||
pm.lock.Lock()
|
||||
defer pm.lock.Unlock()
|
||||
|
||||
var (
|
||||
m *mapping
|
||||
@@ -52,7 +65,7 @@ func Map(container net.Addr, hostIP net.IP, hostPort int) (host net.Addr, err er
|
||||
switch container.(type) {
|
||||
case *net.TCPAddr:
|
||||
proto = "tcp"
|
||||
if allocatedHostPort, err = portallocator.RequestPort(hostIP, proto, hostPort); err != nil {
|
||||
if allocatedHostPort, err = pm.Allocator.RequestPort(hostIP, proto, hostPort); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -65,7 +78,7 @@ func Map(container net.Addr, hostIP net.IP, hostPort int) (host net.Addr, err er
|
||||
proxy = NewProxy(proto, hostIP, allocatedHostPort, container.(*net.TCPAddr).IP, container.(*net.TCPAddr).Port)
|
||||
case *net.UDPAddr:
|
||||
proto = "udp"
|
||||
if allocatedHostPort, err = portallocator.RequestPort(hostIP, proto, hostPort); err != nil {
|
||||
if allocatedHostPort, err = pm.Allocator.RequestPort(hostIP, proto, hostPort); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -83,25 +96,25 @@ func Map(container net.Addr, hostIP net.IP, hostPort int) (host net.Addr, err er
|
||||
// release the allocated port on any further error during return.
|
||||
defer func() {
|
||||
if err != nil {
|
||||
portallocator.ReleasePort(hostIP, proto, allocatedHostPort)
|
||||
pm.Allocator.ReleasePort(hostIP, proto, allocatedHostPort)
|
||||
}
|
||||
}()
|
||||
|
||||
key := getKey(m.host)
|
||||
if _, exists := currentMappings[key]; exists {
|
||||
if _, exists := pm.currentMappings[key]; exists {
|
||||
return nil, ErrPortMappedForIP
|
||||
}
|
||||
|
||||
containerIP, containerPort := getIPAndPort(m.container)
|
||||
if err := forward(iptables.Append, m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort); err != nil {
|
||||
if err := pm.forward(iptables.Append, m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cleanup := func() error {
|
||||
// need to undo the iptables rules before we return
|
||||
proxy.Stop()
|
||||
forward(iptables.Delete, m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort)
|
||||
if err := portallocator.ReleasePort(hostIP, m.proto, allocatedHostPort); err != nil {
|
||||
pm.forward(iptables.Delete, m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort)
|
||||
if err := pm.Allocator.ReleasePort(hostIP, m.proto, allocatedHostPort); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -115,35 +128,35 @@ func Map(container net.Addr, hostIP net.IP, hostPort int) (host net.Addr, err er
|
||||
return nil, err
|
||||
}
|
||||
m.userlandProxy = proxy
|
||||
currentMappings[key] = m
|
||||
pm.currentMappings[key] = m
|
||||
return m.host, nil
|
||||
}
|
||||
|
||||
func Unmap(host net.Addr) error {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
func (pm *PortMapper) Unmap(host net.Addr) error {
|
||||
pm.lock.Lock()
|
||||
defer pm.lock.Unlock()
|
||||
|
||||
key := getKey(host)
|
||||
data, exists := currentMappings[key]
|
||||
data, exists := pm.currentMappings[key]
|
||||
if !exists {
|
||||
return ErrPortNotMapped
|
||||
}
|
||||
|
||||
data.userlandProxy.Stop()
|
||||
|
||||
delete(currentMappings, key)
|
||||
delete(pm.currentMappings, key)
|
||||
|
||||
containerIP, containerPort := getIPAndPort(data.container)
|
||||
hostIP, hostPort := getIPAndPort(data.host)
|
||||
if err := forward(iptables.Delete, data.proto, hostIP, hostPort, containerIP.String(), containerPort); err != nil {
|
||||
if err := pm.forward(iptables.Delete, data.proto, hostIP, hostPort, containerIP.String(), containerPort); err != nil {
|
||||
log.Errorf("Error on iptables delete: %s", err)
|
||||
}
|
||||
|
||||
switch a := host.(type) {
|
||||
case *net.TCPAddr:
|
||||
return portallocator.ReleasePort(a.IP, "tcp", a.Port)
|
||||
return pm.Allocator.ReleasePort(a.IP, "tcp", a.Port)
|
||||
case *net.UDPAddr:
|
||||
return portallocator.ReleasePort(a.IP, "udp", a.Port)
|
||||
return pm.Allocator.ReleasePort(a.IP, "udp", a.Port)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -168,9 +181,9 @@ func getIPAndPort(a net.Addr) (net.IP, int) {
|
||||
return nil, 0
|
||||
}
|
||||
|
||||
func forward(action iptables.Action, proto string, sourceIP net.IP, sourcePort int, containerIP string, containerPort int) error {
|
||||
if chain == nil {
|
||||
func (pm *PortMapper) forward(action iptables.Action, proto string, sourceIP net.IP, sourcePort int, containerIP string, containerPort int) error {
|
||||
if pm.chain == nil {
|
||||
return nil
|
||||
}
|
||||
return chain.Forward(action, sourceIP, sourcePort, proto, containerIP, containerPort)
|
||||
return pm.chain.Forward(action, sourceIP, sourcePort, proto, containerIP, containerPort)
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/daemon/networkdriver/portallocator"
|
||||
"github.com/docker/docker/pkg/iptables"
|
||||
)
|
||||
|
||||
@@ -13,30 +12,26 @@ func init() {
|
||||
NewProxy = NewMockProxyCommand
|
||||
}
|
||||
|
||||
func reset() {
|
||||
chain = nil
|
||||
currentMappings = make(map[string]*mapping)
|
||||
}
|
||||
|
||||
func TestSetIptablesChain(t *testing.T) {
|
||||
defer reset()
|
||||
pm := New()
|
||||
|
||||
c := &iptables.Chain{
|
||||
Name: "TEST",
|
||||
Bridge: "192.168.1.1",
|
||||
}
|
||||
|
||||
if chain != nil {
|
||||
if pm.chain != nil {
|
||||
t.Fatal("chain should be nil at init")
|
||||
}
|
||||
|
||||
SetIptablesChain(c)
|
||||
if chain == nil {
|
||||
pm.SetIptablesChain(c)
|
||||
if pm.chain == nil {
|
||||
t.Fatal("chain should not be nil after set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMapPorts(t *testing.T) {
|
||||
pm := New()
|
||||
dstIp1 := net.ParseIP("192.168.0.1")
|
||||
dstIp2 := net.ParseIP("192.168.0.2")
|
||||
dstAddr1 := &net.TCPAddr{IP: dstIp1, Port: 80}
|
||||
@@ -49,34 +44,34 @@ func TestMapPorts(t *testing.T) {
|
||||
return (addr1.Network() == addr2.Network()) && (addr1.String() == addr2.String())
|
||||
}
|
||||
|
||||
if host, err := Map(srcAddr1, dstIp1, 80); err != nil {
|
||||
if host, err := pm.Map(srcAddr1, dstIp1, 80); err != nil {
|
||||
t.Fatalf("Failed to allocate port: %s", err)
|
||||
} else if !addrEqual(dstAddr1, host) {
|
||||
t.Fatalf("Incorrect mapping result: expected %s:%s, got %s:%s",
|
||||
dstAddr1.String(), dstAddr1.Network(), host.String(), host.Network())
|
||||
}
|
||||
|
||||
if _, err := Map(srcAddr1, dstIp1, 80); err == nil {
|
||||
if _, err := pm.Map(srcAddr1, dstIp1, 80); err == nil {
|
||||
t.Fatalf("Port is in use - mapping should have failed")
|
||||
}
|
||||
|
||||
if _, err := Map(srcAddr2, dstIp1, 80); err == nil {
|
||||
if _, err := pm.Map(srcAddr2, dstIp1, 80); err == nil {
|
||||
t.Fatalf("Port is in use - mapping should have failed")
|
||||
}
|
||||
|
||||
if _, err := Map(srcAddr2, dstIp2, 80); err != nil {
|
||||
if _, err := pm.Map(srcAddr2, dstIp2, 80); err != nil {
|
||||
t.Fatalf("Failed to allocate port: %s", err)
|
||||
}
|
||||
|
||||
if Unmap(dstAddr1) != nil {
|
||||
if pm.Unmap(dstAddr1) != nil {
|
||||
t.Fatalf("Failed to release port")
|
||||
}
|
||||
|
||||
if Unmap(dstAddr2) != nil {
|
||||
if pm.Unmap(dstAddr2) != nil {
|
||||
t.Fatalf("Failed to release port")
|
||||
}
|
||||
|
||||
if Unmap(dstAddr2) == nil {
|
||||
if pm.Unmap(dstAddr2) == nil {
|
||||
t.Fatalf("Port already released, but no error reported")
|
||||
}
|
||||
}
|
||||
@@ -115,6 +110,7 @@ func TestGetUDPIPAndPort(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMapAllPortsSingleInterface(t *testing.T) {
|
||||
pm := New()
|
||||
dstIp1 := net.ParseIP("0.0.0.0")
|
||||
srcAddr1 := &net.TCPAddr{Port: 1080, IP: net.ParseIP("172.16.0.1")}
|
||||
|
||||
@@ -124,26 +120,26 @@ func TestMapAllPortsSingleInterface(t *testing.T) {
|
||||
|
||||
defer func() {
|
||||
for _, val := range hosts {
|
||||
Unmap(val)
|
||||
pm.Unmap(val)
|
||||
}
|
||||
}()
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
start, end := portallocator.PortRange()
|
||||
start, end := pm.Allocator.Begin, pm.Allocator.End
|
||||
for i := start; i < end; i++ {
|
||||
if host, err = Map(srcAddr1, dstIp1, 0); err != nil {
|
||||
if host, err = pm.Map(srcAddr1, dstIp1, 0); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
hosts = append(hosts, host)
|
||||
}
|
||||
|
||||
if _, err := Map(srcAddr1, dstIp1, start); err == nil {
|
||||
if _, err := pm.Map(srcAddr1, dstIp1, start); err == nil {
|
||||
t.Fatalf("Port %d should be bound but is not", start)
|
||||
}
|
||||
|
||||
for _, val := range hosts {
|
||||
if err := Unmap(val); err != nil {
|
||||
if err := pm.Unmap(val); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,16 +11,18 @@ import (
|
||||
|
||||
type State struct {
|
||||
sync.Mutex
|
||||
Running bool
|
||||
Paused bool
|
||||
Restarting bool
|
||||
OOMKilled bool
|
||||
Pid int
|
||||
ExitCode int
|
||||
Error string // contains last known error when starting the container
|
||||
StartedAt time.Time
|
||||
FinishedAt time.Time
|
||||
waitChan chan struct{}
|
||||
Running bool
|
||||
Paused bool
|
||||
Restarting bool
|
||||
OOMKilled bool
|
||||
removalInProgress bool // Not need for this to be persistent on disk.
|
||||
Dead bool
|
||||
Pid int
|
||||
ExitCode int
|
||||
Error string // contains last known error when starting the container
|
||||
StartedAt time.Time
|
||||
FinishedAt time.Time
|
||||
waitChan chan struct{}
|
||||
}
|
||||
|
||||
func NewState() *State {
|
||||
@@ -42,6 +44,14 @@ func (s *State) String() string {
|
||||
return fmt.Sprintf("Up %s", units.HumanDuration(time.Now().UTC().Sub(s.StartedAt)))
|
||||
}
|
||||
|
||||
if s.removalInProgress {
|
||||
return "Removal In Progress"
|
||||
}
|
||||
|
||||
if s.Dead {
|
||||
return "Dead"
|
||||
}
|
||||
|
||||
if s.FinishedAt.IsZero() {
|
||||
return ""
|
||||
}
|
||||
@@ -60,6 +70,11 @@ func (s *State) StateString() string {
|
||||
}
|
||||
return "running"
|
||||
}
|
||||
|
||||
if s.Dead {
|
||||
return "dead"
|
||||
}
|
||||
|
||||
return "exited"
|
||||
}
|
||||
|
||||
@@ -217,3 +232,25 @@ func (s *State) IsPaused() bool {
|
||||
s.Unlock()
|
||||
return res
|
||||
}
|
||||
|
||||
func (s *State) SetRemovalInProgress() error {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
if s.removalInProgress {
|
||||
return fmt.Errorf("Status is already RemovalInProgress")
|
||||
}
|
||||
s.removalInProgress = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *State) ResetRemovalInProgress() {
|
||||
s.Lock()
|
||||
s.removalInProgress = false
|
||||
s.Unlock()
|
||||
}
|
||||
|
||||
func (s *State) SetDead() {
|
||||
s.Lock()
|
||||
s.Dead = true
|
||||
s.Unlock()
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ type Mount struct {
|
||||
Writable bool
|
||||
copyData bool
|
||||
from *Container
|
||||
isBind bool
|
||||
}
|
||||
|
||||
func (mnt *Mount) Export(resource string) (io.ReadCloser, error) {
|
||||
@@ -79,7 +80,7 @@ func (m *Mount) initialize() error {
|
||||
if hostPath, exists := m.container.Volumes[m.MountToPath]; exists {
|
||||
// If this is a bind-mount/volumes-from, maybe it was passed in at start instead of create
|
||||
// We need to make sure bind-mounts/volumes-from passed on start can override existing ones.
|
||||
if !m.volume.IsBindMount && m.from == nil {
|
||||
if (!m.volume.IsBindMount && !m.isBind) && m.from == nil {
|
||||
return nil
|
||||
}
|
||||
if m.volume.Path == hostPath {
|
||||
@@ -172,6 +173,7 @@ func (container *Container) parseVolumeMountConfig() (map[string]*Mount, error)
|
||||
volume: vol,
|
||||
MountToPath: mountToPath,
|
||||
Writable: writable,
|
||||
isBind: true, // in case the volume itself is a normal volume, but is being mounted in as a bindmount here
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,10 +189,13 @@ func (container *Container) parseVolumeMountConfig() (map[string]*Mount, error)
|
||||
if _, exists := container.Volumes[path]; exists {
|
||||
continue
|
||||
}
|
||||
|
||||
if stat, err := os.Stat(filepath.Join(container.basefs, path)); err == nil {
|
||||
realPath, err := container.getResourcePath(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to evaluate the absolute path of symlink")
|
||||
}
|
||||
if stat, err := os.Stat(realPath); err == nil {
|
||||
if !stat.IsDir() {
|
||||
return nil, fmt.Errorf("file exists at %s, can't create volume there")
|
||||
return nil, fmt.Errorf("file exists at %s, can't create volume there", realPath)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,11 +4,20 @@
|
||||
FROM docs/base:latest
|
||||
MAINTAINER Sven Dowideit <SvenDowideit@docker.com> (@SvenDowideit)
|
||||
|
||||
# This section ensures we pull the correct version of each
|
||||
# sub project
|
||||
ENV COMPOSE_BRANCH 1.2.0
|
||||
ENV SWARM_BRANCH v0.2.0
|
||||
ENV MACHINE_BRANCH master
|
||||
ENV DISTRIB_BRANCH master
|
||||
|
||||
|
||||
|
||||
# TODO: need the full repo source to get the git version info
|
||||
COPY . /src
|
||||
|
||||
# Reset the /docs dir so we can replace the theme meta with the new repo's git info
|
||||
RUN git reset --hard
|
||||
# RUN git reset --hard
|
||||
|
||||
# Then copy the desired docs into the /docs/sources/ dir
|
||||
COPY ./sources/ /docs/sources
|
||||
@@ -23,45 +32,77 @@ COPY ./mkdocs.yml mkdocs.yml
|
||||
COPY ./s3_website.json s3_website.json
|
||||
COPY ./release.sh release.sh
|
||||
|
||||
|
||||
# Docker Distribution
|
||||
#
|
||||
#ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/mkdocs.yml /docs/mkdocs-distribution.yml
|
||||
|
||||
ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/images/notifications.png /docs/sources/registry/images/notifications.png
|
||||
ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/images/registry.png /docs/sources/registry/images/registry.png
|
||||
|
||||
ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/overview.md /docs/sources/registry/overview.md
|
||||
RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/registry/overview.md
|
||||
|
||||
ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/deploying.md /docs/sources/registry/deploying.md
|
||||
RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/registry/deploying.md
|
||||
|
||||
ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/configuration.md /docs/sources/registry/configuration.md
|
||||
RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/registry/configuration.md
|
||||
|
||||
ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/storagedrivers.md /docs/sources/registry/storagedrivers.md
|
||||
RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/registry/storagedrivers.md
|
||||
|
||||
ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/notifications.md /docs/sources/registry/notifications.md
|
||||
RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/registry/notifications.md
|
||||
|
||||
ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/spec/api.md /docs/sources/registry/spec/api.md
|
||||
RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/registry/spec/api.md
|
||||
|
||||
ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/spec/json.md /docs/sources/registry/spec/json.md
|
||||
RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/registry/spec/json.md
|
||||
|
||||
ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/spec/auth/token.md /docs/sources/registry/spec/auth/token.md
|
||||
RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/registry/spec/auth/token.md
|
||||
|
||||
# Docker Swarm
|
||||
#ADD https://raw.githubusercontent.com/docker/swarm/master/docs/mkdocs.yml /docs/mkdocs-swarm.yml
|
||||
ADD https://raw.githubusercontent.com/docker/swarm/master/docs/index.md /docs/sources/swarm/index.md
|
||||
#ADD https://raw.githubusercontent.com/docker/swarm/${SWARM_BRANCH}/docs/mkdocs.yml /docs/mkdocs-swarm.yml
|
||||
ADD https://raw.githubusercontent.com/docker/swarm/${SWARM_BRANCH}/docs/index.md /docs/sources/swarm/index.md
|
||||
RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/swarm/index.md
|
||||
ADD https://raw.githubusercontent.com/docker/swarm/master/discovery/README.md /docs/sources/swarm/discovery.md
|
||||
ADD https://raw.githubusercontent.com/docker/swarm/${SWARM_BRANCH}/discovery/README.md /docs/sources/swarm/discovery.md
|
||||
RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/swarm/discovery.md
|
||||
ADD https://raw.githubusercontent.com/docker/swarm/master/api/README.md /docs/sources/swarm/API.md
|
||||
ADD https://raw.githubusercontent.com/docker/swarm/${SWARM_BRANCH}/api/README.md /docs/sources/swarm/API.md
|
||||
RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/swarm/API.md
|
||||
ADD https://raw.githubusercontent.com/docker/swarm/master/scheduler/filter/README.md /docs/sources/swarm/scheduler/filter.md
|
||||
ADD https://raw.githubusercontent.com/docker/swarm/${SWARM_BRANCH}/scheduler/filter/README.md /docs/sources/swarm/scheduler/filter.md
|
||||
RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/swarm/scheduler/filter.md
|
||||
ADD https://raw.githubusercontent.com/docker/swarm/master/scheduler/strategy/README.md /docs/sources/swarm/scheduler/strategy.md
|
||||
ADD https://raw.githubusercontent.com/docker/swarm/${SWARM_BRANCH}/scheduler/strategy/README.md /docs/sources/swarm/scheduler/strategy.md
|
||||
RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/swarm/scheduler/strategy.md
|
||||
|
||||
# Docker Machine
|
||||
#ADD https://raw.githubusercontent.com/docker/machine/master/docs/mkdocs.yml /docs/mkdocs-machine.yml
|
||||
ADD https://raw.githubusercontent.com/docker/machine/master/docs/index.md /docs/sources/machine/index.md
|
||||
#ADD https://raw.githubusercontent.com/docker/machine/${MACHINE_BRANCH}/docs/mkdocs.yml /docs/mkdocs-machine.yml
|
||||
ADD https://raw.githubusercontent.com/docker/machine/${MACHINE_BRANCH}/docs/index.md /docs/sources/machine/index.md
|
||||
RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/machine/index.md
|
||||
|
||||
# Docker Compose
|
||||
#ADD https://raw.githubusercontent.com/docker/compose/master/docs/mkdocs.yml /docs/mkdocs-compose.yml
|
||||
ADD https://raw.githubusercontent.com/docker/compose/master/docs/index.md /docs/sources/compose/index.md
|
||||
#ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/mkdocs.yml /docs/mkdocs-compose.yml
|
||||
ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/index.md /docs/sources/compose/index.md
|
||||
RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/index.md
|
||||
ADD https://raw.githubusercontent.com/docker/compose/master/docs/install.md /docs/sources/compose/install.md
|
||||
ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/install.md /docs/sources/compose/install.md
|
||||
RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/install.md
|
||||
ADD https://raw.githubusercontent.com/docker/compose/master/docs/cli.md /docs/sources/compose/cli.md
|
||||
ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/cli.md /docs/sources/compose/cli.md
|
||||
RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/cli.md
|
||||
ADD https://raw.githubusercontent.com/docker/compose/master/docs/yml.md /docs/sources/compose/yml.md
|
||||
ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/yml.md /docs/sources/compose/yml.md
|
||||
RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/yml.md
|
||||
ADD https://raw.githubusercontent.com/docker/compose/master/docs/env.md /docs/sources/compose/env.md
|
||||
ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/env.md /docs/sources/compose/env.md
|
||||
RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/env.md
|
||||
ADD https://raw.githubusercontent.com/docker/compose/master/docs/completion.md /docs/sources/compose/completion.md
|
||||
ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/completion.md /docs/sources/compose/completion.md
|
||||
RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/completion.md
|
||||
|
||||
ADD https://raw.githubusercontent.com/docker/compose/master/docs/django.md /docs/sources/compose/django.md
|
||||
ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/django.md /docs/sources/compose/django.md
|
||||
RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/django.md
|
||||
ADD https://raw.githubusercontent.com/docker/compose/master/docs/rails.md /docs/sources/compose/rails.md
|
||||
ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/rails.md /docs/sources/compose/rails.md
|
||||
RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/rails.md
|
||||
ADD https://raw.githubusercontent.com/docker/compose/master/docs/wordpress.md /docs/sources/compose/wordpress.md
|
||||
ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/wordpress.md /docs/sources/compose/wordpress.md
|
||||
RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/wordpress.md
|
||||
|
||||
# Then build everything together, ready for mkdocs
|
||||
RUN /docs/build.sh
|
||||
RUN /docs/build.sh
|
||||
@@ -121,7 +121,7 @@ IMAGE [COMMAND] [ARG...]
|
||||
**--lxc-conf**=[]
|
||||
(lxc exec-driver only) Add custom lxc options --lxc-conf="lxc.cgroup.cpuset.cpus = 0,1"
|
||||
|
||||
**--log-driver**="|*json-file*|*none*"
|
||||
**--log-driver**="|*json-file*|*syslog*|*none*"
|
||||
Logging driver for container. Default is defined by daemon `--log-driver` flag.
|
||||
**Warning**: `docker logs` command works only for `json-file` logging driver.
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
% Docker Community
|
||||
% JUNE 2014
|
||||
# NAME
|
||||
docker-login - Register or log in to a Docker registry server, if no server is specified "https://index.docker.io/v1/" is the default.
|
||||
docker-login - Register or log in to a Docker registry.
|
||||
|
||||
# SYNOPSIS
|
||||
**docker login**
|
||||
@@ -13,9 +13,14 @@ docker-login - Register or log in to a Docker registry server, if no server is s
|
||||
[SERVER]
|
||||
|
||||
# DESCRIPTION
|
||||
Register or Login to a docker registry server, if no server is
|
||||
specified "https://index.docker.io/v1/" is the default. If you want to
|
||||
login to a private registry you can specify this by adding the server name.
|
||||
Register or log in to a Docker Registry Service located on the specified
|
||||
`SERVER`. You can specify a URL or a `hostname` for the `SERVER` value. If you
|
||||
do not specify a `SERVER`, the command uses Docker's public registry located at
|
||||
`https://registry-1.docker.io/` by default. To get a username/password for Docker's public registry, create an account on Docker Hub.
|
||||
|
||||
You can log into any public or private repository for which you have
|
||||
credentials. When you log in, the command stores encoded credentials in
|
||||
`$HOME/.dockercfg` on Linux or `%USERPROFILE%/.dockercfg` on Windows.
|
||||
|
||||
# OPTIONS
|
||||
**-e**, **--email**=""
|
||||
@@ -32,7 +37,7 @@ login to a private registry you can specify this by adding the server name.
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
## Login to a local registry
|
||||
## Login to a registry on your localhost
|
||||
|
||||
# docker login localhost:8080
|
||||
|
||||
@@ -43,3 +48,4 @@ login to a private registry you can specify this by adding the server name.
|
||||
April 2014, Originally compiled by William Henry (whenry at redhat dot com)
|
||||
based on docker.com source material and internal work.
|
||||
June 2014, updated by Sven Dowideit <SvenDowideit@home.org.au>
|
||||
April 2015, updated by Mary Anthony for v2 <mary@docker.com>
|
||||
|
||||
@@ -2,23 +2,24 @@
|
||||
% Docker Community
|
||||
% JUNE 2014
|
||||
# NAME
|
||||
docker-logout - Log out from a Docker registry, if no server is specified "https://index.docker.io/v1/" is the default.
|
||||
docker-logout - Log out from a Docker Registry Service.
|
||||
|
||||
# SYNOPSIS
|
||||
**docker logout**
|
||||
[SERVER]
|
||||
|
||||
# DESCRIPTION
|
||||
Log the user out from a Docker registry, if no server is
|
||||
specified "https://index.docker.io/v1/" is the default. If you want to
|
||||
log out from a private registry you can specify this by adding the server name.
|
||||
Log out of a Docker Registry Service located on the specified `SERVER`. You can
|
||||
specify a URL or a `hostname` for the `SERVER` value. If you do not specify a
|
||||
`SERVER`, the command attempts to log you out of Docker's public registry
|
||||
located at `https://registry-1.docker.io/` by default.
|
||||
|
||||
# OPTIONS
|
||||
There are no available options.
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
## Log out from a local registry
|
||||
## Log out from a registry on your localhost
|
||||
|
||||
# docker logout localhost:8080
|
||||
|
||||
@@ -28,3 +29,4 @@ There are no available options.
|
||||
# HISTORY
|
||||
June 2014, Originally compiled by Daniel, Dao Quang Minh (daniel at nitrous dot io)
|
||||
July 2014, updated by Sven Dowideit <SvenDowideit@home.org.au>
|
||||
April 2015, updated by Mary Anthony for v2 <mary@docker.com>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
% Docker Community
|
||||
% JUNE 2014
|
||||
# NAME
|
||||
docker-pull - Pull an image or a repository from the registry
|
||||
docker-pull - Pull an image or a repository from a registry
|
||||
|
||||
# SYNOPSIS
|
||||
**docker pull**
|
||||
@@ -12,10 +12,12 @@ NAME[:TAG]
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
This command pulls down an image or a repository from the registry. If
|
||||
This command pulls down an image or a repository from a registry. If
|
||||
there is more than one image for a repository (e.g., fedora) then all
|
||||
images for that repository name are pulled down including any tags.
|
||||
It is also possible to specify a non-default registry to pull from.
|
||||
|
||||
If you do not specify a `REGISTRY_HOST`, the command uses Docker's public
|
||||
registry located at `registry-1.docker.io` by default.
|
||||
|
||||
# OPTIONS
|
||||
**-a**, **--all-tags**=*true*|*false*
|
||||
@@ -45,7 +47,7 @@ It is also possible to specify a non-default registry to pull from.
|
||||
fedora heisenbug 105182bb5e8b 5 days ago 372.7 MB
|
||||
fedora latest 105182bb5e8b 5 days ago 372.7 MB
|
||||
|
||||
# Pull an image, manually specifying path to the registry and tag
|
||||
# Pull an image, manually specifying path to Docker's public registry and tag
|
||||
# Note that if the image is previously downloaded then the status would be
|
||||
# 'Status: Image is up to date for registry.hub.docker.com/fedora:20'
|
||||
|
||||
@@ -67,3 +69,5 @@ April 2014, Originally compiled by William Henry (whenry at redhat dot com)
|
||||
based on docker.com source material and internal work.
|
||||
June 2014, updated by Sven Dowideit <SvenDowideit@home.org.au>
|
||||
August 2014, updated by Sven Dowideit <SvenDowideit@home.org.au>
|
||||
April 2015, updated by John Willis <john.willis@docker.com>
|
||||
April 2015, updated by Mary Anthony for v2 <mary@docker.com>
|
||||
|
||||
@@ -2,18 +2,18 @@
|
||||
% Docker Community
|
||||
% JUNE 2014
|
||||
# NAME
|
||||
docker-push - Push an image or a repository to the registry
|
||||
docker-push - Push an image or a repository to a registry
|
||||
|
||||
# SYNOPSIS
|
||||
**docker push**
|
||||
[**--help**]
|
||||
NAME[:TAG]
|
||||
NAME[:TAG] | [REGISTRY_HOST[:REGISTRY_PORT]/]NAME[:TAG]
|
||||
|
||||
# DESCRIPTION
|
||||
Push an image or a repository to a registry. The default registry is the Docker
|
||||
Hub located at [hub.docker.com](https://hub.docker.com/). However the
|
||||
image can be pushed to another, perhaps private, registry as demonstrated in
|
||||
the example below.
|
||||
|
||||
This command pushes an image or a repository to a registry. If you do not
|
||||
specify a `REGISTRY_HOST`, the command uses Docker's public registry located at
|
||||
`registry-1.docker.io` by default.
|
||||
|
||||
# OPTIONS
|
||||
**--help**
|
||||
@@ -28,12 +28,10 @@ and then committing it to a new image name:
|
||||
|
||||
# docker commit c16378f943fe rhel-httpd
|
||||
|
||||
Now push the image to the registry using the image ID. In this example
|
||||
the registry is on host named registry-host and listening on port 5000.
|
||||
Default Docker commands will push to the default `hub.docker.com`
|
||||
registry. Instead, push to the local registry, which is on a host called
|
||||
registry-host*. To do this, tag the image with the host name or IP
|
||||
address, and the port of the registry:
|
||||
Now, push the image to the registry using the image ID. In this example the
|
||||
registry is on host named `registry-host` and listening on port `5000`. To do
|
||||
this, tag the image with the host name or IP address, and the port of the
|
||||
registry:
|
||||
|
||||
# docker tag rhel-httpd registry-host:5000/myadmin/rhel-httpd
|
||||
# docker push registry-host:5000/myadmin/rhel-httpd
|
||||
@@ -49,3 +47,5 @@ listed.
|
||||
April 2014, Originally compiled by William Henry (whenry at redhat dot com)
|
||||
based on docker.com source material and internal work.
|
||||
June 2014, updated by Sven Dowideit <SvenDowideit@home.org.au>
|
||||
April 2015, updated by Mary Anthony for v2 <mary@docker.com>
|
||||
|
||||
|
||||
@@ -13,10 +13,9 @@ IMAGE [IMAGE...]
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
This will remove one or more images from the host node. This does not
|
||||
remove images from a registry. You cannot remove an image of a running
|
||||
container unless you use the **-f** option. To see all images on a host
|
||||
use the **docker images** command.
|
||||
Removes one or more images from the host node. This does not remove images from
|
||||
a registry. You cannot remove an image of a running container unless you use the
|
||||
**-f** option. To see all images on a host use the **docker images** command.
|
||||
|
||||
# OPTIONS
|
||||
**-f**, **--force**=*true*|*false*
|
||||
@@ -40,3 +39,4 @@ Here is an example of removing and image:
|
||||
April 2014, Originally compiled by William Henry (whenry at redhat dot com)
|
||||
based on docker.com source material and internal work.
|
||||
June 2014, updated by Sven Dowideit <SvenDowideit@home.org.au>
|
||||
April 2015, updated by Mary Anthony for v2 <mary@docker.com>
|
||||
|
||||
@@ -222,7 +222,7 @@ which interface and port to use.
|
||||
**--lxc-conf**=[]
|
||||
(lxc exec-driver only) Add custom lxc options --lxc-conf="lxc.cgroup.cpuset.cpus = 0,1"
|
||||
|
||||
**--log-driver**="|*json-file*|*none*"
|
||||
**--log-driver**="|*json-file*|*syslog*|*none*"
|
||||
Logging driver for container. Default is defined by daemon `--log-driver` flag.
|
||||
**Warning**: `docker logs` command works only for `json-file` logging driver.
|
||||
|
||||
|
||||
@@ -14,10 +14,9 @@ TERM
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
Search an index for an image with that matches the term TERM. The table
|
||||
of images returned displays the name, description (truncated by default),
|
||||
number of stars awarded, whether the image is official, and whether it
|
||||
is automated.
|
||||
Search Docker Hub for an image with that matches the specified `TERM`. The table
|
||||
of images returned displays the name, description (truncated by default), number
|
||||
of stars awarded, whether the image is official, and whether it is automated.
|
||||
|
||||
*Note* - Search queries will only return up to 25 results
|
||||
|
||||
@@ -36,9 +35,9 @@ is automated.
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
## Search the registry for ranked images
|
||||
## Search Docker Hub for ranked images
|
||||
|
||||
Search the registry for the term 'fedora' and only display those images
|
||||
Search a registry for the term 'fedora' and only display those images
|
||||
ranked 3 or higher:
|
||||
|
||||
$ sudo docker search -s 3 fedora
|
||||
@@ -48,9 +47,9 @@ ranked 3 or higher:
|
||||
mattdm/fedora-small A small Fedora image on which to build. Co... 8
|
||||
goldmann/wildfly A WildFly application server running on a ... 3 [OK]
|
||||
|
||||
## Search the registry for automated images
|
||||
## Search Docker Hub for automated images
|
||||
|
||||
Search the registry for the term 'fedora' and only display automated images
|
||||
Search Docker Hub for the term 'fedora' and only display automated images
|
||||
ranked 1 or higher:
|
||||
|
||||
$ sudo docker search -s 1 -t fedora
|
||||
@@ -62,3 +61,5 @@ ranked 1 or higher:
|
||||
April 2014, Originally compiled by William Henry (whenry at redhat dot com)
|
||||
based on docker.com source material and internal work.
|
||||
June 2014, updated by Sven Dowideit <SvenDowideit@home.org.au>
|
||||
April 2015, updated by Mary Anthony for v2 <mary@docker.com>
|
||||
|
||||
|
||||
@@ -8,11 +8,14 @@ docker-tag - Tag an image into a repository
|
||||
**docker tag**
|
||||
[**-f**|**--force**[=*false*]]
|
||||
[**--help**]
|
||||
IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG]
|
||||
IMAGE[:TAG] [REGISTRY_HOST/][USERNAME/]NAME[:TAG]
|
||||
|
||||
# DESCRIPTION
|
||||
This will give a new alias to an image in the repository. This refers to the
|
||||
entire image name including the optional TAG after the ':'.
|
||||
Assigns a new alias to an image in a registry. An alias refers to the
|
||||
entire image name including the optional `TAG` after the ':'.
|
||||
|
||||
If you do not specify a `REGISTRY_HOST`, the command uses Docker's public
|
||||
registry located at `registry-1.docker.io` by default.
|
||||
|
||||
# "OPTIONS"
|
||||
**-f**, **--force**=*true*|*false*
|
||||
@@ -58,3 +61,5 @@ April 2014, Originally compiled by William Henry (whenry at redhat dot com)
|
||||
based on docker.com source material and internal work.
|
||||
June 2014, updated by Sven Dowideit <SvenDowideit@home.org.au>
|
||||
July 2014, updated by Sven Dowideit <SvenDowideit@home.org.au>
|
||||
April 2015, updated by Mary Anthony for v2 <mary@docker.com>
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ unix://[/path/to/socket] to use.
|
||||
**--label**="[]"
|
||||
Set key=value labels to the daemon (displayed in `docker info`)
|
||||
|
||||
**--log-driver**="*json-file*|*none*"
|
||||
**--log-driver**="*json-file*|*syslog*|*none*"
|
||||
Container's logging driver. Default is `default`.
|
||||
**Warning**: `docker logs` command works only for `json-file` logging driver.
|
||||
|
||||
@@ -172,10 +172,10 @@ inside it)
|
||||
Load an image from a tar archive
|
||||
|
||||
**docker-login(1)**
|
||||
Register or Login to a Docker registry server
|
||||
Register or login to a Docker Registry Service
|
||||
|
||||
**docker-logout(1)**
|
||||
Log the user out of a Docker registry server
|
||||
Log the user out of a Docker Registry Service
|
||||
|
||||
**docker-logs(1)**
|
||||
Fetch the logs of a container
|
||||
@@ -190,10 +190,10 @@ inside it)
|
||||
List containers
|
||||
|
||||
**docker-pull(1)**
|
||||
Pull an image or a repository from a Docker registry server
|
||||
Pull an image or a repository from a Docker Registry Service
|
||||
|
||||
**docker-push(1)**
|
||||
Push an image or a repository to a Docker registry server
|
||||
Push an image or a repository to a Docker Registry Service
|
||||
|
||||
**docker-restart(1)**
|
||||
Restart a running container
|
||||
|
||||
@@ -132,10 +132,19 @@ pages:
|
||||
- ['swarm/scheduler/filter.md', 'Reference', 'Swarm filters']
|
||||
- ['swarm/API.md', 'Reference', 'Swarm API']
|
||||
- ['reference/api/index.md', '**HIDDEN**']
|
||||
- ['registry/overview.md', 'Reference', 'Docker Registry 2.0']
|
||||
- ['registry/deploying.md', 'Reference', ' ▪ Deploy a registry' ]
|
||||
- ['registry/configuration.md', 'Reference', ' ▪ Configure a registry' ]
|
||||
- ['registry/storagedrivers.md', 'Reference', ' ▪ Storage driver model' ]
|
||||
- ['registry/notifications.md', 'Reference', ' ▪ Work with notifications' ]
|
||||
- ['registry/spec/api.md', 'Reference', ' ▪ Registry Service API v2' ]
|
||||
- ['registry/spec/json.md', 'Reference', ' ▪ JSON format' ]
|
||||
- ['registry/spec/auth/token.md', 'Reference', ' ▪ Authenticate via central service' ]
|
||||
- ['reference/api/hub_registry_spec.md', 'Reference', 'Docker Hub and Registry 1.0']
|
||||
- ['reference/api/registry_api.md', 'Reference', ' ▪ Docker Registry API v1']
|
||||
- ['reference/api/registry_api_client_libraries.md', 'Reference', ' ▪ Docker Registry 1.0 API Client Libraries']
|
||||
#- ['reference/image-spec-v1.md', 'Reference', 'Docker Image Specification v1.0.0']
|
||||
- ['reference/api/docker-io_api.md', 'Reference', 'Docker Hub API']
|
||||
- ['reference/api/registry_api.md', 'Reference', 'Docker Registry API']
|
||||
- ['reference/api/registry_api_client_libraries.md', 'Reference', 'Docker Registry API Client Libraries']
|
||||
- ['reference/api/hub_registry_spec.md', 'Reference', 'Docker Hub and Registry Spec']
|
||||
#- ['reference/image-spec-v1.md', 'Reference', 'Docker Image Specification v1.0.0']
|
||||
- ['reference/api/docker_remote_api.md', 'Reference', 'Docker Remote API']
|
||||
- ['reference/api/docker_remote_api_v1.18.md', 'Reference', 'Docker Remote API v1.18']
|
||||
|
||||
@@ -22,10 +22,17 @@ EOF
|
||||
}
|
||||
|
||||
create_robots_txt() {
|
||||
cat > ./sources/robots.txt <<'EOF'
|
||||
User-agent: *
|
||||
Disallow: /
|
||||
EOF
|
||||
if [ "$AWS_S3_BUCKET" == "docs.docker.com" ]; then
|
||||
cat > ./sources/robots.txt <<-'EOF'
|
||||
User-agent: *
|
||||
Allow: /
|
||||
EOF
|
||||
else
|
||||
cat > ./sources/robots.txt <<-'EOF'
|
||||
User-agent: *
|
||||
Disallow: /
|
||||
EOF
|
||||
fi
|
||||
}
|
||||
|
||||
setup_s3() {
|
||||
|
||||
@@ -39,7 +39,8 @@
|
||||
{ "Condition": { "KeyPrefixEquals": "installation/openSUSE/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "installation/SUSE/" } },
|
||||
{ "Condition": { "KeyPrefixEquals": "contributing/contributing/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "project/who-written-for/" } },
|
||||
{ "Condition": { "KeyPrefixEquals": "contributing/devenvironment/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "project/set-up-prereqs/" } },
|
||||
{ "Condition": { "KeyPrefixEquals": "contributing/docs_style-guide/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "project/doc-style/" } }
|
||||
{ "Condition": { "KeyPrefixEquals": "contributing/docs_style-guide/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "project/doc-style/" } },
|
||||
{ "Condition": { "KeyPrefixEquals": "registry/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "registry/overview/" } }
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 99 KiB |
|
Before Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 205 KiB |
|
Before Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 8.7 KiB |
|
Before Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 14 KiB |
BIN
docs/sources/installation/images/windows-boot2docker-cmd.png
Normal file
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 248 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 187 KiB |
@@ -8,31 +8,95 @@ page_keywords: Docker, Docker documentation, Windows, requirements, virtualbox,
|
||||
> Your processor needs to support hardware virtualization.
|
||||
|
||||
The Docker Engine uses Linux-specific kernel features, so to run it on Windows
|
||||
we need to use a lightweight virtual machine (vm). You use the Windows Docker client to
|
||||
control the virtualized Docker Engine to build, run, and manage Docker containers.
|
||||
we need to use a lightweight virtual machine (VM). You use the **Windows Docker
|
||||
Client** to control the virtualized Docker Engine to build, run, and manage
|
||||
Docker containers.
|
||||
|
||||
To make this process easier, we've designed a helper application called
|
||||
[Boot2Docker](https://github.com/boot2docker/boot2docker) that installs the
|
||||
virtual machine and runs the Docker daemon.
|
||||
[Boot2Docker](https://github.com/boot2docker/boot2docker) creates a Linux virtual
|
||||
machine on Windows to run Docker on a Linux operating system.
|
||||
|
||||
Although you will be using Windows Docker client, the docker engine hosting the
|
||||
containers will still be running on Linux. Until the Docker engine for Windows
|
||||
is developed, you can launch only Linux containers from your Windows machine.
|
||||
|
||||
## Demonstration
|
||||
|
||||
<iframe width="640" height="480" src="//www.youtube.com/embed/oSHN8_uiZd4?rel=0" frameborder="0" allowfullscreen></iframe>
|
||||
<iframe width="640" height="480" src="//www.youtube.com/embed/TjMU3bDX4vo?rel=0" frameborder="0" allowfullscreen></iframe>
|
||||
|
||||
## Installation
|
||||
|
||||
1. Download the latest release of the [Docker for Windows Installer](https://github.com/boot2docker/windows-installer/releases/latest)
|
||||
2. Run the installer, which will install VirtualBox, MSYS-git, the boot2docker Linux ISO,
|
||||
and the Boot2Docker management tool.
|
||||
1. Download the latest release of the
|
||||
[Docker for Windows Installer](https://github.com/boot2docker/windows-installer/releases/latest).
|
||||
2. Run the installer, which will install Docker Client or Windows, VirtualBox,
|
||||
Git for Windows (MSYS-git), the boot2docker Linux ISO, and the Boot2Docker
|
||||
management tool.
|
||||

|
||||
3. Run the `Boot2Docker Start` shell script from your Desktop or Program Files > Boot2Docker for Windows.
|
||||
3. Run the **Boot2Docker Start** shortcut from your Desktop or “Program Files →
|
||||
Boot2Docker for Windows”.
|
||||
The Start script will ask you to enter an ssh key passphrase - the simplest
|
||||
(but least secure) is to just hit [Enter].
|
||||
|
||||

|
||||
4. The **Boot2Docker Start** will start a unix shell already configured to manage
|
||||
Docker running inside the virtual machine. Run `docker version` to see
|
||||
if it is working correctly:
|
||||
|
||||
The `Boot2Docker Start` script will connect you to a shell session in the virtual
|
||||
machine. If needed, it will initialize a new VM and start it.
|
||||

|
||||
|
||||
## Running Docker
|
||||
|
||||
{{ include "no-remote-sudo.md" }}
|
||||
|
||||
**Boot2Docker Start** will automatically start a shell with environment variables
|
||||
correctly set so you can start using Docker right away:
|
||||
|
||||
Let's try the `hello-world` example image. Run
|
||||
|
||||
$ docker run hello-world
|
||||
|
||||
This should download the very small `hello-world` image and print a
|
||||
`Hello from Docker.` message.
|
||||
|
||||
## Using docker from Windows Command Line Prompt (cmd.exe)
|
||||
|
||||
Launch a Windows Command Line Prompt (cmd.exe).
|
||||
|
||||
Boot2Docker command requires `ssh.exe` to be in the PATH, therefore we need to
|
||||
include `bin` folder of the Git installation (which has ssh.exe) to the `%PATH%`
|
||||
environment variable by running:
|
||||
|
||||
set PATH=%PATH%;"c:\Program Files (x86)\Git\bin"
|
||||
|
||||
and then we can run the `boot2docker start` command to start the Boot2Docker VM.
|
||||
(Run `boot2docker init` command if you get an error saying machine does not
|
||||
exist.) Then copy the instructions for cmd.exe to set the environment variables
|
||||
to your console window and you are ready to run docker commands such as
|
||||
`docker ps`:
|
||||
|
||||

|
||||
|
||||
## Using docker from PowerShell
|
||||
|
||||
Launch a PowerShell window, then you need to add `ssh.exe` to your PATH:
|
||||
|
||||
$Env:Path = "${Env:Path};c:\Program Files (x86)\Git\bin"
|
||||
|
||||
and after running `boot2docker start` command it will print PowerShell commands
|
||||
to set the environment variables to connect Docker running inside VM. Run these
|
||||
commands and you are ready to run docker commands such as `docker ps`:
|
||||
|
||||

|
||||
|
||||
> NOTE: You can alternatively run `boot2docker shellinit | Invoke-Expression`
|
||||
> command to set the environment variables instead of copying and pasting on
|
||||
> PowerShell.
|
||||
|
||||
# Further Details
|
||||
|
||||
The Boot2Docker management tool provides several commands:
|
||||
|
||||
$ boot2docker
|
||||
Usage: boot2docker.exe [<options>] {help|init|up|ssh|save|down|poweroff|reset|restart|config|status|info|ip|shellinit|delete|download|upgrade|version} [<args>]
|
||||
|
||||
## Upgrading
|
||||
|
||||
@@ -47,46 +111,13 @@ and the Boot2Docker management tool.
|
||||
boot2docker download
|
||||
boot2docker start
|
||||
|
||||
## Running Docker
|
||||
|
||||
{{ include "no-remote-sudo.md" }}
|
||||
|
||||
Boot2Docker will log you in automatically so you can start using Docker right away.
|
||||
|
||||
Let's try the `hello-world` example image. Run
|
||||
|
||||
$ docker run hello-world
|
||||
|
||||
This should download the very small `hello-world` image and print a `Hello from Docker.` message.
|
||||
|
||||
## Login with PUTTY instead of using the CMD
|
||||
|
||||
Boot2Docker generates and uses the public/private key pair in your `%HOMEPATH%\.ssh`
|
||||
directory so to log in you need to use the private key from this same directory.
|
||||
|
||||
The private key needs to be converted into the format PuTTY uses.
|
||||
|
||||
You can do this with
|
||||
[puttygen](http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html):
|
||||
|
||||
- Open `puttygen.exe` and load ("File"->"Load" menu) the private key from
|
||||
`%HOMEPATH%\.ssh\id_boot2docker`
|
||||
- then click: "Save Private Key".
|
||||
- Then use the saved file to login with PuTTY using `docker@127.0.0.1:2022`.
|
||||
|
||||
# Further Details
|
||||
|
||||
The Boot2Docker management tool provides several commands:
|
||||
|
||||
$ ./boot2docker
|
||||
Usage: ./boot2docker [<options>] {help|init|up|ssh|save|down|poweroff|reset|restart|config|status|info|ip|delete|download|version} [<args>]
|
||||
|
||||
|
||||
## Container port redirection
|
||||
|
||||
If you are curious, the username for the boot2docker default user is `docker` and the password is `tcuser`.
|
||||
If you are curious, the username for the boot2docker default user is `docker`
|
||||
and the password is `tcuser`.
|
||||
|
||||
The latest version of `boot2docker` sets up a host only network adaptor which provides access to the container's ports.
|
||||
The latest version of `boot2docker` sets up a host only network adaptor which
|
||||
provides access to the container's ports.
|
||||
|
||||
If you run a container with an exposed port:
|
||||
|
||||
@@ -101,3 +132,25 @@ Typically, it is 192.168.59.103, but it could get changed by Virtualbox's DHCP
|
||||
implementation.
|
||||
|
||||
For further information or to report issues, please see the [Boot2Docker site](http://boot2docker.io)
|
||||
|
||||
## Login with PUTTY instead of using the CMD
|
||||
|
||||
Boot2Docker generates and uses the public/private key pair in your `%USERPROFILE%\.ssh`
|
||||
directory so to log in you need to use the private key from this same directory.
|
||||
|
||||
The private key needs to be converted into the format PuTTY uses.
|
||||
|
||||
You can do this with
|
||||
[puttygen](http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html):
|
||||
|
||||
- Open `puttygen.exe` and load ("File"->"Load" menu) the private key from
|
||||
`%USERPROFILE%\.ssh\id_boot2docker`
|
||||
- then click: "Save Private Key".
|
||||
- Then use the saved file to login with PuTTY using `docker@127.0.0.1:2022`.
|
||||
|
||||
## References
|
||||
|
||||
If you have Docker hosts running and if you don't wish to do a
|
||||
Boot2Docker installation, you can install the docker.exe using
|
||||
unofficial Windows package manager Chocolately. For information
|
||||
on how to do this, see [Docker package on Chocolatey](http://chocolatey.org/packages/docker).
|
||||
|
||||
@@ -259,7 +259,7 @@ Json Parameters:
|
||||
`Ulimits: { "Name": "nofile", "Soft": 1024, "Hard", 2048 }}`
|
||||
- **LogConfig** - Logging configuration to container, format
|
||||
`{ "Type": "<driver_name>", "Config": {"key1": "val1"}}
|
||||
Available types: `json-file`, `none`.
|
||||
Available types: `json-file`, `syslog`, `none`.
|
||||
`json-file` logging driver.
|
||||
- **CgroupParent** - Path to cgroups under which the cgroup for the container will be created. If the path is not absolute, the path is considered to be relative to the cgroups path of the init process. Cgroups will be created if they do not already exist.
|
||||
|
||||
@@ -359,7 +359,10 @@ Return low-level information on the container `id`
|
||||
"MaximumRetryCount": 2,
|
||||
"Name": "on-failure"
|
||||
},
|
||||
"LogConfig": { "Type": "json-file", Config: {} },
|
||||
"LogConfig": {
|
||||
"Config": null,
|
||||
"Type": "json-file"
|
||||
},
|
||||
"SecurityOpt": null,
|
||||
"VolumesFrom": null,
|
||||
"Ulimits": [{}]
|
||||
|
||||
@@ -2,7 +2,7 @@ page_title: Registry Documentation
|
||||
page_description: Documentation for docker Registry and Registry API
|
||||
page_keywords: docker, registry, api, hub
|
||||
|
||||
# The Docker Hub and the Registry spec
|
||||
# The Docker Hub and the Registry 1.0 spec
|
||||
|
||||
## The three roles
|
||||
|
||||
@@ -28,9 +28,9 @@ The Docker Hub is authoritative for that information.
|
||||
There is only one instance of the Docker Hub, run and
|
||||
managed by Docker Inc.
|
||||
|
||||
### Registry
|
||||
### Docker Registry 1.0
|
||||
|
||||
The registry has the following characteristics:
|
||||
The 1.0 registry has the following characteristics:
|
||||
|
||||
- It stores the images and the graph for a set of repositories
|
||||
- It does not have user accounts data
|
||||
|
||||
@@ -2,11 +2,11 @@ page_title: Registry API
|
||||
page_description: API Documentation for Docker Registry
|
||||
page_keywords: API, Docker, index, registry, REST, documentation
|
||||
|
||||
# Docker Registry API
|
||||
# Docker Registry API v1
|
||||
|
||||
## Introduction
|
||||
|
||||
- This is the REST API for the Docker Registry
|
||||
- This is the REST API for the Docker Registry 1.0
|
||||
- It stores the images and the graph for a set of repositories
|
||||
- It does not have user accounts data
|
||||
- It has no notion of user accounts or authorization
|
||||
|
||||
@@ -2,7 +2,7 @@ page_title: Registry API Client Libraries
|
||||
page_description: Various client libraries available to use with the Docker registry API
|
||||
page_keywords: API, Docker, index, registry, REST, documentation, clients, C#, Erlang, Go, Groovy, Java, JavaScript, Perl, PHP, Python, Ruby, Rust, Scala
|
||||
|
||||
# Docker Registry API Client Libraries
|
||||
# Docker Registry 1.0 API Client Libraries
|
||||
|
||||
These libraries have not been tested by the Docker maintainers for
|
||||
compatibility. Please file issues with the library owners. If you find
|
||||
|
||||
@@ -146,6 +146,17 @@ The instructions that handle environment variables in the `Dockerfile` are:
|
||||
`ONBUILD` instructions are **NOT** supported for environment replacement, even
|
||||
the instructions above.
|
||||
|
||||
Environment variable subtitution will use the same value for each variable
|
||||
throughout the entire command. In other words, in this example:
|
||||
|
||||
ENV abc=hello
|
||||
ENV abc=bye def=$abc
|
||||
ENV ghi=$abc
|
||||
|
||||
will result in `def` having a value of `hello`, not `bye`. However,
|
||||
`ghi` will have a value of `bye` because it is not part of the same command
|
||||
that set `abc` to `bye`.
|
||||
|
||||
## The `.dockerignore` file
|
||||
|
||||
If a file named `.dockerignore` exists in the source repository, then it
|
||||
@@ -269,8 +280,14 @@ The cache for `RUN` instructions can be invalidated by `ADD` instructions. See
|
||||
|
||||
- [Issue 783](https://github.com/docker/docker/issues/783) is about file
|
||||
permissions problems that can occur when using the AUFS file system. You
|
||||
might notice it during an attempt to `rm` a file, for example. The issue
|
||||
describes a workaround.
|
||||
might notice it during an attempt to `rm` a file, for example.
|
||||
|
||||
For systems that have recent aufs version (i.e., `dirperm1` mount option can
|
||||
be set), docker will attempt to fix the issue automatically by mounting
|
||||
the layers with `dirperm1` option. More details on `dirperm1` option can be
|
||||
found at [`aufs` man page](http://aufs.sourceforge.net/aufs3/man.html)
|
||||
|
||||
If your system doesnt have support for `dirperm1`, the issue describes a workaround.
|
||||
|
||||
## CMD
|
||||
|
||||
|
||||
@@ -657,6 +657,11 @@ this driver.
|
||||
Default logging driver for Docker. Writes JSON messages to file. `docker logs`
|
||||
command is available only for this logging driver
|
||||
|
||||
## Logging driver: syslog
|
||||
|
||||
Syslog logging driver for Docker. Writes log messages to syslog. `docker logs`
|
||||
command is not available for this logging driver
|
||||
|
||||
## Overriding Dockerfile image defaults
|
||||
|
||||
When a developer builds an image from a [*Dockerfile*](/reference/builder)
|
||||
|
||||
@@ -57,7 +57,14 @@ impact on users. This list will be updated as issues are resolved.
|
||||
* **Unexpected File Permissions in Containers**
|
||||
An idiosyncrasy in AUFS prevents permissions from propagating predictably
|
||||
between upper and lower layers. This can cause issues with accessing private
|
||||
keys, database instances, etc. For complete information and workarounds see
|
||||
keys, database instances, etc.
|
||||
|
||||
For systems that have recent aufs version (i.e., `dirperm1` mount option can
|
||||
be set), docker will attempt to fix the issue automatically by mounting
|
||||
the layers with `dirperm1` option. More details on `dirperm1` option can be
|
||||
found at [`aufs` man page](http://aufs.sourceforge.net/aufs3/man.html)
|
||||
|
||||
For complete information and workarounds see
|
||||
[Github Issue 783](https://github.com/docker/docker/issues/783).
|
||||
|
||||
* **Docker Hub incompatible with Safari 8**
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package graph
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@@ -13,6 +15,7 @@ import (
|
||||
"time"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/docker/distribution/digest"
|
||||
"github.com/docker/docker/autogen/dockerversion"
|
||||
"github.com/docker/docker/daemon/graphdriver"
|
||||
"github.com/docker/docker/image"
|
||||
@@ -241,18 +244,27 @@ func (graph *Graph) newTempFile() (*os.File, error) {
|
||||
return ioutil.TempFile(tmp, "")
|
||||
}
|
||||
|
||||
func bufferToFile(f *os.File, src io.Reader) (int64, error) {
|
||||
n, err := io.Copy(f, src)
|
||||
func bufferToFile(f *os.File, src io.Reader) (int64, digest.Digest, error) {
|
||||
var (
|
||||
h = sha256.New()
|
||||
w = gzip.NewWriter(io.MultiWriter(f, h))
|
||||
)
|
||||
_, err := io.Copy(w, src)
|
||||
w.Close()
|
||||
if err != nil {
|
||||
return n, err
|
||||
return 0, "", err
|
||||
}
|
||||
if err = f.Sync(); err != nil {
|
||||
return n, err
|
||||
return 0, "", err
|
||||
}
|
||||
n, err := f.Seek(0, os.SEEK_CUR)
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
if _, err := f.Seek(0, 0); err != nil {
|
||||
return n, err
|
||||
return 0, "", err
|
||||
}
|
||||
return n, nil
|
||||
return n, digest.NewDigest("sha256", h), nil
|
||||
}
|
||||
|
||||
// setupInitLayer populates a directory with mountpoints suitable
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package graph
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -13,7 +12,6 @@ import (
|
||||
"sync"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/docker/distribution/digest"
|
||||
"github.com/docker/docker/engine"
|
||||
"github.com/docker/docker/image"
|
||||
"github.com/docker/docker/pkg/common"
|
||||
@@ -464,12 +462,7 @@ func (s *TagStore) pushV2Image(r *registry.Session, img *image.Image, endpoint *
|
||||
os.Remove(tf.Name())
|
||||
}()
|
||||
|
||||
h := sha256.New()
|
||||
size, err := bufferToFile(tf, io.TeeReader(arch, h))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
dgst := digest.NewDigest("sha256", h)
|
||||
size, dgst, err := bufferToFile(tf, arch)
|
||||
|
||||
// Send the layer
|
||||
log.Debugf("rendered layer for %s of [%d] size", img.ID, size)
|
||||
|
||||
409
hack/install.sh
@@ -20,213 +20,230 @@ command_exists() {
|
||||
command -v "$@" > /dev/null 2>&1
|
||||
}
|
||||
|
||||
case "$(uname -m)" in
|
||||
*64)
|
||||
;;
|
||||
*)
|
||||
echo >&2 'Error: you are not using a 64bit platform.'
|
||||
echo >&2 'Docker currently only supports 64bit platforms.'
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
echo_docker_as_nonroot() {
|
||||
your_user=your-user
|
||||
[ "$user" != 'root' ] && your_user="$user"
|
||||
# intentionally mixed spaces and tabs here -- tabs are stripped by "<<-EOF", spaces are kept in the output
|
||||
cat <<-EOF
|
||||
|
||||
if command_exists docker || command_exists lxc-docker; then
|
||||
echo >&2 'Warning: "docker" or "lxc-docker" command appears to already exist.'
|
||||
echo >&2 'Please ensure that you do not already have docker installed.'
|
||||
echo >&2 'You may press Ctrl+C now to abort this process and rectify this situation.'
|
||||
( set -x; sleep 20 )
|
||||
fi
|
||||
If you would like to use Docker as a non-root user, you should now consider
|
||||
adding your user to the "docker" group with something like:
|
||||
|
||||
user="$(id -un 2>/dev/null || true)"
|
||||
sudo usermod -aG docker $your_user
|
||||
|
||||
sh_c='sh -c'
|
||||
if [ "$user" != 'root' ]; then
|
||||
if command_exists sudo; then
|
||||
sh_c='sudo -E sh -c'
|
||||
elif command_exists su; then
|
||||
sh_c='su -c'
|
||||
else
|
||||
echo >&2 'Error: this installer needs the ability to run commands as root.'
|
||||
echo >&2 'We are unable to find either "sudo" or "su" available to make this happen.'
|
||||
exit 1
|
||||
Remember that you will have to log out and back in for this to take effect!
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
do_install() {
|
||||
case "$(uname -m)" in
|
||||
*64)
|
||||
;;
|
||||
*)
|
||||
cat >&2 <<-'EOF'
|
||||
Error: you are not using a 64bit platform.
|
||||
Docker currently only supports 64bit platforms.
|
||||
EOF
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
if command_exists docker || command_exists lxc-docker; then
|
||||
cat >&2 <<-'EOF'
|
||||
Warning: "docker" or "lxc-docker" command appears to already exist.
|
||||
Please ensure that you do not already have docker installed.
|
||||
You may press Ctrl+C now to abort this process and rectify this situation.
|
||||
EOF
|
||||
( set -x; sleep 20 )
|
||||
fi
|
||||
fi
|
||||
|
||||
curl=''
|
||||
if command_exists curl; then
|
||||
curl='curl -sSL'
|
||||
elif command_exists wget; then
|
||||
curl='wget -qO-'
|
||||
elif command_exists busybox && busybox --list-modules | grep -q wget; then
|
||||
curl='busybox wget -qO-'
|
||||
fi
|
||||
user="$(id -un 2>/dev/null || true)"
|
||||
|
||||
# perform some very rudimentary platform detection
|
||||
lsb_dist=''
|
||||
if command_exists lsb_release; then
|
||||
lsb_dist="$(lsb_release -si)"
|
||||
fi
|
||||
if [ -z "$lsb_dist" ] && [ -r /etc/lsb-release ]; then
|
||||
lsb_dist="$(. /etc/lsb-release && echo "$DISTRIB_ID")"
|
||||
fi
|
||||
if [ -z "$lsb_dist" ] && [ -r /etc/debian_version ]; then
|
||||
lsb_dist='debian'
|
||||
fi
|
||||
if [ -z "$lsb_dist" ] && [ -r /etc/fedora-release ]; then
|
||||
lsb_dist='fedora'
|
||||
fi
|
||||
if [ -z "$lsb_dist" ] && [ -r /etc/os-release ]; then
|
||||
lsb_dist="$(. /etc/os-release && echo "$ID")"
|
||||
fi
|
||||
|
||||
lsb_dist="$(echo "$lsb_dist" | tr '[:upper:]' '[:lower:]')"
|
||||
case "$lsb_dist" in
|
||||
amzn|fedora)
|
||||
if [ "$lsb_dist" = 'amzn' ]; then
|
||||
(
|
||||
set -x
|
||||
$sh_c 'sleep 3; yum -y -q install docker'
|
||||
)
|
||||
sh_c='sh -c'
|
||||
if [ "$user" != 'root' ]; then
|
||||
if command_exists sudo; then
|
||||
sh_c='sudo -E sh -c'
|
||||
elif command_exists su; then
|
||||
sh_c='su -c'
|
||||
else
|
||||
(
|
||||
set -x
|
||||
$sh_c 'sleep 3; yum -y -q install docker-io'
|
||||
)
|
||||
fi
|
||||
if command_exists docker && [ -e /var/run/docker.sock ]; then
|
||||
(
|
||||
set -x
|
||||
$sh_c 'docker version'
|
||||
) || true
|
||||
fi
|
||||
your_user=your-user
|
||||
[ "$user" != 'root' ] && your_user="$user"
|
||||
echo
|
||||
echo 'If you would like to use Docker as a non-root user, you should now consider'
|
||||
echo 'adding your user to the "docker" group with something like:'
|
||||
echo
|
||||
echo ' sudo usermod -aG docker' $your_user
|
||||
echo
|
||||
echo 'Remember that you will have to log out and back in for this to take effect!'
|
||||
echo
|
||||
exit 0
|
||||
;;
|
||||
|
||||
ubuntu|debian|linuxmint)
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
did_apt_get_update=
|
||||
apt_get_update() {
|
||||
if [ -z "$did_apt_get_update" ]; then
|
||||
( set -x; $sh_c 'sleep 3; apt-get update' )
|
||||
did_apt_get_update=1
|
||||
fi
|
||||
}
|
||||
|
||||
# aufs is preferred over devicemapper; try to ensure the driver is available.
|
||||
if ! grep -q aufs /proc/filesystems && ! $sh_c 'modprobe aufs'; then
|
||||
if uname -r | grep -q -- '-generic' && dpkg -l 'linux-image-*-generic' | grep -q '^ii' 2>/dev/null; then
|
||||
kern_extras="linux-image-extra-$(uname -r) linux-image-extra-virtual"
|
||||
|
||||
apt_get_update
|
||||
( set -x; $sh_c 'sleep 3; apt-get install -y -q '"$kern_extras" ) || true
|
||||
|
||||
if ! grep -q aufs /proc/filesystems && ! $sh_c 'modprobe aufs'; then
|
||||
echo >&2 'Warning: tried to install '"$kern_extras"' (for AUFS)'
|
||||
echo >&2 ' but we still have no AUFS. Docker may not work. Proceeding anyways!'
|
||||
( set -x; sleep 10 )
|
||||
fi
|
||||
else
|
||||
echo >&2 'Warning: current kernel is not supported by the linux-image-extra-virtual'
|
||||
echo >&2 ' package. We have no AUFS support. Consider installing the packages'
|
||||
echo >&2 ' linux-image-virtual kernel and linux-image-extra-virtual for AUFS support.'
|
||||
( set -x; sleep 10 )
|
||||
fi
|
||||
fi
|
||||
|
||||
# install apparmor utils if they're missing and apparmor is enabled in the kernel
|
||||
# otherwise Docker will fail to start
|
||||
if [ "$(cat /sys/module/apparmor/parameters/enabled 2>/dev/null)" = 'Y' ]; then
|
||||
if command -v apparmor_parser &> /dev/null; then
|
||||
echo 'apparmor is enabled in the kernel and apparmor utils were already installed'
|
||||
else
|
||||
echo 'apparmor is enabled in the kernel, but apparmor_parser missing'
|
||||
apt_get_update
|
||||
( set -x; $sh_c 'sleep 3; apt-get install -y -q apparmor' )
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ ! -e /usr/lib/apt/methods/https ]; then
|
||||
apt_get_update
|
||||
( set -x; $sh_c 'sleep 3; apt-get install -y -q apt-transport-https ca-certificates' )
|
||||
fi
|
||||
if [ -z "$curl" ]; then
|
||||
apt_get_update
|
||||
( set -x; $sh_c 'sleep 3; apt-get install -y -q curl ca-certificates' )
|
||||
curl='curl -sSL'
|
||||
fi
|
||||
(
|
||||
set -x
|
||||
if [ "https://get.docker.com/" = "$url" ]; then
|
||||
$sh_c "apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 36A1D7869245C8950F966E92D8576A8BA88D21E9"
|
||||
elif [ "https://test.docker.com/" = "$url" ]; then
|
||||
$sh_c "apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 740B314AE3941731B942C66ADF4FD13717AAD7D6"
|
||||
else
|
||||
$sh_c "$curl ${url}gpg | apt-key add -"
|
||||
fi
|
||||
$sh_c "echo deb ${url}ubuntu docker main > /etc/apt/sources.list.d/docker.list"
|
||||
$sh_c 'sleep 3; apt-get update; apt-get install -y -q lxc-docker'
|
||||
)
|
||||
if command_exists docker && [ -e /var/run/docker.sock ]; then
|
||||
(
|
||||
set -x
|
||||
$sh_c 'docker version'
|
||||
) || true
|
||||
fi
|
||||
your_user=your-user
|
||||
[ "$user" != 'root' ] && your_user="$user"
|
||||
echo
|
||||
echo 'If you would like to use Docker as a non-root user, you should now consider'
|
||||
echo 'adding your user to the "docker" group with something like:'
|
||||
echo
|
||||
echo ' sudo usermod -aG docker' $your_user
|
||||
echo
|
||||
echo 'Remember that you will have to log out and back in for this to take effect!'
|
||||
echo
|
||||
exit 0
|
||||
;;
|
||||
|
||||
gentoo)
|
||||
if [ "$url" = "https://test.docker.com/" ]; then
|
||||
echo >&2
|
||||
echo >&2 ' You appear to be trying to install the latest nightly build in Gentoo.'
|
||||
echo >&2 ' The portage tree should contain the latest stable release of Docker, but'
|
||||
echo >&2 ' if you want something more recent, you can always use the live ebuild'
|
||||
echo >&2 ' provided in the "docker" overlay available via layman. For more'
|
||||
echo >&2 ' instructions, please see the following URL:'
|
||||
echo >&2 ' https://github.com/tianon/docker-overlay#using-this-overlay'
|
||||
echo >&2 ' After adding the "docker" overlay, you should be able to:'
|
||||
echo >&2 ' emerge -av =app-emulation/docker-9999'
|
||||
echo >&2
|
||||
cat >&2 <<-'EOF'
|
||||
Error: this installer needs the ability to run commands as root.
|
||||
We are unable to find either "sudo" or "su" available to make this happen.
|
||||
EOF
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
(
|
||||
set -x
|
||||
$sh_c 'sleep 3; emerge app-emulation/docker'
|
||||
)
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
curl=''
|
||||
if command_exists curl; then
|
||||
curl='curl -sSL'
|
||||
elif command_exists wget; then
|
||||
curl='wget -qO-'
|
||||
elif command_exists busybox && busybox --list-modules | grep -q wget; then
|
||||
curl='busybox wget -qO-'
|
||||
fi
|
||||
|
||||
cat >&2 <<'EOF'
|
||||
# perform some very rudimentary platform detection
|
||||
lsb_dist=''
|
||||
if command_exists lsb_release; then
|
||||
lsb_dist="$(lsb_release -si)"
|
||||
fi
|
||||
if [ -z "$lsb_dist" ] && [ -r /etc/lsb-release ]; then
|
||||
lsb_dist="$(. /etc/lsb-release && echo "$DISTRIB_ID")"
|
||||
fi
|
||||
if [ -z "$lsb_dist" ] && [ -r /etc/debian_version ]; then
|
||||
lsb_dist='debian'
|
||||
fi
|
||||
if [ -z "$lsb_dist" ] && [ -r /etc/fedora-release ]; then
|
||||
lsb_dist='fedora'
|
||||
fi
|
||||
if [ -z "$lsb_dist" ] && [ -r /etc/os-release ]; then
|
||||
lsb_dist="$(. /etc/os-release && echo "$ID")"
|
||||
fi
|
||||
|
||||
Either your platform is not easily detectable, is not supported by this
|
||||
installer script (yet - PRs welcome! [hack/install.sh]), or does not yet have
|
||||
a package for Docker. Please visit the following URL for more detailed
|
||||
installation instructions:
|
||||
lsb_dist="$(echo "$lsb_dist" | tr '[:upper:]' '[:lower:]')"
|
||||
case "$lsb_dist" in
|
||||
amzn|fedora|centos)
|
||||
if [ "$lsb_dist" = 'amzn' ]; then
|
||||
(
|
||||
set -x
|
||||
$sh_c 'sleep 3; yum -y -q install docker'
|
||||
)
|
||||
else
|
||||
(
|
||||
set -x
|
||||
$sh_c 'sleep 3; yum -y -q install docker-io'
|
||||
)
|
||||
fi
|
||||
if command_exists docker && [ -e /var/run/docker.sock ]; then
|
||||
(
|
||||
set -x
|
||||
$sh_c 'docker version'
|
||||
) || true
|
||||
fi
|
||||
echo_docker_as_nonroot
|
||||
exit 0
|
||||
;;
|
||||
|
||||
https://docs.docker.com/en/latest/installation/
|
||||
ubuntu|debian|linuxmint)
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
EOF
|
||||
exit 1
|
||||
did_apt_get_update=
|
||||
apt_get_update() {
|
||||
if [ -z "$did_apt_get_update" ]; then
|
||||
( set -x; $sh_c 'sleep 3; apt-get update' )
|
||||
did_apt_get_update=1
|
||||
fi
|
||||
}
|
||||
|
||||
# aufs is preferred over devicemapper; try to ensure the driver is available.
|
||||
if ! grep -q aufs /proc/filesystems && ! $sh_c 'modprobe aufs'; then
|
||||
if uname -r | grep -q -- '-generic' && dpkg -l 'linux-image-*-generic' | grep -q '^ii' 2>/dev/null; then
|
||||
kern_extras="linux-image-extra-$(uname -r) linux-image-extra-virtual"
|
||||
|
||||
apt_get_update
|
||||
( set -x; $sh_c 'sleep 3; apt-get install -y -q '"$kern_extras" ) || true
|
||||
|
||||
if ! grep -q aufs /proc/filesystems && ! $sh_c 'modprobe aufs'; then
|
||||
echo >&2 'Warning: tried to install '"$kern_extras"' (for AUFS)'
|
||||
echo >&2 ' but we still have no AUFS. Docker may not work. Proceeding anyways!'
|
||||
( set -x; sleep 10 )
|
||||
fi
|
||||
else
|
||||
echo >&2 'Warning: current kernel is not supported by the linux-image-extra-virtual'
|
||||
echo >&2 ' package. We have no AUFS support. Consider installing the packages'
|
||||
echo >&2 ' linux-image-virtual kernel and linux-image-extra-virtual for AUFS support.'
|
||||
( set -x; sleep 10 )
|
||||
fi
|
||||
fi
|
||||
|
||||
# install apparmor utils if they're missing and apparmor is enabled in the kernel
|
||||
# otherwise Docker will fail to start
|
||||
if [ "$(cat /sys/module/apparmor/parameters/enabled 2>/dev/null)" = 'Y' ]; then
|
||||
if command -v apparmor_parser &> /dev/null; then
|
||||
echo 'apparmor is enabled in the kernel and apparmor utils were already installed'
|
||||
else
|
||||
echo 'apparmor is enabled in the kernel, but apparmor_parser missing'
|
||||
apt_get_update
|
||||
( set -x; $sh_c 'sleep 3; apt-get install -y -q apparmor' )
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ ! -e /usr/lib/apt/methods/https ]; then
|
||||
apt_get_update
|
||||
( set -x; $sh_c 'sleep 3; apt-get install -y -q apt-transport-https ca-certificates' )
|
||||
fi
|
||||
if [ -z "$curl" ]; then
|
||||
apt_get_update
|
||||
( set -x; $sh_c 'sleep 3; apt-get install -y -q curl ca-certificates' )
|
||||
curl='curl -sSL'
|
||||
fi
|
||||
(
|
||||
set -x
|
||||
if [ "https://get.docker.com/" = "$url" ]; then
|
||||
$sh_c "apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 36A1D7869245C8950F966E92D8576A8BA88D21E9"
|
||||
elif [ "https://test.docker.com/" = "$url" ]; then
|
||||
$sh_c "apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 740B314AE3941731B942C66ADF4FD13717AAD7D6"
|
||||
else
|
||||
$sh_c "$curl ${url}gpg | apt-key add -"
|
||||
fi
|
||||
$sh_c "echo deb ${url}ubuntu docker main > /etc/apt/sources.list.d/docker.list"
|
||||
$sh_c 'sleep 3; apt-get update; apt-get install -y -q lxc-docker'
|
||||
)
|
||||
if command_exists docker && [ -e /var/run/docker.sock ]; then
|
||||
(
|
||||
set -x
|
||||
$sh_c 'docker version'
|
||||
) || true
|
||||
fi
|
||||
echo_docker_as_nonroot
|
||||
exit 0
|
||||
;;
|
||||
|
||||
gentoo)
|
||||
if [ "$url" = "https://test.docker.com/" ]; then
|
||||
# intentionally mixed spaces and tabs here -- tabs are stripped by "<<-'EOF'", spaces are kept in the output
|
||||
cat >&2 <<-'EOF'
|
||||
|
||||
You appear to be trying to install the latest nightly build in Gentoo.'
|
||||
The portage tree should contain the latest stable release of Docker, but'
|
||||
if you want something more recent, you can always use the live ebuild'
|
||||
provided in the "docker" overlay available via layman. For more'
|
||||
instructions, please see the following URL:'
|
||||
|
||||
https://github.com/tianon/docker-overlay#using-this-overlay'
|
||||
|
||||
After adding the "docker" overlay, you should be able to:'
|
||||
|
||||
emerge -av =app-emulation/docker-9999'
|
||||
|
||||
EOF
|
||||
exit 1
|
||||
fi
|
||||
|
||||
(
|
||||
set -x
|
||||
$sh_c 'sleep 3; emerge app-emulation/docker'
|
||||
)
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
|
||||
# intentionally mixed spaces and tabs here -- tabs are stripped by "<<-'EOF'", spaces are kept in the output
|
||||
cat >&2 <<-'EOF'
|
||||
|
||||
Either your platform is not easily detectable, is not supported by this
|
||||
installer script (yet - PRs welcome! [hack/install.sh]), or does not yet have
|
||||
a package for Docker. Please visit the following URL for more detailed
|
||||
installation instructions:
|
||||
|
||||
https://docs.docker.com/en/latest/installation/
|
||||
|
||||
EOF
|
||||
exit 1
|
||||
}
|
||||
|
||||
# wrapped up in a function so that we have some protection against only getting
|
||||
# half the file during "curl | sh"
|
||||
do_install
|
||||
|
||||
@@ -265,9 +265,6 @@ main() {
|
||||
bundle $SCRIPTDIR/make/$bundle
|
||||
echo
|
||||
done
|
||||
|
||||
# if we get all the way through successfully, let's delete our autogenerated code!
|
||||
rm -r autogen
|
||||
}
|
||||
|
||||
main "$@"
|
||||
|
||||
@@ -75,7 +75,7 @@ rm -rf src/github.com/docker/distribution
|
||||
mkdir -p src/github.com/docker/distribution
|
||||
mv tmp-digest src/github.com/docker/distribution/digest
|
||||
|
||||
clone git github.com/docker/libcontainer 4a72e540feb67091156b907c4700e580a99f5a9d
|
||||
clone git github.com/docker/libcontainer 227771c8f611f03639f0eeb169428761d9504ab5
|
||||
# see src/github.com/docker/libcontainer/update-vendor.sh which is the "source of truth" for libcontainer deps (just like this file)
|
||||
rm -rf src/github.com/docker/libcontainer/vendor
|
||||
eval "$(grep '^clone ' src/github.com/docker/libcontainer/update-vendor.sh | grep -v 'github.com/codegangsta/cli' | grep -v 'github.com/Sirupsen/logrus')"
|
||||
|
||||
@@ -516,3 +516,40 @@ func TestBuildApiDockerfileSymlink(t *testing.T) {
|
||||
|
||||
logDone("container REST API - check build w/bad Dockerfile symlink path")
|
||||
}
|
||||
|
||||
// #9981 - Allow a docker created volume (ie, one in /var/lib/docker/volumes) to be used to overwrite (via passing in Binds on api start) an existing volume
|
||||
func TestPostContainerBindNormalVolume(t *testing.T) {
|
||||
defer deleteAllContainers()
|
||||
|
||||
out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "create", "-v", "/foo", "--name=one", "busybox"))
|
||||
if err != nil {
|
||||
t.Fatal(err, out)
|
||||
}
|
||||
|
||||
fooDir, err := inspectFieldMap("one", "Volumes", "/foo")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "create", "-v", "/foo", "--name=two", "busybox"))
|
||||
if err != nil {
|
||||
t.Fatal(err, out)
|
||||
}
|
||||
|
||||
bindSpec := map[string][]string{"Binds": {fooDir + ":/foo"}}
|
||||
_, err = sockRequest("POST", "/containers/two/start", bindSpec)
|
||||
if err != nil && !strings.Contains(err.Error(), "204 No Content") {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
fooDir2, err := inspectFieldMap("two", "Volumes", "/foo")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if fooDir2 != fooDir {
|
||||
t.Fatal("expected volume path to be %s, got: %s", fooDir, fooDir2)
|
||||
}
|
||||
|
||||
logDone("container REST API - can use path from normal volume as bind-mount to overwrite another volume")
|
||||
}
|
||||
|
||||
@@ -241,9 +241,18 @@ func TestBuildEnvironmentReplacementEnv(t *testing.T) {
|
||||
|
||||
_, err := buildImage(name,
|
||||
`
|
||||
FROM scratch
|
||||
ENV foo foo
|
||||
FROM busybox
|
||||
ENV foo zzz
|
||||
ENV bar ${foo}
|
||||
ENV abc1='$foo'
|
||||
ENV env1=$foo env2=${foo} env3="$foo" env4="${foo}"
|
||||
RUN [ "$abc1" = '$foo' ] && (echo "$abc1" | grep -q foo)
|
||||
ENV abc2="\$foo"
|
||||
RUN [ "$abc2" = '$foo' ] && (echo "$abc2" | grep -q foo)
|
||||
ENV abc3 '$foo'
|
||||
RUN [ "$abc3" = '$foo' ] && (echo "$abc3" | grep -q foo)
|
||||
ENV abc4 "\$foo"
|
||||
RUN [ "$abc4" = '$foo' ] && (echo "$abc4" | grep -q foo)
|
||||
`, true)
|
||||
|
||||
if err != nil {
|
||||
@@ -262,13 +271,19 @@ func TestBuildEnvironmentReplacementEnv(t *testing.T) {
|
||||
}
|
||||
|
||||
found := false
|
||||
envCount := 0
|
||||
|
||||
for _, env := range envResult {
|
||||
parts := strings.SplitN(env, "=", 2)
|
||||
if parts[0] == "bar" {
|
||||
found = true
|
||||
if parts[1] != "foo" {
|
||||
t.Fatalf("Could not find replaced var for env `bar`: got %q instead of `foo`", parts[1])
|
||||
if parts[1] != "zzz" {
|
||||
t.Fatalf("Could not find replaced var for env `bar`: got %q instead of `zzz`", parts[1])
|
||||
}
|
||||
} else if strings.HasPrefix(parts[0], "env") {
|
||||
envCount++
|
||||
if parts[1] != "zzz" {
|
||||
t.Fatalf("%s should be 'foo' but instead its %q", parts[0], parts[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -277,6 +292,10 @@ func TestBuildEnvironmentReplacementEnv(t *testing.T) {
|
||||
t.Fatal("Never found the `bar` env variable")
|
||||
}
|
||||
|
||||
if envCount != 4 {
|
||||
t.Fatalf("Didn't find all env vars - only saw %d\n%s", envCount, envResult)
|
||||
}
|
||||
|
||||
logDone("build - env environment replacement")
|
||||
}
|
||||
|
||||
@@ -363,8 +382,8 @@ func TestBuildHandleEscapes(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, ok := result[`\\\\\\${FOO}`]; !ok {
|
||||
t.Fatal(`Could not find volume \\\\\\${FOO} set from env foo in volumes table`)
|
||||
if _, ok := result[`\\\${FOO}`]; !ok {
|
||||
t.Fatal(`Could not find volume \\\${FOO} set from env foo in volumes table`, result)
|
||||
}
|
||||
|
||||
logDone("build - handle escapes")
|
||||
@@ -1939,6 +1958,7 @@ func TestBuildCancelationKillsSleep(t *testing.T) {
|
||||
|
||||
name := "testbuildcancelation"
|
||||
defer deleteImages(name)
|
||||
defer deleteAllContainers()
|
||||
|
||||
// (Note: one year, will never finish)
|
||||
ctx, err := fakeContext("FROM busybox\nRUN sleep 31536000", nil)
|
||||
@@ -2256,7 +2276,7 @@ func TestBuildRelativeWorkdir(t *testing.T) {
|
||||
|
||||
func TestBuildWorkdirWithEnvVariables(t *testing.T) {
|
||||
name := "testbuildworkdirwithenvvariables"
|
||||
expected := "/test1/test2/$MISSING_VAR"
|
||||
expected := "/test1/test2"
|
||||
defer deleteImages(name)
|
||||
_, err := buildImage(name,
|
||||
`FROM busybox
|
||||
@@ -4025,9 +4045,9 @@ ENV abc=zzz TO=/docker/world/hello
|
||||
ADD $FROM $TO
|
||||
RUN [ "$(cat $TO)" = "hello" ]
|
||||
ENV abc "zzz"
|
||||
RUN [ $abc = \"zzz\" ]
|
||||
RUN [ $abc = "zzz" ]
|
||||
ENV abc 'yyy'
|
||||
RUN [ $abc = \'yyy\' ]
|
||||
RUN [ $abc = 'yyy' ]
|
||||
ENV abc=
|
||||
RUN [ "$abc" = "" ]
|
||||
|
||||
@@ -4043,13 +4063,34 @@ RUN [ "$abc" = "'foo'" ]
|
||||
ENV abc=\"foo\"
|
||||
RUN [ "$abc" = "\"foo\"" ]
|
||||
ENV abc "foo"
|
||||
RUN [ "$abc" = "\"foo\"" ]
|
||||
RUN [ "$abc" = "foo" ]
|
||||
ENV abc 'foo'
|
||||
RUN [ "$abc" = "'foo'" ]
|
||||
RUN [ "$abc" = 'foo' ]
|
||||
ENV abc \'foo\'
|
||||
RUN [ "$abc" = "\\'foo\\'" ]
|
||||
RUN [ "$abc" = "'foo'" ]
|
||||
ENV abc \"foo\"
|
||||
RUN [ "$abc" = "\\\"foo\\\"" ]
|
||||
RUN [ "$abc" = '"foo"' ]
|
||||
|
||||
ENV e1=bar
|
||||
ENV e2=$e1
|
||||
ENV e3=$e11
|
||||
ENV e4=\$e1
|
||||
ENV e5=\$e11
|
||||
RUN [ "$e0,$e1,$e2,$e3,$e4,$e5" = ',bar,bar,,$e1,$e11' ]
|
||||
|
||||
ENV ee1 bar
|
||||
ENV ee2 $ee1
|
||||
ENV ee3 $ee11
|
||||
ENV ee4 \$ee1
|
||||
ENV ee5 \$ee11
|
||||
RUN [ "$ee1,$ee2,$ee3,$ee4,$ee5" = 'bar,bar,,$ee1,$ee11' ]
|
||||
|
||||
ENV eee1="foo"
|
||||
ENV eee2='foo'
|
||||
ENV eee3 "foo"
|
||||
ENV eee4 'foo'
|
||||
RUN [ "$eee1,$eee2,$eee3,$eee4" = 'foo,foo,foo,foo' ]
|
||||
|
||||
`
|
||||
ctx, err := fakeContext(dockerfile, map[string]string{
|
||||
"hello/docker/world": "hello",
|
||||
|
||||
@@ -55,8 +55,6 @@ func TestPullImageWithAliases(t *testing.T) {
|
||||
|
||||
// pulling library/hello-world should show verified message
|
||||
func TestPullVerified(t *testing.T) {
|
||||
t.Skip("problems verifying library/hello-world (to be fixed)")
|
||||
|
||||
// Image must be pulled from central repository to get verified message
|
||||
// unless keychain is manually updated to contain the daemon's sign key.
|
||||
|
||||
|
||||
@@ -413,6 +413,7 @@ func TestRunLinkToContainerNetMode(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRunModeNetContainerHostname(t *testing.T) {
|
||||
testRequires(t, ExecSupport)
|
||||
defer deleteAllContainers()
|
||||
cmd := exec.Command(dockerBinary, "run", "-i", "-d", "--name", "parent", "busybox", "top")
|
||||
out, _, err := runCommandWithOutput(cmd)
|
||||
@@ -475,6 +476,39 @@ func TestRunWithVolumesFromExited(t *testing.T) {
|
||||
logDone("run - regression test for #4979 - volumes-from on exited container")
|
||||
}
|
||||
|
||||
// Volume path is a symlink which also exists on the host, and the host side is a file not a dir
|
||||
// But the volume call is just a normal volume, not a bind mount
|
||||
func TestRunCreateVolumesInSymlinkDir(t *testing.T) {
|
||||
testRequires(t, SameHostDaemon)
|
||||
testRequires(t, NativeExecDriver)
|
||||
defer deleteAllContainers()
|
||||
name := "test-volume-symlink"
|
||||
|
||||
dir, err := ioutil.TempDir("", name)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
f, err := os.OpenFile(filepath.Join(dir, "test"), os.O_CREATE, 0700)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f.Close()
|
||||
|
||||
dockerFile := fmt.Sprintf("FROM busybox\nRUN mkdir -p %s\nRUN ln -s %s /test", dir, dir)
|
||||
if _, err := buildImage(name, dockerFile, false); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer deleteImages(name)
|
||||
|
||||
if out, _, err := dockerCmd(t, "run", "-v", "/test/test", name); err != nil {
|
||||
t.Fatal(err, out)
|
||||
}
|
||||
|
||||
logDone("run - create volume in symlink directory")
|
||||
}
|
||||
|
||||
// Regression test for #4830
|
||||
func TestRunWithRelativePath(t *testing.T) {
|
||||
defer deleteAllContainers()
|
||||
@@ -3203,6 +3237,35 @@ func TestRunNetHost(t *testing.T) {
|
||||
logDone("run - net host mode")
|
||||
}
|
||||
|
||||
func TestRunNetContainerWhichHost(t *testing.T) {
|
||||
testRequires(t, SameHostDaemon)
|
||||
defer deleteAllContainers()
|
||||
|
||||
hostNet, err := os.Readlink("/proc/1/ns/net")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cmd := exec.Command(dockerBinary, "run", "-d", "--net=host", "--name=test", "busybox", "top")
|
||||
out, _, err := runCommandWithOutput(cmd)
|
||||
if err != nil {
|
||||
t.Fatal(err, out)
|
||||
}
|
||||
|
||||
cmd = exec.Command(dockerBinary, "run", "--net=container:test", "busybox", "readlink", "/proc/self/ns/net")
|
||||
out, _, err = runCommandWithOutput(cmd)
|
||||
if err != nil {
|
||||
t.Fatal(err, out)
|
||||
}
|
||||
|
||||
out = strings.Trim(out, "\n")
|
||||
if hostNet != out {
|
||||
t.Fatalf("Container should have host network namespace")
|
||||
}
|
||||
|
||||
logDone("run - net container mode, where container in host mode")
|
||||
}
|
||||
|
||||
func TestRunAllowPortRangeThroughPublish(t *testing.T) {
|
||||
defer deleteAllContainers()
|
||||
|
||||
@@ -3348,3 +3411,97 @@ func TestRunVolumesFromRestartAfterRemoved(t *testing.T) {
|
||||
|
||||
logDone("run - can restart a volumes-from container after producer is removed")
|
||||
}
|
||||
|
||||
func TestRunPidHostWithChildIsKillable(t *testing.T) {
|
||||
defer deleteAllContainers()
|
||||
name := "ibuildthecloud"
|
||||
if out, err := exec.Command(dockerBinary, "run", "-d", "--pid=host", "--name", name, "busybox", "sh", "-c", "sleep 30; echo hi").CombinedOutput(); err != nil {
|
||||
t.Fatal(err, out)
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
errchan := make(chan error)
|
||||
go func() {
|
||||
if out, err := exec.Command(dockerBinary, "kill", name).CombinedOutput(); err != nil {
|
||||
errchan <- fmt.Errorf("%v:\n%s", err, out)
|
||||
}
|
||||
close(errchan)
|
||||
}()
|
||||
select {
|
||||
case err := <-errchan:
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatal("Kill container timed out")
|
||||
}
|
||||
logDone("run - can kill container with pid-host and some childs of pid 1")
|
||||
}
|
||||
|
||||
func TestRunWithTooSmallMemoryLimit(t *testing.T) {
|
||||
defer deleteAllContainers()
|
||||
// this memory limit is 1 byte less than the min, which is 4MB
|
||||
// https://github.com/docker/docker/blob/v1.5.0/daemon/create.go#L22
|
||||
out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-m", "4194303", "busybox"))
|
||||
if err == nil || !strings.Contains(out, "Minimum memory limit allowed is 4MB") {
|
||||
t.Fatalf("expected run to fail when using too low a memory limit: %q", out)
|
||||
}
|
||||
|
||||
logDone("run - can't set too low memory limit")
|
||||
}
|
||||
|
||||
func TestRunWriteToProcAsound(t *testing.T) {
|
||||
defer deleteAllContainers()
|
||||
code, err := runCommand(exec.Command(dockerBinary, "run", "busybox", "sh", "-c", "echo 111 >> /proc/asound/version"))
|
||||
if err == nil || code == 0 {
|
||||
t.Fatal("standard container should not be able to write to /proc/asound")
|
||||
}
|
||||
logDone("run - ro write to /proc/asound")
|
||||
}
|
||||
|
||||
func TestRunReadProcTimer(t *testing.T) {
|
||||
defer deleteAllContainers()
|
||||
out, code, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "busybox", "cat", "/proc/timer_stats"))
|
||||
if err != nil || code != 0 {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if strings.Trim(out, "\n ") != "" {
|
||||
t.Fatalf("expected to receive no output from /proc/timer_stats but received %q", out)
|
||||
}
|
||||
logDone("run - read /proc/timer_stats")
|
||||
}
|
||||
|
||||
func TestRunReadProcLatency(t *testing.T) {
|
||||
// some kernels don't have this configured so skip the test if this file is not found
|
||||
// on the host running the tests.
|
||||
if _, err := os.Stat("/proc/latency_stats"); err != nil {
|
||||
t.Skip()
|
||||
return
|
||||
}
|
||||
defer deleteAllContainers()
|
||||
out, code, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "busybox", "cat", "/proc/latency_stats"))
|
||||
if err != nil || code != 0 {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if strings.Trim(out, "\n ") != "" {
|
||||
t.Fatalf("expected to receive no output from /proc/latency_stats but received %q", out)
|
||||
}
|
||||
logDone("run - read /proc/latency_stats")
|
||||
}
|
||||
|
||||
func TestMountIntoProc(t *testing.T) {
|
||||
defer deleteAllContainers()
|
||||
code, err := runCommand(exec.Command(dockerBinary, "run", "-v", "/proc//sys", "busybox", "true"))
|
||||
if err == nil || code == 0 {
|
||||
t.Fatal("container should not be able to mount into /proc")
|
||||
}
|
||||
logDone("run - mount into proc")
|
||||
}
|
||||
|
||||
func TestMountIntoSys(t *testing.T) {
|
||||
defer deleteAllContainers()
|
||||
_, err := runCommand(exec.Command(dockerBinary, "run", "-v", "/sys/fs/cgroup", "busybox", "true"))
|
||||
if err != nil {
|
||||
t.Fatal("container should be able to mount into /sys")
|
||||
}
|
||||
logDone("run - mount into sys")
|
||||
}
|
||||
|
||||
@@ -240,3 +240,49 @@ func TestStartMultipleContainers(t *testing.T) {
|
||||
|
||||
logDone("start - start multiple containers continue on one failed")
|
||||
}
|
||||
|
||||
func TestStartAttachMultipleContainers(t *testing.T) {
|
||||
|
||||
var cmd *exec.Cmd
|
||||
|
||||
defer deleteAllContainers()
|
||||
// run multiple containers to test
|
||||
for _, container := range []string{"test1", "test2", "test3"} {
|
||||
cmd = exec.Command(dockerBinary, "run", "-d", "--name", container, "busybox", "top")
|
||||
if out, _, err := runCommandWithOutput(cmd); err != nil {
|
||||
t.Fatal(out, err)
|
||||
}
|
||||
}
|
||||
|
||||
// stop all the containers
|
||||
for _, container := range []string{"test1", "test2", "test3"} {
|
||||
cmd = exec.Command(dockerBinary, "stop", container)
|
||||
if out, _, err := runCommandWithOutput(cmd); err != nil {
|
||||
t.Fatal(out, err)
|
||||
}
|
||||
}
|
||||
|
||||
// test start and attach multiple containers at once, expected error
|
||||
for _, option := range []string{"-a", "-i", "-ai"} {
|
||||
cmd = exec.Command(dockerBinary, "start", option, "test1", "test2", "test3")
|
||||
out, _, err := runCommandWithOutput(cmd)
|
||||
if !strings.Contains(out, "You cannot start and attach multiple containers at once.") || err == nil {
|
||||
t.Fatal("Expected error but got none")
|
||||
}
|
||||
}
|
||||
|
||||
// confirm the state of all the containers be stopped
|
||||
for container, expected := range map[string]string{"test1": "false", "test2": "false", "test3": "false"} {
|
||||
cmd = exec.Command(dockerBinary, "inspect", "-f", "{{.State.Running}}", container)
|
||||
out, _, err := runCommandWithOutput(cmd)
|
||||
if err != nil {
|
||||
t.Fatal(out, err)
|
||||
}
|
||||
out = strings.Trim(out, "\r\n")
|
||||
if out != expected {
|
||||
t.Fatal("Container running state wrong")
|
||||
}
|
||||
}
|
||||
|
||||
logDone("start - error on start and attach multiple containers at once")
|
||||
}
|
||||
|
||||
@@ -17,11 +17,13 @@ var (
|
||||
privateRegistryURL = "127.0.0.1:5000"
|
||||
|
||||
dockerBasePath = "/var/lib/docker"
|
||||
execDriverPath = dockerBasePath + "/execdriver/native"
|
||||
volumesConfigPath = dockerBasePath + "/volumes"
|
||||
volumesStoragePath = dockerBasePath + "/vfs/dir"
|
||||
containerStoragePath = dockerBasePath + "/containers"
|
||||
|
||||
runtimePath = "/var/run/docker"
|
||||
execDriverPath = runtimePath + "/execdriver/native"
|
||||
|
||||
workingDirectory string
|
||||
)
|
||||
|
||||
|
||||
@@ -6,6 +6,6 @@ const (
|
||||
// identifies if test suite is running on a unix platform
|
||||
isUnixCli = false
|
||||
|
||||
// this is the expected file permission set on windows: gh#11047
|
||||
expectedFileChmod = "-rwx------"
|
||||
// this is the expected file permission set on windows: gh#11395
|
||||
expectedFileChmod = "-rwxr-xr-x"
|
||||
)
|
||||
|
||||
@@ -28,10 +28,9 @@ func CanonicalTarNameForPath(p string) (string, error) {
|
||||
// chmodTarEntry is used to adjust the file permissions used in tar header based
|
||||
// on the platform the archival is done.
|
||||
func chmodTarEntry(perm os.FileMode) os.FileMode {
|
||||
// Clear r/w on grp/others: no precise equivalen of group/others on NTFS.
|
||||
perm &= 0711
|
||||
perm &= 0755
|
||||
// Add the x bit: make everything +x from windows
|
||||
perm |= 0100
|
||||
perm |= 0111
|
||||
|
||||
return perm
|
||||
}
|
||||
|
||||
@@ -51,11 +51,11 @@ func TestChmodTarEntry(t *testing.T) {
|
||||
cases := []struct {
|
||||
in, expected os.FileMode
|
||||
}{
|
||||
{0000, 0100},
|
||||
{0777, 0711},
|
||||
{0644, 0700},
|
||||
{0755, 0711},
|
||||
{0444, 0500},
|
||||
{0000, 0111},
|
||||
{0777, 0755},
|
||||
{0644, 0755},
|
||||
{0755, 0755},
|
||||
{0444, 0555},
|
||||
}
|
||||
for _, v := range cases {
|
||||
if out := chmodTarEntry(v.in); out != v.expected {
|
||||
|
||||
@@ -220,8 +220,8 @@ func (info *FileInfo) addChanges(oldInfo *FileInfo, changes *[]Change) {
|
||||
oldStat.Gid() != newStat.Gid() ||
|
||||
oldStat.Rdev() != newStat.Rdev() ||
|
||||
// Don't look at size for dirs, its not a good measure of change
|
||||
(oldStat.Size() != newStat.Size() && oldStat.Mode()&syscall.S_IFDIR != syscall.S_IFDIR) ||
|
||||
!sameFsTimeSpec(oldStat.Mtim(), newStat.Mtim()) ||
|
||||
(oldStat.Mode()&syscall.S_IFDIR != syscall.S_IFDIR &&
|
||||
(!sameFsTimeSpec(oldStat.Mtim(), newStat.Mtim()) || (oldStat.Size() != newStat.Size()))) ||
|
||||
bytes.Compare(oldChild.capability, newChild.capability) != 0 {
|
||||
change := Change{
|
||||
Path: newChild.path(),
|
||||
|
||||
@@ -218,7 +218,6 @@ func TestChangesDirsMutated(t *testing.T) {
|
||||
expectedChanges := []Change{
|
||||
{"/dir1", ChangeDelete},
|
||||
{"/dir2", ChangeModify},
|
||||
{"/dir3", ChangeModify},
|
||||
{"/dirnew", ChangeAdd},
|
||||
{"/file1", ChangeDelete},
|
||||
{"/file2", ChangeModify},
|
||||
|
||||
@@ -51,7 +51,7 @@ func (w *BroadcastWriter) Write(p []byte) (n int, err error) {
|
||||
for {
|
||||
line, err := w.buf.ReadString('\n')
|
||||
if err != nil {
|
||||
w.buf.Write([]byte(line))
|
||||
w.buf.WriteString(line)
|
||||
break
|
||||
}
|
||||
for stream, writers := range w.streams {
|
||||
|
||||
@@ -2,8 +2,11 @@ package ioutils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"io"
|
||||
"math/big"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type readCloserWrapper struct {
|
||||
@@ -42,20 +45,40 @@ func NewReaderErrWrapper(r io.Reader, closer func()) io.Reader {
|
||||
}
|
||||
}
|
||||
|
||||
// bufReader allows the underlying reader to continue to produce
|
||||
// output by pre-emptively reading from the wrapped reader.
|
||||
// This is achieved by buffering this data in bufReader's
|
||||
// expanding buffer.
|
||||
type bufReader struct {
|
||||
sync.Mutex
|
||||
buf *bytes.Buffer
|
||||
reader io.Reader
|
||||
err error
|
||||
wait sync.Cond
|
||||
drainBuf []byte
|
||||
buf *bytes.Buffer
|
||||
reader io.Reader
|
||||
err error
|
||||
wait sync.Cond
|
||||
drainBuf []byte
|
||||
reuseBuf []byte
|
||||
maxReuse int64
|
||||
resetTimeout time.Duration
|
||||
bufLenResetThreshold int64
|
||||
maxReadDataReset int64
|
||||
}
|
||||
|
||||
func NewBufReader(r io.Reader) *bufReader {
|
||||
var timeout int
|
||||
if randVal, err := rand.Int(rand.Reader, big.NewInt(120)); err == nil {
|
||||
timeout = int(randVal.Int64()) + 180
|
||||
} else {
|
||||
timeout = 300
|
||||
}
|
||||
reader := &bufReader{
|
||||
buf: &bytes.Buffer{},
|
||||
drainBuf: make([]byte, 1024),
|
||||
reader: r,
|
||||
buf: &bytes.Buffer{},
|
||||
drainBuf: make([]byte, 1024),
|
||||
reuseBuf: make([]byte, 4096),
|
||||
maxReuse: 1000,
|
||||
resetTimeout: time.Second * time.Duration(timeout),
|
||||
bufLenResetThreshold: 100 * 1024,
|
||||
maxReadDataReset: 10 * 1024 * 1024,
|
||||
reader: r,
|
||||
}
|
||||
reader.wait.L = &reader.Mutex
|
||||
go reader.drain()
|
||||
@@ -74,14 +97,94 @@ func NewBufReaderWithDrainbufAndBuffer(r io.Reader, drainBuffer []byte, buffer *
|
||||
}
|
||||
|
||||
func (r *bufReader) drain() {
|
||||
var (
|
||||
duration time.Duration
|
||||
lastReset time.Time
|
||||
now time.Time
|
||||
reset bool
|
||||
bufLen int64
|
||||
dataSinceReset int64
|
||||
maxBufLen int64
|
||||
reuseBufLen int64
|
||||
reuseCount int64
|
||||
)
|
||||
reuseBufLen = int64(len(r.reuseBuf))
|
||||
lastReset = time.Now()
|
||||
for {
|
||||
n, err := r.reader.Read(r.drainBuf)
|
||||
dataSinceReset += int64(n)
|
||||
r.Lock()
|
||||
bufLen = int64(r.buf.Len())
|
||||
if bufLen > maxBufLen {
|
||||
maxBufLen = bufLen
|
||||
}
|
||||
|
||||
// Avoid unbounded growth of the buffer over time.
|
||||
// This has been discovered to be the only non-intrusive
|
||||
// solution to the unbounded growth of the buffer.
|
||||
// Alternative solutions such as compression, multiple
|
||||
// buffers, channels and other similar pieces of code
|
||||
// were reducing throughput, overall Docker performance
|
||||
// or simply crashed Docker.
|
||||
// This solution releases the buffer when specific
|
||||
// conditions are met to avoid the continuous resizing
|
||||
// of the buffer for long lived containers.
|
||||
//
|
||||
// Move data to the front of the buffer if it's
|
||||
// smaller than what reuseBuf can store
|
||||
if bufLen > 0 && reuseBufLen >= bufLen {
|
||||
n, _ := r.buf.Read(r.reuseBuf)
|
||||
r.buf.Write(r.reuseBuf[0:n])
|
||||
// Take action if the buffer has been reused too many
|
||||
// times and if there's data in the buffer.
|
||||
// The timeout is also used as means to avoid doing
|
||||
// these operations more often or less often than
|
||||
// required.
|
||||
// The various conditions try to detect heavy activity
|
||||
// in the buffer which might be indicators of heavy
|
||||
// growth of the buffer.
|
||||
} else if reuseCount >= r.maxReuse && bufLen > 0 {
|
||||
now = time.Now()
|
||||
duration = now.Sub(lastReset)
|
||||
timeoutReached := duration >= r.resetTimeout
|
||||
|
||||
// The timeout has been reached and the
|
||||
// buffered data couldn't be moved to the front
|
||||
// of the buffer, so the buffer gets reset.
|
||||
if timeoutReached && bufLen > reuseBufLen {
|
||||
reset = true
|
||||
}
|
||||
// The amount of buffered data is too high now,
|
||||
// reset the buffer.
|
||||
if timeoutReached && maxBufLen >= r.bufLenResetThreshold {
|
||||
reset = true
|
||||
}
|
||||
// Reset the buffer if a certain amount of
|
||||
// data has gone through the buffer since the
|
||||
// last reset.
|
||||
if timeoutReached && dataSinceReset >= r.maxReadDataReset {
|
||||
reset = true
|
||||
}
|
||||
// The buffered data is moved to a fresh buffer,
|
||||
// swap the old buffer with the new one and
|
||||
// reset all counters.
|
||||
if reset {
|
||||
newbuf := &bytes.Buffer{}
|
||||
newbuf.ReadFrom(r.buf)
|
||||
r.buf = newbuf
|
||||
lastReset = now
|
||||
reset = false
|
||||
dataSinceReset = 0
|
||||
maxBufLen = 0
|
||||
reuseCount = 0
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
r.err = err
|
||||
} else {
|
||||
r.buf.Write(r.drainBuf[0:n])
|
||||
}
|
||||
reuseCount++
|
||||
r.wait.Signal()
|
||||
r.Unlock()
|
||||
if err != nil {
|
||||
|
||||
@@ -32,3 +32,61 @@ func TestBufReader(t *testing.T) {
|
||||
t.Error(string(output))
|
||||
}
|
||||
}
|
||||
|
||||
type repeatedReader struct {
|
||||
readCount int
|
||||
maxReads int
|
||||
data []byte
|
||||
}
|
||||
|
||||
func newRepeatedReader(max int, data []byte) *repeatedReader {
|
||||
return &repeatedReader{0, max, data}
|
||||
}
|
||||
|
||||
func (r *repeatedReader) Read(p []byte) (int, error) {
|
||||
if r.readCount >= r.maxReads {
|
||||
return 0, io.EOF
|
||||
}
|
||||
r.readCount++
|
||||
n := copy(p, r.data)
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func testWithData(data []byte, reads int) {
|
||||
reader := newRepeatedReader(reads, data)
|
||||
bufReader := NewBufReader(reader)
|
||||
io.Copy(ioutil.Discard, bufReader)
|
||||
}
|
||||
|
||||
func Benchmark1M10BytesReads(b *testing.B) {
|
||||
reads := 1000000
|
||||
readSize := int64(10)
|
||||
data := make([]byte, readSize)
|
||||
b.SetBytes(readSize * int64(reads))
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
testWithData(data, reads)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark1M1024BytesReads(b *testing.B) {
|
||||
reads := 1000000
|
||||
readSize := int64(1024)
|
||||
data := make([]byte, readSize)
|
||||
b.SetBytes(readSize * int64(reads))
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
testWithData(data, reads)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark10k32KBytesReads(b *testing.B) {
|
||||
reads := 10000
|
||||
readSize := int64(32 * 1024)
|
||||
data := make([]byte, readSize)
|
||||
b.SetBytes(readSize * int64(reads))
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
testWithData(data, reads)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,6 +64,7 @@ func (config *Config) Read(p []byte) (n int, err error) {
|
||||
return read, err
|
||||
}
|
||||
func (config *Config) Close() error {
|
||||
config.Current = config.Size
|
||||
config.Out.Write(config.Formatter.FormatProg(config.ID, config.Action, &JSONProg{Current: config.Current, Total: config.Size}))
|
||||
return config.In.Close()
|
||||
}
|
||||
|
||||
@@ -3,7 +3,9 @@ package term
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/pkg/term/winconsole"
|
||||
)
|
||||
|
||||
@@ -20,34 +22,49 @@ type Winsize struct {
|
||||
y uint16
|
||||
}
|
||||
|
||||
// GetWinsize gets the window size of the given terminal
|
||||
func StdStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) {
|
||||
switch {
|
||||
case os.Getenv("ConEmuANSI") == "ON":
|
||||
// The ConEmu shell emulates ANSI well by default.
|
||||
return os.Stdin, os.Stdout, os.Stderr
|
||||
case os.Getenv("MSYSTEM") != "":
|
||||
// MSYS (mingw) does not emulate ANSI well.
|
||||
return winconsole.WinConsoleStreams()
|
||||
default:
|
||||
return winconsole.WinConsoleStreams()
|
||||
}
|
||||
}
|
||||
|
||||
// GetFdInfo returns file descriptor and bool indicating whether the file is a terminal.
|
||||
func GetFdInfo(in interface{}) (uintptr, bool) {
|
||||
return winconsole.GetHandleInfo(in)
|
||||
}
|
||||
|
||||
// GetWinsize retrieves the window size of the terminal connected to the passed file descriptor.
|
||||
func GetWinsize(fd uintptr) (*Winsize, error) {
|
||||
ws := &Winsize{}
|
||||
var info *winconsole.CONSOLE_SCREEN_BUFFER_INFO
|
||||
info, err := winconsole.GetConsoleScreenBufferInfo(fd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ws.Width = uint16(info.Window.Right - info.Window.Left + 1)
|
||||
ws.Height = uint16(info.Window.Bottom - info.Window.Top + 1)
|
||||
|
||||
ws.x = 0 // todo azlinux -- this is the pixel size of the Window, and not currently used by any caller
|
||||
ws.y = 0
|
||||
|
||||
return ws, nil
|
||||
// TODO(azlinux): Set the pixel width / height of the console (currently unused by any caller)
|
||||
return &Winsize{
|
||||
Width: uint16(info.Window.Right - info.Window.Left + 1),
|
||||
Height: uint16(info.Window.Bottom - info.Window.Top + 1),
|
||||
x: 0,
|
||||
y: 0}, nil
|
||||
}
|
||||
|
||||
// SetWinsize sets the terminal connected to the given file descriptor to a
|
||||
// given size.
|
||||
// SetWinsize sets the size of the given terminal connected to the passed file descriptor.
|
||||
func SetWinsize(fd uintptr, ws *Winsize) error {
|
||||
// TODO(azlinux): Implement SetWinsize
|
||||
logrus.Debugf("[windows] SetWinsize: WARNING -- Unsupported method invoked")
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||
func IsTerminal(fd uintptr) bool {
|
||||
_, e := winconsole.GetConsoleMode(fd)
|
||||
return e == nil
|
||||
return winconsole.IsConsole(fd)
|
||||
}
|
||||
|
||||
// RestoreTerminal restores the terminal connected to the given file descriptor to a
|
||||
@@ -56,7 +73,7 @@ func RestoreTerminal(fd uintptr, state *State) error {
|
||||
return winconsole.SetConsoleMode(fd, state.mode)
|
||||
}
|
||||
|
||||
// SaveState saves the state of the given console
|
||||
// SaveState saves the state of the terminal connected to the given file descriptor.
|
||||
func SaveState(fd uintptr) (*State, error) {
|
||||
mode, e := winconsole.GetConsoleMode(fd)
|
||||
if e != nil {
|
||||
@@ -65,54 +82,57 @@ func SaveState(fd uintptr) (*State, error) {
|
||||
return &State{mode}, nil
|
||||
}
|
||||
|
||||
// DisableEcho disbales the echo for given file descriptor and returns previous state
|
||||
// see http://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx for these flag settings
|
||||
// DisableEcho disables echo for the terminal connected to the given file descriptor.
|
||||
// -- See http://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx
|
||||
func DisableEcho(fd uintptr, state *State) error {
|
||||
state.mode &^= (winconsole.ENABLE_ECHO_INPUT)
|
||||
state.mode |= (winconsole.ENABLE_PROCESSED_INPUT | winconsole.ENABLE_LINE_INPUT)
|
||||
return winconsole.SetConsoleMode(fd, state.mode)
|
||||
mode := state.mode
|
||||
mode &^= winconsole.ENABLE_ECHO_INPUT
|
||||
mode |= winconsole.ENABLE_PROCESSED_INPUT | winconsole.ENABLE_LINE_INPUT
|
||||
// TODO(azlinux): Core code registers a goroutine to catch os.Interrupt and reset the terminal state.
|
||||
return winconsole.SetConsoleMode(fd, mode)
|
||||
}
|
||||
|
||||
// SetRawTerminal puts the terminal connected to the given file descriptor into raw
|
||||
// mode and returns the previous state of the terminal so that it can be
|
||||
// restored.
|
||||
func SetRawTerminal(fd uintptr) (*State, error) {
|
||||
oldState, err := MakeRaw(fd)
|
||||
state, err := MakeRaw(fd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// TODO (azlinux): implement handling interrupt and restore state of terminal
|
||||
return oldState, err
|
||||
// TODO(azlinux): Core code registers a goroutine to catch os.Interrupt and reset the terminal state.
|
||||
return state, err
|
||||
}
|
||||
|
||||
// MakeRaw puts the terminal connected to the given file descriptor into raw
|
||||
// mode and returns the previous state of the terminal so that it can be
|
||||
// restored.
|
||||
func MakeRaw(fd uintptr) (*State, error) {
|
||||
var state *State
|
||||
state, err := SaveState(fd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx
|
||||
// All three input modes, along with processed output mode, are designed to work together.
|
||||
// It is best to either enable or disable all of these modes as a group.
|
||||
// When all are enabled, the application is said to be in "cooked" mode, which means that most of the processing is handled for the application.
|
||||
// When all are disabled, the application is in "raw" mode, which means that input is unfiltered and any processing is left to the application.
|
||||
state.mode = 0
|
||||
err = winconsole.SetConsoleMode(fd, state.mode)
|
||||
// See
|
||||
// -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx
|
||||
// -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx
|
||||
mode := state.mode
|
||||
|
||||
// Disable these modes
|
||||
mode &^= winconsole.ENABLE_ECHO_INPUT
|
||||
mode &^= winconsole.ENABLE_LINE_INPUT
|
||||
mode &^= winconsole.ENABLE_MOUSE_INPUT
|
||||
mode &^= winconsole.ENABLE_WINDOW_INPUT
|
||||
mode &^= winconsole.ENABLE_PROCESSED_INPUT
|
||||
|
||||
// Enable these modes
|
||||
mode |= winconsole.ENABLE_EXTENDED_FLAGS
|
||||
mode |= winconsole.ENABLE_INSERT_MODE
|
||||
mode |= winconsole.ENABLE_QUICK_EDIT_MODE
|
||||
|
||||
err = winconsole.SetConsoleMode(fd, mode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return state, nil
|
||||
}
|
||||
|
||||
// GetFdInfo returns file descriptor and bool indicating whether the file is a terminal
|
||||
func GetFdInfo(in interface{}) (uintptr, bool) {
|
||||
return winconsole.GetHandleInfo(in)
|
||||
}
|
||||
|
||||
func StdStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) {
|
||||
return winconsole.StdStreams()
|
||||
}
|
||||
|
||||
@@ -12,18 +12,22 @@ import (
|
||||
"sync"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
// Consts for Get/SetConsoleMode function
|
||||
// see http://msdn.microsoft.com/en-us/library/windows/desktop/ms683167(v=vs.85).aspx
|
||||
ENABLE_ECHO_INPUT = 0x0004
|
||||
ENABLE_INSERT_MODE = 0x0020
|
||||
ENABLE_LINE_INPUT = 0x0002
|
||||
ENABLE_MOUSE_INPUT = 0x0010
|
||||
// -- See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx
|
||||
ENABLE_PROCESSED_INPUT = 0x0001
|
||||
ENABLE_QUICK_EDIT_MODE = 0x0040
|
||||
ENABLE_LINE_INPUT = 0x0002
|
||||
ENABLE_ECHO_INPUT = 0x0004
|
||||
ENABLE_WINDOW_INPUT = 0x0008
|
||||
ENABLE_MOUSE_INPUT = 0x0010
|
||||
ENABLE_INSERT_MODE = 0x0020
|
||||
ENABLE_QUICK_EDIT_MODE = 0x0040
|
||||
ENABLE_EXTENDED_FLAGS = 0x0080
|
||||
|
||||
// If parameter is a screen buffer handle, additional values
|
||||
ENABLE_PROCESSED_OUTPUT = 0x0001
|
||||
ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002
|
||||
@@ -97,27 +101,27 @@ const (
|
||||
VK_HOME = 0x24 // HOME key
|
||||
VK_LEFT = 0x25 // LEFT ARROW key
|
||||
VK_UP = 0x26 // UP ARROW key
|
||||
VK_RIGHT = 0x27 //RIGHT ARROW key
|
||||
VK_DOWN = 0x28 //DOWN ARROW key
|
||||
VK_SELECT = 0x29 //SELECT key
|
||||
VK_PRINT = 0x2A //PRINT key
|
||||
VK_EXECUTE = 0x2B //EXECUTE key
|
||||
VK_SNAPSHOT = 0x2C //PRINT SCREEN key
|
||||
VK_INSERT = 0x2D //INS key
|
||||
VK_DELETE = 0x2E //DEL key
|
||||
VK_HELP = 0x2F //HELP key
|
||||
VK_F1 = 0x70 //F1 key
|
||||
VK_F2 = 0x71 //F2 key
|
||||
VK_F3 = 0x72 //F3 key
|
||||
VK_F4 = 0x73 //F4 key
|
||||
VK_F5 = 0x74 //F5 key
|
||||
VK_F6 = 0x75 //F6 key
|
||||
VK_F7 = 0x76 //F7 key
|
||||
VK_F8 = 0x77 //F8 key
|
||||
VK_F9 = 0x78 //F9 key
|
||||
VK_F10 = 0x79 //F10 key
|
||||
VK_F11 = 0x7A //F11 key
|
||||
VK_F12 = 0x7B //F12 key
|
||||
VK_RIGHT = 0x27 // RIGHT ARROW key
|
||||
VK_DOWN = 0x28 // DOWN ARROW key
|
||||
VK_SELECT = 0x29 // SELECT key
|
||||
VK_PRINT = 0x2A // PRINT key
|
||||
VK_EXECUTE = 0x2B // EXECUTE key
|
||||
VK_SNAPSHOT = 0x2C // PRINT SCREEN key
|
||||
VK_INSERT = 0x2D // INS key
|
||||
VK_DELETE = 0x2E // DEL key
|
||||
VK_HELP = 0x2F // HELP key
|
||||
VK_F1 = 0x70 // F1 key
|
||||
VK_F2 = 0x71 // F2 key
|
||||
VK_F3 = 0x72 // F3 key
|
||||
VK_F4 = 0x73 // F4 key
|
||||
VK_F5 = 0x74 // F5 key
|
||||
VK_F6 = 0x75 // F6 key
|
||||
VK_F7 = 0x76 // F7 key
|
||||
VK_F8 = 0x77 // F8 key
|
||||
VK_F9 = 0x78 // F9 key
|
||||
VK_F10 = 0x79 // F10 key
|
||||
VK_F11 = 0x7A // F11 key
|
||||
VK_F12 = 0x7B // F12 key
|
||||
)
|
||||
|
||||
var kernel32DLL = syscall.NewLazyDLL("kernel32.dll")
|
||||
@@ -140,7 +144,12 @@ var (
|
||||
// types for calling various windows API
|
||||
// see http://msdn.microsoft.com/en-us/library/windows/desktop/ms682093(v=vs.85).aspx
|
||||
type (
|
||||
SHORT int16
|
||||
SHORT int16
|
||||
BOOL int32
|
||||
WORD uint16
|
||||
WCHAR uint16
|
||||
DWORD uint32
|
||||
|
||||
SMALL_RECT struct {
|
||||
Left SHORT
|
||||
Top SHORT
|
||||
@@ -153,11 +162,6 @@ type (
|
||||
Y SHORT
|
||||
}
|
||||
|
||||
BOOL int32
|
||||
WORD uint16
|
||||
WCHAR uint16
|
||||
DWORD uint32
|
||||
|
||||
CONSOLE_SCREEN_BUFFER_INFO struct {
|
||||
Size COORD
|
||||
CursorPosition COORD
|
||||
@@ -192,6 +196,10 @@ type (
|
||||
}
|
||||
)
|
||||
|
||||
// TODO(azlinux): Basic type clean-up
|
||||
// -- Convert all uses of uintptr to syscall.Handle to be consistent with Windows syscall
|
||||
// -- Convert, as appropriate, types to use defined Windows types (e.g., DWORD instead of uint32)
|
||||
|
||||
// Implements the TerminalEmulator interface
|
||||
type WindowsTerminal struct {
|
||||
outMutex sync.Mutex
|
||||
@@ -211,14 +219,14 @@ func getStdHandle(stdhandle int) uintptr {
|
||||
return uintptr(handle)
|
||||
}
|
||||
|
||||
func StdStreams() (stdIn io.ReadCloser, stdOut io.Writer, stdErr io.Writer) {
|
||||
func WinConsoleStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) {
|
||||
handler := &WindowsTerminal{
|
||||
inputBuffer: make([]byte, MAX_INPUT_BUFFER),
|
||||
inputEscapeSequence: []byte(KEY_ESC_CSI),
|
||||
inputEvents: make([]INPUT_RECORD, MAX_INPUT_EVENTS),
|
||||
}
|
||||
|
||||
if IsTerminal(os.Stdin.Fd()) {
|
||||
if IsConsole(os.Stdin.Fd()) {
|
||||
stdIn = &terminalReader{
|
||||
wrappedReader: os.Stdin,
|
||||
emulator: handler,
|
||||
@@ -229,7 +237,7 @@ func StdStreams() (stdIn io.ReadCloser, stdOut io.Writer, stdErr io.Writer) {
|
||||
stdIn = os.Stdin
|
||||
}
|
||||
|
||||
if IsTerminal(os.Stdout.Fd()) {
|
||||
if IsConsole(os.Stdout.Fd()) {
|
||||
stdoutHandle := getStdHandle(syscall.STD_OUTPUT_HANDLE)
|
||||
|
||||
// Save current screen buffer info
|
||||
@@ -241,8 +249,6 @@ func StdStreams() (stdIn io.ReadCloser, stdOut io.Writer, stdErr io.Writer) {
|
||||
}
|
||||
handler.screenBufferInfo = screenBufferInfo
|
||||
|
||||
// Set the window size
|
||||
SetWindowSize(stdoutHandle, DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_HEIGHT)
|
||||
buffer = make([]CHAR_INFO, screenBufferInfo.MaximumWindowSize.X*screenBufferInfo.MaximumWindowSize.Y)
|
||||
|
||||
stdOut = &terminalWriter{
|
||||
@@ -255,7 +261,7 @@ func StdStreams() (stdIn io.ReadCloser, stdOut io.Writer, stdErr io.Writer) {
|
||||
stdOut = os.Stdout
|
||||
}
|
||||
|
||||
if IsTerminal(os.Stderr.Fd()) {
|
||||
if IsConsole(os.Stderr.Fd()) {
|
||||
stdErr = &terminalWriter{
|
||||
wrappedWriter: os.Stderr,
|
||||
emulator: handler,
|
||||
@@ -269,19 +275,21 @@ func StdStreams() (stdIn io.ReadCloser, stdOut io.Writer, stdErr io.Writer) {
|
||||
return stdIn, stdOut, stdErr
|
||||
}
|
||||
|
||||
// GetHandleInfo returns file descriptor and bool indicating whether the file is a terminal
|
||||
// GetHandleInfo returns file descriptor and bool indicating whether the file is a console.
|
||||
func GetHandleInfo(in interface{}) (uintptr, bool) {
|
||||
var inFd uintptr
|
||||
var isTerminalIn bool
|
||||
|
||||
switch t := in.(type) {
|
||||
case *terminalReader:
|
||||
in = t.wrappedReader
|
||||
case *terminalWriter:
|
||||
in = t.wrappedWriter
|
||||
}
|
||||
|
||||
if file, ok := in.(*os.File); ok {
|
||||
inFd = file.Fd()
|
||||
isTerminalIn = IsTerminal(inFd)
|
||||
}
|
||||
if tr, ok := in.(*terminalReader); ok {
|
||||
if file, ok := tr.wrappedReader.(*os.File); ok {
|
||||
inFd = file.Fd()
|
||||
isTerminalIn = IsTerminal(inFd)
|
||||
}
|
||||
isTerminalIn = IsConsole(inFd)
|
||||
}
|
||||
return inFd, isTerminalIn
|
||||
}
|
||||
@@ -314,12 +322,12 @@ func SetConsoleMode(handle uintptr, mode uint32) error {
|
||||
// SetCursorVisible sets the cursor visbility
|
||||
// http://msdn.microsoft.com/en-us/library/windows/desktop/ms686019(v=vs.85).aspx
|
||||
func SetCursorVisible(handle uintptr, isVisible BOOL) (bool, error) {
|
||||
var cursorInfo CONSOLE_CURSOR_INFO
|
||||
if err := getError(getConsoleCursorInfoProc.Call(handle, uintptr(unsafe.Pointer(&cursorInfo)), 0)); err != nil {
|
||||
var cursorInfo *CONSOLE_CURSOR_INFO = &CONSOLE_CURSOR_INFO{}
|
||||
if err := getError(getConsoleCursorInfoProc.Call(handle, uintptr(unsafe.Pointer(cursorInfo)), 0)); err != nil {
|
||||
return false, err
|
||||
}
|
||||
cursorInfo.Visible = isVisible
|
||||
if err := getError(setConsoleCursorInfoProc.Call(handle, uintptr(unsafe.Pointer(&cursorInfo)), 0)); err != nil {
|
||||
if err := getError(setConsoleCursorInfoProc.Call(handle, uintptr(unsafe.Pointer(cursorInfo)), 0)); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
@@ -404,25 +412,25 @@ func getNumberOfChars(fromCoord COORD, toCoord COORD, screenSize COORD) uint32 {
|
||||
|
||||
var buffer []CHAR_INFO
|
||||
|
||||
func clearDisplayRect(handle uintptr, fillChar rune, attributes WORD, fromCoord COORD, toCoord COORD, windowSize COORD) (uint32, error) {
|
||||
func clearDisplayRect(handle uintptr, attributes WORD, fromCoord COORD, toCoord COORD) (uint32, error) {
|
||||
var writeRegion SMALL_RECT
|
||||
writeRegion.Top = fromCoord.Y
|
||||
writeRegion.Left = fromCoord.X
|
||||
writeRegion.Top = fromCoord.Y
|
||||
writeRegion.Right = toCoord.X
|
||||
writeRegion.Bottom = toCoord.Y
|
||||
|
||||
// allocate and initialize buffer
|
||||
width := toCoord.X - fromCoord.X + 1
|
||||
height := toCoord.Y - fromCoord.Y + 1
|
||||
size := width * height
|
||||
size := uint32(width) * uint32(height)
|
||||
if size > 0 {
|
||||
for i := 0; i < int(size); i++ {
|
||||
buffer[i].UnicodeChar = WCHAR(fillChar)
|
||||
buffer[i].Attributes = attributes
|
||||
buffer := make([]CHAR_INFO, size)
|
||||
for i := range buffer {
|
||||
buffer[i] = CHAR_INFO{WCHAR(' '), attributes}
|
||||
}
|
||||
|
||||
// Write to buffer
|
||||
r, err := writeConsoleOutput(handle, buffer[:size], windowSize, COORD{X: 0, Y: 0}, &writeRegion)
|
||||
r, err := writeConsoleOutput(handle, buffer, COORD{X: width, Y: height}, COORD{X: 0, Y: 0}, &writeRegion)
|
||||
if !r {
|
||||
if err != nil {
|
||||
return 0, err
|
||||
@@ -433,18 +441,18 @@ func clearDisplayRect(handle uintptr, fillChar rune, attributes WORD, fromCoord
|
||||
return uint32(size), nil
|
||||
}
|
||||
|
||||
func clearDisplayRange(handle uintptr, fillChar rune, attributes WORD, fromCoord COORD, toCoord COORD, windowSize COORD) (uint32, error) {
|
||||
func clearDisplayRange(handle uintptr, attributes WORD, fromCoord COORD, toCoord COORD) (uint32, error) {
|
||||
nw := uint32(0)
|
||||
// start and end on same line
|
||||
if fromCoord.Y == toCoord.Y {
|
||||
return clearDisplayRect(handle, fillChar, attributes, fromCoord, toCoord, windowSize)
|
||||
return clearDisplayRect(handle, attributes, fromCoord, toCoord)
|
||||
}
|
||||
// TODO(azlinux): if full screen, optimize
|
||||
|
||||
// spans more than one line
|
||||
if fromCoord.Y < toCoord.Y {
|
||||
// from start position till end of line for first line
|
||||
n, err := clearDisplayRect(handle, fillChar, attributes, fromCoord, COORD{X: windowSize.X - 1, Y: fromCoord.Y}, windowSize)
|
||||
n, err := clearDisplayRect(handle, attributes, fromCoord, COORD{X: toCoord.X, Y: fromCoord.Y})
|
||||
if err != nil {
|
||||
return nw, err
|
||||
}
|
||||
@@ -452,14 +460,14 @@ func clearDisplayRange(handle uintptr, fillChar rune, attributes WORD, fromCoord
|
||||
// lines between
|
||||
linesBetween := toCoord.Y - fromCoord.Y - 1
|
||||
if linesBetween > 0 {
|
||||
n, err = clearDisplayRect(handle, fillChar, attributes, COORD{X: 0, Y: fromCoord.Y + 1}, COORD{X: windowSize.X - 1, Y: toCoord.Y - 1}, windowSize)
|
||||
n, err = clearDisplayRect(handle, attributes, COORD{X: 0, Y: fromCoord.Y + 1}, COORD{X: toCoord.X, Y: toCoord.Y - 1})
|
||||
if err != nil {
|
||||
return nw, err
|
||||
}
|
||||
nw += n
|
||||
}
|
||||
// lines at end
|
||||
n, err = clearDisplayRect(handle, fillChar, attributes, COORD{X: 0, Y: toCoord.Y}, toCoord, windowSize)
|
||||
n, err = clearDisplayRect(handle, attributes, COORD{X: 0, Y: toCoord.Y}, toCoord)
|
||||
if err != nil {
|
||||
return nw, err
|
||||
}
|
||||
@@ -489,7 +497,7 @@ func setConsoleCursorPosition(handle uintptr, isRelative bool, column int16, lin
|
||||
|
||||
// http://msdn.microsoft.com/en-us/library/windows/desktop/ms683207(v=vs.85).aspx
|
||||
func getNumberOfConsoleInputEvents(handle uintptr) (uint16, error) {
|
||||
var n WORD
|
||||
var n DWORD
|
||||
if err := getError(getNumberOfConsoleInputEventsProc.Call(handle, uintptr(unsafe.Pointer(&n)))); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@@ -498,7 +506,7 @@ func getNumberOfConsoleInputEvents(handle uintptr) (uint16, error) {
|
||||
|
||||
//http://msdn.microsoft.com/en-us/library/windows/desktop/ms684961(v=vs.85).aspx
|
||||
func readConsoleInputKey(handle uintptr, inputBuffer []INPUT_RECORD) (int, error) {
|
||||
var nr WORD
|
||||
var nr DWORD
|
||||
if err := getError(readConsoleInputProc.Call(handle, uintptr(unsafe.Pointer(&inputBuffer[0])), uintptr(len(inputBuffer)), uintptr(unsafe.Pointer(&nr)))); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@@ -587,6 +595,7 @@ func (term *WindowsTerminal) HandleOutputCommand(handle uintptr, command []byte)
|
||||
n = len(command)
|
||||
|
||||
parsedCommand := parseAnsiCommand(command)
|
||||
logrus.Debugf("[windows] HandleOutputCommand: %v", parsedCommand)
|
||||
|
||||
// console settings changes need to happen in atomic way
|
||||
term.outMutex.Lock()
|
||||
@@ -632,16 +641,17 @@ func (term *WindowsTerminal) HandleOutputCommand(handle uintptr, command []byte)
|
||||
return n, err
|
||||
}
|
||||
if line > int16(screenBufferInfo.Window.Bottom) {
|
||||
line = int16(screenBufferInfo.Window.Bottom)
|
||||
line = int16(screenBufferInfo.Window.Bottom) + 1
|
||||
}
|
||||
column, err := parseInt16OrDefault(parsedCommand.getParam(1), 1)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
if column > int16(screenBufferInfo.Window.Right) {
|
||||
column = int16(screenBufferInfo.Window.Right)
|
||||
column = int16(screenBufferInfo.Window.Right) + 1
|
||||
}
|
||||
// The numbers are not 0 based, but 1 based
|
||||
logrus.Debugf("[windows] HandleOutputCommmand: Moving cursor to (%v,%v)", column-1, line-1)
|
||||
if err := setConsoleCursorPosition(handle, false, column-1, line-1); err != nil {
|
||||
return n, err
|
||||
}
|
||||
@@ -709,9 +719,9 @@ func (term *WindowsTerminal) HandleOutputCommand(handle uintptr, command []byte)
|
||||
switch value {
|
||||
case 0:
|
||||
start = screenBufferInfo.CursorPosition
|
||||
// end of the screen
|
||||
end.X = screenBufferInfo.MaximumWindowSize.X - 1
|
||||
end.Y = screenBufferInfo.MaximumWindowSize.Y - 1
|
||||
// end of the buffer
|
||||
end.X = screenBufferInfo.Size.X - 1
|
||||
end.Y = screenBufferInfo.Size.Y - 1
|
||||
// cursor
|
||||
cursor = screenBufferInfo.CursorPosition
|
||||
case 1:
|
||||
@@ -727,20 +737,21 @@ func (term *WindowsTerminal) HandleOutputCommand(handle uintptr, command []byte)
|
||||
// start of the screen
|
||||
start.X = 0
|
||||
start.Y = 0
|
||||
// end of the screen
|
||||
end.X = screenBufferInfo.MaximumWindowSize.X - 1
|
||||
end.Y = screenBufferInfo.MaximumWindowSize.Y - 1
|
||||
// end of the buffer
|
||||
end.X = screenBufferInfo.Size.X - 1
|
||||
end.Y = screenBufferInfo.Size.Y - 1
|
||||
// cursor
|
||||
cursor.X = 0
|
||||
cursor.Y = 0
|
||||
}
|
||||
if _, err := clearDisplayRange(uintptr(handle), ' ', term.screenBufferInfo.Attributes, start, end, screenBufferInfo.MaximumWindowSize); err != nil {
|
||||
if _, err := clearDisplayRange(uintptr(handle), term.screenBufferInfo.Attributes, start, end); err != nil {
|
||||
return n, err
|
||||
}
|
||||
// remember the the cursor position is 1 based
|
||||
if err := setConsoleCursorPosition(handle, false, int16(cursor.X), int16(cursor.Y)); err != nil {
|
||||
return n, err
|
||||
}
|
||||
|
||||
case "K":
|
||||
// [K
|
||||
// Clears all characters from the cursor position to the end of the line (including the character at the cursor position).
|
||||
@@ -760,7 +771,7 @@ func (term *WindowsTerminal) HandleOutputCommand(handle uintptr, command []byte)
|
||||
// start is where cursor is
|
||||
start = screenBufferInfo.CursorPosition
|
||||
// end of line
|
||||
end.X = screenBufferInfo.MaximumWindowSize.X - 1
|
||||
end.X = screenBufferInfo.Size.X - 1
|
||||
end.Y = screenBufferInfo.CursorPosition.Y
|
||||
// cursor remains the same
|
||||
cursor = screenBufferInfo.CursorPosition
|
||||
@@ -776,15 +787,15 @@ func (term *WindowsTerminal) HandleOutputCommand(handle uintptr, command []byte)
|
||||
case 2:
|
||||
// start of the line
|
||||
start.X = 0
|
||||
start.Y = screenBufferInfo.MaximumWindowSize.Y - 1
|
||||
start.Y = screenBufferInfo.CursorPosition.Y - 1
|
||||
// end of the line
|
||||
end.X = screenBufferInfo.MaximumWindowSize.X - 1
|
||||
end.Y = screenBufferInfo.MaximumWindowSize.Y - 1
|
||||
end.X = screenBufferInfo.Size.X - 1
|
||||
end.Y = screenBufferInfo.CursorPosition.Y - 1
|
||||
// cursor
|
||||
cursor.X = 0
|
||||
cursor.Y = screenBufferInfo.MaximumWindowSize.Y - 1
|
||||
cursor.Y = screenBufferInfo.CursorPosition.Y - 1
|
||||
}
|
||||
if _, err := clearDisplayRange(uintptr(handle), ' ', term.screenBufferInfo.Attributes, start, end, screenBufferInfo.MaximumWindowSize); err != nil {
|
||||
if _, err := clearDisplayRange(uintptr(handle), term.screenBufferInfo.Attributes, start, end); err != nil {
|
||||
return n, err
|
||||
}
|
||||
// remember the the cursor position is 1 based
|
||||
@@ -1031,12 +1042,12 @@ func (term *WindowsTerminal) HandleInputSequence(fd uintptr, command []byte) (n
|
||||
}
|
||||
|
||||
func marshal(c COORD) uintptr {
|
||||
// works only on intel-endian machines
|
||||
return uintptr(uint32(uint32(uint16(c.Y))<<16 | uint32(uint16(c.X))))
|
||||
return uintptr(*((*DWORD)(unsafe.Pointer(&c))))
|
||||
}
|
||||
|
||||
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||
func IsTerminal(fd uintptr) bool {
|
||||
// IsConsole returns true if the given file descriptor is a terminal.
|
||||
// -- The code assumes that GetConsoleMode will return an error for file descriptors that are not a console.
|
||||
func IsConsole(fd uintptr) bool {
|
||||
_, e := GetConsoleMode(fd)
|
||||
return e == nil
|
||||
}
|
||||
|
||||