Compare commits
177 Commits
docs
...
v1.6.0-rc3
| Author | SHA1 | Date | |
|---|---|---|---|
| 20d4e6f29e | |||
| 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 |
@@ -1,5 +1,24 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 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
|
||||||
|
|
||||||
|
#### 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)
|
## 1.5.0 (2015-02-10)
|
||||||
|
|
||||||
#### Builder
|
#### Builder
|
||||||
|
|||||||
@@ -441,7 +441,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
|
|||||||
authconfig.ServerAddress = serverAddress
|
authconfig.ServerAddress = serverAddress
|
||||||
cli.configFile.Configs[serverAddress] = authconfig
|
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 {
|
if statusCode == 401 {
|
||||||
delete(cli.configFile.Configs, serverAddress)
|
delete(cli.configFile.Configs, serverAddress)
|
||||||
registry.SaveConfig(cli.configFile)
|
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)
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -559,7 +559,7 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
|
|||||||
cmd.Require(flag.Exact, 0)
|
cmd.Require(flag.Exact, 0)
|
||||||
utils.ParseFlags(cmd, args, false)
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -696,7 +696,7 @@ func (cli *DockerCli) CmdStop(args ...string) error {
|
|||||||
|
|
||||||
var encounteredError error
|
var encounteredError error
|
||||||
for _, name := range cmd.Args() {
|
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 {
|
if err != nil {
|
||||||
fmt.Fprintf(cli.err, "%s\n", err)
|
fmt.Fprintf(cli.err, "%s\n", err)
|
||||||
encounteredError = fmt.Errorf("Error: failed to stop one or more containers")
|
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
|
var encounteredError error
|
||||||
for _, name := range cmd.Args() {
|
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 {
|
if err != nil {
|
||||||
fmt.Fprintf(cli.err, "%s\n", err)
|
fmt.Fprintf(cli.err, "%s\n", err)
|
||||||
encounteredError = fmt.Errorf("Error: failed to restart one or more containers")
|
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 == "" {
|
if sig == "" {
|
||||||
log.Errorf("Unsupported signal: %v. Discarding.", s)
|
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)
|
log.Debugf("Error sending signal: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -769,24 +769,12 @@ func (cli *DockerCli) CmdStart(args ...string) error {
|
|||||||
cmd.Require(flag.Min, 1)
|
cmd.Require(flag.Min, 1)
|
||||||
utils.ParseFlags(cmd, args, true)
|
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 *attach || *openStdin {
|
||||||
if cmd.NArg() > 1 {
|
if cmd.NArg() > 1 {
|
||||||
return fmt.Errorf("You cannot start and attach multiple containers at once.")
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -816,29 +804,37 @@ func (cli *DockerCli) CmdStart(args ...string) error {
|
|||||||
v.Set("stdout", "1")
|
v.Set("stdout", "1")
|
||||||
v.Set("stderr", "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 {
|
cErr = promise.Go(func() error {
|
||||||
return cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), tty, in, cli.out, cli.err, hijacked, nil)
|
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
|
var encounteredError error
|
||||||
for _, name := range cmd.Args() {
|
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 err != nil {
|
||||||
if !*attach && !*openStdin {
|
if !*attach && !*openStdin {
|
||||||
// attach and openStdin is false means it could be starting multiple containers
|
// 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
|
var encounteredError error
|
||||||
for _, name := range cmd.Args() {
|
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)
|
fmt.Fprintf(cli.err, "%s\n", err)
|
||||||
encounteredError = fmt.Errorf("Error: failed to unpause container named %s", name)
|
encounteredError = fmt.Errorf("Error: failed to unpause container named %s", name)
|
||||||
} else {
|
} else {
|
||||||
@@ -903,7 +899,7 @@ func (cli *DockerCli) CmdPause(args ...string) error {
|
|||||||
|
|
||||||
var encounteredError error
|
var encounteredError error
|
||||||
for _, name := range cmd.Args() {
|
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)
|
fmt.Fprintf(cli.err, "%s\n", err)
|
||||||
encounteredError = fmt.Errorf("Error: failed to pause container named %s", name)
|
encounteredError = fmt.Errorf("Error: failed to pause container named %s", name)
|
||||||
} else {
|
} else {
|
||||||
@@ -926,7 +922,7 @@ func (cli *DockerCli) CmdRename(args ...string) error {
|
|||||||
old_name := cmd.Arg(0)
|
old_name := cmd.Arg(0)
|
||||||
new_name := cmd.Arg(1)
|
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)
|
fmt.Fprintf(cli.err, "%s\n", err)
|
||||||
return fmt.Errorf("Error: failed to rename container named %s", old_name)
|
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
|
status := 0
|
||||||
|
|
||||||
for _, name := range cmd.Args() {
|
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 err != nil {
|
||||||
if strings.Contains(err.Error(), "Too many") {
|
if strings.Contains(err.Error(), "Too many") {
|
||||||
fmt.Fprintf(cli.err, "Error: %v", err)
|
fmt.Fprintf(cli.err, "Error: %v", err)
|
||||||
@@ -963,7 +959,7 @@ func (cli *DockerCli) CmdInspect(args ...string) error {
|
|||||||
continue
|
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 err != nil {
|
||||||
if strings.Contains(err.Error(), "No such") {
|
if strings.Contains(err.Error(), "No such") {
|
||||||
fmt.Fprintf(cli.err, "Error: No such image or container: %s\n", name)
|
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:], " "))
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1052,7 +1048,7 @@ func (cli *DockerCli) CmdPort(args ...string) error {
|
|||||||
cmd.Require(flag.Min, 1)
|
cmd.Require(flag.Min, 1)
|
||||||
utils.ParseFlags(cmd, args, true)
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1117,7 +1113,7 @@ func (cli *DockerCli) CmdRmi(args ...string) error {
|
|||||||
|
|
||||||
var encounteredError error
|
var encounteredError error
|
||||||
for _, name := range cmd.Args() {
|
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 {
|
if err != nil {
|
||||||
fmt.Fprintf(cli.err, "%s\n", err)
|
fmt.Fprintf(cli.err, "%s\n", err)
|
||||||
encounteredError = fmt.Errorf("Error: failed to remove one or more images")
|
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)
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1215,7 +1211,7 @@ func (cli *DockerCli) CmdRm(args ...string) error {
|
|||||||
|
|
||||||
var encounteredError error
|
var encounteredError error
|
||||||
for _, name := range cmd.Args() {
|
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 {
|
if err != nil {
|
||||||
fmt.Fprintf(cli.err, "%s\n", err)
|
fmt.Fprintf(cli.err, "%s\n", err)
|
||||||
encounteredError = fmt.Errorf("Error: failed to remove one or more containers")
|
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
|
var encounteredError error
|
||||||
for _, name := range cmd.Args() {
|
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)
|
fmt.Fprintf(cli.err, "%s\n", err)
|
||||||
encounteredError = fmt.Errorf("Error: failed to kill one or more containers")
|
encounteredError = fmt.Errorf("Error: failed to kill one or more containers")
|
||||||
} else {
|
} else {
|
||||||
@@ -1321,32 +1317,8 @@ func (cli *DockerCli) CmdPush(args ...string) error {
|
|||||||
v := url.Values{}
|
v := url.Values{}
|
||||||
v.Set("tag", tag)
|
v.Set("tag", tag)
|
||||||
|
|
||||||
push := func(authConfig registry.AuthConfig) error {
|
_, _, err = cli.clientRequestAttemptLogin("POST", "/images/"+remote+"/push?"+v.Encode(), nil, cli.out, repoInfo.Index, "push")
|
||||||
buf, err := json.Marshal(authConfig)
|
return err
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *DockerCli) CmdPull(args ...string) error {
|
func (cli *DockerCli) CmdPull(args ...string) error {
|
||||||
@@ -1379,36 +1351,8 @@ func (cli *DockerCli) CmdPull(args ...string) error {
|
|||||||
|
|
||||||
cli.LoadConfigFile()
|
cli.LoadConfigFile()
|
||||||
|
|
||||||
// Resolve the Auth config relevant for this server
|
_, _, err = cli.clientRequestAttemptLogin("POST", "/images/create?"+v.Encode(), nil, cli.out, repoInfo.Index, "pull")
|
||||||
authConfig := cli.configFile.ResolveAuthConfig(repoInfo.Index)
|
return err
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *DockerCli) CmdImages(args ...string) error {
|
func (cli *DockerCli) CmdImages(args ...string) error {
|
||||||
@@ -1452,7 +1396,7 @@ func (cli *DockerCli) CmdImages(args ...string) error {
|
|||||||
v.Set("filters", filterJson)
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1530,7 +1474,7 @@ func (cli *DockerCli) CmdImages(args ...string) error {
|
|||||||
v.Set("all", "1")
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -1728,7 +1672,7 @@ func (cli *DockerCli) CmdPs(args ...string) error {
|
|||||||
v.Set("filters", filterJson)
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1869,7 +1813,7 @@ func (cli *DockerCli) CmdCommit(args ...string) error {
|
|||||||
return err
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1980,7 +1924,7 @@ func (cli *DockerCli) CmdDiff(args ...string) error {
|
|||||||
|
|
||||||
utils.ParseFlags(cmd, args, true)
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -2018,7 +1962,7 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
|
|||||||
|
|
||||||
name := cmd.Arg(0)
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -2059,7 +2003,7 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
|
|||||||
utils.ParseFlags(cmd, args, true)
|
utils.ParseFlags(cmd, args, true)
|
||||||
name := cmd.Arg(0)
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -2130,16 +2074,27 @@ func (cli *DockerCli) CmdSearch(args ...string) error {
|
|||||||
|
|
||||||
utils.ParseFlags(cmd, args, true)
|
utils.ParseFlags(cmd, args, true)
|
||||||
|
|
||||||
|
name := cmd.Arg(0)
|
||||||
v := url.Values{}
|
v := url.Values{}
|
||||||
v.Set("term", cmd.Arg(0))
|
v.Set("term", name)
|
||||||
|
|
||||||
body, _, err := readBody(cli.call("GET", "/images/search?"+v.Encode(), nil, true))
|
|
||||||
|
|
||||||
|
// Resolve the Repository name from fqn to hostname + name
|
||||||
|
taglessRemote, _ := parsers.ParseRepositoryTag(name)
|
||||||
|
repoInfo, err := registry.ParseRepositoryInfo(taglessRemote)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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)
|
outs := engine.NewTable("star_count", 0)
|
||||||
if _, err := outs.ReadListFrom(body); err != nil {
|
if _, err := outs.ReadListFrom(rawBody); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
w := tabwriter.NewWriter(cli.out, 10, 1, 3, ' ', 0)
|
w := tabwriter.NewWriter(cli.out, 10, 1, 3, ' ', 0)
|
||||||
@@ -2194,7 +2149,7 @@ func (cli *DockerCli) CmdTag(args ...string) error {
|
|||||||
v.Set("force", "1")
|
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 err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -2296,7 +2251,7 @@ func (cli *DockerCli) createContainer(config *runconfig.Config, hostConfig *runc
|
|||||||
}
|
}
|
||||||
|
|
||||||
//create the container
|
//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 image not found try to pull it
|
||||||
if statusCode == 404 {
|
if statusCode == 404 {
|
||||||
repo, tag := parsers.ParseRepositoryTag(config.Image)
|
repo, tag := parsers.ParseRepositoryTag(config.Image)
|
||||||
@@ -2310,7 +2265,7 @@ func (cli *DockerCli) createContainer(config *runconfig.Config, hostConfig *runc
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// Retry
|
// 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
|
return nil, err
|
||||||
}
|
}
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
@@ -2500,7 +2455,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//start the container
|
//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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2530,13 +2485,13 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
|||||||
if *flAutoRemove {
|
if *flAutoRemove {
|
||||||
// Autoremove: wait for the container to finish, retrieve
|
// Autoremove: wait for the container to finish, retrieve
|
||||||
// the exit code and remove the container
|
// 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
|
return err
|
||||||
}
|
}
|
||||||
if _, status, err = getExitCode(cli, createResponse.ID); err != nil {
|
if _, status, err = getExitCode(cli, createResponse.ID); err != nil {
|
||||||
return err
|
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
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -2576,7 +2531,7 @@ func (cli *DockerCli) CmdCp(args ...string) error {
|
|||||||
copyData.Set("Resource", info[1])
|
copyData.Set("Resource", info[1])
|
||||||
copyData.Set("HostPath", cmd.Arg(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 {
|
if stream != nil {
|
||||||
defer stream.Close()
|
defer stream.Close()
|
||||||
}
|
}
|
||||||
@@ -2671,7 +2626,7 @@ func (cli *DockerCli) CmdExec(args ...string) error {
|
|||||||
return &utils.StatusError{StatusCode: 1}
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -2693,7 +2648,7 @@ func (cli *DockerCli) CmdExec(args ...string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} 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
|
return err
|
||||||
}
|
}
|
||||||
// For now don't print this - wait for when we support exec wait()
|
// 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) {
|
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 {
|
if err != nil {
|
||||||
s.err = err
|
s.err = err
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -12,8 +12,10 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
gosignal "os/signal"
|
gosignal "os/signal"
|
||||||
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/docker/api"
|
"github.com/docker/docker/api"
|
||||||
@@ -54,124 +56,144 @@ func (cli *DockerCli) encodeData(data interface{}) (*bytes.Buffer, error) {
|
|||||||
return params, nil
|
return params, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *DockerCli) call(method, path string, data interface{}, passAuthInfo bool) (io.ReadCloser, int, error) {
|
func (cli *DockerCli) clientRequest(method, path string, in io.Reader, headers map[string][]string) (io.ReadCloser, string, int, error) {
|
||||||
params, err := cli.encodeData(data)
|
expectedPayload := (method == "POST" || method == "PUT")
|
||||||
if err != nil {
|
if expectedPayload && in == 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 {
|
|
||||||
in = bytes.NewReader([]byte{})
|
in = bytes.NewReader([]byte{})
|
||||||
}
|
}
|
||||||
|
|
||||||
req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), in)
|
req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), in)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, "", -1, err
|
||||||
}
|
}
|
||||||
req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION)
|
req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION)
|
||||||
req.URL.Host = cli.addr
|
req.URL.Host = cli.addr
|
||||||
req.URL.Scheme = cli.scheme
|
req.URL.Scheme = cli.scheme
|
||||||
if method == "POST" {
|
|
||||||
req.Header.Set("Content-Type", "text/plain")
|
|
||||||
}
|
|
||||||
|
|
||||||
if headers != nil {
|
if headers != nil {
|
||||||
for k, v := range headers {
|
for k, v := range headers {
|
||||||
req.Header[k] = v
|
req.Header[k] = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if expectedPayload && req.Header.Get("Content-Type") == "" {
|
||||||
|
req.Header.Set("Content-Type", "text/plain")
|
||||||
|
}
|
||||||
|
|
||||||
resp, err := cli.HTTPClient().Do(req)
|
resp, err := cli.HTTPClient().Do(req)
|
||||||
|
statusCode := -1
|
||||||
|
if resp != nil {
|
||||||
|
statusCode = resp.StatusCode
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "connection refused") {
|
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)
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, "", statusCode, err
|
||||||
}
|
}
|
||||||
if len(body) == 0 {
|
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 resp.Body, resp.Header.Get("Content-Type"), statusCode, nil
|
||||||
return utils.DisplayJSONMessagesStream(resp.Body, stdout, cli.outFd, cli.isTerminalOut)
|
}
|
||||||
|
|
||||||
|
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 {
|
if stdout != nil || stderr != nil {
|
||||||
// When TTY is ON, use regular copy
|
// When TTY is ON, use regular copy
|
||||||
|
var err error
|
||||||
if setRawTerminal {
|
if setRawTerminal {
|
||||||
_, err = io.Copy(stdout, resp.Body)
|
_, err = io.Copy(stdout, body)
|
||||||
} else {
|
} else {
|
||||||
_, err = stdcopy.StdCopy(stdout, stderr, resp.Body)
|
_, err = stdcopy.StdCopy(stdout, stderr, body)
|
||||||
}
|
}
|
||||||
log.Debugf("[stream] End of stdout")
|
log.Debugf("[stream] End of stdout")
|
||||||
return err
|
return err
|
||||||
@@ -195,13 +217,13 @@ func (cli *DockerCli) resizeTty(id string, isExec bool) {
|
|||||||
path = "/exec/" + id + "/resize?"
|
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)
|
log.Debugf("Error resize: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func waitForExit(cli *DockerCli, containerId string) (int, error) {
|
func waitForExit(cli *DockerCli, containerID string) (int, error) {
|
||||||
stream, _, err := cli.call("POST", "/containers/"+containerId+"/wait", nil, false)
|
stream, _, err := cli.call("POST", "/containers/"+containerID+"/wait", nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return -1, err
|
return -1, err
|
||||||
}
|
}
|
||||||
@@ -215,8 +237,8 @@ func waitForExit(cli *DockerCli, containerId string) (int, error) {
|
|||||||
|
|
||||||
// getExitCode perform an inspect on the container. It returns
|
// getExitCode perform an inspect on the container. It returns
|
||||||
// the running state and the exit code.
|
// the running state and the exit code.
|
||||||
func getExitCode(cli *DockerCli, containerId string) (bool, int, error) {
|
func getExitCode(cli *DockerCli, containerID string) (bool, int, error) {
|
||||||
stream, _, err := cli.call("GET", "/containers/"+containerId+"/json", nil, false)
|
stream, _, err := cli.call("GET", "/containers/"+containerID+"/json", nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// If we can't connect, then the daemon probably died.
|
// If we can't connect, then the daemon probably died.
|
||||||
if err != ErrConnectionRefused {
|
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
|
// getExecExitCode perform an inspect on the exec command. It returns
|
||||||
// the running state and the exit code.
|
// the running state and the exit code.
|
||||||
func getExecExitCode(cli *DockerCli, execId string) (bool, int, error) {
|
func getExecExitCode(cli *DockerCli, execID string) (bool, int, error) {
|
||||||
stream, _, err := cli.call("GET", "/exec/"+execId+"/json", nil, false)
|
stream, _, err := cli.call("GET", "/exec/"+execID+"/json", nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// If we can't connect, then the daemon probably died.
|
// If we can't connect, then the daemon probably died.
|
||||||
if err != ErrConnectionRefused {
|
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 {
|
func (cli *DockerCli) monitorTtySize(id string, isExec bool) error {
|
||||||
cli.resizeTty(id, isExec)
|
cli.resizeTty(id, isExec)
|
||||||
|
|
||||||
sigchan := make(chan os.Signal, 1)
|
if runtime.GOOS == "windows" {
|
||||||
gosignal.Notify(sigchan, signal.SIGWINCH)
|
go func() {
|
||||||
go func() {
|
prevW, prevH := cli.getTtySize()
|
||||||
for _ = range sigchan {
|
for {
|
||||||
cli.resizeTty(id, isExec)
|
time.Sleep(time.Millisecond * 250)
|
||||||
}
|
w, h := cli.getTtySize()
|
||||||
}()
|
|
||||||
|
if prevW != w || prevH != h {
|
||||||
|
cli.resizeTty(id, isExec)
|
||||||
|
}
|
||||||
|
prevW = w
|
||||||
|
prevH = h
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
} else {
|
||||||
|
sigchan := make(chan os.Signal, 1)
|
||||||
|
gosignal.Notify(sigchan, signal.SIGWINCH)
|
||||||
|
go func() {
|
||||||
|
for _ = range sigchan {
|
||||||
|
cli.resizeTty(id, isExec)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import (
|
|||||||
log "github.com/Sirupsen/logrus"
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/docker/api"
|
"github.com/docker/docker/api"
|
||||||
"github.com/docker/docker/api/types"
|
"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/engine"
|
||||||
"github.com/docker/docker/pkg/listenbuffer"
|
"github.com/docker/docker/pkg/listenbuffer"
|
||||||
"github.com/docker/docker/pkg/parsers"
|
"github.com/docker/docker/pkg/parsers"
|
||||||
@@ -38,7 +38,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
activationLock chan struct{}
|
activationLock chan struct{} = make(chan struct{})
|
||||||
)
|
)
|
||||||
|
|
||||||
type HttpServer struct {
|
type HttpServer struct {
|
||||||
@@ -1527,7 +1527,7 @@ func allocateDaemonPort(addr string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, hostIP := range hostIPs {
|
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)
|
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
|
protoAddrs = job.Args
|
||||||
chErrors = make(chan error, len(protoAddrs))
|
chErrors = make(chan error, len(protoAddrs))
|
||||||
)
|
)
|
||||||
activationLock = make(chan struct{})
|
|
||||||
|
|
||||||
for _, protoAddr := range protoAddrs {
|
for _, protoAddr := range protoAddrs {
|
||||||
protoAddrParts := strings.SplitN(protoAddr, "://", 2)
|
protoAddrParts := strings.SplitN(protoAddr, "://", 2)
|
||||||
|
|||||||
@@ -95,7 +95,9 @@ func AcceptConnections(job *engine.Job) engine.Status {
|
|||||||
go systemd.SdNotify("READY=1")
|
go systemd.SdNotify("READY=1")
|
||||||
|
|
||||||
// close the lock so the listeners start accepting connections
|
// close the lock so the listeners start accepting connections
|
||||||
if activationLock != nil {
|
select {
|
||||||
|
case <-activationLock:
|
||||||
|
default:
|
||||||
close(activationLock)
|
close(activationLock)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -312,7 +312,11 @@ func (b *Builder) dispatch(stepN int, ast *parser.Node) error {
|
|||||||
var str string
|
var str string
|
||||||
str = ast.Value
|
str = ast.Value
|
||||||
if _, ok := replaceEnvAllowed[cmd]; ok {
|
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
|
strList[i+l] = str
|
||||||
msgList[i] = ast.Value
|
msgList[i] = ast.Value
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ func parseNameVal(rest string, key string) (*Node, map[string]bool, error) {
|
|||||||
if blankOK || len(word) > 0 {
|
if blankOK || len(word) > 0 {
|
||||||
words = append(words, word)
|
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
|
// we're doing the old stuff and
|
||||||
// just read the rest of the line
|
// just read the rest of the line
|
||||||
if !strings.Contains(word, "=") {
|
if !strings.Contains(word, "=") {
|
||||||
@@ -107,12 +107,15 @@ func parseNameVal(rest string, key string) (*Node, map[string]bool, error) {
|
|||||||
quote = ch
|
quote = ch
|
||||||
blankOK = true
|
blankOK = true
|
||||||
phase = inQuote
|
phase = inQuote
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
if ch == '\\' {
|
if ch == '\\' {
|
||||||
if pos+1 == len(rest) {
|
if pos+1 == len(rest) {
|
||||||
continue // just skip \ at end
|
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++
|
pos++
|
||||||
ch = rune(rest[pos])
|
ch = rune(rest[pos])
|
||||||
}
|
}
|
||||||
@@ -122,15 +125,17 @@ func parseNameVal(rest string, key string) (*Node, map[string]bool, error) {
|
|||||||
if phase == inQuote {
|
if phase == inQuote {
|
||||||
if ch == quote {
|
if ch == quote {
|
||||||
phase = inWord
|
phase = inWord
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
if ch == '\\' {
|
// \ is special except for ' quotes - can't escape anything for '
|
||||||
|
if ch == '\\' && quote != '\'' {
|
||||||
if pos+1 == len(rest) {
|
if pos+1 == len(rest) {
|
||||||
phase = inWord
|
phase = inWord
|
||||||
continue // just skip \ at end
|
continue // just skip \ at end
|
||||||
}
|
}
|
||||||
pos++
|
pos++
|
||||||
ch = rune(rest[pos])
|
nextCh := rune(rest[pos])
|
||||||
|
word += string(ch)
|
||||||
|
ch = nextCh
|
||||||
}
|
}
|
||||||
word += string(ch)
|
word += string(ch)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,14 @@ ENV name=value\ value2
|
|||||||
ENV name="value'quote space'value2"
|
ENV name="value'quote space'value2"
|
||||||
ENV name='value"double quote"value2'
|
ENV name='value"double quote"value2'
|
||||||
ENV name=value\ value2 name2=value2\ value3
|
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 \
|
ENV name=value \
|
||||||
name1=value1 \
|
name1=value1 \
|
||||||
name2="value2a \
|
name2="value2a \
|
||||||
|
|||||||
@@ -2,9 +2,15 @@
|
|||||||
(env "name" "value")
|
(env "name" "value")
|
||||||
(env "name" "value")
|
(env "name" "value")
|
||||||
(env "name" "value" "name2" "value2")
|
(env "name" "value" "name2" "value2")
|
||||||
(env "name" "value value1")
|
(env "name" "\"value value1\"")
|
||||||
(env "name" "value value2")
|
(env "name" "value\\ value2")
|
||||||
(env "name" "value'quote space'value2")
|
(env "name" "\"value'quote space'value2\"")
|
||||||
(env "name" "value\"double quote\"value2")
|
(env "name" "'value\"double quote\"value2'")
|
||||||
(env "name" "value value2" "name2" "value2 value3")
|
(env "name" "value\\ value2" "name2" "value2\\ value3")
|
||||||
(env "name" "value" "name1" "value1" "name2" "value2a value2b" "name3" "value3an\"value3b\"" "name4" "value4a\\nvalue4b")
|
(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\"")
|
||||||
|
|||||||
@@ -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 ""
|
||||||
|
}
|
||||||
@@ -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
|
package builder
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"regexp"
|
|
||||||
"strings"
|
"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 {
|
func handleJsonArgs(args []string, attributes map[string]bool) []string {
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
return []string{}
|
return []string{}
|
||||||
|
|||||||
@@ -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_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() {
|
__docker_image_repos() {
|
||||||
local repos="$(__docker_q images | awk 'NR>1 && $1 != "<none>" { print $1 }')"
|
local repos="$(__docker_q images | awk 'NR>1 && $1 != "<none>" { print $1 }')"
|
||||||
COMPREPLY=( $(compgen -W "$repos" -- "$cur") )
|
COMPREPLY=( $(compgen -W "$repos" -- "$cur") )
|
||||||
@@ -325,7 +337,7 @@ _docker_cp() {
|
|||||||
(( counter++ ))
|
(( counter++ ))
|
||||||
|
|
||||||
if [ $cword -eq $counter ]; then
|
if [ $cword -eq $counter ]; then
|
||||||
_filedir
|
_filedir -d
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
@@ -437,7 +449,10 @@ _docker_history() {
|
|||||||
_docker_images() {
|
_docker_images() {
|
||||||
case "$prev" in
|
case "$prev" in
|
||||||
--filter|-f)
|
--filter|-f)
|
||||||
COMPREPLY=( $( compgen -W "dangling=true" -- "$cur" ) )
|
COMPREPLY=( $( compgen -W "dangling=true label=" -- "$cur" ) )
|
||||||
|
if [ "$COMPREPLY" = "label=" ]; then
|
||||||
|
compopt -o nospace
|
||||||
|
fi
|
||||||
return
|
return
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
@@ -447,17 +462,20 @@ _docker_images() {
|
|||||||
COMPREPLY=( $( compgen -W "true false" -- "${cur#=}" ) )
|
COMPREPLY=( $( compgen -W "true false" -- "${cur#=}" ) )
|
||||||
return
|
return
|
||||||
;;
|
;;
|
||||||
|
*label=*)
|
||||||
|
return
|
||||||
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
case "$cur" in
|
case "$cur" in
|
||||||
-*)
|
-*)
|
||||||
COMPREPLY=( $( compgen -W "--all -a --filter -f --help --no-trunc --quiet -q" -- "$cur" ) )
|
COMPREPLY=( $( compgen -W "--all -a --filter -f --help --no-trunc --quiet -q" -- "$cur" ) )
|
||||||
;;
|
;;
|
||||||
|
=)
|
||||||
|
return
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
local counter=$(__docker_pos_first_nonflag)
|
__docker_image_repos
|
||||||
if [ $cword -eq $counter ]; then
|
|
||||||
__docker_image_repos
|
|
||||||
fi
|
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
@@ -616,7 +634,7 @@ _docker_ps() {
|
|||||||
__docker_containers_all
|
__docker_containers_all
|
||||||
;;
|
;;
|
||||||
--filter|-f)
|
--filter|-f)
|
||||||
COMPREPLY=( $( compgen -S = -W "exited status" -- "$cur" ) )
|
COMPREPLY=( $( compgen -S = -W "exited id label name status" -- "$cur" ) )
|
||||||
compopt -o nospace
|
compopt -o nospace
|
||||||
return
|
return
|
||||||
;;
|
;;
|
||||||
@@ -626,6 +644,16 @@ _docker_ps() {
|
|||||||
esac
|
esac
|
||||||
|
|
||||||
case "${words[$cword-2]}$prev=" in
|
case "${words[$cword-2]}$prev=" in
|
||||||
|
*id=*)
|
||||||
|
cur="${cur#=}"
|
||||||
|
__docker_container_ids
|
||||||
|
return
|
||||||
|
;;
|
||||||
|
*name=*)
|
||||||
|
cur="${cur#=}"
|
||||||
|
__docker_container_names
|
||||||
|
return
|
||||||
|
;;
|
||||||
*status=*)
|
*status=*)
|
||||||
COMPREPLY=( $( compgen -W "exited paused restarting running" -- "${cur#=}" ) )
|
COMPREPLY=( $( compgen -W "exited paused restarting running" -- "${cur#=}" ) )
|
||||||
return
|
return
|
||||||
@@ -734,6 +762,7 @@ _docker_run() {
|
|||||||
--attach -a
|
--attach -a
|
||||||
--cap-add
|
--cap-add
|
||||||
--cap-drop
|
--cap-drop
|
||||||
|
--cgroup-parent
|
||||||
--cidfile
|
--cidfile
|
||||||
--cpuset
|
--cpuset
|
||||||
--cpu-shares -c
|
--cpu-shares -c
|
||||||
@@ -746,7 +775,10 @@ _docker_run() {
|
|||||||
--expose
|
--expose
|
||||||
--hostname -h
|
--hostname -h
|
||||||
--ipc
|
--ipc
|
||||||
|
--label -l
|
||||||
|
--label-file
|
||||||
--link
|
--link
|
||||||
|
--log-driver
|
||||||
--lxc-conf
|
--lxc-conf
|
||||||
--mac-address
|
--mac-address
|
||||||
--memory -m
|
--memory -m
|
||||||
@@ -798,7 +830,7 @@ _docker_run() {
|
|||||||
__docker_capabilities
|
__docker_capabilities
|
||||||
return
|
return
|
||||||
;;
|
;;
|
||||||
--cidfile|--env-file)
|
--cidfile|--env-file|--label-file)
|
||||||
_filedir
|
_filedir
|
||||||
return
|
return
|
||||||
;;
|
;;
|
||||||
@@ -850,6 +882,10 @@ _docker_run() {
|
|||||||
esac
|
esac
|
||||||
return
|
return
|
||||||
;;
|
;;
|
||||||
|
--log-driver)
|
||||||
|
COMPREPLY=( $( compgen -W "json-file syslog none" -- "$cur") )
|
||||||
|
return
|
||||||
|
;;
|
||||||
--net)
|
--net)
|
||||||
case "$cur" in
|
case "$cur" in
|
||||||
container:*)
|
container:*)
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ func (config *Config) InstallFlags() {
|
|||||||
opts.LabelListVar(&config.Labels, []string{"-label"}, "Set key=value labels to the daemon")
|
opts.LabelListVar(&config.Labels, []string{"-label"}, "Set key=value labels to the daemon")
|
||||||
config.Ulimits = make(map[string]*ulimit.Ulimit)
|
config.Ulimits = make(map[string]*ulimit.Ulimit)
|
||||||
opts.UlimitMapVar(config.Ulimits, []string{"-default-ulimit"}, "Set default ulimits for containers")
|
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 {
|
func getDefaultNetworkMtu() int {
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import (
|
|||||||
"github.com/docker/docker/daemon/execdriver"
|
"github.com/docker/docker/daemon/execdriver"
|
||||||
"github.com/docker/docker/daemon/logger"
|
"github.com/docker/docker/daemon/logger"
|
||||||
"github.com/docker/docker/daemon/logger/jsonfilelog"
|
"github.com/docker/docker/daemon/logger/jsonfilelog"
|
||||||
|
"github.com/docker/docker/daemon/logger/syslog"
|
||||||
"github.com/docker/docker/engine"
|
"github.com/docker/docker/engine"
|
||||||
"github.com/docker/docker/image"
|
"github.com/docker/docker/image"
|
||||||
"github.com/docker/docker/links"
|
"github.com/docker/docker/links"
|
||||||
@@ -359,6 +360,10 @@ func (container *Container) Start() (err error) {
|
|||||||
return nil
|
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
|
// if we encounter an error during start we need to ensure that any other
|
||||||
// setup has been cleaned up properly
|
// setup has been cleaned up properly
|
||||||
defer func() {
|
defer func() {
|
||||||
@@ -1380,6 +1385,12 @@ func (container *Container) startLogging() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
l = dl
|
l = dl
|
||||||
|
case "syslog":
|
||||||
|
dl, err := syslog.New(container.ID[:12])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
l = dl
|
||||||
case "none":
|
case "none":
|
||||||
return nil
|
return nil
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ import (
|
|||||||
"github.com/docker/docker/daemon/graphdriver"
|
"github.com/docker/docker/daemon/graphdriver"
|
||||||
_ "github.com/docker/docker/daemon/graphdriver/vfs"
|
_ "github.com/docker/docker/daemon/graphdriver/vfs"
|
||||||
_ "github.com/docker/docker/daemon/networkdriver/bridge"
|
_ "github.com/docker/docker/daemon/networkdriver/bridge"
|
||||||
"github.com/docker/docker/daemon/networkdriver/portallocator"
|
|
||||||
"github.com/docker/docker/engine"
|
"github.com/docker/docker/engine"
|
||||||
"github.com/docker/docker/graph"
|
"github.com/docker/docker/graph"
|
||||||
"github.com/docker/docker/image"
|
"github.com/docker/docker/image"
|
||||||
@@ -827,12 +826,6 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine) (*Daemon, error)
|
|||||||
}
|
}
|
||||||
config.DisableNetwork = config.BridgeIface == disableNetworkBridge
|
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.
|
// 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.
|
// Some of the init doesn't need a pidfile lock - but let's not try to be smart.
|
||||||
if config.Pidfile != "" {
|
if config.Pidfile != "" {
|
||||||
@@ -1012,7 +1005,8 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine) (*Daemon, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
sysInfo := sysinfo.New(false)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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")
|
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")
|
container.LogEvent("destroy")
|
||||||
if removeVolume {
|
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.
|
// 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 {
|
if container == nil {
|
||||||
return fmt.Errorf("The given container is <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)
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deregister the container before removing its directory, to avoid race conditions
|
// Mark container dead. We don't want anybody to be restarting it.
|
||||||
daemon.idIndex.Delete(container.ID)
|
container.SetDead()
|
||||||
daemon.containers.Delete(container.ID)
|
|
||||||
|
// 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()
|
container.derefVolumes()
|
||||||
if _, err := daemon.containerGraph.Purge(container.ID); err != nil {
|
if _, err := daemon.containerGraph.Purge(container.ID); err != nil {
|
||||||
log.Debugf("Unable to remove container from link graph: %s", err)
|
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)
|
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)
|
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)
|
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)
|
return fmt.Errorf("Unable to remove execdriver data for %s: %s", container.ID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
selinuxFreeLxcContexts(container.ProcessLabel)
|
selinuxFreeLxcContexts(container.ProcessLabel)
|
||||||
|
daemon.idIndex.Delete(container.ID)
|
||||||
|
daemon.containers.Delete(container.ID)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,13 +10,13 @@ import (
|
|||||||
"github.com/docker/docker/pkg/sysinfo"
|
"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 {
|
switch name {
|
||||||
case "lxc":
|
case "lxc":
|
||||||
// we want to give the lxc driver the full docker root because it needs
|
// 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 access and write config and template files in /var/lib/docker/containers/*
|
||||||
// to be backwards compatible
|
// to be backwards compatible
|
||||||
return lxc.NewDriver(root, initPath, sysInfo.AppArmor)
|
return lxc.NewDriver(root, libPath, initPath, sysInfo.AppArmor)
|
||||||
case "native":
|
case "native":
|
||||||
return native.NewDriver(path.Join(root, "execdriver", "native"), initPath)
|
return native.NewDriver(path.Join(root, "execdriver", "native"), initPath)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import (
|
|||||||
"github.com/docker/docker/daemon/execdriver"
|
"github.com/docker/docker/daemon/execdriver"
|
||||||
sysinfo "github.com/docker/docker/pkg/system"
|
sysinfo "github.com/docker/docker/pkg/system"
|
||||||
"github.com/docker/docker/pkg/term"
|
"github.com/docker/docker/pkg/term"
|
||||||
|
"github.com/docker/docker/pkg/version"
|
||||||
"github.com/docker/docker/utils"
|
"github.com/docker/docker/utils"
|
||||||
"github.com/docker/libcontainer"
|
"github.com/docker/libcontainer"
|
||||||
"github.com/docker/libcontainer/cgroups"
|
"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 {
|
type driver struct {
|
||||||
root string // root path for the driver to use
|
root string // root path for the driver to use
|
||||||
|
libPath string
|
||||||
initPath string
|
initPath string
|
||||||
apparmor bool
|
apparmor bool
|
||||||
sharedRoot bool
|
sharedRoot bool
|
||||||
@@ -48,7 +50,10 @@ type activeContainer struct {
|
|||||||
cmd *exec.Cmd
|
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
|
// setup unconfined symlink
|
||||||
if err := linkLxcStart(root); err != nil {
|
if err := linkLxcStart(root); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -60,6 +65,7 @@ func NewDriver(root, initPath string, apparmor bool) (*driver, error) {
|
|||||||
return &driver{
|
return &driver{
|
||||||
apparmor: apparmor,
|
apparmor: apparmor,
|
||||||
root: root,
|
root: root,
|
||||||
|
libPath: libPath,
|
||||||
initPath: initPath,
|
initPath: initPath,
|
||||||
sharedRoot: rootIsShared(),
|
sharedRoot: rootIsShared(),
|
||||||
activeContainers: make(map[string]*activeContainer),
|
activeContainers: make(map[string]*activeContainer),
|
||||||
@@ -115,6 +121,13 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba
|
|||||||
"-n", c.ID,
|
"-n", c.ID,
|
||||||
"-f", configPath,
|
"-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 != "" {
|
if c.Network.ContainerID != "" {
|
||||||
params = append(params,
|
params = append(params,
|
||||||
"--share-net", c.Network.ContainerID,
|
"--share-net", c.Network.ContainerID,
|
||||||
@@ -658,7 +671,7 @@ func rootIsShared() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *driver) containerDir(containerId string) string {
|
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) {
|
func (d *driver) generateLXCConfig(c *execdriver.Command) (string, error) {
|
||||||
@@ -688,7 +701,7 @@ func (d *driver) generateEnvConfig(c *execdriver.Command) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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{
|
c.Mounts = append(c.Mounts, execdriver.Mount{
|
||||||
Source: p,
|
Source: p,
|
||||||
Destination: "/.dockerenv",
|
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) {
|
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)
|
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)
|
cpu = cpuMin + rand.Intn(cpuMax-cpuMin)
|
||||||
)
|
)
|
||||||
|
|
||||||
driver, err := NewDriver(root, "", false)
|
driver, err := NewDriver(root, root, "", false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -76,7 +76,7 @@ func TestCustomLxcConfig(t *testing.T) {
|
|||||||
|
|
||||||
os.MkdirAll(path.Join(root, "containers", "1"), 0777)
|
os.MkdirAll(path.Join(root, "containers", "1"), 0777)
|
||||||
|
|
||||||
driver, err := NewDriver(root, "", false)
|
driver, err := NewDriver(root, root, "", false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -194,7 +194,7 @@ func TestCustomLxcConfigMounts(t *testing.T) {
|
|||||||
}
|
}
|
||||||
os.MkdirAll(path.Join(root, "containers", "1"), 0777)
|
os.MkdirAll(path.Join(root, "containers", "1"), 0777)
|
||||||
|
|
||||||
driver, err := NewDriver(root, "", false)
|
driver, err := NewDriver(root, root, "", false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -248,7 +248,7 @@ func TestCustomLxcConfigMisc(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer os.RemoveAll(root)
|
defer os.RemoveAll(root)
|
||||||
os.MkdirAll(path.Join(root, "containers", "1"), 0777)
|
os.MkdirAll(path.Join(root, "containers", "1"), 0777)
|
||||||
driver, err := NewDriver(root, "", true)
|
driver, err := NewDriver(root, root, "", true)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -313,7 +313,7 @@ func TestCustomLxcConfigMiscOverride(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer os.RemoveAll(root)
|
defer os.RemoveAll(root)
|
||||||
os.MkdirAll(path.Join(root, "containers", "1"), 0777)
|
os.MkdirAll(path.Join(root, "containers", "1"), 0777)
|
||||||
driver, err := NewDriver(root, "", false)
|
driver, err := NewDriver(root, root, "", false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,7 +64,6 @@ func NewDriver(root, initPath string) (*driver, error) {
|
|||||||
root,
|
root,
|
||||||
cgm,
|
cgm,
|
||||||
libcontainer.InitPath(reexec.Self(), DriverName),
|
libcontainer.InitPath(reexec.Self(), DriverName),
|
||||||
libcontainer.TmpfsRoot,
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -163,18 +162,18 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba
|
|||||||
log.Warnf("Your kernel does not support OOM notifications: %s", err)
|
log.Warnf("Your kernel does not support OOM notifications: %s", err)
|
||||||
}
|
}
|
||||||
waitF := p.Wait
|
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,
|
// we need such hack for tracking processes with inerited fds,
|
||||||
// because cmd.Wait() waiting for all streams to be copied
|
// because cmd.Wait() waiting for all streams to be copied
|
||||||
waitF = waitInPIDHost(p, cont)
|
waitF = waitInPIDHost(p, cont)
|
||||||
}
|
}
|
||||||
ps, err := waitF()
|
ps, err := waitF()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err, ok := err.(*exec.ExitError); !ok {
|
execErr, ok := err.(*exec.ExitError)
|
||||||
|
if !ok {
|
||||||
return execdriver.ExitStatus{ExitCode: -1}, err
|
return execdriver.ExitStatus{ExitCode: -1}, err
|
||||||
} else {
|
|
||||||
ps = err.ProcessState
|
|
||||||
}
|
}
|
||||||
|
ps = execErr.ProcessState
|
||||||
}
|
}
|
||||||
cont.Destroy()
|
cont.Destroy()
|
||||||
|
|
||||||
@@ -190,16 +189,17 @@ func waitInPIDHost(p *libcontainer.Process, c libcontainer.Container) func() (*o
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
processes, err := c.Processes()
|
||||||
|
|
||||||
process, err := os.FindProcess(pid)
|
process, err := os.FindProcess(pid)
|
||||||
s, err := process.Wait()
|
s, err := process.Wait()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err, ok := err.(*exec.ExitError); !ok {
|
execErr, ok := err.(*exec.ExitError)
|
||||||
|
if !ok {
|
||||||
return s, err
|
return s, err
|
||||||
} else {
|
|
||||||
s = err.ProcessState
|
|
||||||
}
|
}
|
||||||
|
s = execErr.ProcessState
|
||||||
}
|
}
|
||||||
processes, err := c.Processes()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return s, err
|
return s, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ package aufs
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path"
|
"path"
|
||||||
@@ -47,6 +48,9 @@ var (
|
|||||||
graphdriver.FsMagicAufs,
|
graphdriver.FsMagicAufs,
|
||||||
}
|
}
|
||||||
backingFs = "<unknown>"
|
backingFs = "<unknown>"
|
||||||
|
|
||||||
|
enableDirpermLock sync.Once
|
||||||
|
enableDirperm bool
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -152,6 +156,7 @@ func (a *Driver) Status() [][2]string {
|
|||||||
{"Root Dir", a.rootPath()},
|
{"Root Dir", a.rootPath()},
|
||||||
{"Backing Filesystem", backingFs},
|
{"Backing Filesystem", backingFs},
|
||||||
{"Dirs", fmt.Sprintf("%d", len(ids))},
|
{"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
|
// Mount options are clipped to page size(4096 bytes). If there are more
|
||||||
// layers then these are remounted individually using append.
|
// 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))
|
bp := copy(b, fmt.Sprintf("br:%s=rw", rw))
|
||||||
|
|
||||||
firstMount := true
|
firstMount := true
|
||||||
@@ -446,7 +455,11 @@ func (a *Driver) aufsMount(ro []string, rw, target, mountLabel string) (err erro
|
|||||||
}
|
}
|
||||||
|
|
||||||
if firstMount {
|
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 {
|
if err = mount("none", target, "aufs", 0, data); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -460,3 +473,33 @@ func (a *Driver) aufsMount(ro []string, rw, target, mountLabel string) (err erro
|
|||||||
|
|
||||||
return
|
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>
|
#include <btrfs/version.h>
|
||||||
|
|
||||||
// because around version 3.16, they did not define lib version yet
|
// around version 3.16, they did not define lib version yet
|
||||||
int my_btrfs_lib_version() {
|
#ifndef BTRFS_LIB_VERSION
|
||||||
#ifdef BTRFS_LIB_VERSION
|
#define BTRFS_LIB_VERSION -1
|
||||||
return BTRFS_LIB_VERSION;
|
#endif
|
||||||
#else
|
|
||||||
return -1;
|
// upstream had removed it, but now it will be coming back
|
||||||
|
#ifndef BTRFS_BUILD_VERSION
|
||||||
|
#define BTRFS_BUILD_VERSION "-"
|
||||||
#endif
|
#endif
|
||||||
}
|
|
||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
|
|
||||||
func BtrfsBuildVersion() string {
|
func BtrfsBuildVersion() string {
|
||||||
return string(C.BTRFS_BUILD_VERSION)
|
return string(C.BTRFS_BUILD_VERSION)
|
||||||
}
|
}
|
||||||
|
|
||||||
func BtrfsLibVersion() int {
|
func BtrfsLibVersion() int {
|
||||||
return int(C.BTRFS_LIB_VERSION)
|
return int(C.BTRFS_LIB_VERSION)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ package btrfs
|
|||||||
func BtrfsBuildVersion() string {
|
func BtrfsBuildVersion() string {
|
||||||
return "-"
|
return "-"
|
||||||
}
|
}
|
||||||
|
|
||||||
func BtrfsLibVersion() int {
|
func BtrfsLibVersion() int {
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// +build linux
|
// +build linux,!btrfs_noversion
|
||||||
|
|
||||||
package btrfs
|
package btrfs
|
||||||
|
|
||||||
@@ -6,8 +6,8 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBuildVersion(t *testing.T) {
|
func TestLibVersion(t *testing.T) {
|
||||||
if len(BtrfsBuildVersion()) == 0 {
|
if BtrfsLibVersion() <= 0 {
|
||||||
t.Errorf("expected output from btrfs build version, but got empty string")
|
t.Errorf("expected output from btrfs lib version > 0")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,9 +39,8 @@ var (
|
|||||||
"aufs",
|
"aufs",
|
||||||
"btrfs",
|
"btrfs",
|
||||||
"devicemapper",
|
"devicemapper",
|
||||||
"vfs",
|
|
||||||
// experimental, has to be enabled manually for now
|
|
||||||
"overlay",
|
"overlay",
|
||||||
|
"vfs",
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrNotSupported = errors.New("driver not supported")
|
ErrNotSupported = errors.New("driver not supported")
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
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_USER, path.Base(os.Args[0]))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Syslog{
|
||||||
|
writer: log,
|
||||||
|
tag: tag,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Syslog) Log(msg *logger.Message) error {
|
||||||
|
logMessage := fmt.Sprintf("%s: %s", s.tag, string(msg.Line))
|
||||||
|
if msg.Source == "stderr" {
|
||||||
|
if err := s.writer.Err(logMessage); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if err := s.writer.Info(logMessage); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Syslog) Close() error {
|
||||||
|
if s.writer != nil {
|
||||||
|
return s.writer.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Syslog) Name() string {
|
||||||
|
return "Syslog"
|
||||||
|
}
|
||||||
@@ -77,11 +77,19 @@ var (
|
|||||||
bridgeIPv4Network *net.IPNet
|
bridgeIPv4Network *net.IPNet
|
||||||
bridgeIPv6Addr net.IP
|
bridgeIPv6Addr net.IP
|
||||||
globalIPv6Network *net.IPNet
|
globalIPv6Network *net.IPNet
|
||||||
|
portMapper *portmapper.PortMapper
|
||||||
|
once sync.Once
|
||||||
|
|
||||||
defaultBindingIP = net.ParseIP("0.0.0.0")
|
defaultBindingIP = net.ParseIP("0.0.0.0")
|
||||||
currentInterfaces = ifaces{c: make(map[string]*networkInterface)}
|
currentInterfaces = ifaces{c: make(map[string]*networkInterface)}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func initPortMapper() {
|
||||||
|
once.Do(func() {
|
||||||
|
portMapper = portmapper.New()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func InitDriver(job *engine.Job) engine.Status {
|
func InitDriver(job *engine.Job) engine.Status {
|
||||||
var (
|
var (
|
||||||
networkv4 *net.IPNet
|
networkv4 *net.IPNet
|
||||||
@@ -98,6 +106,7 @@ func InitDriver(job *engine.Job) engine.Status {
|
|||||||
fixedCIDR = job.Getenv("FixedCIDR")
|
fixedCIDR = job.Getenv("FixedCIDR")
|
||||||
fixedCIDRv6 = job.Getenv("FixedCIDRv6")
|
fixedCIDRv6 = job.Getenv("FixedCIDRv6")
|
||||||
)
|
)
|
||||||
|
initPortMapper()
|
||||||
|
|
||||||
if defaultIP := job.Getenv("DefaultBindingIP"); defaultIP != "" {
|
if defaultIP := job.Getenv("DefaultBindingIP"); defaultIP != "" {
|
||||||
defaultBindingIP = net.ParseIP(defaultIP)
|
defaultBindingIP = net.ParseIP(defaultIP)
|
||||||
@@ -234,7 +243,7 @@ func InitDriver(job *engine.Job) engine.Status {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return job.Error(err)
|
return job.Error(err)
|
||||||
}
|
}
|
||||||
portmapper.SetIptablesChain(chain)
|
portMapper.SetIptablesChain(chain)
|
||||||
}
|
}
|
||||||
|
|
||||||
bridgeIPv4Network = networkv4
|
bridgeIPv4Network = networkv4
|
||||||
@@ -349,6 +358,11 @@ func setupIPTables(addr net.Addr, icc, ipmasq bool) error {
|
|||||||
return nil
|
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
|
// 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 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
|
// If the bridge `bridgeIface` already exists, it will only perform the IP address association with the existing
|
||||||
@@ -586,7 +600,7 @@ func Release(job *engine.Job) engine.Status {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, nat := range containerInterface.PortMappings {
|
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)
|
log.Infof("Unable to unmap port %s: %s", nat, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -643,7 +657,7 @@ func AllocatePort(job *engine.Job) engine.Status {
|
|||||||
|
|
||||||
var host net.Addr
|
var host net.Addr
|
||||||
for i := 0; i < MaxAllocatedPortAttempts; i++ {
|
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
|
break
|
||||||
}
|
}
|
||||||
// There is no point in immediately retrying to map an explicitly
|
// There is no point in immediately retrying to map an explicitly
|
||||||
|
|||||||
@@ -16,44 +16,12 @@ const (
|
|||||||
DefaultPortRangeEnd = 65535
|
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
|
type ipMapping map[string]protoMap
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrAllPortsAllocated = errors.New("all ports are allocated")
|
ErrAllPortsAllocated = errors.New("all ports are allocated")
|
||||||
ErrUnknownProtocol = errors.New("unknown protocol")
|
ErrUnknownProtocol = errors.New("unknown protocol")
|
||||||
)
|
defaultIP = net.ParseIP("0.0.0.0")
|
||||||
|
|
||||||
var (
|
|
||||||
mutex sync.Mutex
|
|
||||||
|
|
||||||
defaultIP = net.ParseIP("0.0.0.0")
|
|
||||||
globalMap = ipMapping{}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ErrPortAlreadyAllocated struct {
|
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 {
|
func (e ErrPortAlreadyAllocated) IP() string {
|
||||||
return e.ip
|
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)
|
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.
|
// 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
|
// 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.
|
// in pool and return that port or error if port is already busy.
|
||||||
func RequestPort(ip net.IP, proto string, port int) (int, error) {
|
func (p *PortAllocator) RequestPort(ip net.IP, proto string, port int) (int, error) {
|
||||||
mutex.Lock()
|
p.mutex.Lock()
|
||||||
defer mutex.Unlock()
|
defer p.mutex.Unlock()
|
||||||
|
|
||||||
if proto != "tcp" && proto != "udp" {
|
if proto != "tcp" && proto != "udp" {
|
||||||
return 0, ErrUnknownProtocol
|
return 0, ErrUnknownProtocol
|
||||||
@@ -124,10 +112,14 @@ func RequestPort(ip net.IP, proto string, port int) (int, error) {
|
|||||||
ip = defaultIP
|
ip = defaultIP
|
||||||
}
|
}
|
||||||
ipstr := ip.String()
|
ipstr := ip.String()
|
||||||
protomap, ok := globalMap[ipstr]
|
protomap, ok := p.ipMap[ipstr]
|
||||||
if !ok {
|
if !ok {
|
||||||
protomap = newProtoMap()
|
protomap = protoMap{
|
||||||
globalMap[ipstr] = protomap
|
"tcp": p.newPortMap(),
|
||||||
|
"udp": p.newPortMap(),
|
||||||
|
}
|
||||||
|
|
||||||
|
p.ipMap[ipstr] = protomap
|
||||||
}
|
}
|
||||||
mapping := protomap[proto]
|
mapping := protomap[proto]
|
||||||
if port > 0 {
|
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.
|
// ReleasePort releases port from global ports pool for specified ip and proto.
|
||||||
func ReleasePort(ip net.IP, proto string, port int) error {
|
func (p *PortAllocator) ReleasePort(ip net.IP, proto string, port int) error {
|
||||||
mutex.Lock()
|
p.mutex.Lock()
|
||||||
defer mutex.Unlock()
|
defer p.mutex.Unlock()
|
||||||
|
|
||||||
if ip == nil {
|
if ip == nil {
|
||||||
ip = defaultIP
|
ip = defaultIP
|
||||||
}
|
}
|
||||||
protomap, ok := globalMap[ip.String()]
|
protomap, ok := p.ipMap[ip.String()]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -161,20 +153,29 @@ func ReleasePort(ip net.IP, proto string, port int) error {
|
|||||||
return nil
|
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.
|
// ReleaseAll releases all ports for all ips.
|
||||||
func ReleaseAll() error {
|
func (p *PortAllocator) ReleaseAll() error {
|
||||||
mutex.Lock()
|
p.mutex.Lock()
|
||||||
globalMap = ipMapping{}
|
p.ipMap = ipMapping{}
|
||||||
mutex.Unlock()
|
p.mutex.Unlock()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pm *portMap) findPort() (int, error) {
|
func (pm *portMap) findPort() (int, error) {
|
||||||
port := pm.last
|
port := pm.last
|
||||||
for i := 0; i <= endPortRange-beginPortRange; i++ {
|
for i := 0; i <= pm.end-pm.begin; i++ {
|
||||||
port++
|
port++
|
||||||
if port > endPortRange {
|
if port > pm.end {
|
||||||
port = beginPortRange
|
port = pm.begin
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := pm.p[port]; !ok {
|
if _, ok := pm.p[port]; !ok {
|
||||||
|
|||||||
@@ -5,32 +5,23 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
|
||||||
beginPortRange = DefaultPortRangeStart
|
|
||||||
endPortRange = DefaultPortRangeEnd
|
|
||||||
}
|
|
||||||
|
|
||||||
func reset() {
|
|
||||||
ReleaseAll()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRequestNewPort(t *testing.T) {
|
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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if expected := beginPortRange; port != expected {
|
if expected := p.Begin; port != expected {
|
||||||
t.Fatalf("Expected port %d got %d", expected, port)
|
t.Fatalf("Expected port %d got %d", expected, port)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRequestSpecificPort(t *testing.T) {
|
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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -40,9 +31,9 @@ func TestRequestSpecificPort(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestReleasePort(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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -50,15 +41,15 @@ func TestReleasePort(t *testing.T) {
|
|||||||
t.Fatalf("Expected port 5000 got %d", port)
|
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)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReuseReleasedPort(t *testing.T) {
|
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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -66,20 +57,20 @@ func TestReuseReleasedPort(t *testing.T) {
|
|||||||
t.Fatalf("Expected port 5000 got %d", port)
|
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)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
port, err = RequestPort(defaultIP, "tcp", 5000)
|
port, err = p.RequestPort(defaultIP, "tcp", 5000)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReleaseUnreadledPort(t *testing.T) {
|
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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -87,7 +78,7 @@ func TestReleaseUnreadledPort(t *testing.T) {
|
|||||||
t.Fatalf("Expected port 5000 got %d", port)
|
t.Fatalf("Expected port 5000 got %d", port)
|
||||||
}
|
}
|
||||||
|
|
||||||
port, err = RequestPort(defaultIP, "tcp", 5000)
|
port, err = p.RequestPort(defaultIP, "tcp", 5000)
|
||||||
|
|
||||||
switch err.(type) {
|
switch err.(type) {
|
||||||
case ErrPortAlreadyAllocated:
|
case ErrPortAlreadyAllocated:
|
||||||
@@ -97,42 +88,40 @@ func TestReleaseUnreadledPort(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestUnknowProtocol(t *testing.T) {
|
func TestUnknowProtocol(t *testing.T) {
|
||||||
defer reset()
|
if _, err := New().RequestPort(defaultIP, "tcpp", 0); err != ErrUnknownProtocol {
|
||||||
|
|
||||||
if _, err := RequestPort(defaultIP, "tcpp", 0); err != ErrUnknownProtocol {
|
|
||||||
t.Fatalf("Expected error %s got %s", ErrUnknownProtocol, err)
|
t.Fatalf("Expected error %s got %s", ErrUnknownProtocol, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAllocateAllPorts(t *testing.T) {
|
func TestAllocateAllPorts(t *testing.T) {
|
||||||
defer reset()
|
p := New()
|
||||||
|
|
||||||
for i := 0; i <= endPortRange-beginPortRange; i++ {
|
for i := 0; i <= p.End-p.Begin; i++ {
|
||||||
port, err := RequestPort(defaultIP, "tcp", 0)
|
port, err := p.RequestPort(defaultIP, "tcp", 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
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)
|
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)
|
t.Fatalf("Expected error %s got %s", ErrAllPortsAllocated, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := RequestPort(defaultIP, "udp", 0)
|
_, err := p.RequestPort(defaultIP, "udp", 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// release a port in the middle and ensure we get another tcp port
|
// release a port in the middle and ensure we get another tcp port
|
||||||
port := beginPortRange + 5
|
port := p.Begin + 5
|
||||||
if err := ReleasePort(defaultIP, "tcp", port); err != nil {
|
if err := p.ReleasePort(defaultIP, "tcp", port); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
newPort, err := RequestPort(defaultIP, "tcp", 0)
|
newPort, err := p.RequestPort(defaultIP, "tcp", 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
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
|
// now pm.last == newPort, release it so that it's the only free port of
|
||||||
// the range, and ensure we get it back
|
// 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)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
port, err = RequestPort(defaultIP, "tcp", 0)
|
port, err = p.RequestPort(defaultIP, "tcp", 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -155,34 +144,34 @@ func TestAllocateAllPorts(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkAllocatePorts(b *testing.B) {
|
func BenchmarkAllocatePorts(b *testing.B) {
|
||||||
defer reset()
|
p := New()
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
for i := 0; i <= endPortRange-beginPortRange; i++ {
|
for i := 0; i <= p.End-p.Begin; i++ {
|
||||||
port, err := RequestPort(defaultIP, "tcp", 0)
|
port, err := p.RequestPort(defaultIP, "tcp", 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal(err)
|
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)
|
b.Fatalf("Expected port %d got %d", expected, port)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
reset()
|
p.ReleaseAll()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPortAllocation(t *testing.T) {
|
func TestPortAllocation(t *testing.T) {
|
||||||
defer reset()
|
p := New()
|
||||||
|
|
||||||
ip := net.ParseIP("192.168.0.1")
|
ip := net.ParseIP("192.168.0.1")
|
||||||
ip2 := net.ParseIP("192.168.0.2")
|
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)
|
t.Fatal(err)
|
||||||
} else if port != 80 {
|
} else if port != 80 {
|
||||||
t.Fatalf("Acquire(80) should return 80, not %d", port)
|
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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -190,41 +179,41 @@ func TestPortAllocation(t *testing.T) {
|
|||||||
t.Fatalf("Acquire(0) should return a non-zero port")
|
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")
|
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)
|
t.Fatal(err)
|
||||||
} else if newPort == port {
|
} else if newPort == port {
|
||||||
t.Fatalf("Acquire(0) allocated the same port twice: %d", 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")
|
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")
|
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")
|
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)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if _, err := RequestPort(ip, "tcp", 80); err != nil {
|
if _, err := p.RequestPort(ip, "tcp", 80); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
port, err = RequestPort(ip, "tcp", 0)
|
port, err = p.RequestPort(ip, "tcp", 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
port2, err := RequestPort(ip, "tcp", port+1)
|
port2, err := p.RequestPort(ip, "tcp", port+1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
port3, err := RequestPort(ip, "tcp", 0)
|
port3, err := p.RequestPort(ip, "tcp", 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -234,17 +223,17 @@ func TestPortAllocation(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestNoDuplicateBPR(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)
|
t.Fatal(err)
|
||||||
} else if port != beginPortRange {
|
} else if port != p.Begin {
|
||||||
t.Fatalf("Expected port %d got %d", beginPortRange, port)
|
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)
|
t.Fatal(err)
|
||||||
} else if port == beginPortRange {
|
} else if port == p.Begin {
|
||||||
t.Fatalf("Acquire(0) allocated the same port twice: %d", port)
|
t.Fatalf("Acquire(0) allocated the same port twice: %d", port)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,15 +18,7 @@ type mapping struct {
|
|||||||
container net.Addr
|
container net.Addr
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var NewProxy = NewProxyCommand
|
||||||
chain *iptables.Chain
|
|
||||||
lock sync.Mutex
|
|
||||||
|
|
||||||
// udp:ip:port
|
|
||||||
currentMappings = make(map[string]*mapping)
|
|
||||||
|
|
||||||
NewProxy = NewProxyCommand
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrUnknownBackendAddressType = errors.New("unknown container address type not supported")
|
ErrUnknownBackendAddressType = errors.New("unknown container address type not supported")
|
||||||
@@ -34,13 +26,34 @@ var (
|
|||||||
ErrPortNotMapped = errors.New("port is not mapped")
|
ErrPortNotMapped = errors.New("port is not mapped")
|
||||||
)
|
)
|
||||||
|
|
||||||
func SetIptablesChain(c *iptables.Chain) {
|
type PortMapper struct {
|
||||||
chain = c
|
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) {
|
func New() *PortMapper {
|
||||||
lock.Lock()
|
return NewWithPortAllocator(portallocator.New())
|
||||||
defer lock.Unlock()
|
}
|
||||||
|
|
||||||
|
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 (
|
var (
|
||||||
m *mapping
|
m *mapping
|
||||||
@@ -52,7 +65,7 @@ func Map(container net.Addr, hostIP net.IP, hostPort int) (host net.Addr, err er
|
|||||||
switch container.(type) {
|
switch container.(type) {
|
||||||
case *net.TCPAddr:
|
case *net.TCPAddr:
|
||||||
proto = "tcp"
|
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
|
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)
|
proxy = NewProxy(proto, hostIP, allocatedHostPort, container.(*net.TCPAddr).IP, container.(*net.TCPAddr).Port)
|
||||||
case *net.UDPAddr:
|
case *net.UDPAddr:
|
||||||
proto = "udp"
|
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
|
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.
|
// release the allocated port on any further error during return.
|
||||||
defer func() {
|
defer func() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
portallocator.ReleasePort(hostIP, proto, allocatedHostPort)
|
pm.Allocator.ReleasePort(hostIP, proto, allocatedHostPort)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
key := getKey(m.host)
|
key := getKey(m.host)
|
||||||
if _, exists := currentMappings[key]; exists {
|
if _, exists := pm.currentMappings[key]; exists {
|
||||||
return nil, ErrPortMappedForIP
|
return nil, ErrPortMappedForIP
|
||||||
}
|
}
|
||||||
|
|
||||||
containerIP, containerPort := getIPAndPort(m.container)
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanup := func() error {
|
cleanup := func() error {
|
||||||
// need to undo the iptables rules before we return
|
// need to undo the iptables rules before we return
|
||||||
proxy.Stop()
|
proxy.Stop()
|
||||||
forward(iptables.Delete, m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort)
|
pm.forward(iptables.Delete, m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort)
|
||||||
if err := portallocator.ReleasePort(hostIP, m.proto, allocatedHostPort); err != nil {
|
if err := pm.Allocator.ReleasePort(hostIP, m.proto, allocatedHostPort); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,35 +128,35 @@ func Map(container net.Addr, hostIP net.IP, hostPort int) (host net.Addr, err er
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
m.userlandProxy = proxy
|
m.userlandProxy = proxy
|
||||||
currentMappings[key] = m
|
pm.currentMappings[key] = m
|
||||||
return m.host, nil
|
return m.host, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func Unmap(host net.Addr) error {
|
func (pm *PortMapper) Unmap(host net.Addr) error {
|
||||||
lock.Lock()
|
pm.lock.Lock()
|
||||||
defer lock.Unlock()
|
defer pm.lock.Unlock()
|
||||||
|
|
||||||
key := getKey(host)
|
key := getKey(host)
|
||||||
data, exists := currentMappings[key]
|
data, exists := pm.currentMappings[key]
|
||||||
if !exists {
|
if !exists {
|
||||||
return ErrPortNotMapped
|
return ErrPortNotMapped
|
||||||
}
|
}
|
||||||
|
|
||||||
data.userlandProxy.Stop()
|
data.userlandProxy.Stop()
|
||||||
|
|
||||||
delete(currentMappings, key)
|
delete(pm.currentMappings, key)
|
||||||
|
|
||||||
containerIP, containerPort := getIPAndPort(data.container)
|
containerIP, containerPort := getIPAndPort(data.container)
|
||||||
hostIP, hostPort := getIPAndPort(data.host)
|
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)
|
log.Errorf("Error on iptables delete: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch a := host.(type) {
|
switch a := host.(type) {
|
||||||
case *net.TCPAddr:
|
case *net.TCPAddr:
|
||||||
return portallocator.ReleasePort(a.IP, "tcp", a.Port)
|
return pm.Allocator.ReleasePort(a.IP, "tcp", a.Port)
|
||||||
case *net.UDPAddr:
|
case *net.UDPAddr:
|
||||||
return portallocator.ReleasePort(a.IP, "udp", a.Port)
|
return pm.Allocator.ReleasePort(a.IP, "udp", a.Port)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -168,9 +181,9 @@ func getIPAndPort(a net.Addr) (net.IP, int) {
|
|||||||
return nil, 0
|
return nil, 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func forward(action iptables.Action, proto string, sourceIP net.IP, sourcePort int, containerIP string, containerPort int) error {
|
func (pm *PortMapper) forward(action iptables.Action, proto string, sourceIP net.IP, sourcePort int, containerIP string, containerPort int) error {
|
||||||
if chain == nil {
|
if pm.chain == nil {
|
||||||
return 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"
|
"net"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/docker/daemon/networkdriver/portallocator"
|
|
||||||
"github.com/docker/docker/pkg/iptables"
|
"github.com/docker/docker/pkg/iptables"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -13,30 +12,26 @@ func init() {
|
|||||||
NewProxy = NewMockProxyCommand
|
NewProxy = NewMockProxyCommand
|
||||||
}
|
}
|
||||||
|
|
||||||
func reset() {
|
|
||||||
chain = nil
|
|
||||||
currentMappings = make(map[string]*mapping)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSetIptablesChain(t *testing.T) {
|
func TestSetIptablesChain(t *testing.T) {
|
||||||
defer reset()
|
pm := New()
|
||||||
|
|
||||||
c := &iptables.Chain{
|
c := &iptables.Chain{
|
||||||
Name: "TEST",
|
Name: "TEST",
|
||||||
Bridge: "192.168.1.1",
|
Bridge: "192.168.1.1",
|
||||||
}
|
}
|
||||||
|
|
||||||
if chain != nil {
|
if pm.chain != nil {
|
||||||
t.Fatal("chain should be nil at init")
|
t.Fatal("chain should be nil at init")
|
||||||
}
|
}
|
||||||
|
|
||||||
SetIptablesChain(c)
|
pm.SetIptablesChain(c)
|
||||||
if chain == nil {
|
if pm.chain == nil {
|
||||||
t.Fatal("chain should not be nil after set")
|
t.Fatal("chain should not be nil after set")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMapPorts(t *testing.T) {
|
func TestMapPorts(t *testing.T) {
|
||||||
|
pm := New()
|
||||||
dstIp1 := net.ParseIP("192.168.0.1")
|
dstIp1 := net.ParseIP("192.168.0.1")
|
||||||
dstIp2 := net.ParseIP("192.168.0.2")
|
dstIp2 := net.ParseIP("192.168.0.2")
|
||||||
dstAddr1 := &net.TCPAddr{IP: dstIp1, Port: 80}
|
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())
|
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)
|
t.Fatalf("Failed to allocate port: %s", err)
|
||||||
} else if !addrEqual(dstAddr1, host) {
|
} else if !addrEqual(dstAddr1, host) {
|
||||||
t.Fatalf("Incorrect mapping result: expected %s:%s, got %s:%s",
|
t.Fatalf("Incorrect mapping result: expected %s:%s, got %s:%s",
|
||||||
dstAddr1.String(), dstAddr1.Network(), host.String(), host.Network())
|
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")
|
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")
|
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)
|
t.Fatalf("Failed to allocate port: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if Unmap(dstAddr1) != nil {
|
if pm.Unmap(dstAddr1) != nil {
|
||||||
t.Fatalf("Failed to release port")
|
t.Fatalf("Failed to release port")
|
||||||
}
|
}
|
||||||
|
|
||||||
if Unmap(dstAddr2) != nil {
|
if pm.Unmap(dstAddr2) != nil {
|
||||||
t.Fatalf("Failed to release port")
|
t.Fatalf("Failed to release port")
|
||||||
}
|
}
|
||||||
|
|
||||||
if Unmap(dstAddr2) == nil {
|
if pm.Unmap(dstAddr2) == nil {
|
||||||
t.Fatalf("Port already released, but no error reported")
|
t.Fatalf("Port already released, but no error reported")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -115,6 +110,7 @@ func TestGetUDPIPAndPort(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMapAllPortsSingleInterface(t *testing.T) {
|
func TestMapAllPortsSingleInterface(t *testing.T) {
|
||||||
|
pm := New()
|
||||||
dstIp1 := net.ParseIP("0.0.0.0")
|
dstIp1 := net.ParseIP("0.0.0.0")
|
||||||
srcAddr1 := &net.TCPAddr{Port: 1080, IP: net.ParseIP("172.16.0.1")}
|
srcAddr1 := &net.TCPAddr{Port: 1080, IP: net.ParseIP("172.16.0.1")}
|
||||||
|
|
||||||
@@ -124,26 +120,26 @@ func TestMapAllPortsSingleInterface(t *testing.T) {
|
|||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
for _, val := range hosts {
|
for _, val := range hosts {
|
||||||
Unmap(val)
|
pm.Unmap(val)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
start, end := portallocator.PortRange()
|
start, end := pm.Allocator.Begin, pm.Allocator.End
|
||||||
for i := start; i < end; i++ {
|
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)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
hosts = append(hosts, host)
|
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)
|
t.Fatalf("Port %d should be bound but is not", start)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, val := range hosts {
|
for _, val := range hosts {
|
||||||
if err := Unmap(val); err != nil {
|
if err := pm.Unmap(val); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,16 +11,18 @@ import (
|
|||||||
|
|
||||||
type State struct {
|
type State struct {
|
||||||
sync.Mutex
|
sync.Mutex
|
||||||
Running bool
|
Running bool
|
||||||
Paused bool
|
Paused bool
|
||||||
Restarting bool
|
Restarting bool
|
||||||
OOMKilled bool
|
OOMKilled bool
|
||||||
Pid int
|
removalInProgress bool // Not need for this to be persistent on disk.
|
||||||
ExitCode int
|
Dead bool
|
||||||
Error string // contains last known error when starting the container
|
Pid int
|
||||||
StartedAt time.Time
|
ExitCode int
|
||||||
FinishedAt time.Time
|
Error string // contains last known error when starting the container
|
||||||
waitChan chan struct{}
|
StartedAt time.Time
|
||||||
|
FinishedAt time.Time
|
||||||
|
waitChan chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewState() *State {
|
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)))
|
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() {
|
if s.FinishedAt.IsZero() {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@@ -60,6 +70,11 @@ func (s *State) StateString() string {
|
|||||||
}
|
}
|
||||||
return "running"
|
return "running"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.Dead {
|
||||||
|
return "dead"
|
||||||
|
}
|
||||||
|
|
||||||
return "exited"
|
return "exited"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,3 +232,25 @@ func (s *State) IsPaused() bool {
|
|||||||
s.Unlock()
|
s.Unlock()
|
||||||
return res
|
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
|
Writable bool
|
||||||
copyData bool
|
copyData bool
|
||||||
from *Container
|
from *Container
|
||||||
|
isBind bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mnt *Mount) Export(resource string) (io.ReadCloser, error) {
|
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 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
|
// 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.
|
// 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
|
return nil
|
||||||
}
|
}
|
||||||
if m.volume.Path == hostPath {
|
if m.volume.Path == hostPath {
|
||||||
@@ -172,6 +173,7 @@ func (container *Container) parseVolumeMountConfig() (map[string]*Mount, error)
|
|||||||
volume: vol,
|
volume: vol,
|
||||||
MountToPath: mountToPath,
|
MountToPath: mountToPath,
|
||||||
Writable: writable,
|
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 {
|
if _, exists := container.Volumes[path]; exists {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
realPath, err := container.getResourcePath(path)
|
||||||
if stat, err := os.Stat(filepath.Join(container.basefs, path)); err == nil {
|
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() {
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ IMAGE [COMMAND] [ARG...]
|
|||||||
**--lxc-conf**=[]
|
**--lxc-conf**=[]
|
||||||
(lxc exec-driver only) Add custom lxc options --lxc-conf="lxc.cgroup.cpuset.cpus = 0,1"
|
(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.
|
Logging driver for container. Default is defined by daemon `--log-driver` flag.
|
||||||
**Warning**: `docker logs` command works only for `json-file` logging driver.
|
**Warning**: `docker logs` command works only for `json-file` logging driver.
|
||||||
|
|
||||||
|
|||||||
@@ -222,7 +222,7 @@ which interface and port to use.
|
|||||||
**--lxc-conf**=[]
|
**--lxc-conf**=[]
|
||||||
(lxc exec-driver only) Add custom lxc options --lxc-conf="lxc.cgroup.cpuset.cpus = 0,1"
|
(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.
|
Logging driver for container. Default is defined by daemon `--log-driver` flag.
|
||||||
**Warning**: `docker logs` command works only for `json-file` logging driver.
|
**Warning**: `docker logs` command works only for `json-file` logging driver.
|
||||||
|
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ unix://[/path/to/socket] to use.
|
|||||||
**--label**="[]"
|
**--label**="[]"
|
||||||
Set key=value labels to the daemon (displayed in `docker info`)
|
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`.
|
Container's logging driver. Default is `default`.
|
||||||
**Warning**: `docker logs` command works only for `json-file` logging driver.
|
**Warning**: `docker logs` command works only for `json-file` logging driver.
|
||||||
|
|
||||||
|
|||||||
|
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 |
|
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,12 +8,17 @@ page_keywords: Docker, Docker documentation, Windows, requirements, virtualbox,
|
|||||||
> Your processor needs to support hardware virtualization.
|
> Your processor needs to support hardware virtualization.
|
||||||
|
|
||||||
The Docker Engine uses Linux-specific kernel features, so to run it on Windows
|
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
|
we need to use a lightweight virtual machine (VM). You use the **Windows Docker
|
||||||
control the virtualized Docker Engine to build, run, and manage Docker containers.
|
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
|
To make this process easier, we've designed a helper application called
|
||||||
[Boot2Docker](https://github.com/boot2docker/boot2docker) that installs the
|
[Boot2Docker](https://github.com/boot2docker/boot2docker) creates a Linux virtual
|
||||||
virtual machine and runs the Docker daemon.
|
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
|
## Demonstration
|
||||||
|
|
||||||
@@ -21,18 +26,77 @@ virtual machine and runs the Docker daemon.
|
|||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
1. Download the latest release of the [Docker for Windows Installer](https://github.com/boot2docker/windows-installer/releases/latest)
|
1. Download the latest release of the
|
||||||
2. Run the installer, which will install VirtualBox, MSYS-git, the boot2docker Linux ISO,
|
[Docker for Windows Installer](https://github.com/boot2docker/windows-installer/releases/latest).
|
||||||
and the Boot2Docker management tool.
|
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
|
The Start script will ask you to enter an ssh key passphrase - the simplest
|
||||||
(but least secure) is to just hit [Enter].
|
(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
|
## Upgrading
|
||||||
|
|
||||||
@@ -47,46 +111,13 @@ and the Boot2Docker management tool.
|
|||||||
boot2docker download
|
boot2docker download
|
||||||
boot2docker start
|
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
|
## 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:
|
If you run a container with an exposed port:
|
||||||
|
|
||||||
@@ -101,3 +132,18 @@ Typically, it is 192.168.59.103, but it could get changed by Virtualbox's DHCP
|
|||||||
implementation.
|
implementation.
|
||||||
|
|
||||||
For further information or to report issues, please see the [Boot2Docker site](http://boot2docker.io)
|
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`.
|
||||||
|
|||||||
@@ -259,7 +259,7 @@ Json Parameters:
|
|||||||
`Ulimits: { "Name": "nofile", "Soft": 1024, "Hard", 2048 }}`
|
`Ulimits: { "Name": "nofile", "Soft": 1024, "Hard", 2048 }}`
|
||||||
- **LogConfig** - Logging configuration to container, format
|
- **LogConfig** - Logging configuration to container, format
|
||||||
`{ "Type": "<driver_name>", "Config": {"key1": "val1"}}
|
`{ "Type": "<driver_name>", "Config": {"key1": "val1"}}
|
||||||
Available types: `json-file`, `none`.
|
Available types: `json-file`, `syslog`, `none`.
|
||||||
`json-file` logging driver.
|
`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.
|
- **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.
|
||||||
|
|
||||||
|
|||||||
@@ -146,6 +146,17 @@ The instructions that handle environment variables in the `Dockerfile` are:
|
|||||||
`ONBUILD` instructions are **NOT** supported for environment replacement, even
|
`ONBUILD` instructions are **NOT** supported for environment replacement, even
|
||||||
the instructions above.
|
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
|
## The `.dockerignore` file
|
||||||
|
|
||||||
If a file named `.dockerignore` exists in the source repository, then it
|
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
|
- [Issue 783](https://github.com/docker/docker/issues/783) is about file
|
||||||
permissions problems that can occur when using the AUFS file system. You
|
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
|
might notice it during an attempt to `rm` a file, for example.
|
||||||
describes a workaround.
|
|
||||||
|
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
|
## CMD
|
||||||
|
|
||||||
|
|||||||
@@ -657,6 +657,11 @@ this driver.
|
|||||||
Default logging driver for Docker. Writes JSON messages to file. `docker logs`
|
Default logging driver for Docker. Writes JSON messages to file. `docker logs`
|
||||||
command is available only for this logging driver
|
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
|
## Overriding Dockerfile image defaults
|
||||||
|
|
||||||
When a developer builds an image from a [*Dockerfile*](/reference/builder)
|
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**
|
* **Unexpected File Permissions in Containers**
|
||||||
An idiosyncrasy in AUFS prevents permissions from propagating predictably
|
An idiosyncrasy in AUFS prevents permissions from propagating predictably
|
||||||
between upper and lower layers. This can cause issues with accessing private
|
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).
|
[Github Issue 783](https://github.com/docker/docker/issues/783).
|
||||||
|
|
||||||
* **Docker Hub incompatible with Safari 8**
|
* **Docker Hub incompatible with Safari 8**
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package graph
|
package graph
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"compress/gzip"
|
||||||
|
"crypto/sha256"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@@ -13,6 +15,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
log "github.com/Sirupsen/logrus"
|
||||||
|
"github.com/docker/distribution/digest"
|
||||||
"github.com/docker/docker/autogen/dockerversion"
|
"github.com/docker/docker/autogen/dockerversion"
|
||||||
"github.com/docker/docker/daemon/graphdriver"
|
"github.com/docker/docker/daemon/graphdriver"
|
||||||
"github.com/docker/docker/image"
|
"github.com/docker/docker/image"
|
||||||
@@ -241,18 +244,27 @@ func (graph *Graph) newTempFile() (*os.File, error) {
|
|||||||
return ioutil.TempFile(tmp, "")
|
return ioutil.TempFile(tmp, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func bufferToFile(f *os.File, src io.Reader) (int64, error) {
|
func bufferToFile(f *os.File, src io.Reader) (int64, digest.Digest, error) {
|
||||||
n, err := io.Copy(f, src)
|
var (
|
||||||
|
h = sha256.New()
|
||||||
|
w = gzip.NewWriter(io.MultiWriter(f, h))
|
||||||
|
)
|
||||||
|
_, err := io.Copy(w, src)
|
||||||
|
w.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return n, err
|
return 0, "", err
|
||||||
}
|
}
|
||||||
if err = f.Sync(); err != nil {
|
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 {
|
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
|
// setupInitLayer populates a directory with mountpoints suitable
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package graph
|
package graph
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/sha256"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -13,7 +12,6 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/distribution/digest"
|
|
||||||
"github.com/docker/docker/engine"
|
"github.com/docker/docker/engine"
|
||||||
"github.com/docker/docker/image"
|
"github.com/docker/docker/image"
|
||||||
"github.com/docker/docker/pkg/common"
|
"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())
|
os.Remove(tf.Name())
|
||||||
}()
|
}()
|
||||||
|
|
||||||
h := sha256.New()
|
size, dgst, err := bufferToFile(tf, arch)
|
||||||
size, err := bufferToFile(tf, io.TeeReader(arch, h))
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
dgst := digest.NewDigest("sha256", h)
|
|
||||||
|
|
||||||
// Send the layer
|
// Send the layer
|
||||||
log.Debugf("rendered layer for %s of [%d] size", img.ID, size)
|
log.Debugf("rendered layer for %s of [%d] size", img.ID, size)
|
||||||
|
|||||||
@@ -265,9 +265,6 @@ main() {
|
|||||||
bundle $SCRIPTDIR/make/$bundle
|
bundle $SCRIPTDIR/make/$bundle
|
||||||
echo
|
echo
|
||||||
done
|
done
|
||||||
|
|
||||||
# if we get all the way through successfully, let's delete our autogenerated code!
|
|
||||||
rm -r autogen
|
|
||||||
}
|
}
|
||||||
|
|
||||||
main "$@"
|
main "$@"
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ rm -rf src/github.com/docker/distribution
|
|||||||
mkdir -p src/github.com/docker/distribution
|
mkdir -p src/github.com/docker/distribution
|
||||||
mv tmp-digest src/github.com/docker/distribution/digest
|
mv tmp-digest src/github.com/docker/distribution/digest
|
||||||
|
|
||||||
clone git github.com/docker/libcontainer 4a72e540feb67091156b907c4700e580a99f5a9d
|
clone git github.com/docker/libcontainer c8512754166539461fd860451ff1a0af7491c197
|
||||||
# see src/github.com/docker/libcontainer/update-vendor.sh which is the "source of truth" for libcontainer deps (just like this file)
|
# 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
|
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')"
|
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")
|
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,
|
_, err := buildImage(name,
|
||||||
`
|
`
|
||||||
FROM scratch
|
FROM busybox
|
||||||
ENV foo foo
|
ENV foo zzz
|
||||||
ENV bar ${foo}
|
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)
|
`, true)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -262,13 +271,19 @@ func TestBuildEnvironmentReplacementEnv(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
found := false
|
found := false
|
||||||
|
envCount := 0
|
||||||
|
|
||||||
for _, env := range envResult {
|
for _, env := range envResult {
|
||||||
parts := strings.SplitN(env, "=", 2)
|
parts := strings.SplitN(env, "=", 2)
|
||||||
if parts[0] == "bar" {
|
if parts[0] == "bar" {
|
||||||
found = true
|
found = true
|
||||||
if parts[1] != "foo" {
|
if parts[1] != "zzz" {
|
||||||
t.Fatalf("Could not find replaced var for env `bar`: got %q instead of `foo`", parts[1])
|
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")
|
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")
|
logDone("build - env environment replacement")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -363,8 +382,8 @@ func TestBuildHandleEscapes(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := result[`\\\\\\${FOO}`]; !ok {
|
if _, ok := result[`\\\${FOO}`]; !ok {
|
||||||
t.Fatal(`Could not find volume \\\\\\${FOO} set from env foo in volumes table`)
|
t.Fatal(`Could not find volume \\\${FOO} set from env foo in volumes table`, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
logDone("build - handle escapes")
|
logDone("build - handle escapes")
|
||||||
@@ -1939,6 +1958,7 @@ func TestBuildCancelationKillsSleep(t *testing.T) {
|
|||||||
|
|
||||||
name := "testbuildcancelation"
|
name := "testbuildcancelation"
|
||||||
defer deleteImages(name)
|
defer deleteImages(name)
|
||||||
|
defer deleteAllContainers()
|
||||||
|
|
||||||
// (Note: one year, will never finish)
|
// (Note: one year, will never finish)
|
||||||
ctx, err := fakeContext("FROM busybox\nRUN sleep 31536000", nil)
|
ctx, err := fakeContext("FROM busybox\nRUN sleep 31536000", nil)
|
||||||
@@ -2256,7 +2276,7 @@ func TestBuildRelativeWorkdir(t *testing.T) {
|
|||||||
|
|
||||||
func TestBuildWorkdirWithEnvVariables(t *testing.T) {
|
func TestBuildWorkdirWithEnvVariables(t *testing.T) {
|
||||||
name := "testbuildworkdirwithenvvariables"
|
name := "testbuildworkdirwithenvvariables"
|
||||||
expected := "/test1/test2/$MISSING_VAR"
|
expected := "/test1/test2"
|
||||||
defer deleteImages(name)
|
defer deleteImages(name)
|
||||||
_, err := buildImage(name,
|
_, err := buildImage(name,
|
||||||
`FROM busybox
|
`FROM busybox
|
||||||
@@ -4025,9 +4045,9 @@ ENV abc=zzz TO=/docker/world/hello
|
|||||||
ADD $FROM $TO
|
ADD $FROM $TO
|
||||||
RUN [ "$(cat $TO)" = "hello" ]
|
RUN [ "$(cat $TO)" = "hello" ]
|
||||||
ENV abc "zzz"
|
ENV abc "zzz"
|
||||||
RUN [ $abc = \"zzz\" ]
|
RUN [ $abc = "zzz" ]
|
||||||
ENV abc 'yyy'
|
ENV abc 'yyy'
|
||||||
RUN [ $abc = \'yyy\' ]
|
RUN [ $abc = 'yyy' ]
|
||||||
ENV abc=
|
ENV abc=
|
||||||
RUN [ "$abc" = "" ]
|
RUN [ "$abc" = "" ]
|
||||||
|
|
||||||
@@ -4043,13 +4063,34 @@ RUN [ "$abc" = "'foo'" ]
|
|||||||
ENV abc=\"foo\"
|
ENV abc=\"foo\"
|
||||||
RUN [ "$abc" = "\"foo\"" ]
|
RUN [ "$abc" = "\"foo\"" ]
|
||||||
ENV abc "foo"
|
ENV abc "foo"
|
||||||
RUN [ "$abc" = "\"foo\"" ]
|
RUN [ "$abc" = "foo" ]
|
||||||
ENV abc 'foo'
|
ENV abc 'foo'
|
||||||
RUN [ "$abc" = "'foo'" ]
|
RUN [ "$abc" = 'foo' ]
|
||||||
ENV abc \'foo\'
|
ENV abc \'foo\'
|
||||||
RUN [ "$abc" = "\\'foo\\'" ]
|
RUN [ "$abc" = "'foo'" ]
|
||||||
ENV 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{
|
ctx, err := fakeContext(dockerfile, map[string]string{
|
||||||
"hello/docker/world": "hello",
|
"hello/docker/world": "hello",
|
||||||
|
|||||||
@@ -55,8 +55,6 @@ func TestPullImageWithAliases(t *testing.T) {
|
|||||||
|
|
||||||
// pulling library/hello-world should show verified message
|
// pulling library/hello-world should show verified message
|
||||||
func TestPullVerified(t *testing.T) {
|
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
|
// Image must be pulled from central repository to get verified message
|
||||||
// unless keychain is manually updated to contain the daemon's sign key.
|
// 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) {
|
func TestRunModeNetContainerHostname(t *testing.T) {
|
||||||
|
testRequires(t, ExecSupport)
|
||||||
defer deleteAllContainers()
|
defer deleteAllContainers()
|
||||||
cmd := exec.Command(dockerBinary, "run", "-i", "-d", "--name", "parent", "busybox", "top")
|
cmd := exec.Command(dockerBinary, "run", "-i", "-d", "--name", "parent", "busybox", "top")
|
||||||
out, _, err := runCommandWithOutput(cmd)
|
out, _, err := runCommandWithOutput(cmd)
|
||||||
@@ -475,6 +476,39 @@ func TestRunWithVolumesFromExited(t *testing.T) {
|
|||||||
logDone("run - regression test for #4979 - volumes-from on exited container")
|
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
|
// Regression test for #4830
|
||||||
func TestRunWithRelativePath(t *testing.T) {
|
func TestRunWithRelativePath(t *testing.T) {
|
||||||
defer deleteAllContainers()
|
defer deleteAllContainers()
|
||||||
|
|||||||
@@ -240,3 +240,49 @@ func TestStartMultipleContainers(t *testing.T) {
|
|||||||
|
|
||||||
logDone("start - start multiple containers continue on one failed")
|
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"
|
privateRegistryURL = "127.0.0.1:5000"
|
||||||
|
|
||||||
dockerBasePath = "/var/lib/docker"
|
dockerBasePath = "/var/lib/docker"
|
||||||
execDriverPath = dockerBasePath + "/execdriver/native"
|
|
||||||
volumesConfigPath = dockerBasePath + "/volumes"
|
volumesConfigPath = dockerBasePath + "/volumes"
|
||||||
volumesStoragePath = dockerBasePath + "/vfs/dir"
|
volumesStoragePath = dockerBasePath + "/vfs/dir"
|
||||||
containerStoragePath = dockerBasePath + "/containers"
|
containerStoragePath = dockerBasePath + "/containers"
|
||||||
|
|
||||||
|
runtimePath = "/var/run/docker"
|
||||||
|
execDriverPath = runtimePath + "/execdriver/native"
|
||||||
|
|
||||||
workingDirectory string
|
workingDirectory string
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,6 @@ const (
|
|||||||
// identifies if test suite is running on a unix platform
|
// identifies if test suite is running on a unix platform
|
||||||
isUnixCli = false
|
isUnixCli = false
|
||||||
|
|
||||||
// this is the expected file permission set on windows: gh#11047
|
// this is the expected file permission set on windows: gh#11395
|
||||||
expectedFileChmod = "-rwx------"
|
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
|
// chmodTarEntry is used to adjust the file permissions used in tar header based
|
||||||
// on the platform the archival is done.
|
// on the platform the archival is done.
|
||||||
func chmodTarEntry(perm os.FileMode) os.FileMode {
|
func chmodTarEntry(perm os.FileMode) os.FileMode {
|
||||||
// Clear r/w on grp/others: no precise equivalen of group/others on NTFS.
|
perm &= 0755
|
||||||
perm &= 0711
|
|
||||||
// Add the x bit: make everything +x from windows
|
// Add the x bit: make everything +x from windows
|
||||||
perm |= 0100
|
perm |= 0111
|
||||||
|
|
||||||
return perm
|
return perm
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,11 +51,11 @@ func TestChmodTarEntry(t *testing.T) {
|
|||||||
cases := []struct {
|
cases := []struct {
|
||||||
in, expected os.FileMode
|
in, expected os.FileMode
|
||||||
}{
|
}{
|
||||||
{0000, 0100},
|
{0000, 0111},
|
||||||
{0777, 0711},
|
{0777, 0755},
|
||||||
{0644, 0700},
|
{0644, 0755},
|
||||||
{0755, 0711},
|
{0755, 0755},
|
||||||
{0444, 0500},
|
{0444, 0555},
|
||||||
}
|
}
|
||||||
for _, v := range cases {
|
for _, v := range cases {
|
||||||
if out := chmodTarEntry(v.in); out != v.expected {
|
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.Gid() != newStat.Gid() ||
|
||||||
oldStat.Rdev() != newStat.Rdev() ||
|
oldStat.Rdev() != newStat.Rdev() ||
|
||||||
// Don't look at size for dirs, its not a good measure of change
|
// 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) ||
|
(oldStat.Mode()&syscall.S_IFDIR != syscall.S_IFDIR &&
|
||||||
!sameFsTimeSpec(oldStat.Mtim(), newStat.Mtim()) ||
|
(!sameFsTimeSpec(oldStat.Mtim(), newStat.Mtim()) || (oldStat.Size() != newStat.Size()))) ||
|
||||||
bytes.Compare(oldChild.capability, newChild.capability) != 0 {
|
bytes.Compare(oldChild.capability, newChild.capability) != 0 {
|
||||||
change := Change{
|
change := Change{
|
||||||
Path: newChild.path(),
|
Path: newChild.path(),
|
||||||
|
|||||||
@@ -218,7 +218,6 @@ func TestChangesDirsMutated(t *testing.T) {
|
|||||||
expectedChanges := []Change{
|
expectedChanges := []Change{
|
||||||
{"/dir1", ChangeDelete},
|
{"/dir1", ChangeDelete},
|
||||||
{"/dir2", ChangeModify},
|
{"/dir2", ChangeModify},
|
||||||
{"/dir3", ChangeModify},
|
|
||||||
{"/dirnew", ChangeAdd},
|
{"/dirnew", ChangeAdd},
|
||||||
{"/file1", ChangeDelete},
|
{"/file1", ChangeDelete},
|
||||||
{"/file2", ChangeModify},
|
{"/file2", ChangeModify},
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ func (w *BroadcastWriter) Write(p []byte) (n int, err error) {
|
|||||||
for {
|
for {
|
||||||
line, err := w.buf.ReadString('\n')
|
line, err := w.buf.ReadString('\n')
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.buf.Write([]byte(line))
|
w.buf.WriteString(line)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
for stream, writers := range w.streams {
|
for stream, writers := range w.streams {
|
||||||
|
|||||||
@@ -2,8 +2,11 @@ package ioutils
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/rand"
|
||||||
"io"
|
"io"
|
||||||
|
"math/big"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type readCloserWrapper struct {
|
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 {
|
type bufReader struct {
|
||||||
sync.Mutex
|
sync.Mutex
|
||||||
buf *bytes.Buffer
|
buf *bytes.Buffer
|
||||||
reader io.Reader
|
reader io.Reader
|
||||||
err error
|
err error
|
||||||
wait sync.Cond
|
wait sync.Cond
|
||||||
drainBuf []byte
|
drainBuf []byte
|
||||||
|
reuseBuf []byte
|
||||||
|
maxReuse int64
|
||||||
|
resetTimeout time.Duration
|
||||||
|
bufLenResetThreshold int64
|
||||||
|
maxReadDataReset int64
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewBufReader(r io.Reader) *bufReader {
|
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{
|
reader := &bufReader{
|
||||||
buf: &bytes.Buffer{},
|
buf: &bytes.Buffer{},
|
||||||
drainBuf: make([]byte, 1024),
|
drainBuf: make([]byte, 1024),
|
||||||
reader: r,
|
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
|
reader.wait.L = &reader.Mutex
|
||||||
go reader.drain()
|
go reader.drain()
|
||||||
@@ -74,14 +97,94 @@ func NewBufReaderWithDrainbufAndBuffer(r io.Reader, drainBuffer []byte, buffer *
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *bufReader) drain() {
|
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 {
|
for {
|
||||||
n, err := r.reader.Read(r.drainBuf)
|
n, err := r.reader.Read(r.drainBuf)
|
||||||
|
dataSinceReset += int64(n)
|
||||||
r.Lock()
|
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 {
|
if err != nil {
|
||||||
r.err = err
|
r.err = err
|
||||||
} else {
|
} else {
|
||||||
r.buf.Write(r.drainBuf[0:n])
|
r.buf.Write(r.drainBuf[0:n])
|
||||||
}
|
}
|
||||||
|
reuseCount++
|
||||||
r.wait.Signal()
|
r.wait.Signal()
|
||||||
r.Unlock()
|
r.Unlock()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -32,3 +32,61 @@ func TestBufReader(t *testing.T) {
|
|||||||
t.Error(string(output))
|
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
|
return read, err
|
||||||
}
|
}
|
||||||
func (config *Config) Close() error {
|
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}))
|
config.Out.Write(config.Formatter.FormatProg(config.ID, config.Action, &JSONProg{Current: config.Current, Total: config.Size}))
|
||||||
return config.In.Close()
|
return config.In.Close()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package term
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/docker/docker/pkg/term/winconsole"
|
"github.com/docker/docker/pkg/term/winconsole"
|
||||||
)
|
)
|
||||||
@@ -114,5 +115,23 @@ func GetFdInfo(in interface{}) (uintptr, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func StdStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) {
|
func StdStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) {
|
||||||
return winconsole.StdStreams()
|
var shouldEmulateANSI bool
|
||||||
|
switch {
|
||||||
|
case os.Getenv("ConEmuANSI") == "ON":
|
||||||
|
// ConEmu shell, ansi emulated by default and ConEmu does an extensively
|
||||||
|
// good emulation.
|
||||||
|
shouldEmulateANSI = false
|
||||||
|
case os.Getenv("MSYSTEM") != "":
|
||||||
|
// MSYS (mingw) cannot fully emulate well and still shows escape characters
|
||||||
|
// mostly because it's still running on cmd.exe window.
|
||||||
|
shouldEmulateANSI = true
|
||||||
|
default:
|
||||||
|
shouldEmulateANSI = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if shouldEmulateANSI {
|
||||||
|
return winconsole.StdStreams()
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.Stdin, os.Stdout, os.Stderr
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -241,8 +241,6 @@ func StdStreams() (stdIn io.ReadCloser, stdOut io.Writer, stdErr io.Writer) {
|
|||||||
}
|
}
|
||||||
handler.screenBufferInfo = screenBufferInfo
|
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)
|
buffer = make([]CHAR_INFO, screenBufferInfo.MaximumWindowSize.X*screenBufferInfo.MaximumWindowSize.Y)
|
||||||
|
|
||||||
stdOut = &terminalWriter{
|
stdOut = &terminalWriter{
|
||||||
@@ -283,6 +281,12 @@ func GetHandleInfo(in interface{}) (uintptr, bool) {
|
|||||||
isTerminalIn = IsTerminal(inFd)
|
isTerminalIn = IsTerminal(inFd)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if tr, ok := in.(*terminalWriter); ok {
|
||||||
|
if file, ok := tr.wrappedWriter.(*os.File); ok {
|
||||||
|
inFd = file.Fd()
|
||||||
|
isTerminalIn = IsTerminal(inFd)
|
||||||
|
}
|
||||||
|
}
|
||||||
return inFd, isTerminalIn
|
return inFd, isTerminalIn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
source "$(dirname "$BASH_SOURCE")/.validate"
|
||||||
|
|
||||||
|
IFS=$'\n'
|
||||||
|
files=( $(validate_diff --diff-filter=ACMR --name-only -- 'MAINTAINERS' || true) )
|
||||||
|
unset IFS
|
||||||
|
|
||||||
|
badFiles=()
|
||||||
|
for f in "${files[@]}"; do
|
||||||
|
# we use "git show" here to validate that what's committed is formatted
|
||||||
|
if [ "$(git show "$VALIDATE_HEAD:$f" | tomlv)" ]; then
|
||||||
|
badFiles+=( "$f" )
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ ${#badFiles[@]} -eq 0 ]; then
|
||||||
|
echo 'Congratulations! All toml source files have valid syntax.'
|
||||||
|
else
|
||||||
|
{
|
||||||
|
echo "These files are not valid toml:"
|
||||||
|
for f in "${badFiles[@]}"; do
|
||||||
|
echo " - $f"
|
||||||
|
done
|
||||||
|
echo
|
||||||
|
echo 'Please reformat the above files as valid toml'
|
||||||
|
echo
|
||||||
|
} >&2
|
||||||
|
false
|
||||||
|
fi
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
package registry
|
package registry
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
@@ -71,21 +70,7 @@ func (auth *RequestAuthorization) getToken() (string, error) {
|
|||||||
return auth.tokenCache, nil
|
return auth.tokenCache, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
tlsConfig := tls.Config{
|
client := auth.registryEndpoint.HTTPClient()
|
||||||
MinVersion: tls.VersionTLS10,
|
|
||||||
}
|
|
||||||
if !auth.registryEndpoint.IsSecure {
|
|
||||||
tlsConfig.InsecureSkipVerify = true
|
|
||||||
}
|
|
||||||
|
|
||||||
client := &http.Client{
|
|
||||||
Transport: &http.Transport{
|
|
||||||
DisableKeepAlives: true,
|
|
||||||
Proxy: http.ProxyFromEnvironment,
|
|
||||||
TLSClientConfig: &tlsConfig,
|
|
||||||
},
|
|
||||||
CheckRedirect: AddRequiredHeadersToRedirectedRequests,
|
|
||||||
}
|
|
||||||
factory := HTTPRequestFactory(nil)
|
factory := HTTPRequestFactory(nil)
|
||||||
|
|
||||||
for _, challenge := range auth.registryEndpoint.AuthChallenges {
|
for _, challenge := range auth.registryEndpoint.AuthChallenges {
|
||||||
@@ -252,16 +237,10 @@ func Login(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *utils.HT
|
|||||||
// loginV1 tries to register/login to the v1 registry server.
|
// loginV1 tries to register/login to the v1 registry server.
|
||||||
func loginV1(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *utils.HTTPRequestFactory) (string, error) {
|
func loginV1(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *utils.HTTPRequestFactory) (string, error) {
|
||||||
var (
|
var (
|
||||||
status string
|
status string
|
||||||
reqBody []byte
|
reqBody []byte
|
||||||
err error
|
err error
|
||||||
client = &http.Client{
|
client = registryEndpoint.HTTPClient()
|
||||||
Transport: &http.Transport{
|
|
||||||
DisableKeepAlives: true,
|
|
||||||
Proxy: http.ProxyFromEnvironment,
|
|
||||||
},
|
|
||||||
CheckRedirect: AddRequiredHeadersToRedirectedRequests,
|
|
||||||
}
|
|
||||||
reqStatusCode = 0
|
reqStatusCode = 0
|
||||||
serverAddress = authConfig.ServerAddress
|
serverAddress = authConfig.ServerAddress
|
||||||
)
|
)
|
||||||
@@ -285,7 +264,7 @@ func loginV1(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *utils.
|
|||||||
|
|
||||||
// using `bytes.NewReader(jsonBody)` here causes the server to respond with a 411 status.
|
// using `bytes.NewReader(jsonBody)` here causes the server to respond with a 411 status.
|
||||||
b := strings.NewReader(string(jsonBody))
|
b := strings.NewReader(string(jsonBody))
|
||||||
req1, err := http.Post(serverAddress+"users/", "application/json; charset=utf-8", b)
|
req1, err := client.Post(serverAddress+"users/", "application/json; charset=utf-8", b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("Server Error: %s", err)
|
return "", fmt.Errorf("Server Error: %s", err)
|
||||||
}
|
}
|
||||||
@@ -371,26 +350,10 @@ func loginV1(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *utils.
|
|||||||
// is to be determined.
|
// is to be determined.
|
||||||
func loginV2(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *utils.HTTPRequestFactory) (string, error) {
|
func loginV2(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *utils.HTTPRequestFactory) (string, error) {
|
||||||
log.Debugf("attempting v2 login to registry endpoint %s", registryEndpoint)
|
log.Debugf("attempting v2 login to registry endpoint %s", registryEndpoint)
|
||||||
|
|
||||||
tlsConfig := tls.Config{
|
|
||||||
MinVersion: tls.VersionTLS10,
|
|
||||||
}
|
|
||||||
if !registryEndpoint.IsSecure {
|
|
||||||
tlsConfig.InsecureSkipVerify = true
|
|
||||||
}
|
|
||||||
|
|
||||||
client := &http.Client{
|
|
||||||
Transport: &http.Transport{
|
|
||||||
DisableKeepAlives: true,
|
|
||||||
Proxy: http.ProxyFromEnvironment,
|
|
||||||
TLSClientConfig: &tlsConfig,
|
|
||||||
},
|
|
||||||
CheckRedirect: AddRequiredHeadersToRedirectedRequests,
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
allErrors []error
|
allErrors []error
|
||||||
|
client = registryEndpoint.HTTPClient()
|
||||||
)
|
)
|
||||||
|
|
||||||
for _, challenge := range registryEndpoint.AuthChallenges {
|
for _, challenge := range registryEndpoint.AuthChallenges {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package registry
|
package registry
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@@ -262,3 +263,20 @@ HeaderLoop:
|
|||||||
|
|
||||||
return RegistryInfo{}, fmt.Errorf("v2 registry endpoint returned status %d: %q", resp.StatusCode, http.StatusText(resp.StatusCode))
|
return RegistryInfo{}, fmt.Errorf("v2 registry endpoint returned status %d: %q", resp.StatusCode, http.StatusText(resp.StatusCode))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *Endpoint) HTTPClient() *http.Client {
|
||||||
|
tlsConfig := tls.Config{
|
||||||
|
MinVersion: tls.VersionTLS10,
|
||||||
|
}
|
||||||
|
if !e.IsSecure {
|
||||||
|
tlsConfig.InsecureSkipVerify = true
|
||||||
|
}
|
||||||
|
return &http.Client{
|
||||||
|
Transport: &http.Transport{
|
||||||
|
DisableKeepAlives: true,
|
||||||
|
Proxy: http.ProxyFromEnvironment,
|
||||||
|
TLSClientConfig: &tlsConfig,
|
||||||
|
},
|
||||||
|
CheckRedirect: AddRequiredHeadersToRedirectedRequests,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -511,6 +511,10 @@ func (r *Session) PushImageJSONIndex(remote string, imgList []*ImgData, validate
|
|||||||
}
|
}
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
if res.StatusCode == 401 {
|
||||||
|
return nil, errLoginRequired
|
||||||
|
}
|
||||||
|
|
||||||
var tokens, endpoints []string
|
var tokens, endpoints []string
|
||||||
if !validate {
|
if !validate {
|
||||||
if res.StatusCode != 200 && res.StatusCode != 201 {
|
if res.StatusCode != 200 && res.StatusCode != 201 {
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ type Config struct {
|
|||||||
NetworkDisabled bool
|
NetworkDisabled bool
|
||||||
MacAddress string
|
MacAddress string
|
||||||
OnBuild []string
|
OnBuild []string
|
||||||
SecurityOpt []string
|
|
||||||
Labels map[string]string
|
Labels map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -99,12 +99,11 @@ func (m *Manager) Apply(pid int) error {
|
|||||||
// created then join consists of writing the process pids to cgroup.procs
|
// created then join consists of writing the process pids to cgroup.procs
|
||||||
p, err := d.path(name)
|
p, err := d.path(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if cgroups.IsNotFound(err) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !cgroups.PathExists(p) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
paths[name] = p
|
paths[name] = p
|
||||||
}
|
}
|
||||||
m.Paths = paths
|
m.Paths = paths
|
||||||
@@ -174,9 +173,6 @@ func (m *Manager) Freeze(state configs.FreezerState) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !cgroups.PathExists(dir) {
|
|
||||||
return cgroups.NewNotFoundError("freezer")
|
|
||||||
}
|
|
||||||
|
|
||||||
prevState := m.Cgroups.Freezer
|
prevState := m.Cgroups.Freezer
|
||||||
m.Cgroups.Freezer = state
|
m.Cgroups.Freezer = state
|
||||||
@@ -201,9 +197,6 @@ func (m *Manager) GetPids() ([]int, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if !cgroups.PathExists(dir) {
|
|
||||||
return nil, cgroups.NewNotFoundError("devices")
|
|
||||||
}
|
|
||||||
|
|
||||||
return cgroups.ReadProcsFile(dir)
|
return cgroups.ReadProcsFile(dir)
|
||||||
}
|
}
|
||||||
@@ -227,16 +220,16 @@ func getCgroupData(c *configs.Cgroup, pid int) (*data, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (raw *data) parent(subsystem string) (string, error) {
|
func (raw *data) parent(subsystem, mountpoint string) (string, error) {
|
||||||
initPath, err := cgroups.GetInitCgroupDir(subsystem)
|
initPath, err := cgroups.GetInitCgroupDir(subsystem)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return filepath.Join(raw.root, subsystem, initPath), nil
|
return filepath.Join(mountpoint, initPath), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (raw *data) path(subsystem string) (string, error) {
|
func (raw *data) path(subsystem string) (string, error) {
|
||||||
_, err := cgroups.FindCgroupMountpoint(subsystem)
|
mnt, err := cgroups.FindCgroupMountpoint(subsystem)
|
||||||
// If we didn't mount the subsystem, there is no point we make the path.
|
// If we didn't mount the subsystem, there is no point we make the path.
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@@ -247,7 +240,7 @@ func (raw *data) path(subsystem string) (string, error) {
|
|||||||
return filepath.Join(raw.root, subsystem, raw.cgroup), nil
|
return filepath.Join(raw.root, subsystem, raw.cgroup), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
parent, err := raw.parent(subsystem)
|
parent, err := raw.parent(subsystem, mnt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,12 +17,8 @@ type BlkioGroup struct {
|
|||||||
|
|
||||||
func (s *BlkioGroup) Apply(d *data) error {
|
func (s *BlkioGroup) Apply(d *data) error {
|
||||||
dir, err := d.join("blkio")
|
dir, err := d.join("blkio")
|
||||||
if err != nil {
|
if err != nil && !cgroups.IsNotFound(err) {
|
||||||
if cgroups.IsNotFound(err) {
|
return err
|
||||||
return nil
|
|
||||||
} else {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.Set(dir, d.c); err != nil {
|
if err := s.Set(dir, d.c); err != nil {
|
||||||
|
|||||||
@@ -18,11 +18,7 @@ func (s *CpuGroup) Apply(d *data) error {
|
|||||||
// on a container basis
|
// on a container basis
|
||||||
dir, err := d.join("cpu")
|
dir, err := d.join("cpu")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if cgroups.IsNotFound(err) {
|
return err
|
||||||
return nil
|
|
||||||
} else {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.Set(dir, d.c); err != nil {
|
if err := s.Set(dir, d.c); err != nil {
|
||||||
|
|||||||
@@ -11,11 +11,7 @@ type DevicesGroup struct {
|
|||||||
func (s *DevicesGroup) Apply(d *data) error {
|
func (s *DevicesGroup) Apply(d *data) error {
|
||||||
dir, err := d.join("devices")
|
dir, err := d.join("devices")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if cgroups.IsNotFound(err) {
|
return err
|
||||||
return nil
|
|
||||||
} else {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.Set(dir, d.c); err != nil {
|
if err := s.Set(dir, d.c); err != nil {
|
||||||
|
|||||||
@@ -13,12 +13,8 @@ type FreezerGroup struct {
|
|||||||
|
|
||||||
func (s *FreezerGroup) Apply(d *data) error {
|
func (s *FreezerGroup) Apply(d *data) error {
|
||||||
dir, err := d.join("freezer")
|
dir, err := d.join("freezer")
|
||||||
if err != nil {
|
if err != nil && !cgroups.IsNotFound(err) {
|
||||||
if cgroups.IsNotFound(err) {
|
return err
|
||||||
return nil
|
|
||||||
} else {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.Set(dir, d.c); err != nil {
|
if err := s.Set(dir, d.c); err != nil {
|
||||||
|
|||||||
@@ -16,12 +16,9 @@ type MemoryGroup struct {
|
|||||||
|
|
||||||
func (s *MemoryGroup) Apply(d *data) error {
|
func (s *MemoryGroup) Apply(d *data) error {
|
||||||
dir, err := d.join("memory")
|
dir, err := d.join("memory")
|
||||||
if err != nil {
|
// only return an error for memory if it was specified
|
||||||
if cgroups.IsNotFound(err) {
|
if err != nil && (d.c.Memory != 0 || d.c.MemoryReservation != 0 || d.c.MemorySwap != 0) {
|
||||||
return nil
|
return err
|
||||||
} else {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -43,6 +43,10 @@ var subsystems = map[string]subsystem{
|
|||||||
"freezer": &fs.FreezerGroup{},
|
"freezer": &fs.FreezerGroup{},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
testScopeWait = 4
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
connLock sync.Mutex
|
connLock sync.Mutex
|
||||||
theConn *systemd.Conn
|
theConn *systemd.Conn
|
||||||
@@ -86,16 +90,41 @@ func UseSystemd() bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure the scope name we use doesn't exist. Use the Pid to
|
||||||
|
// avoid collisions between multiple libcontainer users on a
|
||||||
|
// single host.
|
||||||
|
scope := fmt.Sprintf("libcontainer-%d-systemd-test-default-dependencies.scope", os.Getpid())
|
||||||
|
testScopeExists := true
|
||||||
|
for i := 0; i <= testScopeWait; i++ {
|
||||||
|
if _, err := theConn.StopUnit(scope, "replace"); err != nil {
|
||||||
|
if dbusError, ok := err.(dbus.Error); ok {
|
||||||
|
if strings.Contains(dbusError.Name, "org.freedesktop.systemd1.NoSuchUnit") {
|
||||||
|
testScopeExists = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
time.Sleep(time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bail out if we can't kill this scope without testing for DefaultDependencies
|
||||||
|
if testScopeExists {
|
||||||
|
return hasStartTransientUnit
|
||||||
|
}
|
||||||
|
|
||||||
// Assume StartTransientUnit on a scope allows DefaultDependencies
|
// Assume StartTransientUnit on a scope allows DefaultDependencies
|
||||||
hasTransientDefaultDependencies = true
|
hasTransientDefaultDependencies = true
|
||||||
ddf := newProp("DefaultDependencies", false)
|
ddf := newProp("DefaultDependencies", false)
|
||||||
if _, err := theConn.StartTransientUnit("docker-systemd-test-default-dependencies.scope", "replace", ddf); err != nil {
|
if _, err := theConn.StartTransientUnit(scope, "replace", ddf); err != nil {
|
||||||
if dbusError, ok := err.(dbus.Error); ok {
|
if dbusError, ok := err.(dbus.Error); ok {
|
||||||
if dbusError.Name == "org.freedesktop.DBus.Error.PropertyReadOnly" {
|
if strings.Contains(dbusError.Name, "org.freedesktop.DBus.Error.PropertyReadOnly") {
|
||||||
hasTransientDefaultDependencies = false
|
hasTransientDefaultDependencies = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Not critical because of the stop unit logic above.
|
||||||
|
theConn.StopUnit(scope, "replace")
|
||||||
}
|
}
|
||||||
return hasStartTransientUnit
|
return hasStartTransientUnit
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -193,12 +193,13 @@ func (c *linuxContainer) newSetnsProcess(p *Process, cmd *exec.Cmd, parentPipe,
|
|||||||
|
|
||||||
func (c *linuxContainer) newInitConfig(process *Process) *initConfig {
|
func (c *linuxContainer) newInitConfig(process *Process) *initConfig {
|
||||||
return &initConfig{
|
return &initConfig{
|
||||||
Config: c.config,
|
Config: c.config,
|
||||||
Args: process.Args,
|
Args: process.Args,
|
||||||
Env: process.Env,
|
Env: process.Env,
|
||||||
User: process.User,
|
User: process.User,
|
||||||
Cwd: process.Cwd,
|
Cwd: process.Cwd,
|
||||||
Console: process.consolePath,
|
Console: process.consolePath,
|
||||||
|
Capabilities: process.Capabilities,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,13 +40,14 @@ type network struct {
|
|||||||
|
|
||||||
// initConfig is used for transferring parameters from Exec() to Init()
|
// initConfig is used for transferring parameters from Exec() to Init()
|
||||||
type initConfig struct {
|
type initConfig struct {
|
||||||
Args []string `json:"args"`
|
Args []string `json:"args"`
|
||||||
Env []string `json:"env"`
|
Env []string `json:"env"`
|
||||||
Cwd string `json:"cwd"`
|
Cwd string `json:"cwd"`
|
||||||
User string `json:"user"`
|
Capabilities []string `json:"capabilities"`
|
||||||
Config *configs.Config `json:"config"`
|
User string `json:"user"`
|
||||||
Console string `json:"console"`
|
Config *configs.Config `json:"config"`
|
||||||
Networks []*network `json:"network"`
|
Console string `json:"console"`
|
||||||
|
Networks []*network `json:"network"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type initer interface {
|
type initer interface {
|
||||||
@@ -91,7 +92,7 @@ func populateProcessEnvironment(env []string) error {
|
|||||||
|
|
||||||
// finalizeNamespace drops the caps, sets the correct user
|
// finalizeNamespace drops the caps, sets the correct user
|
||||||
// and working dir, and closes any leaked file descriptors
|
// and working dir, and closes any leaked file descriptors
|
||||||
// before execing the command inside the namespace
|
// before executing the command inside the namespace
|
||||||
func finalizeNamespace(config *initConfig) error {
|
func finalizeNamespace(config *initConfig) error {
|
||||||
// Ensure that all non-standard fds we may have accidentally
|
// Ensure that all non-standard fds we may have accidentally
|
||||||
// inherited are marked close-on-exec so they stay out of the
|
// inherited are marked close-on-exec so they stay out of the
|
||||||
@@ -99,7 +100,12 @@ func finalizeNamespace(config *initConfig) error {
|
|||||||
if err := utils.CloseExecFrom(3); err != nil {
|
if err := utils.CloseExecFrom(3); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
w, err := newCapWhitelist(config.Config.Capabilities)
|
|
||||||
|
capabilities := config.Config.Capabilities
|
||||||
|
if config.Capabilities != nil {
|
||||||
|
capabilities = config.Capabilities
|
||||||
|
}
|
||||||
|
w, err := newCapWhitelist(capabilities)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -395,6 +396,90 @@ func TestProcessEnv(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestProcessCaps(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
root, err := newTestRoot()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(root)
|
||||||
|
|
||||||
|
rootfs, err := newRootfs()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer remove(rootfs)
|
||||||
|
|
||||||
|
config := newTemplateConfig(rootfs)
|
||||||
|
|
||||||
|
factory, err := libcontainer.New(root, libcontainer.Cgroupfs)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
container, err := factory.Create("test", config)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer container.Destroy()
|
||||||
|
|
||||||
|
processCaps := append(config.Capabilities, "NET_ADMIN")
|
||||||
|
|
||||||
|
var stdout bytes.Buffer
|
||||||
|
pconfig := libcontainer.Process{
|
||||||
|
Args: []string{"sh", "-c", "cat /proc/self/status"},
|
||||||
|
Env: standardEnvironment,
|
||||||
|
Capabilities: processCaps,
|
||||||
|
Stdin: nil,
|
||||||
|
Stdout: &stdout,
|
||||||
|
}
|
||||||
|
err = container.Start(&pconfig)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for process
|
||||||
|
waitProcess(&pconfig, t)
|
||||||
|
|
||||||
|
outputStatus := string(stdout.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
lines := strings.Split(outputStatus, "\n")
|
||||||
|
|
||||||
|
effectiveCapsLine := ""
|
||||||
|
for _, l := range lines {
|
||||||
|
line := strings.TrimSpace(l)
|
||||||
|
if strings.Contains(line, "CapEff:") {
|
||||||
|
effectiveCapsLine = line
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if effectiveCapsLine == "" {
|
||||||
|
t.Fatal("Couldn't find effective caps: ", outputStatus)
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.Split(effectiveCapsLine, ":")
|
||||||
|
effectiveCapsStr := strings.TrimSpace(parts[1])
|
||||||
|
|
||||||
|
effectiveCaps, err := strconv.ParseUint(effectiveCapsStr, 16, 64)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Could not parse effective caps", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var netAdminMask uint64
|
||||||
|
var netAdminBit uint
|
||||||
|
netAdminBit = 12 // from capability.h
|
||||||
|
netAdminMask = 1 << netAdminBit
|
||||||
|
if effectiveCaps&netAdminMask != netAdminMask {
|
||||||
|
t.Fatal("CAP_NET_ADMIN is not set as expected")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestFreeze(t *testing.T) {
|
func TestFreeze(t *testing.T) {
|
||||||
if testing.Short() {
|
if testing.Short() {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -659,7 +659,7 @@ func networkSetNsAction(iface *net.Interface, rtattr *RtAttr) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Move a particular network interface to a particular network namespace
|
// Move a particular network interface to a particular network namespace
|
||||||
// specified by PID. This is idential to running: ip link set dev $name netns $pid
|
// specified by PID. This is identical to running: ip link set dev $name netns $pid
|
||||||
func NetworkSetNsPid(iface *net.Interface, nspid int) error {
|
func NetworkSetNsPid(iface *net.Interface, nspid int) error {
|
||||||
data := uint32Attr(syscall.IFLA_NET_NS_PID, uint32(nspid))
|
data := uint32Attr(syscall.IFLA_NET_NS_PID, uint32(nspid))
|
||||||
return networkSetNsAction(iface, data)
|
return networkSetNsAction(iface, data)
|
||||||
@@ -673,7 +673,7 @@ func NetworkSetNsFd(iface *net.Interface, fd int) error {
|
|||||||
return networkSetNsAction(iface, data)
|
return networkSetNsAction(iface, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rname a particular interface to a different name
|
// Rename a particular interface to a different name
|
||||||
// !!! Note that you can't rename an active interface. You need to bring it down before renaming it.
|
// !!! Note that you can't rename an active interface. You need to bring it down before renaming it.
|
||||||
// This is identical to running: ip link set dev ${oldName} name ${newName}
|
// This is identical to running: ip link set dev ${oldName} name ${newName}
|
||||||
func NetworkChangeName(iface *net.Interface, newName string) error {
|
func NetworkChangeName(iface *net.Interface, newName string) error {
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
## nsinit
|
||||||
|
|
||||||
|
`nsinit` is a cli application which demonstrates the use of libcontainer.
|
||||||
|
It is able to spawn new containers or join existing containers.
|
||||||
|
|
||||||
|
### How to build?
|
||||||
|
|
||||||
|
First to add the `libcontainer/vendor` into your GOPATH. It's because something related with this [issue](https://github.com/docker/libcontainer/issues/210).
|
||||||
|
|
||||||
|
```
|
||||||
|
export GOPATH=$GOPATH:/your/path/to/libcontainer/vendor
|
||||||
|
```
|
||||||
|
|
||||||
|
Then get into the nsinit folder and get the imported file. Use `make` command to make the nsinit binary.
|
||||||
|
|
||||||
|
```
|
||||||
|
cd libcontainer/nsinit
|
||||||
|
go get
|
||||||
|
make
|
||||||
|
```
|
||||||
|
|
||||||
|
We have finished compiling the nsinit package, but a root filesystem must be provided for use along with a container configuration file.
|
||||||
|
|
||||||
|
Choose a proper place to run your container. For example we use `/busybox`.
|
||||||
|
|
||||||
|
```
|
||||||
|
mkdir /busybox
|
||||||
|
curl -sSL 'https://github.com/jpetazzo/docker-busybox/raw/buildroot-2014.11/rootfs.tar' | tar -xC /busybox
|
||||||
|
```
|
||||||
|
|
||||||
|
Then you may need to write a configure file named `container.json` in the `/busybox` folder.
|
||||||
|
Environment, networking, and different capabilities for the container are specified in this file.
|
||||||
|
The configuration is used for each process executed inside the container
|
||||||
|
See the `sample_configs` folder for examples of what the container configuration should look like.
|
||||||
|
|
||||||
|
```
|
||||||
|
cp libcontainer/sample_configs/minimal.json /busybox/container.json
|
||||||
|
cd /busybox
|
||||||
|
```
|
||||||
|
|
||||||
|
Now the nsinit is ready to work.
|
||||||
|
To execute `/bin/bash` in the current directory as a container just run the following **as root**:
|
||||||
|
```bash
|
||||||
|
nsinit exec --tty /bin/bash
|
||||||
|
```
|
||||||
|
|
||||||
|
If you wish to spawn another process inside the container while your
|
||||||
|
current bash session is running, run the same command again to
|
||||||
|
get another bash shell (or change the command). If the original
|
||||||
|
process (PID 1) dies, all other processes spawned inside the container
|
||||||
|
will be killed and the namespace will be removed.
|
||||||
|
|
||||||
|
You can identify if a process is running in a container by
|
||||||
|
looking to see if `state.json` is in the root of the directory.
|
||||||
|
|
||||||
|
You may also specify an alternate root place where
|
||||||
|
the `container.json` file is read and where the `state.json` file will be saved.
|
||||||
@@ -41,6 +41,10 @@ type Process struct {
|
|||||||
// consolePath is the path to the console allocated to the container.
|
// consolePath is the path to the console allocated to the container.
|
||||||
consolePath string
|
consolePath string
|
||||||
|
|
||||||
|
// Capabilities specify the capabilities to keep when executing the process inside the container
|
||||||
|
// All capbilities not specified will be dropped from the processes capability mask
|
||||||
|
Capabilities []string
|
||||||
|
|
||||||
ops processOperations
|
ops processOperations
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||