Compare commits
34 Commits
v1.6.0-rc2
...
v1.6.0-rc4
| Author | SHA1 | Date | |
|---|---|---|---|
| e2e39fc1cf | |||
| eeb05fc081 | |||
| e1381ae328 | |||
| 45ad064150 | |||
| 72e14a1566 | |||
| 7d7bec86c9 | |||
| a39d49d676 | |||
| 5bf15a013b | |||
| 9461967eec | |||
| 3be7d11cee | |||
| d9910b8fd8 | |||
| f115c32f6b | |||
| 57939badc3 | |||
| 51ee02d478 | |||
| c92860748c | |||
| f582f9717f | |||
| ebcb36a8d2 | |||
| e6e8f2d717 | |||
| 317a510261 | |||
| 5d3a080178 | |||
| 542c84c2d2 | |||
| f1df74d09d | |||
| 4ddbc7a62f | |||
| f72b2c02b8 | |||
| af9dab70f8 | |||
| 10425e83f2 | |||
| 9c528dca85 | |||
| cb2c25ad2d | |||
| 962dec81ec | |||
| 1eae925a3d | |||
| 3ce2cc8ee7 | |||
| 054acc4bee | |||
| 63cb03a55b | |||
| 49b6f23696 |
@@ -1,5 +1,24 @@
|
||||
# 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)
|
||||
|
||||
#### Builder
|
||||
|
||||
@@ -441,7 +441,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
|
||||
authconfig.ServerAddress = serverAddress
|
||||
cli.configFile.Configs[serverAddress] = authconfig
|
||||
|
||||
stream, statusCode, err := cli.call("POST", "/auth", cli.configFile.Configs[serverAddress], false)
|
||||
stream, statusCode, err := cli.call("POST", "/auth", cli.configFile.Configs[serverAddress], nil)
|
||||
if statusCode == 401 {
|
||||
delete(cli.configFile.Configs, serverAddress)
|
||||
registry.SaveConfig(cli.configFile)
|
||||
@@ -527,7 +527,7 @@ func (cli *DockerCli) CmdVersion(args ...string) error {
|
||||
}
|
||||
fmt.Fprintf(cli.out, "OS/Arch (client): %s/%s\n", runtime.GOOS, runtime.GOARCH)
|
||||
|
||||
body, _, err := readBody(cli.call("GET", "/version", nil, false))
|
||||
body, _, err := readBody(cli.call("GET", "/version", nil, nil))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -559,7 +559,7 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
|
||||
cmd.Require(flag.Exact, 0)
|
||||
utils.ParseFlags(cmd, args, false)
|
||||
|
||||
body, _, err := readBody(cli.call("GET", "/info", nil, false))
|
||||
body, _, err := readBody(cli.call("GET", "/info", nil, nil))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -696,7 +696,7 @@ func (cli *DockerCli) CmdStop(args ...string) error {
|
||||
|
||||
var encounteredError error
|
||||
for _, name := range cmd.Args() {
|
||||
_, _, err := readBody(cli.call("POST", "/containers/"+name+"/stop?"+v.Encode(), nil, false))
|
||||
_, _, err := readBody(cli.call("POST", "/containers/"+name+"/stop?"+v.Encode(), nil, nil))
|
||||
if err != nil {
|
||||
fmt.Fprintf(cli.err, "%s\n", err)
|
||||
encounteredError = fmt.Errorf("Error: failed to stop one or more containers")
|
||||
@@ -719,7 +719,7 @@ func (cli *DockerCli) CmdRestart(args ...string) error {
|
||||
|
||||
var encounteredError error
|
||||
for _, name := range cmd.Args() {
|
||||
_, _, err := readBody(cli.call("POST", "/containers/"+name+"/restart?"+v.Encode(), nil, false))
|
||||
_, _, err := readBody(cli.call("POST", "/containers/"+name+"/restart?"+v.Encode(), nil, nil))
|
||||
if err != nil {
|
||||
fmt.Fprintf(cli.err, "%s\n", err)
|
||||
encounteredError = fmt.Errorf("Error: failed to restart one or more containers")
|
||||
@@ -748,7 +748,7 @@ func (cli *DockerCli) forwardAllSignals(cid string) chan os.Signal {
|
||||
if sig == "" {
|
||||
log.Errorf("Unsupported signal: %v. Discarding.", s)
|
||||
}
|
||||
if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/kill?signal=%s", cid, sig), nil, false)); err != nil {
|
||||
if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/kill?signal=%s", cid, sig), nil, nil)); err != nil {
|
||||
log.Debugf("Error sending signal: %s", err)
|
||||
}
|
||||
}
|
||||
@@ -774,7 +774,7 @@ func (cli *DockerCli) CmdStart(args ...string) error {
|
||||
return fmt.Errorf("You cannot start and attach multiple containers at once.")
|
||||
}
|
||||
|
||||
stream, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, false)
|
||||
stream, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -834,7 +834,7 @@ func (cli *DockerCli) CmdStart(args ...string) error {
|
||||
|
||||
var encounteredError error
|
||||
for _, name := range cmd.Args() {
|
||||
_, _, err := readBody(cli.call("POST", "/containers/"+name+"/start", nil, false))
|
||||
_, _, err := readBody(cli.call("POST", "/containers/"+name+"/start", nil, nil))
|
||||
if err != nil {
|
||||
if !*attach && !*openStdin {
|
||||
// attach and openStdin is false means it could be starting multiple containers
|
||||
@@ -882,7 +882,7 @@ func (cli *DockerCli) CmdUnpause(args ...string) error {
|
||||
|
||||
var encounteredError error
|
||||
for _, name := range cmd.Args() {
|
||||
if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/unpause", name), nil, false)); err != nil {
|
||||
if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/unpause", name), nil, nil)); err != nil {
|
||||
fmt.Fprintf(cli.err, "%s\n", err)
|
||||
encounteredError = fmt.Errorf("Error: failed to unpause container named %s", name)
|
||||
} else {
|
||||
@@ -899,7 +899,7 @@ func (cli *DockerCli) CmdPause(args ...string) error {
|
||||
|
||||
var encounteredError error
|
||||
for _, name := range cmd.Args() {
|
||||
if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/pause", name), nil, false)); err != nil {
|
||||
if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/pause", name), nil, nil)); err != nil {
|
||||
fmt.Fprintf(cli.err, "%s\n", err)
|
||||
encounteredError = fmt.Errorf("Error: failed to pause container named %s", name)
|
||||
} else {
|
||||
@@ -922,7 +922,7 @@ func (cli *DockerCli) CmdRename(args ...string) error {
|
||||
old_name := cmd.Arg(0)
|
||||
new_name := cmd.Arg(1)
|
||||
|
||||
if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/rename?name=%s", old_name, new_name), nil, false)); err != nil {
|
||||
if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/rename?name=%s", old_name, new_name), nil, nil)); err != nil {
|
||||
fmt.Fprintf(cli.err, "%s\n", err)
|
||||
return fmt.Errorf("Error: failed to rename container named %s", old_name)
|
||||
}
|
||||
@@ -951,7 +951,7 @@ func (cli *DockerCli) CmdInspect(args ...string) error {
|
||||
status := 0
|
||||
|
||||
for _, name := range cmd.Args() {
|
||||
obj, _, err := readBody(cli.call("GET", "/containers/"+name+"/json", nil, false))
|
||||
obj, _, err := readBody(cli.call("GET", "/containers/"+name+"/json", nil, nil))
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "Too many") {
|
||||
fmt.Fprintf(cli.err, "Error: %v", err)
|
||||
@@ -959,7 +959,7 @@ func (cli *DockerCli) CmdInspect(args ...string) error {
|
||||
continue
|
||||
}
|
||||
|
||||
obj, _, err = readBody(cli.call("GET", "/images/"+name+"/json", nil, false))
|
||||
obj, _, err = readBody(cli.call("GET", "/images/"+name+"/json", nil, nil))
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "No such") {
|
||||
fmt.Fprintf(cli.err, "Error: No such image or container: %s\n", name)
|
||||
@@ -1022,7 +1022,7 @@ func (cli *DockerCli) CmdTop(args ...string) error {
|
||||
val.Set("ps_args", strings.Join(cmd.Args()[1:], " "))
|
||||
}
|
||||
|
||||
stream, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/top?"+val.Encode(), nil, false)
|
||||
stream, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/top?"+val.Encode(), nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1048,7 +1048,7 @@ func (cli *DockerCli) CmdPort(args ...string) error {
|
||||
cmd.Require(flag.Min, 1)
|
||||
utils.ParseFlags(cmd, args, true)
|
||||
|
||||
stream, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, false)
|
||||
stream, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1113,7 +1113,7 @@ func (cli *DockerCli) CmdRmi(args ...string) error {
|
||||
|
||||
var encounteredError error
|
||||
for _, name := range cmd.Args() {
|
||||
body, _, err := readBody(cli.call("DELETE", "/images/"+name+"?"+v.Encode(), nil, false))
|
||||
body, _, err := readBody(cli.call("DELETE", "/images/"+name+"?"+v.Encode(), nil, nil))
|
||||
if err != nil {
|
||||
fmt.Fprintf(cli.err, "%s\n", err)
|
||||
encounteredError = fmt.Errorf("Error: failed to remove one or more images")
|
||||
@@ -1144,7 +1144,7 @@ func (cli *DockerCli) CmdHistory(args ...string) error {
|
||||
|
||||
utils.ParseFlags(cmd, args, true)
|
||||
|
||||
body, _, err := readBody(cli.call("GET", "/images/"+cmd.Arg(0)+"/history", nil, false))
|
||||
body, _, err := readBody(cli.call("GET", "/images/"+cmd.Arg(0)+"/history", nil, nil))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1211,7 +1211,7 @@ func (cli *DockerCli) CmdRm(args ...string) error {
|
||||
|
||||
var encounteredError error
|
||||
for _, name := range cmd.Args() {
|
||||
_, _, err := readBody(cli.call("DELETE", "/containers/"+name+"?"+val.Encode(), nil, false))
|
||||
_, _, err := readBody(cli.call("DELETE", "/containers/"+name+"?"+val.Encode(), nil, nil))
|
||||
if err != nil {
|
||||
fmt.Fprintf(cli.err, "%s\n", err)
|
||||
encounteredError = fmt.Errorf("Error: failed to remove one or more containers")
|
||||
@@ -1232,7 +1232,7 @@ func (cli *DockerCli) CmdKill(args ...string) error {
|
||||
|
||||
var encounteredError error
|
||||
for _, name := range cmd.Args() {
|
||||
if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/kill?signal=%s", name, *signal), nil, false)); err != nil {
|
||||
if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/kill?signal=%s", name, *signal), nil, nil)); err != nil {
|
||||
fmt.Fprintf(cli.err, "%s\n", err)
|
||||
encounteredError = fmt.Errorf("Error: failed to kill one or more containers")
|
||||
} else {
|
||||
@@ -1317,32 +1317,8 @@ func (cli *DockerCli) CmdPush(args ...string) error {
|
||||
v := url.Values{}
|
||||
v.Set("tag", tag)
|
||||
|
||||
push := func(authConfig registry.AuthConfig) error {
|
||||
buf, err := json.Marshal(authConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
registryAuthHeader := []string{
|
||||
base64.URLEncoding.EncodeToString(buf),
|
||||
}
|
||||
|
||||
return cli.stream("POST", "/images/"+remote+"/push?"+v.Encode(), nil, cli.out, map[string][]string{
|
||||
"X-Registry-Auth": registryAuthHeader,
|
||||
})
|
||||
}
|
||||
|
||||
if err := push(authConfig); err != nil {
|
||||
if strings.Contains(err.Error(), "Status 401") {
|
||||
fmt.Fprintln(cli.out, "\nPlease login prior to push:")
|
||||
if err := cli.CmdLogin(repoInfo.Index.GetAuthConfigKey()); err != nil {
|
||||
return err
|
||||
}
|
||||
authConfig := cli.configFile.ResolveAuthConfig(repoInfo.Index)
|
||||
return push(authConfig)
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
_, _, err = cli.clientRequestAttemptLogin("POST", "/images/"+remote+"/push?"+v.Encode(), nil, cli.out, repoInfo.Index, "push")
|
||||
return err
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdPull(args ...string) error {
|
||||
@@ -1375,36 +1351,8 @@ func (cli *DockerCli) CmdPull(args ...string) error {
|
||||
|
||||
cli.LoadConfigFile()
|
||||
|
||||
// Resolve the Auth config relevant for this server
|
||||
authConfig := cli.configFile.ResolveAuthConfig(repoInfo.Index)
|
||||
|
||||
pull := func(authConfig registry.AuthConfig) error {
|
||||
buf, err := json.Marshal(authConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
registryAuthHeader := []string{
|
||||
base64.URLEncoding.EncodeToString(buf),
|
||||
}
|
||||
|
||||
return cli.stream("POST", "/images/create?"+v.Encode(), nil, cli.out, map[string][]string{
|
||||
"X-Registry-Auth": registryAuthHeader,
|
||||
})
|
||||
}
|
||||
|
||||
if err := pull(authConfig); err != nil {
|
||||
if strings.Contains(err.Error(), "Status 401") {
|
||||
fmt.Fprintln(cli.out, "\nPlease login prior to pull:")
|
||||
if err := cli.CmdLogin(repoInfo.Index.GetAuthConfigKey()); err != nil {
|
||||
return err
|
||||
}
|
||||
authConfig := cli.configFile.ResolveAuthConfig(repoInfo.Index)
|
||||
return pull(authConfig)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
_, _, err = cli.clientRequestAttemptLogin("POST", "/images/create?"+v.Encode(), nil, cli.out, repoInfo.Index, "pull")
|
||||
return err
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdImages(args ...string) error {
|
||||
@@ -1448,7 +1396,7 @@ func (cli *DockerCli) CmdImages(args ...string) error {
|
||||
v.Set("filters", filterJson)
|
||||
}
|
||||
|
||||
body, _, err := readBody(cli.call("GET", "/images/json?"+v.Encode(), nil, false))
|
||||
body, _, err := readBody(cli.call("GET", "/images/json?"+v.Encode(), nil, nil))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1526,7 +1474,7 @@ func (cli *DockerCli) CmdImages(args ...string) error {
|
||||
v.Set("all", "1")
|
||||
}
|
||||
|
||||
body, _, err := readBody(cli.call("GET", "/images/json?"+v.Encode(), nil, false))
|
||||
body, _, err := readBody(cli.call("GET", "/images/json?"+v.Encode(), nil, nil))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -1724,7 +1672,7 @@ func (cli *DockerCli) CmdPs(args ...string) error {
|
||||
v.Set("filters", filterJson)
|
||||
}
|
||||
|
||||
body, _, err := readBody(cli.call("GET", "/containers/json?"+v.Encode(), nil, false))
|
||||
body, _, err := readBody(cli.call("GET", "/containers/json?"+v.Encode(), nil, nil))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1865,7 +1813,7 @@ func (cli *DockerCli) CmdCommit(args ...string) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
stream, _, err := cli.call("POST", "/commit?"+v.Encode(), config, false)
|
||||
stream, _, err := cli.call("POST", "/commit?"+v.Encode(), config, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1976,7 +1924,7 @@ func (cli *DockerCli) CmdDiff(args ...string) error {
|
||||
|
||||
utils.ParseFlags(cmd, args, true)
|
||||
|
||||
body, _, err := readBody(cli.call("GET", "/containers/"+cmd.Arg(0)+"/changes", nil, false))
|
||||
body, _, err := readBody(cli.call("GET", "/containers/"+cmd.Arg(0)+"/changes", nil, nil))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -2014,7 +1962,7 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
|
||||
|
||||
name := cmd.Arg(0)
|
||||
|
||||
stream, _, err := cli.call("GET", "/containers/"+name+"/json", nil, false)
|
||||
stream, _, err := cli.call("GET", "/containers/"+name+"/json", nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -2055,7 +2003,7 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
|
||||
utils.ParseFlags(cmd, args, true)
|
||||
name := cmd.Arg(0)
|
||||
|
||||
stream, _, err := cli.call("GET", "/containers/"+name+"/json", nil, false)
|
||||
stream, _, err := cli.call("GET", "/containers/"+name+"/json", nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -2126,16 +2074,27 @@ func (cli *DockerCli) CmdSearch(args ...string) error {
|
||||
|
||||
utils.ParseFlags(cmd, args, true)
|
||||
|
||||
name := cmd.Arg(0)
|
||||
v := url.Values{}
|
||||
v.Set("term", cmd.Arg(0))
|
||||
|
||||
body, _, err := readBody(cli.call("GET", "/images/search?"+v.Encode(), nil, true))
|
||||
v.Set("term", name)
|
||||
|
||||
// Resolve the Repository name from fqn to hostname + name
|
||||
taglessRemote, _ := parsers.ParseRepositoryTag(name)
|
||||
repoInfo, err := registry.ParseRepositoryInfo(taglessRemote)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cli.LoadConfigFile()
|
||||
|
||||
body, statusCode, errReq := cli.clientRequestAttemptLogin("GET", "/images/search?"+v.Encode(), nil, nil, repoInfo.Index, "search")
|
||||
rawBody, _, err := readBody(body, statusCode, errReq)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
outs := engine.NewTable("star_count", 0)
|
||||
if _, err := outs.ReadListFrom(body); err != nil {
|
||||
if _, err := outs.ReadListFrom(rawBody); err != nil {
|
||||
return err
|
||||
}
|
||||
w := tabwriter.NewWriter(cli.out, 10, 1, 3, ' ', 0)
|
||||
@@ -2190,7 +2149,7 @@ func (cli *DockerCli) CmdTag(args ...string) error {
|
||||
v.Set("force", "1")
|
||||
}
|
||||
|
||||
if _, _, err := readBody(cli.call("POST", "/images/"+cmd.Arg(0)+"/tag?"+v.Encode(), nil, false)); err != nil {
|
||||
if _, _, err := readBody(cli.call("POST", "/images/"+cmd.Arg(0)+"/tag?"+v.Encode(), nil, nil)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@@ -2292,7 +2251,7 @@ func (cli *DockerCli) createContainer(config *runconfig.Config, hostConfig *runc
|
||||
}
|
||||
|
||||
//create the container
|
||||
stream, statusCode, err := cli.call("POST", "/containers/create?"+containerValues.Encode(), mergedConfig, false)
|
||||
stream, statusCode, err := cli.call("POST", "/containers/create?"+containerValues.Encode(), mergedConfig, nil)
|
||||
//if image not found try to pull it
|
||||
if statusCode == 404 {
|
||||
repo, tag := parsers.ParseRepositoryTag(config.Image)
|
||||
@@ -2306,7 +2265,7 @@ func (cli *DockerCli) createContainer(config *runconfig.Config, hostConfig *runc
|
||||
return nil, err
|
||||
}
|
||||
// Retry
|
||||
if stream, _, err = cli.call("POST", "/containers/create?"+containerValues.Encode(), mergedConfig, false); err != nil {
|
||||
if stream, _, err = cli.call("POST", "/containers/create?"+containerValues.Encode(), mergedConfig, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if err != nil {
|
||||
@@ -2496,7 +2455,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
||||
}
|
||||
|
||||
//start the container
|
||||
if _, _, err = readBody(cli.call("POST", "/containers/"+createResponse.ID+"/start", nil, false)); err != nil {
|
||||
if _, _, err = readBody(cli.call("POST", "/containers/"+createResponse.ID+"/start", nil, nil)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -2526,13 +2485,13 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
||||
if *flAutoRemove {
|
||||
// Autoremove: wait for the container to finish, retrieve
|
||||
// the exit code and remove the container
|
||||
if _, _, err := readBody(cli.call("POST", "/containers/"+createResponse.ID+"/wait", nil, false)); err != nil {
|
||||
if _, _, err := readBody(cli.call("POST", "/containers/"+createResponse.ID+"/wait", nil, nil)); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, status, err = getExitCode(cli, createResponse.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, _, err := readBody(cli.call("DELETE", "/containers/"+createResponse.ID+"?v=1", nil, false)); err != nil {
|
||||
if _, _, err := readBody(cli.call("DELETE", "/containers/"+createResponse.ID+"?v=1", nil, nil)); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
@@ -2572,7 +2531,7 @@ func (cli *DockerCli) CmdCp(args ...string) error {
|
||||
copyData.Set("Resource", info[1])
|
||||
copyData.Set("HostPath", cmd.Arg(1))
|
||||
|
||||
stream, statusCode, err := cli.call("POST", "/containers/"+info[0]+"/copy", copyData, false)
|
||||
stream, statusCode, err := cli.call("POST", "/containers/"+info[0]+"/copy", copyData, nil)
|
||||
if stream != nil {
|
||||
defer stream.Close()
|
||||
}
|
||||
@@ -2667,7 +2626,7 @@ func (cli *DockerCli) CmdExec(args ...string) error {
|
||||
return &utils.StatusError{StatusCode: 1}
|
||||
}
|
||||
|
||||
stream, _, err := cli.call("POST", "/containers/"+execConfig.Container+"/exec", execConfig, false)
|
||||
stream, _, err := cli.call("POST", "/containers/"+execConfig.Container+"/exec", execConfig, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -2689,7 +2648,7 @@ func (cli *DockerCli) CmdExec(args ...string) error {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if _, _, err := readBody(cli.call("POST", "/exec/"+execID+"/start", execConfig, false)); err != nil {
|
||||
if _, _, err := readBody(cli.call("POST", "/exec/"+execID+"/start", execConfig, nil)); err != nil {
|
||||
return err
|
||||
}
|
||||
// For now don't print this - wait for when we support exec wait()
|
||||
@@ -2781,7 +2740,7 @@ type containerStats struct {
|
||||
}
|
||||
|
||||
func (s *containerStats) Collect(cli *DockerCli) {
|
||||
stream, _, err := cli.call("GET", "/containers/"+s.Name+"/stats", nil, false)
|
||||
stream, _, err := cli.call("GET", "/containers/"+s.Name+"/stats", nil, nil)
|
||||
if err != nil {
|
||||
s.err = err
|
||||
return
|
||||
|
||||
@@ -12,8 +12,10 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
gosignal "os/signal"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/api"
|
||||
@@ -54,124 +56,144 @@ func (cli *DockerCli) encodeData(data interface{}) (*bytes.Buffer, error) {
|
||||
return params, nil
|
||||
}
|
||||
|
||||
func (cli *DockerCli) call(method, path string, data interface{}, passAuthInfo bool) (io.ReadCloser, int, error) {
|
||||
params, err := cli.encodeData(data)
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), params)
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
if passAuthInfo {
|
||||
cli.LoadConfigFile()
|
||||
// Resolve the Auth config relevant for this server
|
||||
authConfig := cli.configFile.Configs[registry.IndexServerAddress()]
|
||||
getHeaders := func(authConfig registry.AuthConfig) (map[string][]string, error) {
|
||||
buf, err := json.Marshal(authConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
registryAuthHeader := []string{
|
||||
base64.URLEncoding.EncodeToString(buf),
|
||||
}
|
||||
return map[string][]string{"X-Registry-Auth": registryAuthHeader}, nil
|
||||
}
|
||||
if headers, err := getHeaders(authConfig); err == nil && headers != nil {
|
||||
for k, v := range headers {
|
||||
req.Header[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION)
|
||||
req.URL.Host = cli.addr
|
||||
req.URL.Scheme = cli.scheme
|
||||
if data != nil {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
} else if method == "POST" {
|
||||
req.Header.Set("Content-Type", "text/plain")
|
||||
}
|
||||
resp, err := cli.HTTPClient().Do(req)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "connection refused") {
|
||||
return nil, -1, ErrConnectionRefused
|
||||
}
|
||||
|
||||
if cli.tlsConfig == nil {
|
||||
return nil, -1, fmt.Errorf("%v. Are you trying to connect to a TLS-enabled daemon without TLS?", err)
|
||||
}
|
||||
return nil, -1, fmt.Errorf("An error occurred trying to connect: %v", err)
|
||||
|
||||
}
|
||||
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 400 {
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
if len(body) == 0 {
|
||||
return nil, resp.StatusCode, fmt.Errorf("Error: request returned %s for API route and version %s, check if the server supports the requested API version", http.StatusText(resp.StatusCode), req.URL)
|
||||
}
|
||||
return nil, resp.StatusCode, fmt.Errorf("Error response from daemon: %s", bytes.TrimSpace(body))
|
||||
}
|
||||
|
||||
return resp.Body, resp.StatusCode, nil
|
||||
}
|
||||
|
||||
func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer, headers map[string][]string) error {
|
||||
return cli.streamHelper(method, path, true, in, out, nil, headers)
|
||||
}
|
||||
|
||||
func (cli *DockerCli) streamHelper(method, path string, setRawTerminal bool, in io.Reader, stdout, stderr io.Writer, headers map[string][]string) error {
|
||||
if (method == "POST" || method == "PUT") && in == nil {
|
||||
func (cli *DockerCli) clientRequest(method, path string, in io.Reader, headers map[string][]string) (io.ReadCloser, string, int, error) {
|
||||
expectedPayload := (method == "POST" || method == "PUT")
|
||||
if expectedPayload && in == nil {
|
||||
in = bytes.NewReader([]byte{})
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), in)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, "", -1, err
|
||||
}
|
||||
req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION)
|
||||
req.URL.Host = cli.addr
|
||||
req.URL.Scheme = cli.scheme
|
||||
if method == "POST" {
|
||||
req.Header.Set("Content-Type", "text/plain")
|
||||
}
|
||||
|
||||
if headers != nil {
|
||||
for k, v := range headers {
|
||||
req.Header[k] = v
|
||||
}
|
||||
}
|
||||
if expectedPayload && req.Header.Get("Content-Type") == "" {
|
||||
req.Header.Set("Content-Type", "text/plain")
|
||||
}
|
||||
|
||||
resp, err := cli.HTTPClient().Do(req)
|
||||
statusCode := -1
|
||||
if resp != nil {
|
||||
statusCode = resp.StatusCode
|
||||
}
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "connection refused") {
|
||||
return fmt.Errorf("Cannot connect to the Docker daemon. Is 'docker -d' running on this host?")
|
||||
return nil, "", statusCode, ErrConnectionRefused
|
||||
}
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 400 {
|
||||
if cli.tlsConfig == nil {
|
||||
return nil, "", statusCode, fmt.Errorf("%v. Are you trying to connect to a TLS-enabled daemon without TLS?", err)
|
||||
}
|
||||
|
||||
return nil, "", statusCode, fmt.Errorf("An error occurred trying to connect: %v", err)
|
||||
}
|
||||
|
||||
if statusCode < 200 || statusCode >= 400 {
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, "", statusCode, err
|
||||
}
|
||||
if len(body) == 0 {
|
||||
return fmt.Errorf("Error :%s", http.StatusText(resp.StatusCode))
|
||||
return nil, "", statusCode, fmt.Errorf("Error: request returned %s for API route and version %s, check if the server supports the requested API version", http.StatusText(statusCode), req.URL)
|
||||
}
|
||||
return fmt.Errorf("Error: %s", bytes.TrimSpace(body))
|
||||
return nil, "", statusCode, fmt.Errorf("Error response from daemon: %s", bytes.TrimSpace(body))
|
||||
}
|
||||
|
||||
if api.MatchesContentType(resp.Header.Get("Content-Type"), "application/json") {
|
||||
return utils.DisplayJSONMessagesStream(resp.Body, stdout, cli.outFd, cli.isTerminalOut)
|
||||
return resp.Body, resp.Header.Get("Content-Type"), statusCode, nil
|
||||
}
|
||||
|
||||
func (cli *DockerCli) clientRequestAttemptLogin(method, path string, in io.Reader, out io.Writer, index *registry.IndexInfo, cmdName string) (io.ReadCloser, int, error) {
|
||||
cmdAttempt := func(authConfig registry.AuthConfig) (io.ReadCloser, int, error) {
|
||||
buf, err := json.Marshal(authConfig)
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
registryAuthHeader := []string{
|
||||
base64.URLEncoding.EncodeToString(buf),
|
||||
}
|
||||
|
||||
// begin the request
|
||||
body, contentType, statusCode, err := cli.clientRequest(method, path, in, map[string][]string{
|
||||
"X-Registry-Auth": registryAuthHeader,
|
||||
})
|
||||
if err == nil && out != nil {
|
||||
// If we are streaming output, complete the stream since
|
||||
// errors may not appear until later.
|
||||
err = cli.streamBody(body, contentType, true, out, nil)
|
||||
}
|
||||
if err != nil {
|
||||
// Since errors in a stream appear after status 200 has been written,
|
||||
// we may need to change the status code.
|
||||
if strings.Contains(err.Error(), "Authentication is required") ||
|
||||
strings.Contains(err.Error(), "Status 401") ||
|
||||
strings.Contains(err.Error(), "status code 401") {
|
||||
statusCode = http.StatusUnauthorized
|
||||
}
|
||||
}
|
||||
return body, statusCode, err
|
||||
}
|
||||
|
||||
// Resolve the Auth config relevant for this server
|
||||
authConfig := cli.configFile.ResolveAuthConfig(index)
|
||||
body, statusCode, err := cmdAttempt(authConfig)
|
||||
if statusCode == http.StatusUnauthorized {
|
||||
fmt.Fprintf(cli.out, "\nPlease login prior to %s:\n", cmdName)
|
||||
if err = cli.CmdLogin(index.GetAuthConfigKey()); err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
authConfig = cli.configFile.ResolveAuthConfig(index)
|
||||
return cmdAttempt(authConfig)
|
||||
}
|
||||
return body, statusCode, err
|
||||
}
|
||||
|
||||
func (cli *DockerCli) call(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, int, error) {
|
||||
params, err := cli.encodeData(data)
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
|
||||
if data != nil {
|
||||
if headers == nil {
|
||||
headers = make(map[string][]string)
|
||||
}
|
||||
headers["Content-Type"] = []string{"application/json"}
|
||||
}
|
||||
|
||||
body, _, statusCode, err := cli.clientRequest(method, path, params, headers)
|
||||
return body, statusCode, err
|
||||
}
|
||||
func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer, headers map[string][]string) error {
|
||||
return cli.streamHelper(method, path, true, in, out, nil, headers)
|
||||
}
|
||||
|
||||
func (cli *DockerCli) streamHelper(method, path string, setRawTerminal bool, in io.Reader, stdout, stderr io.Writer, headers map[string][]string) error {
|
||||
body, contentType, _, err := cli.clientRequest(method, path, in, headers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return cli.streamBody(body, contentType, setRawTerminal, stdout, stderr)
|
||||
}
|
||||
|
||||
func (cli *DockerCli) streamBody(body io.ReadCloser, contentType string, setRawTerminal bool, stdout, stderr io.Writer) error {
|
||||
defer body.Close()
|
||||
|
||||
if api.MatchesContentType(contentType, "application/json") {
|
||||
return utils.DisplayJSONMessagesStream(body, stdout, cli.outFd, cli.isTerminalOut)
|
||||
}
|
||||
if stdout != nil || stderr != nil {
|
||||
// When TTY is ON, use regular copy
|
||||
var err error
|
||||
if setRawTerminal {
|
||||
_, err = io.Copy(stdout, resp.Body)
|
||||
_, err = io.Copy(stdout, body)
|
||||
} else {
|
||||
_, err = stdcopy.StdCopy(stdout, stderr, resp.Body)
|
||||
_, err = stdcopy.StdCopy(stdout, stderr, body)
|
||||
}
|
||||
log.Debugf("[stream] End of stdout")
|
||||
return err
|
||||
@@ -195,13 +217,13 @@ func (cli *DockerCli) resizeTty(id string, isExec bool) {
|
||||
path = "/exec/" + id + "/resize?"
|
||||
}
|
||||
|
||||
if _, _, err := readBody(cli.call("POST", path+v.Encode(), nil, false)); err != nil {
|
||||
if _, _, err := readBody(cli.call("POST", path+v.Encode(), nil, nil)); err != nil {
|
||||
log.Debugf("Error resize: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func waitForExit(cli *DockerCli, containerId string) (int, error) {
|
||||
stream, _, err := cli.call("POST", "/containers/"+containerId+"/wait", nil, false)
|
||||
func waitForExit(cli *DockerCli, containerID string) (int, error) {
|
||||
stream, _, err := cli.call("POST", "/containers/"+containerID+"/wait", nil, nil)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
@@ -215,8 +237,8 @@ func waitForExit(cli *DockerCli, containerId string) (int, error) {
|
||||
|
||||
// getExitCode perform an inspect on the container. It returns
|
||||
// the running state and the exit code.
|
||||
func getExitCode(cli *DockerCli, containerId string) (bool, int, error) {
|
||||
stream, _, err := cli.call("GET", "/containers/"+containerId+"/json", nil, false)
|
||||
func getExitCode(cli *DockerCli, containerID string) (bool, int, error) {
|
||||
stream, _, err := cli.call("GET", "/containers/"+containerID+"/json", nil, nil)
|
||||
if err != nil {
|
||||
// If we can't connect, then the daemon probably died.
|
||||
if err != ErrConnectionRefused {
|
||||
@@ -236,8 +258,8 @@ func getExitCode(cli *DockerCli, containerId string) (bool, int, error) {
|
||||
|
||||
// getExecExitCode perform an inspect on the exec command. It returns
|
||||
// the running state and the exit code.
|
||||
func getExecExitCode(cli *DockerCli, execId string) (bool, int, error) {
|
||||
stream, _, err := cli.call("GET", "/exec/"+execId+"/json", nil, false)
|
||||
func getExecExitCode(cli *DockerCli, execID string) (bool, int, error) {
|
||||
stream, _, err := cli.call("GET", "/exec/"+execID+"/json", nil, nil)
|
||||
if err != nil {
|
||||
// If we can't connect, then the daemon probably died.
|
||||
if err != ErrConnectionRefused {
|
||||
@@ -257,13 +279,29 @@ func getExecExitCode(cli *DockerCli, execId string) (bool, int, error) {
|
||||
func (cli *DockerCli) monitorTtySize(id string, isExec bool) error {
|
||||
cli.resizeTty(id, isExec)
|
||||
|
||||
sigchan := make(chan os.Signal, 1)
|
||||
gosignal.Notify(sigchan, signal.SIGWINCH)
|
||||
go func() {
|
||||
for _ = range sigchan {
|
||||
cli.resizeTty(id, isExec)
|
||||
}
|
||||
}()
|
||||
if runtime.GOOS == "windows" {
|
||||
go func() {
|
||||
prevW, prevH := cli.getTtySize()
|
||||
for {
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ import (
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/api"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/daemon/networkdriver/portallocator"
|
||||
"github.com/docker/docker/daemon/networkdriver/bridge"
|
||||
"github.com/docker/docker/engine"
|
||||
"github.com/docker/docker/pkg/listenbuffer"
|
||||
"github.com/docker/docker/pkg/parsers"
|
||||
@@ -38,7 +38,7 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
activationLock chan struct{}
|
||||
activationLock chan struct{} = make(chan struct{})
|
||||
)
|
||||
|
||||
type HttpServer struct {
|
||||
@@ -1527,7 +1527,7 @@ func allocateDaemonPort(addr string) error {
|
||||
}
|
||||
|
||||
for _, hostIP := range hostIPs {
|
||||
if _, err := portallocator.RequestPort(hostIP, "tcp", intPort); err != nil {
|
||||
if _, err := bridge.RequestPort(hostIP, "tcp", intPort); err != nil {
|
||||
return fmt.Errorf("failed to allocate daemon listening port %d (err: %v)", intPort, err)
|
||||
}
|
||||
}
|
||||
@@ -1578,7 +1578,6 @@ func ServeApi(job *engine.Job) engine.Status {
|
||||
protoAddrs = job.Args
|
||||
chErrors = make(chan error, len(protoAddrs))
|
||||
)
|
||||
activationLock = make(chan struct{})
|
||||
|
||||
for _, protoAddr := range protoAddrs {
|
||||
protoAddrParts := strings.SplitN(protoAddr, "://", 2)
|
||||
|
||||
@@ -95,7 +95,9 @@ func AcceptConnections(job *engine.Job) engine.Status {
|
||||
go systemd.SdNotify("READY=1")
|
||||
|
||||
// close the lock so the listeners start accepting connections
|
||||
if activationLock != nil {
|
||||
select {
|
||||
case <-activationLock:
|
||||
default:
|
||||
close(activationLock)
|
||||
}
|
||||
|
||||
|
||||
@@ -58,6 +58,18 @@ __docker_containers_unpauseable() {
|
||||
__docker_containers_all '.State.Paused'
|
||||
}
|
||||
|
||||
__docker_container_names() {
|
||||
local containers=( $(__docker_q ps -aq --no-trunc) )
|
||||
local names=( $(__docker_q inspect --format '{{.Name}}' "${containers[@]}") )
|
||||
names=( "${names[@]#/}" ) # trim off the leading "/" from the container names
|
||||
COMPREPLY=( $(compgen -W "${names[*]}" -- "$cur") )
|
||||
}
|
||||
|
||||
__docker_container_ids() {
|
||||
local containers=( $(__docker_q ps -aq) )
|
||||
COMPREPLY=( $(compgen -W "${containers[*]}" -- "$cur") )
|
||||
}
|
||||
|
||||
__docker_image_repos() {
|
||||
local repos="$(__docker_q images | awk 'NR>1 && $1 != "<none>" { print $1 }')"
|
||||
COMPREPLY=( $(compgen -W "$repos" -- "$cur") )
|
||||
@@ -437,7 +449,10 @@ _docker_history() {
|
||||
_docker_images() {
|
||||
case "$prev" in
|
||||
--filter|-f)
|
||||
COMPREPLY=( $( compgen -W "dangling=true" -- "$cur" ) )
|
||||
COMPREPLY=( $( compgen -W "dangling=true label=" -- "$cur" ) )
|
||||
if [ "$COMPREPLY" = "label=" ]; then
|
||||
compopt -o nospace
|
||||
fi
|
||||
return
|
||||
;;
|
||||
esac
|
||||
@@ -447,17 +462,20 @@ _docker_images() {
|
||||
COMPREPLY=( $( compgen -W "true false" -- "${cur#=}" ) )
|
||||
return
|
||||
;;
|
||||
*label=*)
|
||||
return
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "--all -a --filter -f --help --no-trunc --quiet -q" -- "$cur" ) )
|
||||
;;
|
||||
=)
|
||||
return
|
||||
;;
|
||||
*)
|
||||
local counter=$(__docker_pos_first_nonflag)
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_image_repos
|
||||
fi
|
||||
__docker_image_repos
|
||||
;;
|
||||
esac
|
||||
}
|
||||
@@ -616,7 +634,7 @@ _docker_ps() {
|
||||
__docker_containers_all
|
||||
;;
|
||||
--filter|-f)
|
||||
COMPREPLY=( $( compgen -S = -W "exited status" -- "$cur" ) )
|
||||
COMPREPLY=( $( compgen -S = -W "exited id label name status" -- "$cur" ) )
|
||||
compopt -o nospace
|
||||
return
|
||||
;;
|
||||
@@ -626,6 +644,16 @@ _docker_ps() {
|
||||
esac
|
||||
|
||||
case "${words[$cword-2]}$prev=" in
|
||||
*id=*)
|
||||
cur="${cur#=}"
|
||||
__docker_container_ids
|
||||
return
|
||||
;;
|
||||
*name=*)
|
||||
cur="${cur#=}"
|
||||
__docker_container_names
|
||||
return
|
||||
;;
|
||||
*status=*)
|
||||
COMPREPLY=( $( compgen -W "exited paused restarting running" -- "${cur#=}" ) )
|
||||
return
|
||||
@@ -734,6 +762,7 @@ _docker_run() {
|
||||
--attach -a
|
||||
--cap-add
|
||||
--cap-drop
|
||||
--cgroup-parent
|
||||
--cidfile
|
||||
--cpuset
|
||||
--cpu-shares -c
|
||||
@@ -746,7 +775,10 @@ _docker_run() {
|
||||
--expose
|
||||
--hostname -h
|
||||
--ipc
|
||||
--label -l
|
||||
--label-file
|
||||
--link
|
||||
--log-driver
|
||||
--lxc-conf
|
||||
--mac-address
|
||||
--memory -m
|
||||
@@ -798,7 +830,7 @@ _docker_run() {
|
||||
__docker_capabilities
|
||||
return
|
||||
;;
|
||||
--cidfile|--env-file)
|
||||
--cidfile|--env-file|--label-file)
|
||||
_filedir
|
||||
return
|
||||
;;
|
||||
@@ -850,6 +882,10 @@ _docker_run() {
|
||||
esac
|
||||
return
|
||||
;;
|
||||
--log-driver)
|
||||
COMPREPLY=( $( compgen -W "json-file syslog none" -- "$cur") )
|
||||
return
|
||||
;;
|
||||
--net)
|
||||
case "$cur" in
|
||||
container:*)
|
||||
|
||||
@@ -360,6 +360,10 @@ func (container *Container) Start() (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if container.removalInProgress || container.Dead {
|
||||
return fmt.Errorf("Container is marked for removal and cannot be started.")
|
||||
}
|
||||
|
||||
// if we encounter an error during start we need to ensure that any other
|
||||
// setup has been cleaned up properly
|
||||
defer func() {
|
||||
|
||||
@@ -25,7 +25,6 @@ import (
|
||||
"github.com/docker/docker/daemon/graphdriver"
|
||||
_ "github.com/docker/docker/daemon/graphdriver/vfs"
|
||||
_ "github.com/docker/docker/daemon/networkdriver/bridge"
|
||||
"github.com/docker/docker/daemon/networkdriver/portallocator"
|
||||
"github.com/docker/docker/engine"
|
||||
"github.com/docker/docker/graph"
|
||||
"github.com/docker/docker/image"
|
||||
@@ -827,12 +826,6 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine) (*Daemon, error)
|
||||
}
|
||||
config.DisableNetwork = config.BridgeIface == disableNetworkBridge
|
||||
|
||||
// register portallocator release on shutdown
|
||||
eng.OnShutdown(func() {
|
||||
if err := portallocator.ReleaseAll(); err != nil {
|
||||
log.Errorf("portallocator.ReleaseAll(): %s", err)
|
||||
}
|
||||
})
|
||||
// Claim the pidfile first, to avoid any and all unexpected race conditions.
|
||||
// Some of the init doesn't need a pidfile lock - but let's not try to be smart.
|
||||
if config.Pidfile != "" {
|
||||
|
||||
@@ -61,8 +61,15 @@ func (daemon *Daemon) ContainerRm(job *engine.Job) engine.Status {
|
||||
return job.Errorf("Conflict, You cannot remove a running container. Stop the container before attempting removal or use -f")
|
||||
}
|
||||
}
|
||||
if err := daemon.Rm(container); err != nil {
|
||||
return job.Errorf("Cannot destroy container %s: %s", name, err)
|
||||
|
||||
if forceRemove {
|
||||
if err := daemon.ForceRm(container); err != nil {
|
||||
log.Errorf("Cannot destroy container %s: %v", name, err)
|
||||
}
|
||||
} else {
|
||||
if err := daemon.Rm(container); err != nil {
|
||||
return job.Errorf("Cannot destroy container %s: %v", name, err)
|
||||
}
|
||||
}
|
||||
container.LogEvent("destroy")
|
||||
if removeVolume {
|
||||
@@ -81,8 +88,16 @@ func (daemon *Daemon) DeleteVolumes(volumeIDs map[string]struct{}) {
|
||||
}
|
||||
}
|
||||
|
||||
func (daemon *Daemon) Rm(container *Container) (err error) {
|
||||
return daemon.commonRm(container, false)
|
||||
}
|
||||
|
||||
func (daemon *Daemon) ForceRm(container *Container) (err error) {
|
||||
return daemon.commonRm(container, true)
|
||||
}
|
||||
|
||||
// Destroy unregisters a container from the daemon and cleanly removes its contents from the filesystem.
|
||||
func (daemon *Daemon) Rm(container *Container) error {
|
||||
func (daemon *Daemon) commonRm(container *Container, forceRemove bool) (err error) {
|
||||
if container == nil {
|
||||
return fmt.Errorf("The given container is <nil>")
|
||||
}
|
||||
@@ -92,19 +107,40 @@ func (daemon *Daemon) Rm(container *Container) error {
|
||||
return fmt.Errorf("Container %v not found - maybe it was already destroyed?", container.ID)
|
||||
}
|
||||
|
||||
if err := container.Stop(3); err != nil {
|
||||
// Container state RemovalInProgress should be used to avoid races.
|
||||
if err = container.SetRemovalInProgress(); err != nil {
|
||||
return fmt.Errorf("Failed to set container state to RemovalInProgress: %s", err)
|
||||
}
|
||||
|
||||
defer container.ResetRemovalInProgress()
|
||||
|
||||
if err = container.Stop(3); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Deregister the container before removing its directory, to avoid race conditions
|
||||
daemon.idIndex.Delete(container.ID)
|
||||
daemon.containers.Delete(container.ID)
|
||||
// Mark container dead. We don't want anybody to be restarting it.
|
||||
container.SetDead()
|
||||
|
||||
// Save container state to disk. So that if error happens before
|
||||
// container meta file got removed from disk, then a restart of
|
||||
// docker should not make a dead container alive.
|
||||
container.ToDisk()
|
||||
|
||||
// If force removal is required, delete container from various
|
||||
// indexes even if removal failed.
|
||||
defer func() {
|
||||
if err != nil && forceRemove {
|
||||
daemon.idIndex.Delete(container.ID)
|
||||
daemon.containers.Delete(container.ID)
|
||||
}
|
||||
}()
|
||||
|
||||
container.derefVolumes()
|
||||
if _, err := daemon.containerGraph.Purge(container.ID); err != nil {
|
||||
log.Debugf("Unable to remove container from link graph: %s", err)
|
||||
}
|
||||
|
||||
if err := daemon.driver.Remove(container.ID); err != nil {
|
||||
if err = daemon.driver.Remove(container.ID); err != nil {
|
||||
return fmt.Errorf("Driver %s failed to remove root filesystem %s: %s", daemon.driver, container.ID, err)
|
||||
}
|
||||
|
||||
@@ -113,15 +149,17 @@ func (daemon *Daemon) Rm(container *Container) error {
|
||||
return fmt.Errorf("Driver %s failed to remove init filesystem %s: %s", daemon.driver, initID, err)
|
||||
}
|
||||
|
||||
if err := os.RemoveAll(container.root); err != nil {
|
||||
if err = os.RemoveAll(container.root); err != nil {
|
||||
return fmt.Errorf("Unable to remove filesystem for %v: %v", container.ID, err)
|
||||
}
|
||||
|
||||
if err := daemon.execDriver.Clean(container.ID); err != nil {
|
||||
if err = daemon.execDriver.Clean(container.ID); err != nil {
|
||||
return fmt.Errorf("Unable to remove execdriver data for %s: %s", container.ID, err)
|
||||
}
|
||||
|
||||
selinuxFreeLxcContexts(container.ProcessLabel)
|
||||
daemon.idIndex.Delete(container.ID)
|
||||
daemon.containers.Delete(container.ID)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -156,32 +156,40 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba
|
||||
startCallback(&c.ProcessConfig, pid)
|
||||
}
|
||||
|
||||
oomKillNotification, err := cont.NotifyOOM()
|
||||
if err != nil {
|
||||
oomKillNotification = nil
|
||||
log.Warnf("Your kernel does not support OOM notifications: %s", err)
|
||||
}
|
||||
oom := notifyOnOOM(cont)
|
||||
waitF := p.Wait
|
||||
if nss := cont.Config().Namespaces; nss.Contains(configs.NEWPID) {
|
||||
if nss := cont.Config().Namespaces; !nss.Contains(configs.NEWPID) {
|
||||
// we need such hack for tracking processes with inerited fds,
|
||||
// because cmd.Wait() waiting for all streams to be copied
|
||||
waitF = waitInPIDHost(p, cont)
|
||||
}
|
||||
ps, err := waitF()
|
||||
if err != nil {
|
||||
if err, ok := err.(*exec.ExitError); !ok {
|
||||
execErr, ok := err.(*exec.ExitError)
|
||||
if !ok {
|
||||
return execdriver.ExitStatus{ExitCode: -1}, err
|
||||
} else {
|
||||
ps = err.ProcessState
|
||||
}
|
||||
ps = execErr.ProcessState
|
||||
}
|
||||
cont.Destroy()
|
||||
|
||||
_, oomKill := <-oomKillNotification
|
||||
|
||||
_, oomKill := <-oom
|
||||
return execdriver.ExitStatus{ExitCode: utils.ExitStatus(ps.Sys().(syscall.WaitStatus)), OOMKilled: oomKill}, nil
|
||||
}
|
||||
|
||||
// notifyOnOOM returns a channel that signals if the container received an OOM notification
|
||||
// for any process. If it is unable to subscribe to OOM notifications then a closed
|
||||
// channel is returned as it will be non-blocking and return the correct result when read.
|
||||
func notifyOnOOM(container libcontainer.Container) <-chan struct{} {
|
||||
oom, err := container.NotifyOOM()
|
||||
if err != nil {
|
||||
log.Warnf("Your kernel does not support OOM notifications: %s", err)
|
||||
c := make(chan struct{})
|
||||
close(c)
|
||||
return c
|
||||
}
|
||||
return oom
|
||||
}
|
||||
|
||||
func waitInPIDHost(p *libcontainer.Process, c libcontainer.Container) func() (*os.ProcessState, error) {
|
||||
return func() (*os.ProcessState, error) {
|
||||
pid, err := p.Pid()
|
||||
@@ -189,16 +197,17 @@ func waitInPIDHost(p *libcontainer.Process, c libcontainer.Container) func() (*o
|
||||
return nil, err
|
||||
}
|
||||
|
||||
processes, err := c.Processes()
|
||||
|
||||
process, err := os.FindProcess(pid)
|
||||
s, err := process.Wait()
|
||||
if err != nil {
|
||||
if err, ok := err.(*exec.ExitError); !ok {
|
||||
execErr, ok := err.(*exec.ExitError)
|
||||
if !ok {
|
||||
return s, err
|
||||
} else {
|
||||
s = err.ProcessState
|
||||
}
|
||||
s = execErr.ProcessState
|
||||
}
|
||||
processes, err := c.Processes()
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ package aufs
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
@@ -47,6 +48,9 @@ var (
|
||||
graphdriver.FsMagicAufs,
|
||||
}
|
||||
backingFs = "<unknown>"
|
||||
|
||||
enableDirpermLock sync.Once
|
||||
enableDirperm bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -152,6 +156,7 @@ func (a *Driver) Status() [][2]string {
|
||||
{"Root Dir", a.rootPath()},
|
||||
{"Backing Filesystem", backingFs},
|
||||
{"Dirs", fmt.Sprintf("%d", len(ids))},
|
||||
{"Dirperm1 Supported", fmt.Sprintf("%v", useDirperm())},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -422,7 +427,11 @@ func (a *Driver) aufsMount(ro []string, rw, target, mountLabel string) (err erro
|
||||
// Mount options are clipped to page size(4096 bytes). If there are more
|
||||
// layers then these are remounted individually using append.
|
||||
|
||||
b := make([]byte, syscall.Getpagesize()-len(mountLabel)-54) // room for xino & mountLabel
|
||||
offset := 54
|
||||
if useDirperm() {
|
||||
offset += len("dirperm1")
|
||||
}
|
||||
b := make([]byte, syscall.Getpagesize()-len(mountLabel)-offset) // room for xino & mountLabel
|
||||
bp := copy(b, fmt.Sprintf("br:%s=rw", rw))
|
||||
|
||||
firstMount := true
|
||||
@@ -446,7 +455,11 @@ func (a *Driver) aufsMount(ro []string, rw, target, mountLabel string) (err erro
|
||||
}
|
||||
|
||||
if firstMount {
|
||||
data := label.FormatMountLabel(fmt.Sprintf("%s,dio,xino=/dev/shm/aufs.xino", string(b[:bp])), mountLabel)
|
||||
opts := "dio,xino=/dev/shm/aufs.xino"
|
||||
if useDirperm() {
|
||||
opts += ",dirperm1"
|
||||
}
|
||||
data := label.FormatMountLabel(fmt.Sprintf("%s,%s", string(b[:bp]), opts), mountLabel)
|
||||
if err = mount("none", target, "aufs", 0, data); err != nil {
|
||||
return
|
||||
}
|
||||
@@ -460,3 +473,33 @@ func (a *Driver) aufsMount(ro []string, rw, target, mountLabel string) (err erro
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// useDirperm checks dirperm1 mount option can be used with the current
|
||||
// version of aufs.
|
||||
func useDirperm() bool {
|
||||
enableDirpermLock.Do(func() {
|
||||
base, err := ioutil.TempDir("", "docker-aufs-base")
|
||||
if err != nil {
|
||||
log.Errorf("error checking dirperm1: %v", err)
|
||||
return
|
||||
}
|
||||
defer os.RemoveAll(base)
|
||||
|
||||
union, err := ioutil.TempDir("", "docker-aufs-union")
|
||||
if err != nil {
|
||||
log.Errorf("error checking dirperm1: %v", err)
|
||||
return
|
||||
}
|
||||
defer os.RemoveAll(union)
|
||||
|
||||
opts := fmt.Sprintf("br:%s,dirperm1,xino=/dev/shm/aufs.xino", base)
|
||||
if err := mount("none", union, "aufs", 0, opts); err != nil {
|
||||
return
|
||||
}
|
||||
enableDirperm = true
|
||||
if err := Unmount(union); err != nil {
|
||||
log.Errorf("error checking dirperm1: failed to unmount %v", err)
|
||||
}
|
||||
})
|
||||
return enableDirperm
|
||||
}
|
||||
|
||||
@@ -39,9 +39,8 @@ var (
|
||||
"aufs",
|
||||
"btrfs",
|
||||
"devicemapper",
|
||||
"vfs",
|
||||
// experimental, has to be enabled manually for now
|
||||
"overlay",
|
||||
"vfs",
|
||||
}
|
||||
|
||||
ErrNotSupported = errors.New("driver not supported")
|
||||
|
||||
@@ -77,11 +77,19 @@ var (
|
||||
bridgeIPv4Network *net.IPNet
|
||||
bridgeIPv6Addr net.IP
|
||||
globalIPv6Network *net.IPNet
|
||||
portMapper *portmapper.PortMapper
|
||||
once sync.Once
|
||||
|
||||
defaultBindingIP = net.ParseIP("0.0.0.0")
|
||||
currentInterfaces = ifaces{c: make(map[string]*networkInterface)}
|
||||
)
|
||||
|
||||
func initPortMapper() {
|
||||
once.Do(func() {
|
||||
portMapper = portmapper.New()
|
||||
})
|
||||
}
|
||||
|
||||
func InitDriver(job *engine.Job) engine.Status {
|
||||
var (
|
||||
networkv4 *net.IPNet
|
||||
@@ -98,6 +106,7 @@ func InitDriver(job *engine.Job) engine.Status {
|
||||
fixedCIDR = job.Getenv("FixedCIDR")
|
||||
fixedCIDRv6 = job.Getenv("FixedCIDRv6")
|
||||
)
|
||||
initPortMapper()
|
||||
|
||||
if defaultIP := job.Getenv("DefaultBindingIP"); defaultIP != "" {
|
||||
defaultBindingIP = net.ParseIP(defaultIP)
|
||||
@@ -234,7 +243,7 @@ func InitDriver(job *engine.Job) engine.Status {
|
||||
if err != nil {
|
||||
return job.Error(err)
|
||||
}
|
||||
portmapper.SetIptablesChain(chain)
|
||||
portMapper.SetIptablesChain(chain)
|
||||
}
|
||||
|
||||
bridgeIPv4Network = networkv4
|
||||
@@ -349,6 +358,11 @@ func setupIPTables(addr net.Addr, icc, ipmasq bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func RequestPort(ip net.IP, proto string, port int) (int, error) {
|
||||
initPortMapper()
|
||||
return portMapper.Allocator.RequestPort(ip, proto, port)
|
||||
}
|
||||
|
||||
// configureBridge attempts to create and configure a network bridge interface named `bridgeIface` on the host
|
||||
// If bridgeIP is empty, it will try to find a non-conflicting IP from the Docker-specified private ranges
|
||||
// If the bridge `bridgeIface` already exists, it will only perform the IP address association with the existing
|
||||
@@ -586,7 +600,7 @@ func Release(job *engine.Job) engine.Status {
|
||||
}
|
||||
|
||||
for _, nat := range containerInterface.PortMappings {
|
||||
if err := portmapper.Unmap(nat); err != nil {
|
||||
if err := portMapper.Unmap(nat); err != nil {
|
||||
log.Infof("Unable to unmap port %s: %s", nat, err)
|
||||
}
|
||||
}
|
||||
@@ -643,7 +657,7 @@ func AllocatePort(job *engine.Job) engine.Status {
|
||||
|
||||
var host net.Addr
|
||||
for i := 0; i < MaxAllocatedPortAttempts; i++ {
|
||||
if host, err = portmapper.Map(container, ip, hostPort); err == nil {
|
||||
if host, err = portMapper.Map(container, ip, hostPort); err == nil {
|
||||
break
|
||||
}
|
||||
// There is no point in immediately retrying to map an explicitly
|
||||
|
||||
@@ -16,44 +16,12 @@ const (
|
||||
DefaultPortRangeEnd = 65535
|
||||
)
|
||||
|
||||
var (
|
||||
beginPortRange = DefaultPortRangeStart
|
||||
endPortRange = DefaultPortRangeEnd
|
||||
)
|
||||
|
||||
type portMap struct {
|
||||
p map[int]struct{}
|
||||
last int
|
||||
}
|
||||
|
||||
func newPortMap() *portMap {
|
||||
return &portMap{
|
||||
p: map[int]struct{}{},
|
||||
last: endPortRange,
|
||||
}
|
||||
}
|
||||
|
||||
type protoMap map[string]*portMap
|
||||
|
||||
func newProtoMap() protoMap {
|
||||
return protoMap{
|
||||
"tcp": newPortMap(),
|
||||
"udp": newPortMap(),
|
||||
}
|
||||
}
|
||||
|
||||
type ipMapping map[string]protoMap
|
||||
|
||||
var (
|
||||
ErrAllPortsAllocated = errors.New("all ports are allocated")
|
||||
ErrUnknownProtocol = errors.New("unknown protocol")
|
||||
)
|
||||
|
||||
var (
|
||||
mutex sync.Mutex
|
||||
|
||||
defaultIP = net.ParseIP("0.0.0.0")
|
||||
globalMap = ipMapping{}
|
||||
defaultIP = net.ParseIP("0.0.0.0")
|
||||
)
|
||||
|
||||
type ErrPortAlreadyAllocated struct {
|
||||
@@ -68,32 +36,6 @@ func NewErrPortAlreadyAllocated(ip string, port int) ErrPortAlreadyAllocated {
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
const portRangeKernelParam = "/proc/sys/net/ipv4/ip_local_port_range"
|
||||
portRangeFallback := fmt.Sprintf("using fallback port range %d-%d", beginPortRange, endPortRange)
|
||||
|
||||
file, err := os.Open(portRangeKernelParam)
|
||||
if err != nil {
|
||||
log.Warnf("port allocator - %s due to error: %v", portRangeFallback, 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("port allocator - failed to parse system ephemeral port range from %s - %s: %v", portRangeKernelParam, portRangeFallback, err)
|
||||
return
|
||||
}
|
||||
beginPortRange = start
|
||||
endPortRange = end
|
||||
}
|
||||
|
||||
func PortRange() (int, int) {
|
||||
return beginPortRange, endPortRange
|
||||
}
|
||||
|
||||
func (e ErrPortAlreadyAllocated) IP() string {
|
||||
return e.ip
|
||||
}
|
||||
@@ -110,12 +52,57 @@ func (e ErrPortAlreadyAllocated) Error() string {
|
||||
return fmt.Sprintf("Bind for %s:%d failed: port is already allocated", e.ip, e.port)
|
||||
}
|
||||
|
||||
type (
|
||||
PortAllocator struct {
|
||||
mutex sync.Mutex
|
||||
ipMap ipMapping
|
||||
Begin int
|
||||
End int
|
||||
}
|
||||
portMap struct {
|
||||
p map[int]struct{}
|
||||
begin, end int
|
||||
last int
|
||||
}
|
||||
protoMap map[string]*portMap
|
||||
)
|
||||
|
||||
func New() *PortAllocator {
|
||||
start, end, err := getDynamicPortRange()
|
||||
if err != nil {
|
||||
log.Warn(err)
|
||||
start, end = DefaultPortRangeStart, DefaultPortRangeEnd
|
||||
}
|
||||
return &PortAllocator{
|
||||
ipMap: ipMapping{},
|
||||
Begin: start,
|
||||
End: end,
|
||||
}
|
||||
}
|
||||
|
||||
func getDynamicPortRange() (start int, end int, err error) {
|
||||
const portRangeKernelParam = "/proc/sys/net/ipv4/ip_local_port_range"
|
||||
portRangeFallback := fmt.Sprintf("using fallback port range %d-%d", DefaultPortRangeStart, DefaultPortRangeEnd)
|
||||
file, err := os.Open(portRangeKernelParam)
|
||||
if err != nil {
|
||||
return 0, 0, fmt.Errorf("port allocator - %s due to error: %v", portRangeFallback, err)
|
||||
}
|
||||
n, err := fmt.Fscanf(bufio.NewReader(file), "%d\t%d", &start, &end)
|
||||
if n != 2 || err != nil {
|
||||
if err == nil {
|
||||
err = fmt.Errorf("unexpected count of parsed numbers (%d)", n)
|
||||
}
|
||||
return 0, 0, fmt.Errorf("port allocator - failed to parse system ephemeral port range from %s - %s: %v", portRangeKernelParam, portRangeFallback, err)
|
||||
}
|
||||
return start, end, nil
|
||||
}
|
||||
|
||||
// RequestPort requests new port from global ports pool for specified ip and proto.
|
||||
// If port is 0 it returns first free port. Otherwise it cheks port availability
|
||||
// in pool and return that port or error if port is already busy.
|
||||
func RequestPort(ip net.IP, proto string, port int) (int, error) {
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
func (p *PortAllocator) RequestPort(ip net.IP, proto string, port int) (int, error) {
|
||||
p.mutex.Lock()
|
||||
defer p.mutex.Unlock()
|
||||
|
||||
if proto != "tcp" && proto != "udp" {
|
||||
return 0, ErrUnknownProtocol
|
||||
@@ -125,10 +112,14 @@ func RequestPort(ip net.IP, proto string, port int) (int, error) {
|
||||
ip = defaultIP
|
||||
}
|
||||
ipstr := ip.String()
|
||||
protomap, ok := globalMap[ipstr]
|
||||
protomap, ok := p.ipMap[ipstr]
|
||||
if !ok {
|
||||
protomap = newProtoMap()
|
||||
globalMap[ipstr] = protomap
|
||||
protomap = protoMap{
|
||||
"tcp": p.newPortMap(),
|
||||
"udp": p.newPortMap(),
|
||||
}
|
||||
|
||||
p.ipMap[ipstr] = protomap
|
||||
}
|
||||
mapping := protomap[proto]
|
||||
if port > 0 {
|
||||
@@ -147,14 +138,14 @@ func RequestPort(ip net.IP, proto string, port int) (int, error) {
|
||||
}
|
||||
|
||||
// ReleasePort releases port from global ports pool for specified ip and proto.
|
||||
func ReleasePort(ip net.IP, proto string, port int) error {
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
func (p *PortAllocator) ReleasePort(ip net.IP, proto string, port int) error {
|
||||
p.mutex.Lock()
|
||||
defer p.mutex.Unlock()
|
||||
|
||||
if ip == nil {
|
||||
ip = defaultIP
|
||||
}
|
||||
protomap, ok := globalMap[ip.String()]
|
||||
protomap, ok := p.ipMap[ip.String()]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
@@ -162,20 +153,29 @@ func ReleasePort(ip net.IP, proto string, port int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *PortAllocator) newPortMap() *portMap {
|
||||
return &portMap{
|
||||
p: map[int]struct{}{},
|
||||
begin: p.Begin,
|
||||
end: p.End,
|
||||
last: p.End,
|
||||
}
|
||||
}
|
||||
|
||||
// ReleaseAll releases all ports for all ips.
|
||||
func ReleaseAll() error {
|
||||
mutex.Lock()
|
||||
globalMap = ipMapping{}
|
||||
mutex.Unlock()
|
||||
func (p *PortAllocator) ReleaseAll() error {
|
||||
p.mutex.Lock()
|
||||
p.ipMap = ipMapping{}
|
||||
p.mutex.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pm *portMap) findPort() (int, error) {
|
||||
port := pm.last
|
||||
for i := 0; i <= endPortRange-beginPortRange; i++ {
|
||||
for i := 0; i <= pm.end-pm.begin; i++ {
|
||||
port++
|
||||
if port > endPortRange {
|
||||
port = beginPortRange
|
||||
if port > pm.end {
|
||||
port = pm.begin
|
||||
}
|
||||
|
||||
if _, ok := pm.p[port]; !ok {
|
||||
|
||||
@@ -5,32 +5,23 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func init() {
|
||||
beginPortRange = DefaultPortRangeStart
|
||||
endPortRange = DefaultPortRangeEnd
|
||||
}
|
||||
|
||||
func reset() {
|
||||
ReleaseAll()
|
||||
}
|
||||
|
||||
func TestRequestNewPort(t *testing.T) {
|
||||
defer reset()
|
||||
p := New()
|
||||
|
||||
port, err := RequestPort(defaultIP, "tcp", 0)
|
||||
port, err := p.RequestPort(defaultIP, "tcp", 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if expected := beginPortRange; port != expected {
|
||||
if expected := p.Begin; port != expected {
|
||||
t.Fatalf("Expected port %d got %d", expected, port)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequestSpecificPort(t *testing.T) {
|
||||
defer reset()
|
||||
p := New()
|
||||
|
||||
port, err := RequestPort(defaultIP, "tcp", 5000)
|
||||
port, err := p.RequestPort(defaultIP, "tcp", 5000)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -40,9 +31,9 @@ func TestRequestSpecificPort(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestReleasePort(t *testing.T) {
|
||||
defer reset()
|
||||
p := New()
|
||||
|
||||
port, err := RequestPort(defaultIP, "tcp", 5000)
|
||||
port, err := p.RequestPort(defaultIP, "tcp", 5000)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -50,15 +41,15 @@ func TestReleasePort(t *testing.T) {
|
||||
t.Fatalf("Expected port 5000 got %d", port)
|
||||
}
|
||||
|
||||
if err := ReleasePort(defaultIP, "tcp", 5000); err != nil {
|
||||
if err := p.ReleasePort(defaultIP, "tcp", 5000); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReuseReleasedPort(t *testing.T) {
|
||||
defer reset()
|
||||
p := New()
|
||||
|
||||
port, err := RequestPort(defaultIP, "tcp", 5000)
|
||||
port, err := p.RequestPort(defaultIP, "tcp", 5000)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -66,20 +57,20 @@ func TestReuseReleasedPort(t *testing.T) {
|
||||
t.Fatalf("Expected port 5000 got %d", port)
|
||||
}
|
||||
|
||||
if err := ReleasePort(defaultIP, "tcp", 5000); err != nil {
|
||||
if err := p.ReleasePort(defaultIP, "tcp", 5000); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
port, err = RequestPort(defaultIP, "tcp", 5000)
|
||||
port, err = p.RequestPort(defaultIP, "tcp", 5000)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReleaseUnreadledPort(t *testing.T) {
|
||||
defer reset()
|
||||
p := New()
|
||||
|
||||
port, err := RequestPort(defaultIP, "tcp", 5000)
|
||||
port, err := p.RequestPort(defaultIP, "tcp", 5000)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -87,7 +78,7 @@ func TestReleaseUnreadledPort(t *testing.T) {
|
||||
t.Fatalf("Expected port 5000 got %d", port)
|
||||
}
|
||||
|
||||
port, err = RequestPort(defaultIP, "tcp", 5000)
|
||||
port, err = p.RequestPort(defaultIP, "tcp", 5000)
|
||||
|
||||
switch err.(type) {
|
||||
case ErrPortAlreadyAllocated:
|
||||
@@ -97,42 +88,40 @@ func TestReleaseUnreadledPort(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUnknowProtocol(t *testing.T) {
|
||||
defer reset()
|
||||
|
||||
if _, err := RequestPort(defaultIP, "tcpp", 0); err != ErrUnknownProtocol {
|
||||
if _, err := New().RequestPort(defaultIP, "tcpp", 0); err != ErrUnknownProtocol {
|
||||
t.Fatalf("Expected error %s got %s", ErrUnknownProtocol, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllocateAllPorts(t *testing.T) {
|
||||
defer reset()
|
||||
p := New()
|
||||
|
||||
for i := 0; i <= endPortRange-beginPortRange; i++ {
|
||||
port, err := RequestPort(defaultIP, "tcp", 0)
|
||||
for i := 0; i <= p.End-p.Begin; i++ {
|
||||
port, err := p.RequestPort(defaultIP, "tcp", 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if expected := beginPortRange + i; port != expected {
|
||||
if expected := p.Begin + i; port != expected {
|
||||
t.Fatalf("Expected port %d got %d", expected, port)
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := RequestPort(defaultIP, "tcp", 0); err != ErrAllPortsAllocated {
|
||||
if _, err := p.RequestPort(defaultIP, "tcp", 0); err != ErrAllPortsAllocated {
|
||||
t.Fatalf("Expected error %s got %s", ErrAllPortsAllocated, err)
|
||||
}
|
||||
|
||||
_, err := RequestPort(defaultIP, "udp", 0)
|
||||
_, err := p.RequestPort(defaultIP, "udp", 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// release a port in the middle and ensure we get another tcp port
|
||||
port := beginPortRange + 5
|
||||
if err := ReleasePort(defaultIP, "tcp", port); err != nil {
|
||||
port := p.Begin + 5
|
||||
if err := p.ReleasePort(defaultIP, "tcp", port); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
newPort, err := RequestPort(defaultIP, "tcp", 0)
|
||||
newPort, err := p.RequestPort(defaultIP, "tcp", 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -142,10 +131,10 @@ func TestAllocateAllPorts(t *testing.T) {
|
||||
|
||||
// now pm.last == newPort, release it so that it's the only free port of
|
||||
// the range, and ensure we get it back
|
||||
if err := ReleasePort(defaultIP, "tcp", newPort); err != nil {
|
||||
if err := p.ReleasePort(defaultIP, "tcp", newPort); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
port, err = RequestPort(defaultIP, "tcp", 0)
|
||||
port, err = p.RequestPort(defaultIP, "tcp", 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -155,34 +144,34 @@ func TestAllocateAllPorts(t *testing.T) {
|
||||
}
|
||||
|
||||
func BenchmarkAllocatePorts(b *testing.B) {
|
||||
defer reset()
|
||||
p := New()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
for i := 0; i <= endPortRange-beginPortRange; i++ {
|
||||
port, err := RequestPort(defaultIP, "tcp", 0)
|
||||
for i := 0; i <= p.End-p.Begin; i++ {
|
||||
port, err := p.RequestPort(defaultIP, "tcp", 0)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
if expected := beginPortRange + i; port != expected {
|
||||
if expected := p.Begin + i; port != expected {
|
||||
b.Fatalf("Expected port %d got %d", expected, port)
|
||||
}
|
||||
}
|
||||
reset()
|
||||
p.ReleaseAll()
|
||||
}
|
||||
}
|
||||
|
||||
func TestPortAllocation(t *testing.T) {
|
||||
defer reset()
|
||||
p := New()
|
||||
|
||||
ip := net.ParseIP("192.168.0.1")
|
||||
ip2 := net.ParseIP("192.168.0.2")
|
||||
if port, err := RequestPort(ip, "tcp", 80); err != nil {
|
||||
if port, err := p.RequestPort(ip, "tcp", 80); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if port != 80 {
|
||||
t.Fatalf("Acquire(80) should return 80, not %d", port)
|
||||
}
|
||||
port, err := RequestPort(ip, "tcp", 0)
|
||||
port, err := p.RequestPort(ip, "tcp", 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -190,41 +179,41 @@ func TestPortAllocation(t *testing.T) {
|
||||
t.Fatalf("Acquire(0) should return a non-zero port")
|
||||
}
|
||||
|
||||
if _, err := RequestPort(ip, "tcp", port); err == nil {
|
||||
if _, err := p.RequestPort(ip, "tcp", port); err == nil {
|
||||
t.Fatalf("Acquiring a port already in use should return an error")
|
||||
}
|
||||
|
||||
if newPort, err := RequestPort(ip, "tcp", 0); err != nil {
|
||||
if newPort, err := p.RequestPort(ip, "tcp", 0); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if newPort == port {
|
||||
t.Fatalf("Acquire(0) allocated the same port twice: %d", port)
|
||||
}
|
||||
|
||||
if _, err := RequestPort(ip, "tcp", 80); err == nil {
|
||||
if _, err := p.RequestPort(ip, "tcp", 80); err == nil {
|
||||
t.Fatalf("Acquiring a port already in use should return an error")
|
||||
}
|
||||
if _, err := RequestPort(ip2, "tcp", 80); err != nil {
|
||||
if _, err := p.RequestPort(ip2, "tcp", 80); err != nil {
|
||||
t.Fatalf("It should be possible to allocate the same port on a different interface")
|
||||
}
|
||||
if _, err := RequestPort(ip2, "tcp", 80); err == nil {
|
||||
if _, err := p.RequestPort(ip2, "tcp", 80); err == nil {
|
||||
t.Fatalf("Acquiring a port already in use should return an error")
|
||||
}
|
||||
if err := ReleasePort(ip, "tcp", 80); err != nil {
|
||||
if err := p.ReleasePort(ip, "tcp", 80); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := RequestPort(ip, "tcp", 80); err != nil {
|
||||
if _, err := p.RequestPort(ip, "tcp", 80); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
port, err = RequestPort(ip, "tcp", 0)
|
||||
port, err = p.RequestPort(ip, "tcp", 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
port2, err := RequestPort(ip, "tcp", port+1)
|
||||
port2, err := p.RequestPort(ip, "tcp", port+1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
port3, err := RequestPort(ip, "tcp", 0)
|
||||
port3, err := p.RequestPort(ip, "tcp", 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -234,17 +223,17 @@ func TestPortAllocation(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestNoDuplicateBPR(t *testing.T) {
|
||||
defer reset()
|
||||
p := New()
|
||||
|
||||
if port, err := RequestPort(defaultIP, "tcp", beginPortRange); err != nil {
|
||||
if port, err := p.RequestPort(defaultIP, "tcp", p.Begin); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if port != beginPortRange {
|
||||
t.Fatalf("Expected port %d got %d", beginPortRange, port)
|
||||
} else if port != p.Begin {
|
||||
t.Fatalf("Expected port %d got %d", p.Begin, port)
|
||||
}
|
||||
|
||||
if port, err := RequestPort(defaultIP, "tcp", 0); err != nil {
|
||||
if port, err := p.RequestPort(defaultIP, "tcp", 0); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if port == beginPortRange {
|
||||
} else if port == p.Begin {
|
||||
t.Fatalf("Acquire(0) allocated the same port twice: %d", port)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,15 +18,7 @@ type mapping struct {
|
||||
container net.Addr
|
||||
}
|
||||
|
||||
var (
|
||||
chain *iptables.Chain
|
||||
lock sync.Mutex
|
||||
|
||||
// udp:ip:port
|
||||
currentMappings = make(map[string]*mapping)
|
||||
|
||||
NewProxy = NewProxyCommand
|
||||
)
|
||||
var NewProxy = NewProxyCommand
|
||||
|
||||
var (
|
||||
ErrUnknownBackendAddressType = errors.New("unknown container address type not supported")
|
||||
@@ -34,13 +26,34 @@ var (
|
||||
ErrPortNotMapped = errors.New("port is not mapped")
|
||||
)
|
||||
|
||||
func SetIptablesChain(c *iptables.Chain) {
|
||||
chain = c
|
||||
type PortMapper struct {
|
||||
chain *iptables.Chain
|
||||
|
||||
// udp:ip:port
|
||||
currentMappings map[string]*mapping
|
||||
lock sync.Mutex
|
||||
|
||||
Allocator *portallocator.PortAllocator
|
||||
}
|
||||
|
||||
func Map(container net.Addr, hostIP net.IP, hostPort int) (host net.Addr, err error) {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
func New() *PortMapper {
|
||||
return NewWithPortAllocator(portallocator.New())
|
||||
}
|
||||
|
||||
func NewWithPortAllocator(allocator *portallocator.PortAllocator) *PortMapper {
|
||||
return &PortMapper{
|
||||
currentMappings: make(map[string]*mapping),
|
||||
Allocator: allocator,
|
||||
}
|
||||
}
|
||||
|
||||
func (pm *PortMapper) SetIptablesChain(c *iptables.Chain) {
|
||||
pm.chain = c
|
||||
}
|
||||
|
||||
func (pm *PortMapper) Map(container net.Addr, hostIP net.IP, hostPort int) (host net.Addr, err error) {
|
||||
pm.lock.Lock()
|
||||
defer pm.lock.Unlock()
|
||||
|
||||
var (
|
||||
m *mapping
|
||||
@@ -52,7 +65,7 @@ func Map(container net.Addr, hostIP net.IP, hostPort int) (host net.Addr, err er
|
||||
switch container.(type) {
|
||||
case *net.TCPAddr:
|
||||
proto = "tcp"
|
||||
if allocatedHostPort, err = portallocator.RequestPort(hostIP, proto, hostPort); err != nil {
|
||||
if allocatedHostPort, err = pm.Allocator.RequestPort(hostIP, proto, hostPort); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -65,7 +78,7 @@ func Map(container net.Addr, hostIP net.IP, hostPort int) (host net.Addr, err er
|
||||
proxy = NewProxy(proto, hostIP, allocatedHostPort, container.(*net.TCPAddr).IP, container.(*net.TCPAddr).Port)
|
||||
case *net.UDPAddr:
|
||||
proto = "udp"
|
||||
if allocatedHostPort, err = portallocator.RequestPort(hostIP, proto, hostPort); err != nil {
|
||||
if allocatedHostPort, err = pm.Allocator.RequestPort(hostIP, proto, hostPort); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -83,25 +96,25 @@ func Map(container net.Addr, hostIP net.IP, hostPort int) (host net.Addr, err er
|
||||
// release the allocated port on any further error during return.
|
||||
defer func() {
|
||||
if err != nil {
|
||||
portallocator.ReleasePort(hostIP, proto, allocatedHostPort)
|
||||
pm.Allocator.ReleasePort(hostIP, proto, allocatedHostPort)
|
||||
}
|
||||
}()
|
||||
|
||||
key := getKey(m.host)
|
||||
if _, exists := currentMappings[key]; exists {
|
||||
if _, exists := pm.currentMappings[key]; exists {
|
||||
return nil, ErrPortMappedForIP
|
||||
}
|
||||
|
||||
containerIP, containerPort := getIPAndPort(m.container)
|
||||
if err := forward(iptables.Append, m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort); err != nil {
|
||||
if err := pm.forward(iptables.Append, m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cleanup := func() error {
|
||||
// need to undo the iptables rules before we return
|
||||
proxy.Stop()
|
||||
forward(iptables.Delete, m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort)
|
||||
if err := portallocator.ReleasePort(hostIP, m.proto, allocatedHostPort); err != nil {
|
||||
pm.forward(iptables.Delete, m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort)
|
||||
if err := pm.Allocator.ReleasePort(hostIP, m.proto, allocatedHostPort); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -115,35 +128,35 @@ func Map(container net.Addr, hostIP net.IP, hostPort int) (host net.Addr, err er
|
||||
return nil, err
|
||||
}
|
||||
m.userlandProxy = proxy
|
||||
currentMappings[key] = m
|
||||
pm.currentMappings[key] = m
|
||||
return m.host, nil
|
||||
}
|
||||
|
||||
func Unmap(host net.Addr) error {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
func (pm *PortMapper) Unmap(host net.Addr) error {
|
||||
pm.lock.Lock()
|
||||
defer pm.lock.Unlock()
|
||||
|
||||
key := getKey(host)
|
||||
data, exists := currentMappings[key]
|
||||
data, exists := pm.currentMappings[key]
|
||||
if !exists {
|
||||
return ErrPortNotMapped
|
||||
}
|
||||
|
||||
data.userlandProxy.Stop()
|
||||
|
||||
delete(currentMappings, key)
|
||||
delete(pm.currentMappings, key)
|
||||
|
||||
containerIP, containerPort := getIPAndPort(data.container)
|
||||
hostIP, hostPort := getIPAndPort(data.host)
|
||||
if err := forward(iptables.Delete, data.proto, hostIP, hostPort, containerIP.String(), containerPort); err != nil {
|
||||
if err := pm.forward(iptables.Delete, data.proto, hostIP, hostPort, containerIP.String(), containerPort); err != nil {
|
||||
log.Errorf("Error on iptables delete: %s", err)
|
||||
}
|
||||
|
||||
switch a := host.(type) {
|
||||
case *net.TCPAddr:
|
||||
return portallocator.ReleasePort(a.IP, "tcp", a.Port)
|
||||
return pm.Allocator.ReleasePort(a.IP, "tcp", a.Port)
|
||||
case *net.UDPAddr:
|
||||
return portallocator.ReleasePort(a.IP, "udp", a.Port)
|
||||
return pm.Allocator.ReleasePort(a.IP, "udp", a.Port)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -168,9 +181,9 @@ func getIPAndPort(a net.Addr) (net.IP, int) {
|
||||
return nil, 0
|
||||
}
|
||||
|
||||
func forward(action iptables.Action, proto string, sourceIP net.IP, sourcePort int, containerIP string, containerPort int) error {
|
||||
if chain == nil {
|
||||
func (pm *PortMapper) forward(action iptables.Action, proto string, sourceIP net.IP, sourcePort int, containerIP string, containerPort int) error {
|
||||
if pm.chain == nil {
|
||||
return nil
|
||||
}
|
||||
return chain.Forward(action, sourceIP, sourcePort, proto, containerIP, containerPort)
|
||||
return pm.chain.Forward(action, sourceIP, sourcePort, proto, containerIP, containerPort)
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/daemon/networkdriver/portallocator"
|
||||
"github.com/docker/docker/pkg/iptables"
|
||||
)
|
||||
|
||||
@@ -13,30 +12,26 @@ func init() {
|
||||
NewProxy = NewMockProxyCommand
|
||||
}
|
||||
|
||||
func reset() {
|
||||
chain = nil
|
||||
currentMappings = make(map[string]*mapping)
|
||||
}
|
||||
|
||||
func TestSetIptablesChain(t *testing.T) {
|
||||
defer reset()
|
||||
pm := New()
|
||||
|
||||
c := &iptables.Chain{
|
||||
Name: "TEST",
|
||||
Bridge: "192.168.1.1",
|
||||
}
|
||||
|
||||
if chain != nil {
|
||||
if pm.chain != nil {
|
||||
t.Fatal("chain should be nil at init")
|
||||
}
|
||||
|
||||
SetIptablesChain(c)
|
||||
if chain == nil {
|
||||
pm.SetIptablesChain(c)
|
||||
if pm.chain == nil {
|
||||
t.Fatal("chain should not be nil after set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMapPorts(t *testing.T) {
|
||||
pm := New()
|
||||
dstIp1 := net.ParseIP("192.168.0.1")
|
||||
dstIp2 := net.ParseIP("192.168.0.2")
|
||||
dstAddr1 := &net.TCPAddr{IP: dstIp1, Port: 80}
|
||||
@@ -49,34 +44,34 @@ func TestMapPorts(t *testing.T) {
|
||||
return (addr1.Network() == addr2.Network()) && (addr1.String() == addr2.String())
|
||||
}
|
||||
|
||||
if host, err := Map(srcAddr1, dstIp1, 80); err != nil {
|
||||
if host, err := pm.Map(srcAddr1, dstIp1, 80); err != nil {
|
||||
t.Fatalf("Failed to allocate port: %s", err)
|
||||
} else if !addrEqual(dstAddr1, host) {
|
||||
t.Fatalf("Incorrect mapping result: expected %s:%s, got %s:%s",
|
||||
dstAddr1.String(), dstAddr1.Network(), host.String(), host.Network())
|
||||
}
|
||||
|
||||
if _, err := Map(srcAddr1, dstIp1, 80); err == nil {
|
||||
if _, err := pm.Map(srcAddr1, dstIp1, 80); err == nil {
|
||||
t.Fatalf("Port is in use - mapping should have failed")
|
||||
}
|
||||
|
||||
if _, err := Map(srcAddr2, dstIp1, 80); err == nil {
|
||||
if _, err := pm.Map(srcAddr2, dstIp1, 80); err == nil {
|
||||
t.Fatalf("Port is in use - mapping should have failed")
|
||||
}
|
||||
|
||||
if _, err := Map(srcAddr2, dstIp2, 80); err != nil {
|
||||
if _, err := pm.Map(srcAddr2, dstIp2, 80); err != nil {
|
||||
t.Fatalf("Failed to allocate port: %s", err)
|
||||
}
|
||||
|
||||
if Unmap(dstAddr1) != nil {
|
||||
if pm.Unmap(dstAddr1) != nil {
|
||||
t.Fatalf("Failed to release port")
|
||||
}
|
||||
|
||||
if Unmap(dstAddr2) != nil {
|
||||
if pm.Unmap(dstAddr2) != nil {
|
||||
t.Fatalf("Failed to release port")
|
||||
}
|
||||
|
||||
if Unmap(dstAddr2) == nil {
|
||||
if pm.Unmap(dstAddr2) == nil {
|
||||
t.Fatalf("Port already released, but no error reported")
|
||||
}
|
||||
}
|
||||
@@ -115,6 +110,7 @@ func TestGetUDPIPAndPort(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMapAllPortsSingleInterface(t *testing.T) {
|
||||
pm := New()
|
||||
dstIp1 := net.ParseIP("0.0.0.0")
|
||||
srcAddr1 := &net.TCPAddr{Port: 1080, IP: net.ParseIP("172.16.0.1")}
|
||||
|
||||
@@ -124,26 +120,26 @@ func TestMapAllPortsSingleInterface(t *testing.T) {
|
||||
|
||||
defer func() {
|
||||
for _, val := range hosts {
|
||||
Unmap(val)
|
||||
pm.Unmap(val)
|
||||
}
|
||||
}()
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
start, end := portallocator.PortRange()
|
||||
start, end := pm.Allocator.Begin, pm.Allocator.End
|
||||
for i := start; i < end; i++ {
|
||||
if host, err = Map(srcAddr1, dstIp1, 0); err != nil {
|
||||
if host, err = pm.Map(srcAddr1, dstIp1, 0); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
hosts = append(hosts, host)
|
||||
}
|
||||
|
||||
if _, err := Map(srcAddr1, dstIp1, start); err == nil {
|
||||
if _, err := pm.Map(srcAddr1, dstIp1, start); err == nil {
|
||||
t.Fatalf("Port %d should be bound but is not", start)
|
||||
}
|
||||
|
||||
for _, val := range hosts {
|
||||
if err := Unmap(val); err != nil {
|
||||
if err := pm.Unmap(val); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,16 +11,18 @@ import (
|
||||
|
||||
type State struct {
|
||||
sync.Mutex
|
||||
Running bool
|
||||
Paused bool
|
||||
Restarting bool
|
||||
OOMKilled bool
|
||||
Pid int
|
||||
ExitCode int
|
||||
Error string // contains last known error when starting the container
|
||||
StartedAt time.Time
|
||||
FinishedAt time.Time
|
||||
waitChan chan struct{}
|
||||
Running bool
|
||||
Paused bool
|
||||
Restarting bool
|
||||
OOMKilled bool
|
||||
removalInProgress bool // Not need for this to be persistent on disk.
|
||||
Dead bool
|
||||
Pid int
|
||||
ExitCode int
|
||||
Error string // contains last known error when starting the container
|
||||
StartedAt time.Time
|
||||
FinishedAt time.Time
|
||||
waitChan chan struct{}
|
||||
}
|
||||
|
||||
func NewState() *State {
|
||||
@@ -42,6 +44,14 @@ func (s *State) String() string {
|
||||
return fmt.Sprintf("Up %s", units.HumanDuration(time.Now().UTC().Sub(s.StartedAt)))
|
||||
}
|
||||
|
||||
if s.removalInProgress {
|
||||
return "Removal In Progress"
|
||||
}
|
||||
|
||||
if s.Dead {
|
||||
return "Dead"
|
||||
}
|
||||
|
||||
if s.FinishedAt.IsZero() {
|
||||
return ""
|
||||
}
|
||||
@@ -60,6 +70,11 @@ func (s *State) StateString() string {
|
||||
}
|
||||
return "running"
|
||||
}
|
||||
|
||||
if s.Dead {
|
||||
return "dead"
|
||||
}
|
||||
|
||||
return "exited"
|
||||
}
|
||||
|
||||
@@ -217,3 +232,25 @@ func (s *State) IsPaused() bool {
|
||||
s.Unlock()
|
||||
return res
|
||||
}
|
||||
|
||||
func (s *State) SetRemovalInProgress() error {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
if s.removalInProgress {
|
||||
return fmt.Errorf("Status is already RemovalInProgress")
|
||||
}
|
||||
s.removalInProgress = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *State) ResetRemovalInProgress() {
|
||||
s.Lock()
|
||||
s.removalInProgress = false
|
||||
s.Unlock()
|
||||
}
|
||||
|
||||
func (s *State) SetDead() {
|
||||
s.Lock()
|
||||
s.Dead = true
|
||||
s.Unlock()
|
||||
}
|
||||
|
||||
@@ -189,10 +189,13 @@ func (container *Container) parseVolumeMountConfig() (map[string]*Mount, error)
|
||||
if _, exists := container.Volumes[path]; exists {
|
||||
continue
|
||||
}
|
||||
|
||||
if stat, err := os.Stat(filepath.Join(container.basefs, path)); err == nil {
|
||||
realPath, err := container.getResourcePath(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to evaluate the absolute path of symlink")
|
||||
}
|
||||
if stat, err := os.Stat(realPath); err == nil {
|
||||
if !stat.IsDir() {
|
||||
return nil, fmt.Errorf("file exists at %s, can't create volume there")
|
||||
return nil, fmt.Errorf("file exists at %s, can't create volume there", realPath)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
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.
|
||||
|
||||
The Docker Engine uses Linux-specific kernel features, so to run it on Windows
|
||||
we need to use a lightweight virtual machine (vm). You use the Windows Docker client to
|
||||
control the virtualized Docker Engine to build, run, and manage Docker containers.
|
||||
we need to use a lightweight virtual machine (VM). You use the **Windows Docker
|
||||
Client** to control the virtualized Docker Engine to build, run, and manage
|
||||
Docker containers.
|
||||
|
||||
To make this process easier, we've designed a helper application called
|
||||
[Boot2Docker](https://github.com/boot2docker/boot2docker) that installs the
|
||||
virtual machine and runs the Docker daemon.
|
||||
[Boot2Docker](https://github.com/boot2docker/boot2docker) creates a Linux virtual
|
||||
machine on Windows to run Docker on a Linux operating system.
|
||||
|
||||
Although you will be using Windows Docker client, the docker engine hosting the
|
||||
containers will still be running on Linux. Until the Docker engine for Windows
|
||||
is developed, you can launch only Linux containers from your Windows machine.
|
||||
|
||||
## Demonstration
|
||||
|
||||
@@ -21,18 +26,77 @@ virtual machine and runs the Docker daemon.
|
||||
|
||||
## Installation
|
||||
|
||||
1. Download the latest release of the [Docker for Windows Installer](https://github.com/boot2docker/windows-installer/releases/latest)
|
||||
2. Run the installer, which will install VirtualBox, MSYS-git, the boot2docker Linux ISO,
|
||||
and the Boot2Docker management tool.
|
||||
1. Download the latest release of the
|
||||
[Docker for Windows Installer](https://github.com/boot2docker/windows-installer/releases/latest).
|
||||
2. Run the installer, which will install Docker Client or Windows, VirtualBox,
|
||||
Git for Windows (MSYS-git), the boot2docker Linux ISO, and the Boot2Docker
|
||||
management tool.
|
||||

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

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

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

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

|
||||
|
||||
> NOTE: You can alternatively run `boot2docker shellinit | Invoke-Expression`
|
||||
> command to set the environment variables instead of copying and pasting on
|
||||
> PowerShell.
|
||||
|
||||
# Further Details
|
||||
|
||||
The Boot2Docker management tool provides several commands:
|
||||
|
||||
$ boot2docker
|
||||
Usage: boot2docker.exe [<options>] {help|init|up|ssh|save|down|poweroff|reset|restart|config|status|info|ip|shellinit|delete|download|upgrade|version} [<args>]
|
||||
|
||||
## Upgrading
|
||||
|
||||
@@ -47,46 +111,13 @@ and the Boot2Docker management tool.
|
||||
boot2docker download
|
||||
boot2docker start
|
||||
|
||||
## Running Docker
|
||||
|
||||
{{ include "no-remote-sudo.md" }}
|
||||
|
||||
Boot2Docker will log you in automatically so you can start using Docker right away.
|
||||
|
||||
Let's try the `hello-world` example image. Run
|
||||
|
||||
$ docker run hello-world
|
||||
|
||||
This should download the very small `hello-world` image and print a `Hello from Docker.` message.
|
||||
|
||||
## Login with PUTTY instead of using the CMD
|
||||
|
||||
Boot2Docker generates and uses the public/private key pair in your `%HOMEPATH%\.ssh`
|
||||
directory so to log in you need to use the private key from this same directory.
|
||||
|
||||
The private key needs to be converted into the format PuTTY uses.
|
||||
|
||||
You can do this with
|
||||
[puttygen](http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html):
|
||||
|
||||
- Open `puttygen.exe` and load ("File"->"Load" menu) the private key from
|
||||
`%HOMEPATH%\.ssh\id_boot2docker`
|
||||
- then click: "Save Private Key".
|
||||
- Then use the saved file to login with PuTTY using `docker@127.0.0.1:2022`.
|
||||
|
||||
# Further Details
|
||||
|
||||
The Boot2Docker management tool provides several commands:
|
||||
|
||||
$ ./boot2docker
|
||||
Usage: ./boot2docker [<options>] {help|init|up|ssh|save|down|poweroff|reset|restart|config|status|info|ip|delete|download|version} [<args>]
|
||||
|
||||
|
||||
## Container port redirection
|
||||
|
||||
If you are curious, the username for the boot2docker default user is `docker` and the password is `tcuser`.
|
||||
If you are curious, the username for the boot2docker default user is `docker`
|
||||
and the password is `tcuser`.
|
||||
|
||||
The latest version of `boot2docker` sets up a host only network adaptor which provides access to the container's ports.
|
||||
The latest version of `boot2docker` sets up a host only network adaptor which
|
||||
provides access to the container's ports.
|
||||
|
||||
If you run a container with an exposed port:
|
||||
|
||||
@@ -101,3 +132,18 @@ Typically, it is 192.168.59.103, but it could get changed by Virtualbox's DHCP
|
||||
implementation.
|
||||
|
||||
For further information or to report issues, please see the [Boot2Docker site](http://boot2docker.io)
|
||||
|
||||
## Login with PUTTY instead of using the CMD
|
||||
|
||||
Boot2Docker generates and uses the public/private key pair in your `%USERPROFILE%\.ssh`
|
||||
directory so to log in you need to use the private key from this same directory.
|
||||
|
||||
The private key needs to be converted into the format PuTTY uses.
|
||||
|
||||
You can do this with
|
||||
[puttygen](http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html):
|
||||
|
||||
- Open `puttygen.exe` and load ("File"->"Load" menu) the private key from
|
||||
`%USERPROFILE%\.ssh\id_boot2docker`
|
||||
- then click: "Save Private Key".
|
||||
- Then use the saved file to login with PuTTY using `docker@127.0.0.1:2022`.
|
||||
|
||||
@@ -280,8 +280,14 @@ The cache for `RUN` instructions can be invalidated by `ADD` instructions. See
|
||||
|
||||
- [Issue 783](https://github.com/docker/docker/issues/783) is about file
|
||||
permissions problems that can occur when using the AUFS file system. You
|
||||
might notice it during an attempt to `rm` a file, for example. The issue
|
||||
describes a workaround.
|
||||
might notice it during an attempt to `rm` a file, for example.
|
||||
|
||||
For systems that have recent aufs version (i.e., `dirperm1` mount option can
|
||||
be set), docker will attempt to fix the issue automatically by mounting
|
||||
the layers with `dirperm1` option. More details on `dirperm1` option can be
|
||||
found at [`aufs` man page](http://aufs.sourceforge.net/aufs3/man.html)
|
||||
|
||||
If your system doesnt have support for `dirperm1`, the issue describes a workaround.
|
||||
|
||||
## CMD
|
||||
|
||||
|
||||
@@ -57,7 +57,14 @@ impact on users. This list will be updated as issues are resolved.
|
||||
* **Unexpected File Permissions in Containers**
|
||||
An idiosyncrasy in AUFS prevents permissions from propagating predictably
|
||||
between upper and lower layers. This can cause issues with accessing private
|
||||
keys, database instances, etc. For complete information and workarounds see
|
||||
keys, database instances, etc.
|
||||
|
||||
For systems that have recent aufs version (i.e., `dirperm1` mount option can
|
||||
be set), docker will attempt to fix the issue automatically by mounting
|
||||
the layers with `dirperm1` option. More details on `dirperm1` option can be
|
||||
found at [`aufs` man page](http://aufs.sourceforge.net/aufs3/man.html)
|
||||
|
||||
For complete information and workarounds see
|
||||
[Github Issue 783](https://github.com/docker/docker/issues/783).
|
||||
|
||||
* **Docker Hub incompatible with Safari 8**
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package graph
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@@ -13,6 +15,7 @@ import (
|
||||
"time"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/docker/distribution/digest"
|
||||
"github.com/docker/docker/autogen/dockerversion"
|
||||
"github.com/docker/docker/daemon/graphdriver"
|
||||
"github.com/docker/docker/image"
|
||||
@@ -241,18 +244,27 @@ func (graph *Graph) newTempFile() (*os.File, error) {
|
||||
return ioutil.TempFile(tmp, "")
|
||||
}
|
||||
|
||||
func bufferToFile(f *os.File, src io.Reader) (int64, error) {
|
||||
n, err := io.Copy(f, src)
|
||||
func bufferToFile(f *os.File, src io.Reader) (int64, digest.Digest, error) {
|
||||
var (
|
||||
h = sha256.New()
|
||||
w = gzip.NewWriter(io.MultiWriter(f, h))
|
||||
)
|
||||
_, err := io.Copy(w, src)
|
||||
w.Close()
|
||||
if err != nil {
|
||||
return n, err
|
||||
return 0, "", err
|
||||
}
|
||||
if err = f.Sync(); err != nil {
|
||||
return n, err
|
||||
return 0, "", err
|
||||
}
|
||||
n, err := f.Seek(0, os.SEEK_CUR)
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
if _, err := f.Seek(0, 0); err != nil {
|
||||
return n, err
|
||||
return 0, "", err
|
||||
}
|
||||
return n, nil
|
||||
return n, digest.NewDigest("sha256", h), nil
|
||||
}
|
||||
|
||||
// setupInitLayer populates a directory with mountpoints suitable
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package graph
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -13,7 +12,6 @@ import (
|
||||
"sync"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/docker/distribution/digest"
|
||||
"github.com/docker/docker/engine"
|
||||
"github.com/docker/docker/image"
|
||||
"github.com/docker/docker/pkg/common"
|
||||
@@ -464,12 +462,7 @@ func (s *TagStore) pushV2Image(r *registry.Session, img *image.Image, endpoint *
|
||||
os.Remove(tf.Name())
|
||||
}()
|
||||
|
||||
h := sha256.New()
|
||||
size, err := bufferToFile(tf, io.TeeReader(arch, h))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
dgst := digest.NewDigest("sha256", h)
|
||||
size, dgst, err := bufferToFile(tf, arch)
|
||||
|
||||
// Send the layer
|
||||
log.Debugf("rendered layer for %s of [%d] size", img.ID, size)
|
||||
|
||||
@@ -75,7 +75,7 @@ rm -rf src/github.com/docker/distribution
|
||||
mkdir -p src/github.com/docker/distribution
|
||||
mv tmp-digest src/github.com/docker/distribution/digest
|
||||
|
||||
clone git github.com/docker/libcontainer a6044b701c166fe538fc760f9e2dcea3d737cd2a
|
||||
clone git github.com/docker/libcontainer d00b8369852285d6a830a8d3b966608b2ed89705
|
||||
# see src/github.com/docker/libcontainer/update-vendor.sh which is the "source of truth" for libcontainer deps (just like this file)
|
||||
rm -rf src/github.com/docker/libcontainer/vendor
|
||||
eval "$(grep '^clone ' src/github.com/docker/libcontainer/update-vendor.sh | grep -v 'github.com/codegangsta/cli' | grep -v 'github.com/Sirupsen/logrus')"
|
||||
|
||||
@@ -476,6 +476,39 @@ func TestRunWithVolumesFromExited(t *testing.T) {
|
||||
logDone("run - regression test for #4979 - volumes-from on exited container")
|
||||
}
|
||||
|
||||
// Volume path is a symlink which also exists on the host, and the host side is a file not a dir
|
||||
// But the volume call is just a normal volume, not a bind mount
|
||||
func TestRunCreateVolumesInSymlinkDir(t *testing.T) {
|
||||
testRequires(t, SameHostDaemon)
|
||||
testRequires(t, NativeExecDriver)
|
||||
defer deleteAllContainers()
|
||||
name := "test-volume-symlink"
|
||||
|
||||
dir, err := ioutil.TempDir("", name)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
f, err := os.OpenFile(filepath.Join(dir, "test"), os.O_CREATE, 0700)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f.Close()
|
||||
|
||||
dockerFile := fmt.Sprintf("FROM busybox\nRUN mkdir -p %s\nRUN ln -s %s /test", dir, dir)
|
||||
if _, err := buildImage(name, dockerFile, false); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer deleteImages(name)
|
||||
|
||||
if out, _, err := dockerCmd(t, "run", "-v", "/test/test", name); err != nil {
|
||||
t.Fatal(err, out)
|
||||
}
|
||||
|
||||
logDone("run - create volume in symlink directory")
|
||||
}
|
||||
|
||||
// Regression test for #4830
|
||||
func TestRunWithRelativePath(t *testing.T) {
|
||||
defer deleteAllContainers()
|
||||
|
||||
@@ -51,7 +51,7 @@ func (w *BroadcastWriter) Write(p []byte) (n int, err error) {
|
||||
for {
|
||||
line, err := w.buf.ReadString('\n')
|
||||
if err != nil {
|
||||
w.buf.Write([]byte(line))
|
||||
w.buf.WriteString(line)
|
||||
break
|
||||
}
|
||||
for stream, writers := range w.streams {
|
||||
|
||||
@@ -64,6 +64,7 @@ func (config *Config) Read(p []byte) (n int, err error) {
|
||||
return read, err
|
||||
}
|
||||
func (config *Config) Close() error {
|
||||
config.Current = config.Size
|
||||
config.Out.Write(config.Formatter.FormatProg(config.ID, config.Action, &JSONProg{Current: config.Current, Total: config.Size}))
|
||||
return config.In.Close()
|
||||
}
|
||||
|
||||
@@ -241,8 +241,6 @@ func StdStreams() (stdIn io.ReadCloser, stdOut io.Writer, stdErr io.Writer) {
|
||||
}
|
||||
handler.screenBufferInfo = screenBufferInfo
|
||||
|
||||
// Set the window size
|
||||
SetWindowSize(stdoutHandle, DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_HEIGHT)
|
||||
buffer = make([]CHAR_INFO, screenBufferInfo.MaximumWindowSize.X*screenBufferInfo.MaximumWindowSize.Y)
|
||||
|
||||
stdOut = &terminalWriter{
|
||||
@@ -283,6 +281,12 @@ func GetHandleInfo(in interface{}) (uintptr, bool) {
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package registry
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
@@ -71,21 +70,7 @@ func (auth *RequestAuthorization) getToken() (string, error) {
|
||||
return auth.tokenCache, nil
|
||||
}
|
||||
|
||||
tlsConfig := tls.Config{
|
||||
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,
|
||||
}
|
||||
client := auth.registryEndpoint.HTTPClient()
|
||||
factory := HTTPRequestFactory(nil)
|
||||
|
||||
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.
|
||||
func loginV1(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *utils.HTTPRequestFactory) (string, error) {
|
||||
var (
|
||||
status string
|
||||
reqBody []byte
|
||||
err error
|
||||
client = &http.Client{
|
||||
Transport: &http.Transport{
|
||||
DisableKeepAlives: true,
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
},
|
||||
CheckRedirect: AddRequiredHeadersToRedirectedRequests,
|
||||
}
|
||||
status string
|
||||
reqBody []byte
|
||||
err error
|
||||
client = registryEndpoint.HTTPClient()
|
||||
reqStatusCode = 0
|
||||
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.
|
||||
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 {
|
||||
return "", fmt.Errorf("Server Error: %s", err)
|
||||
}
|
||||
@@ -371,26 +350,10 @@ func loginV1(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *utils.
|
||||
// is to be determined.
|
||||
func loginV2(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *utils.HTTPRequestFactory) (string, error) {
|
||||
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 (
|
||||
err error
|
||||
allErrors []error
|
||||
client = registryEndpoint.HTTPClient()
|
||||
)
|
||||
|
||||
for _, challenge := range registryEndpoint.AuthChallenges {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package registry
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
@@ -262,3 +263,20 @@ HeaderLoop:
|
||||
|
||||
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()
|
||||
|
||||
if res.StatusCode == 401 {
|
||||
return nil, errLoginRequired
|
||||
}
|
||||
|
||||
var tokens, endpoints []string
|
||||
if !validate {
|
||||
if res.StatusCode != 200 && res.StatusCode != 201 {
|
||||
|
||||
@@ -33,7 +33,6 @@ type Config struct {
|
||||
NetworkDisabled bool
|
||||
MacAddress string
|
||||
OnBuild []string
|
||||
SecurityOpt []string
|
||||
Labels map[string]string
|
||||
}
|
||||
|
||||
|
||||
@@ -67,12 +67,12 @@ func generateProfile(out io.Writer) error {
|
||||
data := &data{
|
||||
Name: "docker-default",
|
||||
}
|
||||
if tuntablesExists() {
|
||||
if tunablesExists() {
|
||||
data.Imports = append(data.Imports, "#include <tunables/global>")
|
||||
} else {
|
||||
data.Imports = append(data.Imports, "@{PROC}=/proc/")
|
||||
}
|
||||
if abstrctionsEsists() {
|
||||
if abstractionsExists() {
|
||||
data.InnerImports = append(data.InnerImports, "#include <abstractions/base>")
|
||||
}
|
||||
if err := compiled.Execute(out, data); err != nil {
|
||||
@@ -82,13 +82,13 @@ func generateProfile(out io.Writer) error {
|
||||
}
|
||||
|
||||
// check if the tunables/global exist
|
||||
func tuntablesExists() bool {
|
||||
func tunablesExists() bool {
|
||||
_, err := os.Stat("/etc/apparmor.d/tunables/global")
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// check if abstractions/base exist
|
||||
func abstrctionsEsists() bool {
|
||||
func abstractionsExists() bool {
|
||||
_, err := os.Stat("/etc/apparmor.d/abstractions/base")
|
||||
return err == nil
|
||||
}
|
||||
|
||||
@@ -220,16 +220,16 @@ func getCgroupData(c *configs.Cgroup, pid int) (*data, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (raw *data) parent(subsystem string) (string, error) {
|
||||
func (raw *data) parent(subsystem, mountpoint string) (string, error) {
|
||||
initPath, err := cgroups.GetInitCgroupDir(subsystem)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return filepath.Join(raw.root, subsystem, initPath), nil
|
||||
return filepath.Join(mountpoint, initPath), nil
|
||||
}
|
||||
|
||||
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 err != nil {
|
||||
return "", err
|
||||
@@ -240,7 +240,7 @@ func (raw *data) path(subsystem string) (string, error) {
|
||||
return filepath.Join(raw.root, subsystem, raw.cgroup), nil
|
||||
}
|
||||
|
||||
parent, err := raw.parent(subsystem)
|
||||
parent, err := raw.parent(subsystem, mnt)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package fs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -41,6 +42,10 @@ func (s *FreezerGroup) Set(path string, cgroup *configs.Cgroup) error {
|
||||
}
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
}
|
||||
case configs.Undefined:
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("Invalid argument '%s' to freezer.state", string(cgroup.Freezer))
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package fs
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/docker/libcontainer/configs"
|
||||
)
|
||||
|
||||
func TestFreezerSetState(t *testing.T) {
|
||||
helper := NewCgroupTestUtil("freezer", t)
|
||||
defer helper.cleanup()
|
||||
|
||||
helper.writeFileContents(map[string]string{
|
||||
"freezer.state": string(configs.Frozen),
|
||||
})
|
||||
|
||||
helper.CgroupData.c.Freezer = configs.Thawed
|
||||
freezer := &FreezerGroup{}
|
||||
if err := freezer.Set(helper.CgroupPath, helper.CgroupData.c); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
value, err := getCgroupParamString(helper.CgroupPath, "freezer.state")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to parse freezer.state - %s", err)
|
||||
}
|
||||
if value != string(configs.Thawed) {
|
||||
t.Fatal("Got the wrong value, set freezer.state failed.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFreezerSetInvalidState(t *testing.T) {
|
||||
helper := NewCgroupTestUtil("freezer", t)
|
||||
defer helper.cleanup()
|
||||
|
||||
const (
|
||||
invalidArg configs.FreezerState = "Invalid"
|
||||
)
|
||||
|
||||
helper.CgroupData.c.Freezer = invalidArg
|
||||
freezer := &FreezerGroup{}
|
||||
if err := freezer.Set(helper.CgroupPath, helper.CgroupData.c); err == nil {
|
||||
t.Fatal("Failed to return invalid argument error")
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,10 @@ var subsystems = map[string]subsystem{
|
||||
"freezer": &fs.FreezerGroup{},
|
||||
}
|
||||
|
||||
const (
|
||||
testScopeWait = 4
|
||||
)
|
||||
|
||||
var (
|
||||
connLock sync.Mutex
|
||||
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
|
||||
hasTransientDefaultDependencies = true
|
||||
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 strings.Contains(dbusError.Name, "org.freedesktop.DBus.Error.PropertyReadOnly") {
|
||||
hasTransientDefaultDependencies = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Not critical because of the stop unit logic above.
|
||||
theConn.StopUnit(scope, "replace")
|
||||
}
|
||||
return hasStartTransientUnit
|
||||
}
|
||||
@@ -189,16 +218,7 @@ func (m *Manager) Apply(pid int) error {
|
||||
}
|
||||
|
||||
paths := make(map[string]string)
|
||||
for _, sysname := range []string{
|
||||
"devices",
|
||||
"memory",
|
||||
"cpu",
|
||||
"cpuset",
|
||||
"cpuacct",
|
||||
"blkio",
|
||||
"perf_event",
|
||||
"freezer",
|
||||
} {
|
||||
for sysname := range subsystems {
|
||||
subsystemPath, err := getSubsystemPath(m.Cgroups, sysname)
|
||||
if err != nil {
|
||||
// Don't fail if a cgroup hierarchy was not found, just skip this subsystem
|
||||
|
||||
@@ -140,7 +140,9 @@ func (c *linuxContainer) commandTemplate(p *Process, childPipe *os.File) (*exec.
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{}
|
||||
}
|
||||
cmd.ExtraFiles = []*os.File{childPipe}
|
||||
cmd.SysProcAttr.Pdeathsig = syscall.SIGKILL
|
||||
// NOTE: when running a container with no PID namespace and the parent process spawning the container is
|
||||
// PID1 the pdeathsig is being delivered to the container's init process by the kernel for some reason
|
||||
// even with the parent still running.
|
||||
if c.config.ParentDeathSignal > 0 {
|
||||
cmd.SysProcAttr.Pdeathsig = syscall.Signal(c.config.ParentDeathSignal)
|
||||
}
|
||||
@@ -193,12 +195,13 @@ func (c *linuxContainer) newSetnsProcess(p *Process, cmd *exec.Cmd, parentPipe,
|
||||
|
||||
func (c *linuxContainer) newInitConfig(process *Process) *initConfig {
|
||||
return &initConfig{
|
||||
Config: c.config,
|
||||
Args: process.Args,
|
||||
Env: process.Env,
|
||||
User: process.User,
|
||||
Cwd: process.Cwd,
|
||||
Console: process.consolePath,
|
||||
Config: c.config,
|
||||
Args: process.Args,
|
||||
Env: process.Env,
|
||||
User: process.User,
|
||||
Cwd: process.Cwd,
|
||||
Console: process.consolePath,
|
||||
Capabilities: process.Capabilities,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,13 +40,14 @@ type network struct {
|
||||
|
||||
// initConfig is used for transferring parameters from Exec() to Init()
|
||||
type initConfig struct {
|
||||
Args []string `json:"args"`
|
||||
Env []string `json:"env"`
|
||||
Cwd string `json:"cwd"`
|
||||
User string `json:"user"`
|
||||
Config *configs.Config `json:"config"`
|
||||
Console string `json:"console"`
|
||||
Networks []*network `json:"network"`
|
||||
Args []string `json:"args"`
|
||||
Env []string `json:"env"`
|
||||
Cwd string `json:"cwd"`
|
||||
Capabilities []string `json:"capabilities"`
|
||||
User string `json:"user"`
|
||||
Config *configs.Config `json:"config"`
|
||||
Console string `json:"console"`
|
||||
Networks []*network `json:"network"`
|
||||
}
|
||||
|
||||
type initer interface {
|
||||
@@ -68,7 +69,8 @@ func newContainerInit(t initType, pipe *os.File) (initer, error) {
|
||||
}, nil
|
||||
case initStandard:
|
||||
return &linuxStandardInit{
|
||||
config: config,
|
||||
parentPid: syscall.Getppid(),
|
||||
config: config,
|
||||
}, nil
|
||||
}
|
||||
return nil, fmt.Errorf("unknown init type %q", t)
|
||||
@@ -99,7 +101,12 @@ func finalizeNamespace(config *initConfig) error {
|
||||
if err := utils.CloseExecFrom(3); err != nil {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"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) {
|
||||
if testing.Short() {
|
||||
return
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
## 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 add the `libcontainer/vendor` into your GOPATH. It's because libcontainer
|
||||
vendors all its dependencies, so it can be built predictably.
|
||||
|
||||
```
|
||||
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 configuration 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
|
||||
```
|
||||
|
||||
You can customize `container.json` per your needs. After that, 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 --config container.json /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 directory from 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 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
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ package libcontainer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -44,8 +45,12 @@ func (p *setnsProcess) startTime() (string, error) {
|
||||
return system.GetProcessStartTime(p.pid())
|
||||
}
|
||||
|
||||
func (p *setnsProcess) signal(s os.Signal) error {
|
||||
return p.cmd.Process.Signal(s)
|
||||
func (p *setnsProcess) signal(sig os.Signal) error {
|
||||
s, ok := sig.(syscall.Signal)
|
||||
if !ok {
|
||||
return errors.New("os: unsupported signal type")
|
||||
}
|
||||
return syscall.Kill(p.cmd.Process.Pid, s)
|
||||
}
|
||||
|
||||
func (p *setnsProcess) start() (err error) {
|
||||
@@ -235,6 +240,10 @@ func (p *initProcess) createNetworkInterfaces() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *initProcess) signal(s os.Signal) error {
|
||||
return p.cmd.Process.Signal(s)
|
||||
func (p *initProcess) signal(sig os.Signal) error {
|
||||
s, ok := sig.(syscall.Signal)
|
||||
if !ok {
|
||||
return errors.New("os: unsupported signal type")
|
||||
}
|
||||
return syscall.Kill(p.cmd.Process.Pid, s)
|
||||
}
|
||||
|
||||
@@ -13,7 +13,8 @@ import (
|
||||
)
|
||||
|
||||
type linuxStandardInit struct {
|
||||
config *initConfig
|
||||
parentPid int
|
||||
config *initConfig
|
||||
}
|
||||
|
||||
func (l *linuxStandardInit) Init() error {
|
||||
@@ -85,9 +86,10 @@ func (l *linuxStandardInit) Init() error {
|
||||
if err := pdeath.Restore(); err != nil {
|
||||
return err
|
||||
}
|
||||
// Signal self if parent is already dead. Does nothing if running in a new
|
||||
// PID namespace, as Getppid will always return 0.
|
||||
if syscall.Getppid() == 1 {
|
||||
// compare the parent from the inital start of the init process and make sure that it did not change.
|
||||
// if the parent changes that means it died and we were reparened to something else so we should
|
||||
// just kill ourself and not cause problems for someone else.
|
||||
if syscall.Getppid() != l.parentPid {
|
||||
return syscall.Kill(syscall.Getpid(), syscall.SIGKILL)
|
||||
}
|
||||
return system.Execv(l.config.Args[0], l.config.Args[0:], os.Environ())
|
||||
|
||||