Compare commits
1 Commits
v1.6.0
..
v1.6.0-rc1
| Author | SHA1 | Date | |
|---|---|---|---|
| 746e830230 |
@@ -4,10 +4,8 @@
|
||||
|
||||
#### Builder
|
||||
+ Building images from an image ID
|
||||
+ build containers with resource constraints, ie `docker build --cpu-shares=100 --memory=1024m...`
|
||||
+ `commit --change` to apply specified Dockerfile instructions while committing the image
|
||||
+ `import --change` to apply specified Dockerfile instructions while importing the image
|
||||
+ basic build cancellation
|
||||
|
||||
#### Client
|
||||
+ Windows Support
|
||||
@@ -15,10 +13,9 @@
|
||||
#### Runtime
|
||||
+ Container and image Labels
|
||||
+ `--cgroup-parent` for specifying a parent cgroup to place container cgroup within
|
||||
+ Logging drivers, `json-file`, `syslog`, or `none`
|
||||
+ Logging drivers, `json-file` or `syslog`
|
||||
+ 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)
|
||||
|
||||
|
||||
@@ -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], nil)
|
||||
stream, statusCode, err := cli.call("POST", "/auth", cli.configFile.Configs[serverAddress], false)
|
||||
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, nil))
|
||||
body, _, err := readBody(cli.call("GET", "/version", nil, false))
|
||||
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, nil))
|
||||
body, _, err := readBody(cli.call("GET", "/info", nil, false))
|
||||
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, nil))
|
||||
_, _, err := readBody(cli.call("POST", "/containers/"+name+"/stop?"+v.Encode(), nil, false))
|
||||
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, nil))
|
||||
_, _, err := readBody(cli.call("POST", "/containers/"+name+"/restart?"+v.Encode(), nil, false))
|
||||
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, nil)); err != nil {
|
||||
if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/kill?signal=%s", cid, sig), nil, false)); 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, nil)
|
||||
stream, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, false)
|
||||
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, nil))
|
||||
_, _, err := readBody(cli.call("POST", "/containers/"+name+"/start", nil, false))
|
||||
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, nil)); err != nil {
|
||||
if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/unpause", name), nil, false)); 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, nil)); err != nil {
|
||||
if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/pause", name), nil, false)); 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, nil)); err != nil {
|
||||
if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/rename?name=%s", old_name, new_name), nil, false)); 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, nil))
|
||||
obj, _, err := readBody(cli.call("GET", "/containers/"+name+"/json", nil, false))
|
||||
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, nil))
|
||||
obj, _, err = readBody(cli.call("GET", "/images/"+name+"/json", nil, false))
|
||||
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, nil)
|
||||
stream, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/top?"+val.Encode(), nil, false)
|
||||
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, nil)
|
||||
stream, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, false)
|
||||
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, nil))
|
||||
body, _, err := readBody(cli.call("DELETE", "/images/"+name+"?"+v.Encode(), nil, false))
|
||||
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, nil))
|
||||
body, _, err := readBody(cli.call("GET", "/images/"+cmd.Arg(0)+"/history", nil, false))
|
||||
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, nil))
|
||||
_, _, err := readBody(cli.call("DELETE", "/containers/"+name+"?"+val.Encode(), nil, false))
|
||||
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, nil)); err != nil {
|
||||
if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/kill?signal=%s", name, *signal), nil, false)); err != nil {
|
||||
fmt.Fprintf(cli.err, "%s\n", err)
|
||||
encounteredError = fmt.Errorf("Error: failed to kill one or more containers")
|
||||
} else {
|
||||
@@ -1317,8 +1317,32 @@ func (cli *DockerCli) CmdPush(args ...string) error {
|
||||
v := url.Values{}
|
||||
v.Set("tag", tag)
|
||||
|
||||
_, _, err = cli.clientRequestAttemptLogin("POST", "/images/"+remote+"/push?"+v.Encode(), nil, cli.out, repoInfo.Index, "push")
|
||||
return err
|
||||
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
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdPull(args ...string) error {
|
||||
@@ -1351,8 +1375,36 @@ func (cli *DockerCli) CmdPull(args ...string) error {
|
||||
|
||||
cli.LoadConfigFile()
|
||||
|
||||
_, _, err = cli.clientRequestAttemptLogin("POST", "/images/create?"+v.Encode(), nil, cli.out, repoInfo.Index, "pull")
|
||||
return err
|
||||
// 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
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdImages(args ...string) error {
|
||||
@@ -1396,7 +1448,7 @@ func (cli *DockerCli) CmdImages(args ...string) error {
|
||||
v.Set("filters", filterJson)
|
||||
}
|
||||
|
||||
body, _, err := readBody(cli.call("GET", "/images/json?"+v.Encode(), nil, nil))
|
||||
body, _, err := readBody(cli.call("GET", "/images/json?"+v.Encode(), nil, false))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1474,7 +1526,7 @@ func (cli *DockerCli) CmdImages(args ...string) error {
|
||||
v.Set("all", "1")
|
||||
}
|
||||
|
||||
body, _, err := readBody(cli.call("GET", "/images/json?"+v.Encode(), nil, nil))
|
||||
body, _, err := readBody(cli.call("GET", "/images/json?"+v.Encode(), nil, false))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -1672,7 +1724,7 @@ func (cli *DockerCli) CmdPs(args ...string) error {
|
||||
v.Set("filters", filterJson)
|
||||
}
|
||||
|
||||
body, _, err := readBody(cli.call("GET", "/containers/json?"+v.Encode(), nil, nil))
|
||||
body, _, err := readBody(cli.call("GET", "/containers/json?"+v.Encode(), nil, false))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1813,7 +1865,7 @@ func (cli *DockerCli) CmdCommit(args ...string) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
stream, _, err := cli.call("POST", "/commit?"+v.Encode(), config, nil)
|
||||
stream, _, err := cli.call("POST", "/commit?"+v.Encode(), config, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1924,7 +1976,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, nil))
|
||||
body, _, err := readBody(cli.call("GET", "/containers/"+cmd.Arg(0)+"/changes", nil, false))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -1962,7 +2014,7 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
|
||||
|
||||
name := cmd.Arg(0)
|
||||
|
||||
stream, _, err := cli.call("GET", "/containers/"+name+"/json", nil, nil)
|
||||
stream, _, err := cli.call("GET", "/containers/"+name+"/json", nil, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -2003,7 +2055,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, nil)
|
||||
stream, _, err := cli.call("GET", "/containers/"+name+"/json", nil, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -2074,27 +2126,16 @@ func (cli *DockerCli) CmdSearch(args ...string) error {
|
||||
|
||||
utils.ParseFlags(cmd, args, true)
|
||||
|
||||
name := cmd.Arg(0)
|
||||
v := url.Values{}
|
||||
v.Set("term", name)
|
||||
v.Set("term", cmd.Arg(0))
|
||||
|
||||
body, _, err := readBody(cli.call("GET", "/images/search?"+v.Encode(), nil, true))
|
||||
|
||||
// Resolve the Repository name from fqn to hostname + name
|
||||
taglessRemote, _ := parsers.ParseRepositoryTag(name)
|
||||
repoInfo, err := registry.ParseRepositoryInfo(taglessRemote)
|
||||
if err != nil {
|
||||
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(rawBody); err != nil {
|
||||
if _, err := outs.ReadListFrom(body); err != nil {
|
||||
return err
|
||||
}
|
||||
w := tabwriter.NewWriter(cli.out, 10, 1, 3, ' ', 0)
|
||||
@@ -2149,7 +2190,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, nil)); err != nil {
|
||||
if _, _, err := readBody(cli.call("POST", "/images/"+cmd.Arg(0)+"/tag?"+v.Encode(), nil, false)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@@ -2251,7 +2292,7 @@ func (cli *DockerCli) createContainer(config *runconfig.Config, hostConfig *runc
|
||||
}
|
||||
|
||||
//create the container
|
||||
stream, statusCode, err := cli.call("POST", "/containers/create?"+containerValues.Encode(), mergedConfig, nil)
|
||||
stream, statusCode, err := cli.call("POST", "/containers/create?"+containerValues.Encode(), mergedConfig, false)
|
||||
//if image not found try to pull it
|
||||
if statusCode == 404 {
|
||||
repo, tag := parsers.ParseRepositoryTag(config.Image)
|
||||
@@ -2265,7 +2306,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, nil); err != nil {
|
||||
if stream, _, err = cli.call("POST", "/containers/create?"+containerValues.Encode(), mergedConfig, false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if err != nil {
|
||||
@@ -2455,7 +2496,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
||||
}
|
||||
|
||||
//start the container
|
||||
if _, _, err = readBody(cli.call("POST", "/containers/"+createResponse.ID+"/start", nil, nil)); err != nil {
|
||||
if _, _, err = readBody(cli.call("POST", "/containers/"+createResponse.ID+"/start", nil, false)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -2485,13 +2526,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, nil)); err != nil {
|
||||
if _, _, err := readBody(cli.call("POST", "/containers/"+createResponse.ID+"/wait", nil, false)); 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, nil)); err != nil {
|
||||
if _, _, err := readBody(cli.call("DELETE", "/containers/"+createResponse.ID+"?v=1", nil, false)); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
@@ -2531,7 +2572,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, nil)
|
||||
stream, statusCode, err := cli.call("POST", "/containers/"+info[0]+"/copy", copyData, false)
|
||||
if stream != nil {
|
||||
defer stream.Close()
|
||||
}
|
||||
@@ -2626,7 +2667,7 @@ func (cli *DockerCli) CmdExec(args ...string) error {
|
||||
return &utils.StatusError{StatusCode: 1}
|
||||
}
|
||||
|
||||
stream, _, err := cli.call("POST", "/containers/"+execConfig.Container+"/exec", execConfig, nil)
|
||||
stream, _, err := cli.call("POST", "/containers/"+execConfig.Container+"/exec", execConfig, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -2648,7 +2689,7 @@ func (cli *DockerCli) CmdExec(args ...string) error {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if _, _, err := readBody(cli.call("POST", "/exec/"+execID+"/start", execConfig, nil)); err != nil {
|
||||
if _, _, err := readBody(cli.call("POST", "/exec/"+execID+"/start", execConfig, false)); err != nil {
|
||||
return err
|
||||
}
|
||||
// For now don't print this - wait for when we support exec wait()
|
||||
@@ -2740,7 +2781,7 @@ type containerStats struct {
|
||||
}
|
||||
|
||||
func (s *containerStats) Collect(cli *DockerCli) {
|
||||
stream, _, err := cli.call("GET", "/containers/"+s.Name+"/stats", nil, nil)
|
||||
stream, _, err := cli.call("GET", "/containers/"+s.Name+"/stats", nil, false)
|
||||
if err != nil {
|
||||
s.err = err
|
||||
return
|
||||
|
||||
@@ -12,10 +12,8 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
gosignal "os/signal"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/api"
|
||||
@@ -56,144 +54,124 @@ func (cli *DockerCli) encodeData(data interface{}) (*bytes.Buffer, error) {
|
||||
return params, 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 nil, "", -1, err
|
||||
}
|
||||
req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION)
|
||||
req.URL.Host = cli.addr
|
||||
req.URL.Scheme = cli.scheme
|
||||
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 nil, "", statusCode, ErrConnectionRefused
|
||||
}
|
||||
|
||||
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 nil, "", statusCode, err
|
||||
}
|
||||
if len(body) == 0 {
|
||||
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 nil, "", statusCode, fmt.Errorf("Error response from daemon: %s", bytes.TrimSpace(body))
|
||||
}
|
||||
|
||||
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) {
|
||||
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
|
||||
}
|
||||
|
||||
if data != nil {
|
||||
if headers == nil {
|
||||
headers = make(map[string][]string)
|
||||
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
|
||||
}
|
||||
headers["Content-Type"] = []string{"application/json"}
|
||||
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)
|
||||
|
||||
}
|
||||
|
||||
body, _, statusCode, err := cli.clientRequest(method, path, params, headers)
|
||||
return body, statusCode, 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 {
|
||||
body, contentType, _, err := cli.clientRequest(method, path, in, headers)
|
||||
if (method == "POST" || method == "PUT") && 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 cli.streamBody(body, contentType, setRawTerminal, stdout, stderr)
|
||||
}
|
||||
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")
|
||||
}
|
||||
|
||||
func (cli *DockerCli) streamBody(body io.ReadCloser, contentType string, setRawTerminal bool, stdout, stderr io.Writer) error {
|
||||
defer body.Close()
|
||||
if headers != nil {
|
||||
for k, v := range headers {
|
||||
req.Header[k] = v
|
||||
}
|
||||
}
|
||||
resp, err := cli.HTTPClient().Do(req)
|
||||
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 err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if api.MatchesContentType(contentType, "application/json") {
|
||||
return utils.DisplayJSONMessagesStream(body, stdout, cli.outFd, cli.isTerminalOut)
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 400 {
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(body) == 0 {
|
||||
return fmt.Errorf("Error :%s", http.StatusText(resp.StatusCode))
|
||||
}
|
||||
return fmt.Errorf("Error: %s", bytes.TrimSpace(body))
|
||||
}
|
||||
|
||||
if api.MatchesContentType(resp.Header.Get("Content-Type"), "application/json") {
|
||||
return utils.DisplayJSONMessagesStream(resp.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, body)
|
||||
_, err = io.Copy(stdout, resp.Body)
|
||||
} else {
|
||||
_, err = stdcopy.StdCopy(stdout, stderr, body)
|
||||
_, err = stdcopy.StdCopy(stdout, stderr, resp.Body)
|
||||
}
|
||||
log.Debugf("[stream] End of stdout")
|
||||
return err
|
||||
@@ -217,13 +195,13 @@ func (cli *DockerCli) resizeTty(id string, isExec bool) {
|
||||
path = "/exec/" + id + "/resize?"
|
||||
}
|
||||
|
||||
if _, _, err := readBody(cli.call("POST", path+v.Encode(), nil, nil)); err != nil {
|
||||
if _, _, err := readBody(cli.call("POST", path+v.Encode(), nil, false)); 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, nil)
|
||||
func waitForExit(cli *DockerCli, containerId string) (int, error) {
|
||||
stream, _, err := cli.call("POST", "/containers/"+containerId+"/wait", nil, false)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
@@ -237,8 +215,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, nil)
|
||||
func getExitCode(cli *DockerCli, containerId string) (bool, int, error) {
|
||||
stream, _, err := cli.call("GET", "/containers/"+containerId+"/json", nil, false)
|
||||
if err != nil {
|
||||
// If we can't connect, then the daemon probably died.
|
||||
if err != ErrConnectionRefused {
|
||||
@@ -258,8 +236,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, nil)
|
||||
func getExecExitCode(cli *DockerCli, execId string) (bool, int, error) {
|
||||
stream, _, err := cli.call("GET", "/exec/"+execId+"/json", nil, false)
|
||||
if err != nil {
|
||||
// If we can't connect, then the daemon probably died.
|
||||
if err != ErrConnectionRefused {
|
||||
@@ -279,29 +257,13 @@ func getExecExitCode(cli *DockerCli, execID string) (bool, int, error) {
|
||||
func (cli *DockerCli) monitorTtySize(id string, isExec bool) error {
|
||||
cli.resizeTty(id, isExec)
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
go func() {
|
||||
prevH, prevW := cli.getTtySize()
|
||||
for {
|
||||
time.Sleep(time.Millisecond * 250)
|
||||
h, w := cli.getTtySize()
|
||||
|
||||
if prevW != w || prevH != h {
|
||||
cli.resizeTty(id, isExec)
|
||||
}
|
||||
prevH = h
|
||||
prevW = w
|
||||
}
|
||||
}()
|
||||
} else {
|
||||
sigchan := make(chan os.Signal, 1)
|
||||
gosignal.Notify(sigchan, signal.SIGWINCH)
|
||||
go func() {
|
||||
for _ = range sigchan {
|
||||
cli.resizeTty(id, isExec)
|
||||
}
|
||||
}()
|
||||
}
|
||||
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/bridge"
|
||||
"github.com/docker/docker/daemon/networkdriver/portallocator"
|
||||
"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{} = make(chan struct{})
|
||||
activationLock chan struct{}
|
||||
)
|
||||
|
||||
type HttpServer struct {
|
||||
@@ -1527,7 +1527,7 @@ func allocateDaemonPort(addr string) error {
|
||||
}
|
||||
|
||||
for _, hostIP := range hostIPs {
|
||||
if _, err := bridge.RequestPort(hostIP, "tcp", intPort); err != nil {
|
||||
if _, err := portallocator.RequestPort(hostIP, "tcp", intPort); err != nil {
|
||||
return fmt.Errorf("failed to allocate daemon listening port %d (err: %v)", intPort, err)
|
||||
}
|
||||
}
|
||||
@@ -1578,6 +1578,7 @@ 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,9 +95,7 @@ func AcceptConnections(job *engine.Job) engine.Status {
|
||||
go systemd.SdNotify("READY=1")
|
||||
|
||||
// close the lock so the listeners start accepting connections
|
||||
select {
|
||||
case <-activationLock:
|
||||
default:
|
||||
if activationLock != nil {
|
||||
close(activationLock)
|
||||
}
|
||||
|
||||
|
||||
@@ -58,18 +58,6 @@ __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") )
|
||||
@@ -337,7 +325,7 @@ _docker_cp() {
|
||||
(( counter++ ))
|
||||
|
||||
if [ $cword -eq $counter ]; then
|
||||
_filedir -d
|
||||
_filedir
|
||||
return
|
||||
fi
|
||||
;;
|
||||
@@ -449,10 +437,7 @@ _docker_history() {
|
||||
_docker_images() {
|
||||
case "$prev" in
|
||||
--filter|-f)
|
||||
COMPREPLY=( $( compgen -W "dangling=true label=" -- "$cur" ) )
|
||||
if [ "$COMPREPLY" = "label=" ]; then
|
||||
compopt -o nospace
|
||||
fi
|
||||
COMPREPLY=( $( compgen -W "dangling=true" -- "$cur" ) )
|
||||
return
|
||||
;;
|
||||
esac
|
||||
@@ -462,20 +447,17 @@ _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
|
||||
;;
|
||||
*)
|
||||
__docker_image_repos
|
||||
local counter=$(__docker_pos_first_nonflag)
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_image_repos
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
@@ -634,7 +616,7 @@ _docker_ps() {
|
||||
__docker_containers_all
|
||||
;;
|
||||
--filter|-f)
|
||||
COMPREPLY=( $( compgen -S = -W "exited id label name status" -- "$cur" ) )
|
||||
COMPREPLY=( $( compgen -S = -W "exited status" -- "$cur" ) )
|
||||
compopt -o nospace
|
||||
return
|
||||
;;
|
||||
@@ -644,16 +626,6 @@ _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
|
||||
@@ -762,7 +734,6 @@ _docker_run() {
|
||||
--attach -a
|
||||
--cap-add
|
||||
--cap-drop
|
||||
--cgroup-parent
|
||||
--cidfile
|
||||
--cpuset
|
||||
--cpu-shares -c
|
||||
@@ -775,10 +746,7 @@ _docker_run() {
|
||||
--expose
|
||||
--hostname -h
|
||||
--ipc
|
||||
--label -l
|
||||
--label-file
|
||||
--link
|
||||
--log-driver
|
||||
--lxc-conf
|
||||
--mac-address
|
||||
--memory -m
|
||||
@@ -830,7 +798,7 @@ _docker_run() {
|
||||
__docker_capabilities
|
||||
return
|
||||
;;
|
||||
--cidfile|--env-file|--label-file)
|
||||
--cidfile|--env-file)
|
||||
_filedir
|
||||
return
|
||||
;;
|
||||
@@ -882,10 +850,6 @@ _docker_run() {
|
||||
esac
|
||||
return
|
||||
;;
|
||||
--log-driver)
|
||||
COMPREPLY=( $( compgen -W "json-file syslog none" -- "$cur") )
|
||||
return
|
||||
;;
|
||||
--net)
|
||||
case "$cur" in
|
||||
container:*)
|
||||
|
||||
@@ -49,7 +49,7 @@ post-start script
|
||||
fi
|
||||
if ! printf "%s" "$DOCKER_OPTS" | grep -qE -e '-H|--host'; then
|
||||
while ! [ -e /var/run/docker.sock ]; do
|
||||
initctl status $UPSTART_JOB | grep -qE "(stop|respawn)/" && exit 1
|
||||
initctl status $UPSTART_JOB | grep -q "stop/" && exit 1
|
||||
echo "Waiting for /var/run/docker.sock"
|
||||
sleep 0.1
|
||||
done
|
||||
|
||||
@@ -360,10 +360,6 @@ 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() {
|
||||
@@ -1379,7 +1375,6 @@ func (container *Container) startLogging() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
container.LogPath = pth
|
||||
|
||||
dl, err := jsonfilelog.New(pth)
|
||||
if err != nil {
|
||||
|
||||
@@ -25,6 +25,7 @@ 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"
|
||||
@@ -287,8 +288,19 @@ func (daemon *Daemon) register(container *Container, updateSuffixarray bool) err
|
||||
if err := container.ToDisk(); err != nil {
|
||||
log.Debugf("saving stopped state to disk %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
info := daemon.execDriver.Info(container.ID)
|
||||
if !info.IsRunning() {
|
||||
log.Debugf("Container %s was supposed to be running but is not.", container.ID)
|
||||
|
||||
log.Debugf("Marking as stopped")
|
||||
|
||||
container.SetStopped(&execdriver.ExitStatus{ExitCode: -127})
|
||||
if err := container.ToDisk(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -815,6 +827,12 @@ 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 != "" {
|
||||
@@ -994,8 +1012,7 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine) (*Daemon, error)
|
||||
}
|
||||
|
||||
sysInfo := sysinfo.New(false)
|
||||
const runDir = "/var/run/docker"
|
||||
ed, err := execdrivers.NewDriver(config.ExecDriver, runDir, config.Root, sysInitPath, sysInfo)
|
||||
ed, err := execdrivers.NewDriver(config.ExecDriver, config.Root, sysInitPath, sysInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -61,15 +61,8 @@ 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 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)
|
||||
}
|
||||
if err := daemon.Rm(container); err != nil {
|
||||
return job.Errorf("Cannot destroy container %s: %s", name, err)
|
||||
}
|
||||
container.LogEvent("destroy")
|
||||
if removeVolume {
|
||||
@@ -88,16 +81,8 @@ 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) commonRm(container *Container, forceRemove bool) (err error) {
|
||||
func (daemon *Daemon) Rm(container *Container) error {
|
||||
if container == nil {
|
||||
return fmt.Errorf("The given container is <nil>")
|
||||
}
|
||||
@@ -107,40 +92,19 @@ func (daemon *Daemon) commonRm(container *Container, forceRemove bool) (err erro
|
||||
return fmt.Errorf("Container %v not found - maybe it was already destroyed?", container.ID)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
if err := container.Stop(3); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
}()
|
||||
|
||||
// Deregister the container before removing its directory, to avoid race conditions
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -149,17 +113,15 @@ func (daemon *Daemon) commonRm(container *Container, forceRemove bool) (err erro
|
||||
return fmt.Errorf("Driver %s failed to remove init filesystem %s: %s", daemon.driver, initID, err)
|
||||
}
|
||||
|
||||
if err = os.RemoveAll(container.root); err != nil {
|
||||
if err := os.RemoveAll(container.root); err != nil {
|
||||
return fmt.Errorf("Unable to remove filesystem for %v: %v", container.ID, err)
|
||||
}
|
||||
|
||||
if err = daemon.execDriver.Clean(container.ID); err != nil {
|
||||
if err := daemon.execDriver.Clean(container.ID); err != nil {
|
||||
return fmt.Errorf("Unable to remove execdriver data for %s: %s", container.ID, err)
|
||||
}
|
||||
|
||||
selinuxFreeLxcContexts(container.ProcessLabel)
|
||||
daemon.idIndex.Delete(container.ID)
|
||||
daemon.containers.Delete(container.ID)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -10,13 +10,13 @@ import (
|
||||
"github.com/docker/docker/pkg/sysinfo"
|
||||
)
|
||||
|
||||
func NewDriver(name, root, libPath, initPath string, sysInfo *sysinfo.SysInfo) (execdriver.Driver, error) {
|
||||
func NewDriver(name, root, initPath string, sysInfo *sysinfo.SysInfo) (execdriver.Driver, error) {
|
||||
switch name {
|
||||
case "lxc":
|
||||
// we want to give the lxc driver the full docker root because it needs
|
||||
// to access and write config and template files in /var/lib/docker/containers/*
|
||||
// to be backwards compatible
|
||||
return lxc.NewDriver(root, libPath, initPath, sysInfo.AppArmor)
|
||||
return lxc.NewDriver(root, initPath, sysInfo.AppArmor)
|
||||
case "native":
|
||||
return native.NewDriver(path.Join(root, "execdriver", "native"), initPath)
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ import (
|
||||
"github.com/docker/docker/daemon/execdriver"
|
||||
sysinfo "github.com/docker/docker/pkg/system"
|
||||
"github.com/docker/docker/pkg/term"
|
||||
"github.com/docker/docker/pkg/version"
|
||||
"github.com/docker/docker/utils"
|
||||
"github.com/docker/libcontainer"
|
||||
"github.com/docker/libcontainer/cgroups"
|
||||
@@ -36,7 +35,6 @@ var ErrExec = errors.New("Unsupported: Exec is not supported by the lxc driver")
|
||||
|
||||
type driver struct {
|
||||
root string // root path for the driver to use
|
||||
libPath string
|
||||
initPath string
|
||||
apparmor bool
|
||||
sharedRoot bool
|
||||
@@ -50,10 +48,7 @@ type activeContainer struct {
|
||||
cmd *exec.Cmd
|
||||
}
|
||||
|
||||
func NewDriver(root, libPath, initPath string, apparmor bool) (*driver, error) {
|
||||
if err := os.MkdirAll(root, 0700); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
func NewDriver(root, initPath string, apparmor bool) (*driver, error) {
|
||||
// setup unconfined symlink
|
||||
if err := linkLxcStart(root); err != nil {
|
||||
return nil, err
|
||||
@@ -65,7 +60,6 @@ func NewDriver(root, libPath, initPath string, apparmor bool) (*driver, error) {
|
||||
return &driver{
|
||||
apparmor: apparmor,
|
||||
root: root,
|
||||
libPath: libPath,
|
||||
initPath: initPath,
|
||||
sharedRoot: rootIsShared(),
|
||||
activeContainers: make(map[string]*activeContainer),
|
||||
@@ -121,13 +115,6 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba
|
||||
"-n", c.ID,
|
||||
"-f", configPath,
|
||||
}
|
||||
|
||||
// From lxc>=1.1 the default behavior is to daemonize containers after start
|
||||
lxcVersion := version.Version(d.version())
|
||||
if lxcVersion.GreaterThanOrEqualTo(version.Version("1.1")) {
|
||||
params = append(params, "-F")
|
||||
}
|
||||
|
||||
if c.Network.ContainerID != "" {
|
||||
params = append(params,
|
||||
"--share-net", c.Network.ContainerID,
|
||||
@@ -671,7 +658,7 @@ func rootIsShared() bool {
|
||||
}
|
||||
|
||||
func (d *driver) containerDir(containerId string) string {
|
||||
return path.Join(d.libPath, "containers", containerId)
|
||||
return path.Join(d.root, "containers", containerId)
|
||||
}
|
||||
|
||||
func (d *driver) generateLXCConfig(c *execdriver.Command) (string, error) {
|
||||
@@ -701,7 +688,7 @@ func (d *driver) generateEnvConfig(c *execdriver.Command) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p := path.Join(d.libPath, "containers", c.ID, "config.env")
|
||||
p := path.Join(d.root, "containers", c.ID, "config.env")
|
||||
c.Mounts = append(c.Mounts, execdriver.Mount{
|
||||
Source: p,
|
||||
Destination: "/.dockerenv",
|
||||
@@ -793,8 +780,5 @@ func (d *driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessCo
|
||||
}
|
||||
|
||||
func (d *driver) Stats(id string) (*execdriver.ResourceStats, error) {
|
||||
if _, ok := d.activeContainers[id]; !ok {
|
||||
return nil, fmt.Errorf("%s is not a key in active containers", id)
|
||||
}
|
||||
return execdriver.Stats(d.containerDir(id), d.activeContainers[id].container.Cgroups.Memory, d.machineMemory)
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ func TestLXCConfig(t *testing.T) {
|
||||
cpu = cpuMin + rand.Intn(cpuMax-cpuMin)
|
||||
)
|
||||
|
||||
driver, err := NewDriver(root, root, "", false)
|
||||
driver, err := NewDriver(root, "", false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -76,7 +76,7 @@ func TestCustomLxcConfig(t *testing.T) {
|
||||
|
||||
os.MkdirAll(path.Join(root, "containers", "1"), 0777)
|
||||
|
||||
driver, err := NewDriver(root, root, "", false)
|
||||
driver, err := NewDriver(root, "", false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -194,7 +194,7 @@ func TestCustomLxcConfigMounts(t *testing.T) {
|
||||
}
|
||||
os.MkdirAll(path.Join(root, "containers", "1"), 0777)
|
||||
|
||||
driver, err := NewDriver(root, root, "", false)
|
||||
driver, err := NewDriver(root, "", false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -248,7 +248,7 @@ func TestCustomLxcConfigMisc(t *testing.T) {
|
||||
}
|
||||
defer os.RemoveAll(root)
|
||||
os.MkdirAll(path.Join(root, "containers", "1"), 0777)
|
||||
driver, err := NewDriver(root, root, "", true)
|
||||
driver, err := NewDriver(root, "", true)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -313,7 +313,7 @@ func TestCustomLxcConfigMiscOverride(t *testing.T) {
|
||||
}
|
||||
defer os.RemoveAll(root)
|
||||
os.MkdirAll(path.Join(root, "containers", "1"), 0777)
|
||||
driver, err := NewDriver(root, root, "", false)
|
||||
driver, err := NewDriver(root, "", false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -64,6 +64,7 @@ func NewDriver(root, initPath string) (*driver, error) {
|
||||
root,
|
||||
cgm,
|
||||
libcontainer.InitPath(reexec.Self(), DriverName),
|
||||
libcontainer.TmpfsRoot,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -156,68 +157,32 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba
|
||||
startCallback(&c.ProcessConfig, pid)
|
||||
}
|
||||
|
||||
oom := notifyOnOOM(cont)
|
||||
oomKillNotification, err := cont.NotifyOOM()
|
||||
if err != nil {
|
||||
oomKillNotification = nil
|
||||
log.Warnf("Your kernel does not support OOM notifications: %s", err)
|
||||
}
|
||||
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 {
|
||||
execErr, ok := err.(*exec.ExitError)
|
||||
if !ok {
|
||||
if err, ok := err.(*exec.ExitError); !ok {
|
||||
return execdriver.ExitStatus{ExitCode: -1}, err
|
||||
} else {
|
||||
ps = err.ProcessState
|
||||
}
|
||||
ps = execErr.ProcessState
|
||||
}
|
||||
cont.Destroy()
|
||||
_, oomKill := <-oom
|
||||
|
||||
_, oomKill := <-oomKillNotification
|
||||
|
||||
return execdriver.ExitStatus{ExitCode: utils.ExitStatus(ps.Sys().(syscall.WaitStatus)), OOMKilled: oomKill}, nil
|
||||
}
|
||||
|
||||
// notifyOnOOM returns a channel that signals if the container received an OOM notification
|
||||
// for any process. If it is unable to subscribe to OOM notifications then a closed
|
||||
// channel is returned as it will be non-blocking and return the correct result when read.
|
||||
func notifyOnOOM(container libcontainer.Container) <-chan struct{} {
|
||||
oom, err := container.NotifyOOM()
|
||||
if err != nil {
|
||||
log.Warnf("Your kernel does not support OOM notifications: %s", err)
|
||||
c := make(chan struct{})
|
||||
close(c)
|
||||
return c
|
||||
}
|
||||
return oom
|
||||
}
|
||||
|
||||
func killCgroupProcs(c libcontainer.Container) {
|
||||
var procs []*os.Process
|
||||
if err := c.Pause(); err != nil {
|
||||
log.Warn(err)
|
||||
}
|
||||
pids, err := c.Processes()
|
||||
if err != nil {
|
||||
// don't care about childs if we can't get them, this is mostly because cgroup already deleted
|
||||
log.Warnf("Failed to get processes from container %s: %v", c.ID(), err)
|
||||
}
|
||||
for _, pid := range pids {
|
||||
if p, err := os.FindProcess(pid); err == nil {
|
||||
procs = append(procs, p)
|
||||
if err := p.Kill(); err != nil {
|
||||
log.Warn(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := c.Resume(); err != nil {
|
||||
log.Warn(err)
|
||||
}
|
||||
for _, p := range procs {
|
||||
if _, err := p.Wait(); err != nil {
|
||||
log.Warn(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func waitInPIDHost(p *libcontainer.Process, c libcontainer.Container) func() (*os.ProcessState, error) {
|
||||
return func() (*os.ProcessState, error) {
|
||||
pid, err := p.Pid()
|
||||
@@ -228,13 +193,26 @@ func waitInPIDHost(p *libcontainer.Process, c libcontainer.Container) func() (*o
|
||||
process, err := os.FindProcess(pid)
|
||||
s, err := process.Wait()
|
||||
if err != nil {
|
||||
execErr, ok := err.(*exec.ExitError)
|
||||
if !ok {
|
||||
if err, ok := err.(*exec.ExitError); !ok {
|
||||
return s, err
|
||||
} else {
|
||||
s = err.ProcessState
|
||||
}
|
||||
s = execErr.ProcessState
|
||||
}
|
||||
killCgroupProcs(c)
|
||||
processes, err := c.Processes()
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
|
||||
for _, pid := range processes {
|
||||
process, err := os.FindProcess(pid)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to kill process: %d", pid)
|
||||
continue
|
||||
}
|
||||
process.Kill()
|
||||
}
|
||||
|
||||
p.Wait()
|
||||
return s, err
|
||||
}
|
||||
@@ -270,25 +248,29 @@ func (d *driver) Unpause(c *execdriver.Command) error {
|
||||
|
||||
func (d *driver) Terminate(c *execdriver.Command) error {
|
||||
defer d.cleanContainer(c.ID)
|
||||
container, err := d.factory.Load(c.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
// lets check the start time for the process
|
||||
active := d.activeContainers[c.ID]
|
||||
if active == nil {
|
||||
return fmt.Errorf("active container for %s does not exist", c.ID)
|
||||
}
|
||||
defer container.Destroy()
|
||||
state, err := container.State()
|
||||
state, err := active.State()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pid := state.InitProcessPid
|
||||
|
||||
currentStartTime, err := system.GetProcessStartTime(pid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if state.InitProcessStartTime == currentStartTime {
|
||||
err = syscall.Kill(pid, 9)
|
||||
syscall.Wait4(pid, nil, 0, nil)
|
||||
}
|
||||
|
||||
return err
|
||||
|
||||
}
|
||||
|
||||
func (d *driver) Info(id string) execdriver.Info {
|
||||
|
||||
@@ -23,7 +23,6 @@ package aufs
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
@@ -48,9 +47,6 @@ var (
|
||||
graphdriver.FsMagicAufs,
|
||||
}
|
||||
backingFs = "<unknown>"
|
||||
|
||||
enableDirpermLock sync.Once
|
||||
enableDirperm bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -156,7 +152,6 @@ 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())},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -427,11 +422,7 @@ 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.
|
||||
|
||||
offset := 54
|
||||
if useDirperm() {
|
||||
offset += len("dirperm1")
|
||||
}
|
||||
b := make([]byte, syscall.Getpagesize()-len(mountLabel)-offset) // room for xino & mountLabel
|
||||
b := make([]byte, syscall.Getpagesize()-len(mountLabel)-54) // room for xino & mountLabel
|
||||
bp := copy(b, fmt.Sprintf("br:%s=rw", rw))
|
||||
|
||||
firstMount := true
|
||||
@@ -455,11 +446,7 @@ func (a *Driver) aufsMount(ro []string, rw, target, mountLabel string) (err erro
|
||||
}
|
||||
|
||||
if firstMount {
|
||||
opts := "dio,xino=/dev/shm/aufs.xino"
|
||||
if useDirperm() {
|
||||
opts += ",dirperm1"
|
||||
}
|
||||
data := label.FormatMountLabel(fmt.Sprintf("%s,%s", string(b[:bp]), opts), mountLabel)
|
||||
data := label.FormatMountLabel(fmt.Sprintf("%s,dio,xino=/dev/shm/aufs.xino", string(b[:bp])), mountLabel)
|
||||
if err = mount("none", target, "aufs", 0, data); err != nil {
|
||||
return
|
||||
}
|
||||
@@ -473,33 +460,3 @@ func (a *Driver) aufsMount(ro []string, rw, target, mountLabel string) (err erro
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// useDirperm checks dirperm1 mount option can be used with the current
|
||||
// version of aufs.
|
||||
func useDirperm() bool {
|
||||
enableDirpermLock.Do(func() {
|
||||
base, err := ioutil.TempDir("", "docker-aufs-base")
|
||||
if err != nil {
|
||||
log.Errorf("error checking dirperm1: %v", err)
|
||||
return
|
||||
}
|
||||
defer os.RemoveAll(base)
|
||||
|
||||
union, err := ioutil.TempDir("", "docker-aufs-union")
|
||||
if err != nil {
|
||||
log.Errorf("error checking dirperm1: %v", err)
|
||||
return
|
||||
}
|
||||
defer os.RemoveAll(union)
|
||||
|
||||
opts := fmt.Sprintf("br:%s,dirperm1,xino=/dev/shm/aufs.xino", base)
|
||||
if err := mount("none", union, "aufs", 0, opts); err != nil {
|
||||
return
|
||||
}
|
||||
enableDirperm = true
|
||||
if err := Unmount(union); err != nil {
|
||||
log.Errorf("error checking dirperm1: failed to unmount %v", err)
|
||||
}
|
||||
})
|
||||
return enableDirperm
|
||||
}
|
||||
|
||||
@@ -5,22 +5,20 @@ package btrfs
|
||||
/*
|
||||
#include <btrfs/version.h>
|
||||
|
||||
// around version 3.16, they did not define lib version yet
|
||||
#ifndef BTRFS_LIB_VERSION
|
||||
#define BTRFS_LIB_VERSION -1
|
||||
#endif
|
||||
|
||||
// upstream had removed it, but now it will be coming back
|
||||
#ifndef BTRFS_BUILD_VERSION
|
||||
#define BTRFS_BUILD_VERSION "-"
|
||||
// because around version 3.16, they did not define lib version yet
|
||||
int my_btrfs_lib_version() {
|
||||
#ifdef BTRFS_LIB_VERSION
|
||||
return BTRFS_LIB_VERSION;
|
||||
#else
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
func BtrfsBuildVersion() string {
|
||||
return string(C.BTRFS_BUILD_VERSION)
|
||||
}
|
||||
|
||||
func BtrfsLibVersion() int {
|
||||
return int(C.BTRFS_LIB_VERSION)
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ package btrfs
|
||||
func BtrfsBuildVersion() string {
|
||||
return "-"
|
||||
}
|
||||
|
||||
func BtrfsLibVersion() int {
|
||||
return -1
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// +build linux,!btrfs_noversion
|
||||
// +build linux
|
||||
|
||||
package btrfs
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLibVersion(t *testing.T) {
|
||||
if BtrfsLibVersion() <= 0 {
|
||||
t.Errorf("expected output from btrfs lib version > 0")
|
||||
func TestBuildVersion(t *testing.T) {
|
||||
if len(BtrfsBuildVersion()) == 0 {
|
||||
t.Errorf("expected output from btrfs build version, but got empty string")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,8 +39,9 @@ var (
|
||||
"aufs",
|
||||
"btrfs",
|
||||
"devicemapper",
|
||||
"overlay",
|
||||
"vfs",
|
||||
// experimental, has to be enabled manually for now
|
||||
"overlay",
|
||||
}
|
||||
|
||||
ErrNotSupported = errors.New("driver not supported")
|
||||
|
||||
@@ -17,20 +17,29 @@ type Syslog struct {
|
||||
}
|
||||
|
||||
func New(tag string) (logger.Logger, error) {
|
||||
log, err := syslog.New(syslog.LOG_DAEMON, fmt.Sprintf("%s/%s", path.Base(os.Args[0]), tag))
|
||||
log, err := syslog.New(syslog.LOG_USER, path.Base(os.Args[0]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Syslog{
|
||||
writer: log,
|
||||
tag: tag,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Syslog) Log(msg *logger.Message) error {
|
||||
logMessage := fmt.Sprintf("%s: %s", s.tag, string(msg.Line))
|
||||
if msg.Source == "stderr" {
|
||||
return s.writer.Err(string(msg.Line))
|
||||
if err := s.writer.Err(logMessage); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
} else {
|
||||
if err := s.writer.Info(logMessage); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return s.writer.Info(string(msg.Line))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Syslog) Close() error {
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
@@ -78,19 +77,11 @@ 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
|
||||
@@ -108,14 +99,6 @@ func InitDriver(job *engine.Job) engine.Status {
|
||||
fixedCIDRv6 = job.Getenv("FixedCIDRv6")
|
||||
)
|
||||
|
||||
// try to modprobe bridge first
|
||||
// see gh#12177
|
||||
if out, err := exec.Command("modprobe", "-va", "bridge", "nf_nat").Output(); err != nil {
|
||||
log.Warnf("Running modprobe bridge nf_nat failed with message: %s, error: %v", out, err)
|
||||
}
|
||||
|
||||
initPortMapper()
|
||||
|
||||
if defaultIP := job.Getenv("DefaultBindingIP"); defaultIP != "" {
|
||||
defaultBindingIP = net.ParseIP(defaultIP)
|
||||
}
|
||||
@@ -251,7 +234,7 @@ func InitDriver(job *engine.Job) engine.Status {
|
||||
if err != nil {
|
||||
return job.Error(err)
|
||||
}
|
||||
portMapper.SetIptablesChain(chain)
|
||||
portmapper.SetIptablesChain(chain)
|
||||
}
|
||||
|
||||
bridgeIPv4Network = networkv4
|
||||
@@ -366,11 +349,6 @@ 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
|
||||
@@ -608,7 +586,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)
|
||||
}
|
||||
}
|
||||
@@ -665,7 +643,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,12 +16,44 @@ 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")
|
||||
defaultIP = net.ParseIP("0.0.0.0")
|
||||
)
|
||||
|
||||
var (
|
||||
mutex sync.Mutex
|
||||
|
||||
defaultIP = net.ParseIP("0.0.0.0")
|
||||
globalMap = ipMapping{}
|
||||
)
|
||||
|
||||
type ErrPortAlreadyAllocated struct {
|
||||
@@ -36,6 +68,32 @@ 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
|
||||
}
|
||||
@@ -52,57 +110,12 @@ 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 (p *PortAllocator) RequestPort(ip net.IP, proto string, port int) (int, error) {
|
||||
p.mutex.Lock()
|
||||
defer p.mutex.Unlock()
|
||||
func RequestPort(ip net.IP, proto string, port int) (int, error) {
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
|
||||
if proto != "tcp" && proto != "udp" {
|
||||
return 0, ErrUnknownProtocol
|
||||
@@ -112,14 +125,10 @@ func (p *PortAllocator) RequestPort(ip net.IP, proto string, port int) (int, err
|
||||
ip = defaultIP
|
||||
}
|
||||
ipstr := ip.String()
|
||||
protomap, ok := p.ipMap[ipstr]
|
||||
protomap, ok := globalMap[ipstr]
|
||||
if !ok {
|
||||
protomap = protoMap{
|
||||
"tcp": p.newPortMap(),
|
||||
"udp": p.newPortMap(),
|
||||
}
|
||||
|
||||
p.ipMap[ipstr] = protomap
|
||||
protomap = newProtoMap()
|
||||
globalMap[ipstr] = protomap
|
||||
}
|
||||
mapping := protomap[proto]
|
||||
if port > 0 {
|
||||
@@ -138,14 +147,14 @@ func (p *PortAllocator) RequestPort(ip net.IP, proto string, port int) (int, err
|
||||
}
|
||||
|
||||
// ReleasePort releases port from global ports pool for specified ip and proto.
|
||||
func (p *PortAllocator) ReleasePort(ip net.IP, proto string, port int) error {
|
||||
p.mutex.Lock()
|
||||
defer p.mutex.Unlock()
|
||||
func ReleasePort(ip net.IP, proto string, port int) error {
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
|
||||
if ip == nil {
|
||||
ip = defaultIP
|
||||
}
|
||||
protomap, ok := p.ipMap[ip.String()]
|
||||
protomap, ok := globalMap[ip.String()]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
@@ -153,29 +162,20 @@ func (p *PortAllocator) 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 (p *PortAllocator) ReleaseAll() error {
|
||||
p.mutex.Lock()
|
||||
p.ipMap = ipMapping{}
|
||||
p.mutex.Unlock()
|
||||
func ReleaseAll() error {
|
||||
mutex.Lock()
|
||||
globalMap = ipMapping{}
|
||||
mutex.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pm *portMap) findPort() (int, error) {
|
||||
port := pm.last
|
||||
for i := 0; i <= pm.end-pm.begin; i++ {
|
||||
for i := 0; i <= endPortRange-beginPortRange; i++ {
|
||||
port++
|
||||
if port > pm.end {
|
||||
port = pm.begin
|
||||
if port > endPortRange {
|
||||
port = beginPortRange
|
||||
}
|
||||
|
||||
if _, ok := pm.p[port]; !ok {
|
||||
|
||||
@@ -5,23 +5,32 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRequestNewPort(t *testing.T) {
|
||||
p := New()
|
||||
func init() {
|
||||
beginPortRange = DefaultPortRangeStart
|
||||
endPortRange = DefaultPortRangeEnd
|
||||
}
|
||||
|
||||
port, err := p.RequestPort(defaultIP, "tcp", 0)
|
||||
func reset() {
|
||||
ReleaseAll()
|
||||
}
|
||||
|
||||
func TestRequestNewPort(t *testing.T) {
|
||||
defer reset()
|
||||
|
||||
port, err := RequestPort(defaultIP, "tcp", 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if expected := p.Begin; port != expected {
|
||||
if expected := beginPortRange; port != expected {
|
||||
t.Fatalf("Expected port %d got %d", expected, port)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequestSpecificPort(t *testing.T) {
|
||||
p := New()
|
||||
defer reset()
|
||||
|
||||
port, err := p.RequestPort(defaultIP, "tcp", 5000)
|
||||
port, err := RequestPort(defaultIP, "tcp", 5000)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -31,9 +40,9 @@ func TestRequestSpecificPort(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestReleasePort(t *testing.T) {
|
||||
p := New()
|
||||
defer reset()
|
||||
|
||||
port, err := p.RequestPort(defaultIP, "tcp", 5000)
|
||||
port, err := RequestPort(defaultIP, "tcp", 5000)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -41,15 +50,15 @@ func TestReleasePort(t *testing.T) {
|
||||
t.Fatalf("Expected port 5000 got %d", port)
|
||||
}
|
||||
|
||||
if err := p.ReleasePort(defaultIP, "tcp", 5000); err != nil {
|
||||
if err := ReleasePort(defaultIP, "tcp", 5000); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReuseReleasedPort(t *testing.T) {
|
||||
p := New()
|
||||
defer reset()
|
||||
|
||||
port, err := p.RequestPort(defaultIP, "tcp", 5000)
|
||||
port, err := RequestPort(defaultIP, "tcp", 5000)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -57,20 +66,20 @@ func TestReuseReleasedPort(t *testing.T) {
|
||||
t.Fatalf("Expected port 5000 got %d", port)
|
||||
}
|
||||
|
||||
if err := p.ReleasePort(defaultIP, "tcp", 5000); err != nil {
|
||||
if err := ReleasePort(defaultIP, "tcp", 5000); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
port, err = p.RequestPort(defaultIP, "tcp", 5000)
|
||||
port, err = RequestPort(defaultIP, "tcp", 5000)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReleaseUnreadledPort(t *testing.T) {
|
||||
p := New()
|
||||
defer reset()
|
||||
|
||||
port, err := p.RequestPort(defaultIP, "tcp", 5000)
|
||||
port, err := RequestPort(defaultIP, "tcp", 5000)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -78,7 +87,7 @@ func TestReleaseUnreadledPort(t *testing.T) {
|
||||
t.Fatalf("Expected port 5000 got %d", port)
|
||||
}
|
||||
|
||||
port, err = p.RequestPort(defaultIP, "tcp", 5000)
|
||||
port, err = RequestPort(defaultIP, "tcp", 5000)
|
||||
|
||||
switch err.(type) {
|
||||
case ErrPortAlreadyAllocated:
|
||||
@@ -88,40 +97,42 @@ func TestReleaseUnreadledPort(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUnknowProtocol(t *testing.T) {
|
||||
if _, err := New().RequestPort(defaultIP, "tcpp", 0); err != ErrUnknownProtocol {
|
||||
defer reset()
|
||||
|
||||
if _, err := RequestPort(defaultIP, "tcpp", 0); err != ErrUnknownProtocol {
|
||||
t.Fatalf("Expected error %s got %s", ErrUnknownProtocol, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllocateAllPorts(t *testing.T) {
|
||||
p := New()
|
||||
defer reset()
|
||||
|
||||
for i := 0; i <= p.End-p.Begin; i++ {
|
||||
port, err := p.RequestPort(defaultIP, "tcp", 0)
|
||||
for i := 0; i <= endPortRange-beginPortRange; i++ {
|
||||
port, err := RequestPort(defaultIP, "tcp", 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if expected := p.Begin + i; port != expected {
|
||||
if expected := beginPortRange + i; port != expected {
|
||||
t.Fatalf("Expected port %d got %d", expected, port)
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := p.RequestPort(defaultIP, "tcp", 0); err != ErrAllPortsAllocated {
|
||||
if _, err := RequestPort(defaultIP, "tcp", 0); err != ErrAllPortsAllocated {
|
||||
t.Fatalf("Expected error %s got %s", ErrAllPortsAllocated, err)
|
||||
}
|
||||
|
||||
_, err := p.RequestPort(defaultIP, "udp", 0)
|
||||
_, err := RequestPort(defaultIP, "udp", 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// release a port in the middle and ensure we get another tcp port
|
||||
port := p.Begin + 5
|
||||
if err := p.ReleasePort(defaultIP, "tcp", port); err != nil {
|
||||
port := beginPortRange + 5
|
||||
if err := ReleasePort(defaultIP, "tcp", port); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
newPort, err := p.RequestPort(defaultIP, "tcp", 0)
|
||||
newPort, err := RequestPort(defaultIP, "tcp", 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -131,10 +142,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 := p.ReleasePort(defaultIP, "tcp", newPort); err != nil {
|
||||
if err := ReleasePort(defaultIP, "tcp", newPort); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
port, err = p.RequestPort(defaultIP, "tcp", 0)
|
||||
port, err = RequestPort(defaultIP, "tcp", 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -144,34 +155,34 @@ func TestAllocateAllPorts(t *testing.T) {
|
||||
}
|
||||
|
||||
func BenchmarkAllocatePorts(b *testing.B) {
|
||||
p := New()
|
||||
defer reset()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
for i := 0; i <= p.End-p.Begin; i++ {
|
||||
port, err := p.RequestPort(defaultIP, "tcp", 0)
|
||||
for i := 0; i <= endPortRange-beginPortRange; i++ {
|
||||
port, err := RequestPort(defaultIP, "tcp", 0)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
if expected := p.Begin + i; port != expected {
|
||||
if expected := beginPortRange + i; port != expected {
|
||||
b.Fatalf("Expected port %d got %d", expected, port)
|
||||
}
|
||||
}
|
||||
p.ReleaseAll()
|
||||
reset()
|
||||
}
|
||||
}
|
||||
|
||||
func TestPortAllocation(t *testing.T) {
|
||||
p := New()
|
||||
defer reset()
|
||||
|
||||
ip := net.ParseIP("192.168.0.1")
|
||||
ip2 := net.ParseIP("192.168.0.2")
|
||||
if port, err := p.RequestPort(ip, "tcp", 80); err != nil {
|
||||
if port, err := 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 := p.RequestPort(ip, "tcp", 0)
|
||||
port, err := RequestPort(ip, "tcp", 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -179,41 +190,41 @@ func TestPortAllocation(t *testing.T) {
|
||||
t.Fatalf("Acquire(0) should return a non-zero port")
|
||||
}
|
||||
|
||||
if _, err := p.RequestPort(ip, "tcp", port); err == nil {
|
||||
if _, err := RequestPort(ip, "tcp", port); err == nil {
|
||||
t.Fatalf("Acquiring a port already in use should return an error")
|
||||
}
|
||||
|
||||
if newPort, err := p.RequestPort(ip, "tcp", 0); err != nil {
|
||||
if newPort, err := 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 := p.RequestPort(ip, "tcp", 80); err == nil {
|
||||
if _, err := RequestPort(ip, "tcp", 80); err == nil {
|
||||
t.Fatalf("Acquiring a port already in use should return an error")
|
||||
}
|
||||
if _, err := p.RequestPort(ip2, "tcp", 80); err != nil {
|
||||
if _, err := RequestPort(ip2, "tcp", 80); err != nil {
|
||||
t.Fatalf("It should be possible to allocate the same port on a different interface")
|
||||
}
|
||||
if _, err := p.RequestPort(ip2, "tcp", 80); err == nil {
|
||||
if _, err := RequestPort(ip2, "tcp", 80); err == nil {
|
||||
t.Fatalf("Acquiring a port already in use should return an error")
|
||||
}
|
||||
if err := p.ReleasePort(ip, "tcp", 80); err != nil {
|
||||
if err := ReleasePort(ip, "tcp", 80); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := p.RequestPort(ip, "tcp", 80); err != nil {
|
||||
if _, err := RequestPort(ip, "tcp", 80); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
port, err = p.RequestPort(ip, "tcp", 0)
|
||||
port, err = RequestPort(ip, "tcp", 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
port2, err := p.RequestPort(ip, "tcp", port+1)
|
||||
port2, err := RequestPort(ip, "tcp", port+1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
port3, err := p.RequestPort(ip, "tcp", 0)
|
||||
port3, err := RequestPort(ip, "tcp", 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -223,17 +234,17 @@ func TestPortAllocation(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestNoDuplicateBPR(t *testing.T) {
|
||||
p := New()
|
||||
defer reset()
|
||||
|
||||
if port, err := p.RequestPort(defaultIP, "tcp", p.Begin); err != nil {
|
||||
if port, err := RequestPort(defaultIP, "tcp", beginPortRange); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if port != p.Begin {
|
||||
t.Fatalf("Expected port %d got %d", p.Begin, port)
|
||||
} else if port != beginPortRange {
|
||||
t.Fatalf("Expected port %d got %d", beginPortRange, port)
|
||||
}
|
||||
|
||||
if port, err := p.RequestPort(defaultIP, "tcp", 0); err != nil {
|
||||
if port, err := RequestPort(defaultIP, "tcp", 0); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if port == p.Begin {
|
||||
} else if port == beginPortRange {
|
||||
t.Fatalf("Acquire(0) allocated the same port twice: %d", port)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,15 @@ type mapping struct {
|
||||
container net.Addr
|
||||
}
|
||||
|
||||
var NewProxy = NewProxyCommand
|
||||
var (
|
||||
chain *iptables.Chain
|
||||
lock sync.Mutex
|
||||
|
||||
// udp:ip:port
|
||||
currentMappings = make(map[string]*mapping)
|
||||
|
||||
NewProxy = NewProxyCommand
|
||||
)
|
||||
|
||||
var (
|
||||
ErrUnknownBackendAddressType = errors.New("unknown container address type not supported")
|
||||
@@ -26,34 +34,13 @@ var (
|
||||
ErrPortNotMapped = errors.New("port is not mapped")
|
||||
)
|
||||
|
||||
type PortMapper struct {
|
||||
chain *iptables.Chain
|
||||
|
||||
// udp:ip:port
|
||||
currentMappings map[string]*mapping
|
||||
lock sync.Mutex
|
||||
|
||||
Allocator *portallocator.PortAllocator
|
||||
func SetIptablesChain(c *iptables.Chain) {
|
||||
chain = c
|
||||
}
|
||||
|
||||
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()
|
||||
func Map(container net.Addr, hostIP net.IP, hostPort int) (host net.Addr, err error) {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
var (
|
||||
m *mapping
|
||||
@@ -65,7 +52,7 @@ func (pm *PortMapper) Map(container net.Addr, hostIP net.IP, hostPort int) (host
|
||||
switch container.(type) {
|
||||
case *net.TCPAddr:
|
||||
proto = "tcp"
|
||||
if allocatedHostPort, err = pm.Allocator.RequestPort(hostIP, proto, hostPort); err != nil {
|
||||
if allocatedHostPort, err = portallocator.RequestPort(hostIP, proto, hostPort); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -78,7 +65,7 @@ func (pm *PortMapper) Map(container net.Addr, hostIP net.IP, hostPort int) (host
|
||||
proxy = NewProxy(proto, hostIP, allocatedHostPort, container.(*net.TCPAddr).IP, container.(*net.TCPAddr).Port)
|
||||
case *net.UDPAddr:
|
||||
proto = "udp"
|
||||
if allocatedHostPort, err = pm.Allocator.RequestPort(hostIP, proto, hostPort); err != nil {
|
||||
if allocatedHostPort, err = portallocator.RequestPort(hostIP, proto, hostPort); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -96,25 +83,25 @@ func (pm *PortMapper) Map(container net.Addr, hostIP net.IP, hostPort int) (host
|
||||
// release the allocated port on any further error during return.
|
||||
defer func() {
|
||||
if err != nil {
|
||||
pm.Allocator.ReleasePort(hostIP, proto, allocatedHostPort)
|
||||
portallocator.ReleasePort(hostIP, proto, allocatedHostPort)
|
||||
}
|
||||
}()
|
||||
|
||||
key := getKey(m.host)
|
||||
if _, exists := pm.currentMappings[key]; exists {
|
||||
if _, exists := currentMappings[key]; exists {
|
||||
return nil, ErrPortMappedForIP
|
||||
}
|
||||
|
||||
containerIP, containerPort := getIPAndPort(m.container)
|
||||
if err := pm.forward(iptables.Append, m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort); err != nil {
|
||||
if err := 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()
|
||||
pm.forward(iptables.Delete, m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort)
|
||||
if err := pm.Allocator.ReleasePort(hostIP, m.proto, allocatedHostPort); err != nil {
|
||||
forward(iptables.Delete, m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort)
|
||||
if err := portallocator.ReleasePort(hostIP, m.proto, allocatedHostPort); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -128,35 +115,35 @@ func (pm *PortMapper) Map(container net.Addr, hostIP net.IP, hostPort int) (host
|
||||
return nil, err
|
||||
}
|
||||
m.userlandProxy = proxy
|
||||
pm.currentMappings[key] = m
|
||||
currentMappings[key] = m
|
||||
return m.host, nil
|
||||
}
|
||||
|
||||
func (pm *PortMapper) Unmap(host net.Addr) error {
|
||||
pm.lock.Lock()
|
||||
defer pm.lock.Unlock()
|
||||
func Unmap(host net.Addr) error {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
key := getKey(host)
|
||||
data, exists := pm.currentMappings[key]
|
||||
data, exists := currentMappings[key]
|
||||
if !exists {
|
||||
return ErrPortNotMapped
|
||||
}
|
||||
|
||||
data.userlandProxy.Stop()
|
||||
|
||||
delete(pm.currentMappings, key)
|
||||
delete(currentMappings, key)
|
||||
|
||||
containerIP, containerPort := getIPAndPort(data.container)
|
||||
hostIP, hostPort := getIPAndPort(data.host)
|
||||
if err := pm.forward(iptables.Delete, data.proto, hostIP, hostPort, containerIP.String(), containerPort); err != nil {
|
||||
if err := 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 pm.Allocator.ReleasePort(a.IP, "tcp", a.Port)
|
||||
return portallocator.ReleasePort(a.IP, "tcp", a.Port)
|
||||
case *net.UDPAddr:
|
||||
return pm.Allocator.ReleasePort(a.IP, "udp", a.Port)
|
||||
return portallocator.ReleasePort(a.IP, "udp", a.Port)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -181,9 +168,9 @@ func getIPAndPort(a net.Addr) (net.IP, int) {
|
||||
return nil, 0
|
||||
}
|
||||
|
||||
func (pm *PortMapper) forward(action iptables.Action, proto string, sourceIP net.IP, sourcePort int, containerIP string, containerPort int) error {
|
||||
if pm.chain == nil {
|
||||
func forward(action iptables.Action, proto string, sourceIP net.IP, sourcePort int, containerIP string, containerPort int) error {
|
||||
if chain == nil {
|
||||
return nil
|
||||
}
|
||||
return pm.chain.Forward(action, sourceIP, sourcePort, proto, containerIP, containerPort)
|
||||
return chain.Forward(action, sourceIP, sourcePort, proto, containerIP, containerPort)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/daemon/networkdriver/portallocator"
|
||||
"github.com/docker/docker/pkg/iptables"
|
||||
)
|
||||
|
||||
@@ -12,26 +13,30 @@ func init() {
|
||||
NewProxy = NewMockProxyCommand
|
||||
}
|
||||
|
||||
func reset() {
|
||||
chain = nil
|
||||
currentMappings = make(map[string]*mapping)
|
||||
}
|
||||
|
||||
func TestSetIptablesChain(t *testing.T) {
|
||||
pm := New()
|
||||
defer reset()
|
||||
|
||||
c := &iptables.Chain{
|
||||
Name: "TEST",
|
||||
Bridge: "192.168.1.1",
|
||||
}
|
||||
|
||||
if pm.chain != nil {
|
||||
if chain != nil {
|
||||
t.Fatal("chain should be nil at init")
|
||||
}
|
||||
|
||||
pm.SetIptablesChain(c)
|
||||
if pm.chain == nil {
|
||||
SetIptablesChain(c)
|
||||
if 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}
|
||||
@@ -44,34 +49,34 @@ func TestMapPorts(t *testing.T) {
|
||||
return (addr1.Network() == addr2.Network()) && (addr1.String() == addr2.String())
|
||||
}
|
||||
|
||||
if host, err := pm.Map(srcAddr1, dstIp1, 80); err != nil {
|
||||
if host, err := 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 := pm.Map(srcAddr1, dstIp1, 80); err == nil {
|
||||
if _, err := Map(srcAddr1, dstIp1, 80); err == nil {
|
||||
t.Fatalf("Port is in use - mapping should have failed")
|
||||
}
|
||||
|
||||
if _, err := pm.Map(srcAddr2, dstIp1, 80); err == nil {
|
||||
if _, err := Map(srcAddr2, dstIp1, 80); err == nil {
|
||||
t.Fatalf("Port is in use - mapping should have failed")
|
||||
}
|
||||
|
||||
if _, err := pm.Map(srcAddr2, dstIp2, 80); err != nil {
|
||||
if _, err := Map(srcAddr2, dstIp2, 80); err != nil {
|
||||
t.Fatalf("Failed to allocate port: %s", err)
|
||||
}
|
||||
|
||||
if pm.Unmap(dstAddr1) != nil {
|
||||
if Unmap(dstAddr1) != nil {
|
||||
t.Fatalf("Failed to release port")
|
||||
}
|
||||
|
||||
if pm.Unmap(dstAddr2) != nil {
|
||||
if Unmap(dstAddr2) != nil {
|
||||
t.Fatalf("Failed to release port")
|
||||
}
|
||||
|
||||
if pm.Unmap(dstAddr2) == nil {
|
||||
if Unmap(dstAddr2) == nil {
|
||||
t.Fatalf("Port already released, but no error reported")
|
||||
}
|
||||
}
|
||||
@@ -110,7 +115,6 @@ 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")}
|
||||
|
||||
@@ -120,26 +124,26 @@ func TestMapAllPortsSingleInterface(t *testing.T) {
|
||||
|
||||
defer func() {
|
||||
for _, val := range hosts {
|
||||
pm.Unmap(val)
|
||||
Unmap(val)
|
||||
}
|
||||
}()
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
start, end := pm.Allocator.Begin, pm.Allocator.End
|
||||
start, end := portallocator.PortRange()
|
||||
for i := start; i < end; i++ {
|
||||
if host, err = pm.Map(srcAddr1, dstIp1, 0); err != nil {
|
||||
if host, err = Map(srcAddr1, dstIp1, 0); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
hosts = append(hosts, host)
|
||||
}
|
||||
|
||||
if _, err := pm.Map(srcAddr1, dstIp1, start); err == nil {
|
||||
if _, err := Map(srcAddr1, dstIp1, start); err == nil {
|
||||
t.Fatalf("Port %d should be bound but is not", start)
|
||||
}
|
||||
|
||||
for _, val := range hosts {
|
||||
if err := pm.Unmap(val); err != nil {
|
||||
if err := Unmap(val); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,18 +11,16 @@ import (
|
||||
|
||||
type State struct {
|
||||
sync.Mutex
|
||||
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{}
|
||||
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{}
|
||||
}
|
||||
|
||||
func NewState() *State {
|
||||
@@ -44,14 +42,6 @@ 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 ""
|
||||
}
|
||||
@@ -70,11 +60,6 @@ func (s *State) StateString() string {
|
||||
}
|
||||
return "running"
|
||||
}
|
||||
|
||||
if s.Dead {
|
||||
return "dead"
|
||||
}
|
||||
|
||||
return "exited"
|
||||
}
|
||||
|
||||
@@ -232,25 +217,3 @@ 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,13 +189,10 @@ func (container *Container) parseVolumeMountConfig() (map[string]*Mount, error)
|
||||
if _, exists := container.Volumes[path]; exists {
|
||||
continue
|
||||
}
|
||||
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, err := os.Stat(filepath.Join(container.basefs, path)); err == nil {
|
||||
if !stat.IsDir() {
|
||||
return nil, fmt.Errorf("file exists at %s, can't create volume there", realPath)
|
||||
return nil, fmt.Errorf("file exists at %s, can't create volume there")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,20 +4,11 @@
|
||||
FROM docs/base:latest
|
||||
MAINTAINER Sven Dowideit <SvenDowideit@docker.com> (@SvenDowideit)
|
||||
|
||||
# This section ensures we pull the correct version of each
|
||||
# sub project
|
||||
ENV COMPOSE_BRANCH 1.2.0
|
||||
ENV SWARM_BRANCH v0.2.0
|
||||
ENV MACHINE_BRANCH master
|
||||
ENV DISTRIB_BRANCH master
|
||||
|
||||
|
||||
|
||||
# TODO: need the full repo source to get the git version info
|
||||
COPY . /src
|
||||
|
||||
# Reset the /docs dir so we can replace the theme meta with the new repo's git info
|
||||
# RUN git reset --hard
|
||||
RUN git reset --hard
|
||||
|
||||
# Then copy the desired docs into the /docs/sources/ dir
|
||||
COPY ./sources/ /docs/sources
|
||||
@@ -32,77 +23,45 @@ COPY ./mkdocs.yml mkdocs.yml
|
||||
COPY ./s3_website.json s3_website.json
|
||||
COPY ./release.sh release.sh
|
||||
|
||||
|
||||
# Docker Distribution
|
||||
#
|
||||
#ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/mkdocs.yml /docs/mkdocs-distribution.yml
|
||||
|
||||
ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/images/notifications.png /docs/sources/registry/images/notifications.png
|
||||
ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/images/registry.png /docs/sources/registry/images/registry.png
|
||||
|
||||
ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/overview.md /docs/sources/registry/overview.md
|
||||
RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/registry/overview.md
|
||||
|
||||
ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/deploying.md /docs/sources/registry/deploying.md
|
||||
RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/registry/deploying.md
|
||||
|
||||
ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/configuration.md /docs/sources/registry/configuration.md
|
||||
RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/registry/configuration.md
|
||||
|
||||
ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/storagedrivers.md /docs/sources/registry/storagedrivers.md
|
||||
RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/registry/storagedrivers.md
|
||||
|
||||
ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/notifications.md /docs/sources/registry/notifications.md
|
||||
RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/registry/notifications.md
|
||||
|
||||
ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/spec/api.md /docs/sources/registry/spec/api.md
|
||||
RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/registry/spec/api.md
|
||||
|
||||
ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/spec/json.md /docs/sources/registry/spec/json.md
|
||||
RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/registry/spec/json.md
|
||||
|
||||
ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/spec/auth/token.md /docs/sources/registry/spec/auth/token.md
|
||||
RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/registry/spec/auth/token.md
|
||||
|
||||
# Docker Swarm
|
||||
#ADD https://raw.githubusercontent.com/docker/swarm/${SWARM_BRANCH}/docs/mkdocs.yml /docs/mkdocs-swarm.yml
|
||||
ADD https://raw.githubusercontent.com/docker/swarm/${SWARM_BRANCH}/docs/index.md /docs/sources/swarm/index.md
|
||||
#ADD https://raw.githubusercontent.com/docker/swarm/master/docs/mkdocs.yml /docs/mkdocs-swarm.yml
|
||||
ADD https://raw.githubusercontent.com/docker/swarm/master/docs/index.md /docs/sources/swarm/index.md
|
||||
RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/swarm/index.md
|
||||
ADD https://raw.githubusercontent.com/docker/swarm/${SWARM_BRANCH}/discovery/README.md /docs/sources/swarm/discovery.md
|
||||
ADD https://raw.githubusercontent.com/docker/swarm/master/discovery/README.md /docs/sources/swarm/discovery.md
|
||||
RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/swarm/discovery.md
|
||||
ADD https://raw.githubusercontent.com/docker/swarm/${SWARM_BRANCH}/api/README.md /docs/sources/swarm/API.md
|
||||
ADD https://raw.githubusercontent.com/docker/swarm/master/api/README.md /docs/sources/swarm/API.md
|
||||
RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/swarm/API.md
|
||||
ADD https://raw.githubusercontent.com/docker/swarm/${SWARM_BRANCH}/scheduler/filter/README.md /docs/sources/swarm/scheduler/filter.md
|
||||
ADD https://raw.githubusercontent.com/docker/swarm/master/scheduler/filter/README.md /docs/sources/swarm/scheduler/filter.md
|
||||
RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/swarm/scheduler/filter.md
|
||||
ADD https://raw.githubusercontent.com/docker/swarm/${SWARM_BRANCH}/scheduler/strategy/README.md /docs/sources/swarm/scheduler/strategy.md
|
||||
ADD https://raw.githubusercontent.com/docker/swarm/master/scheduler/strategy/README.md /docs/sources/swarm/scheduler/strategy.md
|
||||
RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/swarm/scheduler/strategy.md
|
||||
|
||||
# Docker Machine
|
||||
#ADD https://raw.githubusercontent.com/docker/machine/${MACHINE_BRANCH}/docs/mkdocs.yml /docs/mkdocs-machine.yml
|
||||
ADD https://raw.githubusercontent.com/docker/machine/${MACHINE_BRANCH}/docs/index.md /docs/sources/machine/index.md
|
||||
#ADD https://raw.githubusercontent.com/docker/machine/master/docs/mkdocs.yml /docs/mkdocs-machine.yml
|
||||
ADD https://raw.githubusercontent.com/docker/machine/master/docs/index.md /docs/sources/machine/index.md
|
||||
RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/machine/index.md
|
||||
|
||||
# Docker Compose
|
||||
#ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/mkdocs.yml /docs/mkdocs-compose.yml
|
||||
ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/index.md /docs/sources/compose/index.md
|
||||
#ADD https://raw.githubusercontent.com/docker/compose/master/docs/mkdocs.yml /docs/mkdocs-compose.yml
|
||||
ADD https://raw.githubusercontent.com/docker/compose/master/docs/index.md /docs/sources/compose/index.md
|
||||
RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/index.md
|
||||
ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/install.md /docs/sources/compose/install.md
|
||||
ADD https://raw.githubusercontent.com/docker/compose/master/docs/install.md /docs/sources/compose/install.md
|
||||
RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/install.md
|
||||
ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/cli.md /docs/sources/compose/cli.md
|
||||
ADD https://raw.githubusercontent.com/docker/compose/master/docs/cli.md /docs/sources/compose/cli.md
|
||||
RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/cli.md
|
||||
ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/yml.md /docs/sources/compose/yml.md
|
||||
ADD https://raw.githubusercontent.com/docker/compose/master/docs/yml.md /docs/sources/compose/yml.md
|
||||
RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/yml.md
|
||||
ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/env.md /docs/sources/compose/env.md
|
||||
ADD https://raw.githubusercontent.com/docker/compose/master/docs/env.md /docs/sources/compose/env.md
|
||||
RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/env.md
|
||||
ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/completion.md /docs/sources/compose/completion.md
|
||||
ADD https://raw.githubusercontent.com/docker/compose/master/docs/completion.md /docs/sources/compose/completion.md
|
||||
RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/completion.md
|
||||
|
||||
ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/django.md /docs/sources/compose/django.md
|
||||
ADD https://raw.githubusercontent.com/docker/compose/master/docs/django.md /docs/sources/compose/django.md
|
||||
RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/django.md
|
||||
ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/rails.md /docs/sources/compose/rails.md
|
||||
ADD https://raw.githubusercontent.com/docker/compose/master/docs/rails.md /docs/sources/compose/rails.md
|
||||
RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/rails.md
|
||||
ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/wordpress.md /docs/sources/compose/wordpress.md
|
||||
ADD https://raw.githubusercontent.com/docker/compose/master/docs/wordpress.md /docs/sources/compose/wordpress.md
|
||||
RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/wordpress.md
|
||||
|
||||
# Then build everything together, ready for mkdocs
|
||||
RUN /docs/build.sh
|
||||
RUN /docs/build.sh
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
% Docker Community
|
||||
% JUNE 2014
|
||||
# NAME
|
||||
docker-login - Register or log in to a Docker registry.
|
||||
docker-login - Register or log in to a Docker registry server, if no server is specified "https://index.docker.io/v1/" is the default.
|
||||
|
||||
# SYNOPSIS
|
||||
**docker login**
|
||||
@@ -13,14 +13,9 @@ docker-login - Register or log in to a Docker registry.
|
||||
[SERVER]
|
||||
|
||||
# DESCRIPTION
|
||||
Register or log in to a Docker Registry Service located on the specified
|
||||
`SERVER`. You can specify a URL or a `hostname` for the `SERVER` value. If you
|
||||
do not specify a `SERVER`, the command uses Docker's public registry located at
|
||||
`https://registry-1.docker.io/` by default. To get a username/password for Docker's public registry, create an account on Docker Hub.
|
||||
|
||||
You can log into any public or private repository for which you have
|
||||
credentials. When you log in, the command stores encoded credentials in
|
||||
`$HOME/.dockercfg` on Linux or `%USERPROFILE%/.dockercfg` on Windows.
|
||||
Register or Login to a docker registry server, if no server is
|
||||
specified "https://index.docker.io/v1/" is the default. If you want to
|
||||
login to a private registry you can specify this by adding the server name.
|
||||
|
||||
# OPTIONS
|
||||
**-e**, **--email**=""
|
||||
@@ -37,7 +32,7 @@ credentials. When you log in, the command stores encoded credentials in
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
## Login to a registry on your localhost
|
||||
## Login to a local registry
|
||||
|
||||
# docker login localhost:8080
|
||||
|
||||
@@ -48,4 +43,3 @@ credentials. When you log in, the command stores encoded credentials in
|
||||
April 2014, Originally compiled by William Henry (whenry at redhat dot com)
|
||||
based on docker.com source material and internal work.
|
||||
June 2014, updated by Sven Dowideit <SvenDowideit@home.org.au>
|
||||
April 2015, updated by Mary Anthony for v2 <mary@docker.com>
|
||||
|
||||
@@ -2,24 +2,23 @@
|
||||
% Docker Community
|
||||
% JUNE 2014
|
||||
# NAME
|
||||
docker-logout - Log out from a Docker Registry Service.
|
||||
docker-logout - Log out from a Docker registry, if no server is specified "https://index.docker.io/v1/" is the default.
|
||||
|
||||
# SYNOPSIS
|
||||
**docker logout**
|
||||
[SERVER]
|
||||
|
||||
# DESCRIPTION
|
||||
Log out of a Docker Registry Service located on the specified `SERVER`. You can
|
||||
specify a URL or a `hostname` for the `SERVER` value. If you do not specify a
|
||||
`SERVER`, the command attempts to log you out of Docker's public registry
|
||||
located at `https://registry-1.docker.io/` by default.
|
||||
Log the user out from a Docker registry, if no server is
|
||||
specified "https://index.docker.io/v1/" is the default. If you want to
|
||||
log out from a private registry you can specify this by adding the server name.
|
||||
|
||||
# OPTIONS
|
||||
There are no available options.
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
## Log out from a registry on your localhost
|
||||
## Log out from a local registry
|
||||
|
||||
# docker logout localhost:8080
|
||||
|
||||
@@ -29,4 +28,3 @@ There are no available options.
|
||||
# HISTORY
|
||||
June 2014, Originally compiled by Daniel, Dao Quang Minh (daniel at nitrous dot io)
|
||||
July 2014, updated by Sven Dowideit <SvenDowideit@home.org.au>
|
||||
April 2015, updated by Mary Anthony for v2 <mary@docker.com>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
% Docker Community
|
||||
% JUNE 2014
|
||||
# NAME
|
||||
docker-pull - Pull an image or a repository from a registry
|
||||
docker-pull - Pull an image or a repository from the registry
|
||||
|
||||
# SYNOPSIS
|
||||
**docker pull**
|
||||
@@ -12,12 +12,10 @@ NAME[:TAG]
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
This command pulls down an image or a repository from a registry. If
|
||||
This command pulls down an image or a repository from the registry. If
|
||||
there is more than one image for a repository (e.g., fedora) then all
|
||||
images for that repository name are pulled down including any tags.
|
||||
|
||||
If you do not specify a `REGISTRY_HOST`, the command uses Docker's public
|
||||
registry located at `registry-1.docker.io` by default.
|
||||
It is also possible to specify a non-default registry to pull from.
|
||||
|
||||
# OPTIONS
|
||||
**-a**, **--all-tags**=*true*|*false*
|
||||
@@ -47,7 +45,7 @@ registry located at `registry-1.docker.io` by default.
|
||||
fedora heisenbug 105182bb5e8b 5 days ago 372.7 MB
|
||||
fedora latest 105182bb5e8b 5 days ago 372.7 MB
|
||||
|
||||
# Pull an image, manually specifying path to Docker's public registry and tag
|
||||
# Pull an image, manually specifying path to the registry and tag
|
||||
# Note that if the image is previously downloaded then the status would be
|
||||
# 'Status: Image is up to date for registry.hub.docker.com/fedora:20'
|
||||
|
||||
@@ -69,5 +67,3 @@ April 2014, Originally compiled by William Henry (whenry at redhat dot com)
|
||||
based on docker.com source material and internal work.
|
||||
June 2014, updated by Sven Dowideit <SvenDowideit@home.org.au>
|
||||
August 2014, updated by Sven Dowideit <SvenDowideit@home.org.au>
|
||||
April 2015, updated by John Willis <john.willis@docker.com>
|
||||
April 2015, updated by Mary Anthony for v2 <mary@docker.com>
|
||||
|
||||
@@ -2,18 +2,18 @@
|
||||
% Docker Community
|
||||
% JUNE 2014
|
||||
# NAME
|
||||
docker-push - Push an image or a repository to a registry
|
||||
docker-push - Push an image or a repository to the registry
|
||||
|
||||
# SYNOPSIS
|
||||
**docker push**
|
||||
[**--help**]
|
||||
NAME[:TAG] | [REGISTRY_HOST[:REGISTRY_PORT]/]NAME[:TAG]
|
||||
NAME[:TAG]
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
This command pushes an image or a repository to a registry. If you do not
|
||||
specify a `REGISTRY_HOST`, the command uses Docker's public registry located at
|
||||
`registry-1.docker.io` by default.
|
||||
Push an image or a repository to a registry. The default registry is the Docker
|
||||
Hub located at [hub.docker.com](https://hub.docker.com/). However the
|
||||
image can be pushed to another, perhaps private, registry as demonstrated in
|
||||
the example below.
|
||||
|
||||
# OPTIONS
|
||||
**--help**
|
||||
@@ -28,10 +28,12 @@ and then committing it to a new image name:
|
||||
|
||||
# docker commit c16378f943fe rhel-httpd
|
||||
|
||||
Now, push the image to the registry using the image ID. In this example the
|
||||
registry is on host named `registry-host` and listening on port `5000`. To do
|
||||
this, tag the image with the host name or IP address, and the port of the
|
||||
registry:
|
||||
Now push the image to the registry using the image ID. In this example
|
||||
the registry is on host named registry-host and listening on port 5000.
|
||||
Default Docker commands will push to the default `hub.docker.com`
|
||||
registry. Instead, push to the local registry, which is on a host called
|
||||
registry-host*. To do this, tag the image with the host name or IP
|
||||
address, and the port of the registry:
|
||||
|
||||
# docker tag rhel-httpd registry-host:5000/myadmin/rhel-httpd
|
||||
# docker push registry-host:5000/myadmin/rhel-httpd
|
||||
@@ -47,5 +49,3 @@ listed.
|
||||
April 2014, Originally compiled by William Henry (whenry at redhat dot com)
|
||||
based on docker.com source material and internal work.
|
||||
June 2014, updated by Sven Dowideit <SvenDowideit@home.org.au>
|
||||
April 2015, updated by Mary Anthony for v2 <mary@docker.com>
|
||||
|
||||
|
||||
@@ -13,9 +13,10 @@ IMAGE [IMAGE...]
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
Removes one or more images from the host node. This does not remove images from
|
||||
a registry. You cannot remove an image of a running container unless you use the
|
||||
**-f** option. To see all images on a host use the **docker images** command.
|
||||
This will remove one or more images from the host node. This does not
|
||||
remove images from a registry. You cannot remove an image of a running
|
||||
container unless you use the **-f** option. To see all images on a host
|
||||
use the **docker images** command.
|
||||
|
||||
# OPTIONS
|
||||
**-f**, **--force**=*true*|*false*
|
||||
@@ -39,4 +40,3 @@ Here is an example of removing and image:
|
||||
April 2014, Originally compiled by William Henry (whenry at redhat dot com)
|
||||
based on docker.com source material and internal work.
|
||||
June 2014, updated by Sven Dowideit <SvenDowideit@home.org.au>
|
||||
April 2015, updated by Mary Anthony for v2 <mary@docker.com>
|
||||
|
||||
@@ -14,9 +14,10 @@ TERM
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
Search Docker Hub for an image with that matches the specified `TERM`. The table
|
||||
of images returned displays the name, description (truncated by default), number
|
||||
of stars awarded, whether the image is official, and whether it is automated.
|
||||
Search an index for an image with that matches the term TERM. The table
|
||||
of images returned displays the name, description (truncated by default),
|
||||
number of stars awarded, whether the image is official, and whether it
|
||||
is automated.
|
||||
|
||||
*Note* - Search queries will only return up to 25 results
|
||||
|
||||
@@ -35,9 +36,9 @@ of stars awarded, whether the image is official, and whether it is automated.
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
## Search Docker Hub for ranked images
|
||||
## Search the registry for ranked images
|
||||
|
||||
Search a registry for the term 'fedora' and only display those images
|
||||
Search the registry for the term 'fedora' and only display those images
|
||||
ranked 3 or higher:
|
||||
|
||||
$ sudo docker search -s 3 fedora
|
||||
@@ -47,9 +48,9 @@ ranked 3 or higher:
|
||||
mattdm/fedora-small A small Fedora image on which to build. Co... 8
|
||||
goldmann/wildfly A WildFly application server running on a ... 3 [OK]
|
||||
|
||||
## Search Docker Hub for automated images
|
||||
## Search the registry for automated images
|
||||
|
||||
Search Docker Hub for the term 'fedora' and only display automated images
|
||||
Search the registry for the term 'fedora' and only display automated images
|
||||
ranked 1 or higher:
|
||||
|
||||
$ sudo docker search -s 1 -t fedora
|
||||
@@ -61,5 +62,3 @@ ranked 1 or higher:
|
||||
April 2014, Originally compiled by William Henry (whenry at redhat dot com)
|
||||
based on docker.com source material and internal work.
|
||||
June 2014, updated by Sven Dowideit <SvenDowideit@home.org.au>
|
||||
April 2015, updated by Mary Anthony for v2 <mary@docker.com>
|
||||
|
||||
|
||||
@@ -8,14 +8,11 @@ docker-tag - Tag an image into a repository
|
||||
**docker tag**
|
||||
[**-f**|**--force**[=*false*]]
|
||||
[**--help**]
|
||||
IMAGE[:TAG] [REGISTRY_HOST/][USERNAME/]NAME[:TAG]
|
||||
IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG]
|
||||
|
||||
# DESCRIPTION
|
||||
Assigns a new alias to an image in a registry. An alias refers to the
|
||||
entire image name including the optional `TAG` after the ':'.
|
||||
|
||||
If you do not specify a `REGISTRY_HOST`, the command uses Docker's public
|
||||
registry located at `registry-1.docker.io` by default.
|
||||
This will give a new alias to an image in the repository. This refers to the
|
||||
entire image name including the optional TAG after the ':'.
|
||||
|
||||
# "OPTIONS"
|
||||
**-f**, **--force**=*true*|*false*
|
||||
@@ -61,5 +58,3 @@ April 2014, Originally compiled by William Henry (whenry at redhat dot com)
|
||||
based on docker.com source material and internal work.
|
||||
June 2014, updated by Sven Dowideit <SvenDowideit@home.org.au>
|
||||
July 2014, updated by Sven Dowideit <SvenDowideit@home.org.au>
|
||||
April 2015, updated by Mary Anthony for v2 <mary@docker.com>
|
||||
|
||||
|
||||
@@ -172,10 +172,10 @@ inside it)
|
||||
Load an image from a tar archive
|
||||
|
||||
**docker-login(1)**
|
||||
Register or login to a Docker Registry Service
|
||||
Register or Login to a Docker registry server
|
||||
|
||||
**docker-logout(1)**
|
||||
Log the user out of a Docker Registry Service
|
||||
Log the user out of a Docker registry server
|
||||
|
||||
**docker-logs(1)**
|
||||
Fetch the logs of a container
|
||||
@@ -190,10 +190,10 @@ inside it)
|
||||
List containers
|
||||
|
||||
**docker-pull(1)**
|
||||
Pull an image or a repository from a Docker Registry Service
|
||||
Pull an image or a repository from a Docker registry server
|
||||
|
||||
**docker-push(1)**
|
||||
Push an image or a repository to a Docker Registry Service
|
||||
Push an image or a repository to a Docker registry server
|
||||
|
||||
**docker-restart(1)**
|
||||
Restart a running container
|
||||
|
||||
@@ -132,19 +132,10 @@ pages:
|
||||
- ['swarm/scheduler/filter.md', 'Reference', 'Swarm filters']
|
||||
- ['swarm/API.md', 'Reference', 'Swarm API']
|
||||
- ['reference/api/index.md', '**HIDDEN**']
|
||||
- ['registry/overview.md', 'Reference', 'Docker Registry 2.0']
|
||||
- ['registry/deploying.md', 'Reference', ' ▪ Deploy a registry' ]
|
||||
- ['registry/configuration.md', 'Reference', ' ▪ Configure a registry' ]
|
||||
- ['registry/storagedrivers.md', 'Reference', ' ▪ Storage driver model' ]
|
||||
- ['registry/notifications.md', 'Reference', ' ▪ Work with notifications' ]
|
||||
- ['registry/spec/api.md', 'Reference', ' ▪ Registry Service API v2' ]
|
||||
- ['registry/spec/json.md', 'Reference', ' ▪ JSON format' ]
|
||||
- ['registry/spec/auth/token.md', 'Reference', ' ▪ Authenticate via central service' ]
|
||||
- ['reference/api/hub_registry_spec.md', 'Reference', 'Docker Hub and Registry 1.0']
|
||||
- ['reference/api/registry_api.md', 'Reference', ' ▪ Docker Registry API v1']
|
||||
- ['reference/api/registry_api_client_libraries.md', 'Reference', ' ▪ Docker Registry 1.0 API Client Libraries']
|
||||
#- ['reference/image-spec-v1.md', 'Reference', 'Docker Image Specification v1.0.0']
|
||||
- ['reference/api/docker-io_api.md', 'Reference', 'Docker Hub API']
|
||||
- ['reference/api/registry_api.md', 'Reference', 'Docker Registry API']
|
||||
- ['reference/api/registry_api_client_libraries.md', 'Reference', 'Docker Registry API Client Libraries']
|
||||
- ['reference/api/hub_registry_spec.md', 'Reference', 'Docker Hub and Registry Spec']
|
||||
#- ['reference/image-spec-v1.md', 'Reference', 'Docker Image Specification v1.0.0']
|
||||
- ['reference/api/docker_remote_api.md', 'Reference', 'Docker Remote API']
|
||||
- ['reference/api/docker_remote_api_v1.18.md', 'Reference', 'Docker Remote API v1.18']
|
||||
|
||||
@@ -22,17 +22,10 @@ EOF
|
||||
}
|
||||
|
||||
create_robots_txt() {
|
||||
if [ "$AWS_S3_BUCKET" == "docs.docker.com" ]; then
|
||||
cat > ./sources/robots.txt <<-'EOF'
|
||||
User-agent: *
|
||||
Allow: /
|
||||
EOF
|
||||
else
|
||||
cat > ./sources/robots.txt <<-'EOF'
|
||||
User-agent: *
|
||||
Disallow: /
|
||||
EOF
|
||||
fi
|
||||
cat > ./sources/robots.txt <<'EOF'
|
||||
User-agent: *
|
||||
Disallow: /
|
||||
EOF
|
||||
}
|
||||
|
||||
setup_s3() {
|
||||
|
||||
@@ -39,8 +39,7 @@
|
||||
{ "Condition": { "KeyPrefixEquals": "installation/openSUSE/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "installation/SUSE/" } },
|
||||
{ "Condition": { "KeyPrefixEquals": "contributing/contributing/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "project/who-written-for/" } },
|
||||
{ "Condition": { "KeyPrefixEquals": "contributing/devenvironment/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "project/set-up-prereqs/" } },
|
||||
{ "Condition": { "KeyPrefixEquals": "contributing/docs_style-guide/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "project/doc-style/" } },
|
||||
{ "Condition": { "KeyPrefixEquals": "registry/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "registry/overview/" } }
|
||||
{ "Condition": { "KeyPrefixEquals": "contributing/docs_style-guide/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "project/doc-style/" } }
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 62 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 99 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 205 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 8.7 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 248 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 187 KiB After Width: | Height: | Size: 22 KiB |
@@ -8,95 +8,31 @@ 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) 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.
|
||||
[Boot2Docker](https://github.com/boot2docker/boot2docker) that installs the
|
||||
virtual machine and runs the Docker daemon.
|
||||
|
||||
## Demonstration
|
||||
|
||||
<iframe width="640" height="480" src="//www.youtube.com/embed/TjMU3bDX4vo?rel=0" frameborder="0" allowfullscreen></iframe>
|
||||
<iframe width="640" height="480" src="//www.youtube.com/embed/oSHN8_uiZd4?rel=0" frameborder="0" allowfullscreen></iframe>
|
||||
|
||||
## Installation
|
||||
|
||||
1. Download the latest release of the
|
||||
[Docker for Windows Installer](https://github.com/boot2docker/windows-installer/releases/latest).
|
||||
2. Run the installer, which will install Docker Client or Windows, VirtualBox,
|
||||
Git for Windows (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 VirtualBox, MSYS-git, the boot2docker Linux ISO,
|
||||
and the Boot2Docker management tool.
|
||||

|
||||
3. Run the **Boot2Docker Start** shortcut from your Desktop or “Program Files →
|
||||
Boot2Docker for Windows”.
|
||||
3. Run the `Boot2Docker Start` shell script 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:
|
||||

|
||||
|
||||

|
||||
|
||||
## 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>]
|
||||
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.
|
||||
|
||||
## Upgrading
|
||||
|
||||
@@ -111,13 +47,46 @@ The Boot2Docker management tool provides several commands:
|
||||
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:
|
||||
|
||||
@@ -132,25 +101,3 @@ Typically, it is 192.168.59.103, but it could get changed by Virtualbox's DHCP
|
||||
implementation.
|
||||
|
||||
For further information or to report issues, please see the [Boot2Docker site](http://boot2docker.io)
|
||||
|
||||
## Login with PUTTY instead of using the CMD
|
||||
|
||||
Boot2Docker generates and uses the public/private key pair in your `%USERPROFILE%\.ssh`
|
||||
directory so to log in you need to use the private key from this same directory.
|
||||
|
||||
The private key needs to be converted into the format PuTTY uses.
|
||||
|
||||
You can do this with
|
||||
[puttygen](http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html):
|
||||
|
||||
- Open `puttygen.exe` and load ("File"->"Load" menu) the private key from
|
||||
`%USERPROFILE%\.ssh\id_boot2docker`
|
||||
- then click: "Save Private Key".
|
||||
- Then use the saved file to login with PuTTY using `docker@127.0.0.1:2022`.
|
||||
|
||||
## References
|
||||
|
||||
If you have Docker hosts running and if you don't wish to do a
|
||||
Boot2Docker installation, you can install the docker.exe using
|
||||
unofficial Windows package manager Chocolately. For information
|
||||
on how to do this, see [Docker package on Chocolatey](http://chocolatey.org/packages/docker).
|
||||
|
||||
@@ -359,10 +359,7 @@ Return low-level information on the container `id`
|
||||
"MaximumRetryCount": 2,
|
||||
"Name": "on-failure"
|
||||
},
|
||||
"LogConfig": {
|
||||
"Config": null,
|
||||
"Type": "json-file"
|
||||
},
|
||||
"LogConfig": { "Type": "json-file", Config: {} },
|
||||
"SecurityOpt": null,
|
||||
"VolumesFrom": null,
|
||||
"Ulimits": [{}]
|
||||
|
||||
@@ -2,7 +2,7 @@ page_title: Registry Documentation
|
||||
page_description: Documentation for docker Registry and Registry API
|
||||
page_keywords: docker, registry, api, hub
|
||||
|
||||
# The Docker Hub and the Registry 1.0 spec
|
||||
# The Docker Hub and the Registry spec
|
||||
|
||||
## The three roles
|
||||
|
||||
@@ -28,9 +28,9 @@ The Docker Hub is authoritative for that information.
|
||||
There is only one instance of the Docker Hub, run and
|
||||
managed by Docker Inc.
|
||||
|
||||
### Docker Registry 1.0
|
||||
### Registry
|
||||
|
||||
The 1.0 registry has the following characteristics:
|
||||
The registry has the following characteristics:
|
||||
|
||||
- It stores the images and the graph for a set of repositories
|
||||
- It does not have user accounts data
|
||||
|
||||
@@ -2,11 +2,11 @@ page_title: Registry API
|
||||
page_description: API Documentation for Docker Registry
|
||||
page_keywords: API, Docker, index, registry, REST, documentation
|
||||
|
||||
# Docker Registry API v1
|
||||
# Docker Registry API
|
||||
|
||||
## Introduction
|
||||
|
||||
- This is the REST API for the Docker Registry 1.0
|
||||
- This is the REST API for the Docker Registry
|
||||
- It stores the images and the graph for a set of repositories
|
||||
- It does not have user accounts data
|
||||
- It has no notion of user accounts or authorization
|
||||
|
||||
@@ -2,7 +2,7 @@ page_title: Registry API Client Libraries
|
||||
page_description: Various client libraries available to use with the Docker registry API
|
||||
page_keywords: API, Docker, index, registry, REST, documentation, clients, C#, Erlang, Go, Groovy, Java, JavaScript, Perl, PHP, Python, Ruby, Rust, Scala
|
||||
|
||||
# Docker Registry 1.0 API Client Libraries
|
||||
# Docker Registry API Client Libraries
|
||||
|
||||
These libraries have not been tested by the Docker maintainers for
|
||||
compatibility. Please file issues with the library owners. If you find
|
||||
|
||||
@@ -280,14 +280,8 @@ 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.
|
||||
|
||||
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.
|
||||
might notice it during an attempt to `rm` a file, for example. The issue
|
||||
describes a workaround.
|
||||
|
||||
## CMD
|
||||
|
||||
|
||||
@@ -57,14 +57,7 @@ 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 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
|
||||
keys, database instances, etc. For complete information and workarounds see
|
||||
[Github Issue 783](https://github.com/docker/docker/issues/783).
|
||||
|
||||
* **Docker Hub incompatible with Safari 8**
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package graph
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@@ -15,7 +13,6 @@ 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"
|
||||
@@ -244,27 +241,18 @@ func (graph *Graph) newTempFile() (*os.File, error) {
|
||||
return ioutil.TempFile(tmp, "")
|
||||
}
|
||||
|
||||
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()
|
||||
func bufferToFile(f *os.File, src io.Reader) (int64, error) {
|
||||
n, err := io.Copy(f, src)
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
return n, err
|
||||
}
|
||||
if err = f.Sync(); err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
n, err := f.Seek(0, os.SEEK_CUR)
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
return n, err
|
||||
}
|
||||
if _, err := f.Seek(0, 0); err != nil {
|
||||
return 0, "", err
|
||||
return n, err
|
||||
}
|
||||
return n, digest.NewDigest("sha256", h), nil
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// setupInitLayer populates a directory with mountpoints suitable
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package graph
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -12,6 +13,7 @@ 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"
|
||||
@@ -462,7 +464,12 @@ func (s *TagStore) pushV2Image(r *registry.Session, img *image.Image, endpoint *
|
||||
os.Remove(tf.Name())
|
||||
}()
|
||||
|
||||
size, dgst, err := bufferToFile(tf, arch)
|
||||
h := sha256.New()
|
||||
size, err := bufferToFile(tf, io.TeeReader(arch, h))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
dgst := digest.NewDigest("sha256", h)
|
||||
|
||||
// Send the layer
|
||||
log.Debugf("rendered layer for %s of [%d] size", img.ID, size)
|
||||
|
||||
@@ -20,230 +20,213 @@ command_exists() {
|
||||
command -v "$@" > /dev/null 2>&1
|
||||
}
|
||||
|
||||
echo_docker_as_nonroot() {
|
||||
your_user=your-user
|
||||
[ "$user" != 'root' ] && your_user="$user"
|
||||
# intentionally mixed spaces and tabs here -- tabs are stripped by "<<-EOF", spaces are kept in the output
|
||||
cat <<-EOF
|
||||
case "$(uname -m)" in
|
||||
*64)
|
||||
;;
|
||||
*)
|
||||
echo >&2 'Error: you are not using a 64bit platform.'
|
||||
echo >&2 'Docker currently only supports 64bit platforms.'
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
If you would like to use Docker as a non-root user, you should now consider
|
||||
adding your user to the "docker" group with something like:
|
||||
if command_exists docker || command_exists lxc-docker; then
|
||||
echo >&2 'Warning: "docker" or "lxc-docker" command appears to already exist.'
|
||||
echo >&2 'Please ensure that you do not already have docker installed.'
|
||||
echo >&2 'You may press Ctrl+C now to abort this process and rectify this situation.'
|
||||
( set -x; sleep 20 )
|
||||
fi
|
||||
|
||||
sudo usermod -aG docker $your_user
|
||||
user="$(id -un 2>/dev/null || true)"
|
||||
|
||||
Remember that you will have to log out and back in for this to take effect!
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
do_install() {
|
||||
case "$(uname -m)" in
|
||||
*64)
|
||||
;;
|
||||
*)
|
||||
cat >&2 <<-'EOF'
|
||||
Error: you are not using a 64bit platform.
|
||||
Docker currently only supports 64bit platforms.
|
||||
EOF
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
if command_exists docker || command_exists lxc-docker; then
|
||||
cat >&2 <<-'EOF'
|
||||
Warning: "docker" or "lxc-docker" command appears to already exist.
|
||||
Please ensure that you do not already have docker installed.
|
||||
You may press Ctrl+C now to abort this process and rectify this situation.
|
||||
EOF
|
||||
( set -x; sleep 20 )
|
||||
sh_c='sh -c'
|
||||
if [ "$user" != 'root' ]; then
|
||||
if command_exists sudo; then
|
||||
sh_c='sudo -E sh -c'
|
||||
elif command_exists su; then
|
||||
sh_c='su -c'
|
||||
else
|
||||
echo >&2 'Error: this installer needs the ability to run commands as root.'
|
||||
echo >&2 'We are unable to find either "sudo" or "su" available to make this happen.'
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
user="$(id -un 2>/dev/null || true)"
|
||||
curl=''
|
||||
if command_exists curl; then
|
||||
curl='curl -sSL'
|
||||
elif command_exists wget; then
|
||||
curl='wget -qO-'
|
||||
elif command_exists busybox && busybox --list-modules | grep -q wget; then
|
||||
curl='busybox wget -qO-'
|
||||
fi
|
||||
|
||||
sh_c='sh -c'
|
||||
if [ "$user" != 'root' ]; then
|
||||
if command_exists sudo; then
|
||||
sh_c='sudo -E sh -c'
|
||||
elif command_exists su; then
|
||||
sh_c='su -c'
|
||||
# perform some very rudimentary platform detection
|
||||
lsb_dist=''
|
||||
if command_exists lsb_release; then
|
||||
lsb_dist="$(lsb_release -si)"
|
||||
fi
|
||||
if [ -z "$lsb_dist" ] && [ -r /etc/lsb-release ]; then
|
||||
lsb_dist="$(. /etc/lsb-release && echo "$DISTRIB_ID")"
|
||||
fi
|
||||
if [ -z "$lsb_dist" ] && [ -r /etc/debian_version ]; then
|
||||
lsb_dist='debian'
|
||||
fi
|
||||
if [ -z "$lsb_dist" ] && [ -r /etc/fedora-release ]; then
|
||||
lsb_dist='fedora'
|
||||
fi
|
||||
if [ -z "$lsb_dist" ] && [ -r /etc/os-release ]; then
|
||||
lsb_dist="$(. /etc/os-release && echo "$ID")"
|
||||
fi
|
||||
|
||||
lsb_dist="$(echo "$lsb_dist" | tr '[:upper:]' '[:lower:]')"
|
||||
case "$lsb_dist" in
|
||||
amzn|fedora)
|
||||
if [ "$lsb_dist" = 'amzn' ]; then
|
||||
(
|
||||
set -x
|
||||
$sh_c 'sleep 3; yum -y -q install docker'
|
||||
)
|
||||
else
|
||||
cat >&2 <<-'EOF'
|
||||
Error: this installer needs the ability to run commands as root.
|
||||
We are unable to find either "sudo" or "su" available to make this happen.
|
||||
EOF
|
||||
exit 1
|
||||
(
|
||||
set -x
|
||||
$sh_c 'sleep 3; yum -y -q install docker-io'
|
||||
)
|
||||
fi
|
||||
fi
|
||||
if command_exists docker && [ -e /var/run/docker.sock ]; then
|
||||
(
|
||||
set -x
|
||||
$sh_c 'docker version'
|
||||
) || true
|
||||
fi
|
||||
your_user=your-user
|
||||
[ "$user" != 'root' ] && your_user="$user"
|
||||
echo
|
||||
echo 'If you would like to use Docker as a non-root user, you should now consider'
|
||||
echo 'adding your user to the "docker" group with something like:'
|
||||
echo
|
||||
echo ' sudo usermod -aG docker' $your_user
|
||||
echo
|
||||
echo 'Remember that you will have to log out and back in for this to take effect!'
|
||||
echo
|
||||
exit 0
|
||||
;;
|
||||
|
||||
curl=''
|
||||
if command_exists curl; then
|
||||
curl='curl -sSL'
|
||||
elif command_exists wget; then
|
||||
curl='wget -qO-'
|
||||
elif command_exists busybox && busybox --list-modules | grep -q wget; then
|
||||
curl='busybox wget -qO-'
|
||||
fi
|
||||
ubuntu|debian|linuxmint)
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# perform some very rudimentary platform detection
|
||||
lsb_dist=''
|
||||
if command_exists lsb_release; then
|
||||
lsb_dist="$(lsb_release -si)"
|
||||
fi
|
||||
if [ -z "$lsb_dist" ] && [ -r /etc/lsb-release ]; then
|
||||
lsb_dist="$(. /etc/lsb-release && echo "$DISTRIB_ID")"
|
||||
fi
|
||||
if [ -z "$lsb_dist" ] && [ -r /etc/debian_version ]; then
|
||||
lsb_dist='debian'
|
||||
fi
|
||||
if [ -z "$lsb_dist" ] && [ -r /etc/fedora-release ]; then
|
||||
lsb_dist='fedora'
|
||||
fi
|
||||
if [ -z "$lsb_dist" ] && [ -r /etc/os-release ]; then
|
||||
lsb_dist="$(. /etc/os-release && echo "$ID")"
|
||||
fi
|
||||
|
||||
lsb_dist="$(echo "$lsb_dist" | tr '[:upper:]' '[:lower:]')"
|
||||
case "$lsb_dist" in
|
||||
amzn|fedora|centos)
|
||||
if [ "$lsb_dist" = 'amzn' ]; then
|
||||
(
|
||||
set -x
|
||||
$sh_c 'sleep 3; yum -y -q install docker'
|
||||
)
|
||||
else
|
||||
(
|
||||
set -x
|
||||
$sh_c 'sleep 3; yum -y -q install docker-io'
|
||||
)
|
||||
did_apt_get_update=
|
||||
apt_get_update() {
|
||||
if [ -z "$did_apt_get_update" ]; then
|
||||
( set -x; $sh_c 'sleep 3; apt-get update' )
|
||||
did_apt_get_update=1
|
||||
fi
|
||||
if command_exists docker && [ -e /var/run/docker.sock ]; then
|
||||
(
|
||||
set -x
|
||||
$sh_c 'docker version'
|
||||
) || true
|
||||
fi
|
||||
echo_docker_as_nonroot
|
||||
exit 0
|
||||
;;
|
||||
}
|
||||
|
||||
ubuntu|debian|linuxmint)
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
# aufs is preferred over devicemapper; try to ensure the driver is available.
|
||||
if ! grep -q aufs /proc/filesystems && ! $sh_c 'modprobe aufs'; then
|
||||
if uname -r | grep -q -- '-generic' && dpkg -l 'linux-image-*-generic' | grep -q '^ii' 2>/dev/null; then
|
||||
kern_extras="linux-image-extra-$(uname -r) linux-image-extra-virtual"
|
||||
|
||||
did_apt_get_update=
|
||||
apt_get_update() {
|
||||
if [ -z "$did_apt_get_update" ]; then
|
||||
( set -x; $sh_c 'sleep 3; apt-get update' )
|
||||
did_apt_get_update=1
|
||||
fi
|
||||
}
|
||||
apt_get_update
|
||||
( set -x; $sh_c 'sleep 3; apt-get install -y -q '"$kern_extras" ) || true
|
||||
|
||||
# aufs is preferred over devicemapper; try to ensure the driver is available.
|
||||
if ! grep -q aufs /proc/filesystems && ! $sh_c 'modprobe aufs'; then
|
||||
if uname -r | grep -q -- '-generic' && dpkg -l 'linux-image-*-generic' | grep -q '^ii' 2>/dev/null; then
|
||||
kern_extras="linux-image-extra-$(uname -r) linux-image-extra-virtual"
|
||||
|
||||
apt_get_update
|
||||
( set -x; $sh_c 'sleep 3; apt-get install -y -q '"$kern_extras" ) || true
|
||||
|
||||
if ! grep -q aufs /proc/filesystems && ! $sh_c 'modprobe aufs'; then
|
||||
echo >&2 'Warning: tried to install '"$kern_extras"' (for AUFS)'
|
||||
echo >&2 ' but we still have no AUFS. Docker may not work. Proceeding anyways!'
|
||||
( set -x; sleep 10 )
|
||||
fi
|
||||
else
|
||||
echo >&2 'Warning: current kernel is not supported by the linux-image-extra-virtual'
|
||||
echo >&2 ' package. We have no AUFS support. Consider installing the packages'
|
||||
echo >&2 ' linux-image-virtual kernel and linux-image-extra-virtual for AUFS support.'
|
||||
if ! grep -q aufs /proc/filesystems && ! $sh_c 'modprobe aufs'; then
|
||||
echo >&2 'Warning: tried to install '"$kern_extras"' (for AUFS)'
|
||||
echo >&2 ' but we still have no AUFS. Docker may not work. Proceeding anyways!'
|
||||
( set -x; sleep 10 )
|
||||
fi
|
||||
else
|
||||
echo >&2 'Warning: current kernel is not supported by the linux-image-extra-virtual'
|
||||
echo >&2 ' package. We have no AUFS support. Consider installing the packages'
|
||||
echo >&2 ' linux-image-virtual kernel and linux-image-extra-virtual for AUFS support.'
|
||||
( set -x; sleep 10 )
|
||||
fi
|
||||
fi
|
||||
|
||||
# install apparmor utils if they're missing and apparmor is enabled in the kernel
|
||||
# otherwise Docker will fail to start
|
||||
if [ "$(cat /sys/module/apparmor/parameters/enabled 2>/dev/null)" = 'Y' ]; then
|
||||
if command -v apparmor_parser &> /dev/null; then
|
||||
echo 'apparmor is enabled in the kernel and apparmor utils were already installed'
|
||||
else
|
||||
echo 'apparmor is enabled in the kernel, but apparmor_parser missing'
|
||||
apt_get_update
|
||||
( set -x; $sh_c 'sleep 3; apt-get install -y -q apparmor' )
|
||||
fi
|
||||
# install apparmor utils if they're missing and apparmor is enabled in the kernel
|
||||
# otherwise Docker will fail to start
|
||||
if [ "$(cat /sys/module/apparmor/parameters/enabled 2>/dev/null)" = 'Y' ]; then
|
||||
if command -v apparmor_parser &> /dev/null; then
|
||||
echo 'apparmor is enabled in the kernel and apparmor utils were already installed'
|
||||
else
|
||||
echo 'apparmor is enabled in the kernel, but apparmor_parser missing'
|
||||
apt_get_update
|
||||
( set -x; $sh_c 'sleep 3; apt-get install -y -q apparmor' )
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ ! -e /usr/lib/apt/methods/https ]; then
|
||||
apt_get_update
|
||||
( set -x; $sh_c 'sleep 3; apt-get install -y -q apt-transport-https ca-certificates' )
|
||||
fi
|
||||
if [ -z "$curl" ]; then
|
||||
apt_get_update
|
||||
( set -x; $sh_c 'sleep 3; apt-get install -y -q curl ca-certificates' )
|
||||
curl='curl -sSL'
|
||||
if [ ! -e /usr/lib/apt/methods/https ]; then
|
||||
apt_get_update
|
||||
( set -x; $sh_c 'sleep 3; apt-get install -y -q apt-transport-https ca-certificates' )
|
||||
fi
|
||||
if [ -z "$curl" ]; then
|
||||
apt_get_update
|
||||
( set -x; $sh_c 'sleep 3; apt-get install -y -q curl ca-certificates' )
|
||||
curl='curl -sSL'
|
||||
fi
|
||||
(
|
||||
set -x
|
||||
if [ "https://get.docker.com/" = "$url" ]; then
|
||||
$sh_c "apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 36A1D7869245C8950F966E92D8576A8BA88D21E9"
|
||||
elif [ "https://test.docker.com/" = "$url" ]; then
|
||||
$sh_c "apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 740B314AE3941731B942C66ADF4FD13717AAD7D6"
|
||||
else
|
||||
$sh_c "$curl ${url}gpg | apt-key add -"
|
||||
fi
|
||||
$sh_c "echo deb ${url}ubuntu docker main > /etc/apt/sources.list.d/docker.list"
|
||||
$sh_c 'sleep 3; apt-get update; apt-get install -y -q lxc-docker'
|
||||
)
|
||||
if command_exists docker && [ -e /var/run/docker.sock ]; then
|
||||
(
|
||||
set -x
|
||||
if [ "https://get.docker.com/" = "$url" ]; then
|
||||
$sh_c "apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 36A1D7869245C8950F966E92D8576A8BA88D21E9"
|
||||
elif [ "https://test.docker.com/" = "$url" ]; then
|
||||
$sh_c "apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 740B314AE3941731B942C66ADF4FD13717AAD7D6"
|
||||
else
|
||||
$sh_c "$curl ${url}gpg | apt-key add -"
|
||||
fi
|
||||
$sh_c "echo deb ${url}ubuntu docker main > /etc/apt/sources.list.d/docker.list"
|
||||
$sh_c 'sleep 3; apt-get update; apt-get install -y -q lxc-docker'
|
||||
)
|
||||
if command_exists docker && [ -e /var/run/docker.sock ]; then
|
||||
(
|
||||
set -x
|
||||
$sh_c 'docker version'
|
||||
) || true
|
||||
fi
|
||||
echo_docker_as_nonroot
|
||||
exit 0
|
||||
;;
|
||||
$sh_c 'docker version'
|
||||
) || true
|
||||
fi
|
||||
your_user=your-user
|
||||
[ "$user" != 'root' ] && your_user="$user"
|
||||
echo
|
||||
echo 'If you would like to use Docker as a non-root user, you should now consider'
|
||||
echo 'adding your user to the "docker" group with something like:'
|
||||
echo
|
||||
echo ' sudo usermod -aG docker' $your_user
|
||||
echo
|
||||
echo 'Remember that you will have to log out and back in for this to take effect!'
|
||||
echo
|
||||
exit 0
|
||||
;;
|
||||
|
||||
gentoo)
|
||||
if [ "$url" = "https://test.docker.com/" ]; then
|
||||
# intentionally mixed spaces and tabs here -- tabs are stripped by "<<-'EOF'", spaces are kept in the output
|
||||
cat >&2 <<-'EOF'
|
||||
gentoo)
|
||||
if [ "$url" = "https://test.docker.com/" ]; then
|
||||
echo >&2
|
||||
echo >&2 ' You appear to be trying to install the latest nightly build in Gentoo.'
|
||||
echo >&2 ' The portage tree should contain the latest stable release of Docker, but'
|
||||
echo >&2 ' if you want something more recent, you can always use the live ebuild'
|
||||
echo >&2 ' provided in the "docker" overlay available via layman. For more'
|
||||
echo >&2 ' instructions, please see the following URL:'
|
||||
echo >&2 ' https://github.com/tianon/docker-overlay#using-this-overlay'
|
||||
echo >&2 ' After adding the "docker" overlay, you should be able to:'
|
||||
echo >&2 ' emerge -av =app-emulation/docker-9999'
|
||||
echo >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
You appear to be trying to install the latest nightly build in Gentoo.'
|
||||
The portage tree should contain the latest stable release of Docker, but'
|
||||
if you want something more recent, you can always use the live ebuild'
|
||||
provided in the "docker" overlay available via layman. For more'
|
||||
instructions, please see the following URL:'
|
||||
(
|
||||
set -x
|
||||
$sh_c 'sleep 3; emerge app-emulation/docker'
|
||||
)
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
|
||||
https://github.com/tianon/docker-overlay#using-this-overlay'
|
||||
cat >&2 <<'EOF'
|
||||
|
||||
After adding the "docker" overlay, you should be able to:'
|
||||
Either your platform is not easily detectable, is not supported by this
|
||||
installer script (yet - PRs welcome! [hack/install.sh]), or does not yet have
|
||||
a package for Docker. Please visit the following URL for more detailed
|
||||
installation instructions:
|
||||
|
||||
emerge -av =app-emulation/docker-9999'
|
||||
https://docs.docker.com/en/latest/installation/
|
||||
|
||||
EOF
|
||||
exit 1
|
||||
fi
|
||||
|
||||
(
|
||||
set -x
|
||||
$sh_c 'sleep 3; emerge app-emulation/docker'
|
||||
)
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
|
||||
# intentionally mixed spaces and tabs here -- tabs are stripped by "<<-'EOF'", spaces are kept in the output
|
||||
cat >&2 <<-'EOF'
|
||||
|
||||
Either your platform is not easily detectable, is not supported by this
|
||||
installer script (yet - PRs welcome! [hack/install.sh]), or does not yet have
|
||||
a package for Docker. Please visit the following URL for more detailed
|
||||
installation instructions:
|
||||
|
||||
https://docs.docker.com/en/latest/installation/
|
||||
|
||||
EOF
|
||||
exit 1
|
||||
}
|
||||
|
||||
# wrapped up in a function so that we have some protection against only getting
|
||||
# half the file during "curl | sh"
|
||||
do_install
|
||||
EOF
|
||||
exit 1
|
||||
|
||||
@@ -265,6 +265,9 @@ main() {
|
||||
bundle $SCRIPTDIR/make/$bundle
|
||||
echo
|
||||
done
|
||||
|
||||
# if we get all the way through successfully, let's delete our autogenerated code!
|
||||
rm -r autogen
|
||||
}
|
||||
|
||||
main "$@"
|
||||
|
||||
@@ -75,7 +75,7 @@ rm -rf src/github.com/docker/distribution
|
||||
mkdir -p src/github.com/docker/distribution
|
||||
mv tmp-digest src/github.com/docker/distribution/digest
|
||||
|
||||
clone git github.com/docker/libcontainer bd8ec36106086f72b66e1be85a81202b93503e44
|
||||
clone git github.com/docker/libcontainer fd0087d3acdc4c5865de1829d4accee5e3ebb658
|
||||
# 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')"
|
||||
|
||||
@@ -1958,7 +1958,6 @@ func TestBuildCancelationKillsSleep(t *testing.T) {
|
||||
|
||||
name := "testbuildcancelation"
|
||||
defer deleteImages(name)
|
||||
defer deleteAllContainers()
|
||||
|
||||
// (Note: one year, will never finish)
|
||||
ctx, err := fakeContext("FROM busybox\nRUN sleep 31536000", nil)
|
||||
|
||||
@@ -413,7 +413,6 @@ func TestRunLinkToContainerNetMode(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRunModeNetContainerHostname(t *testing.T) {
|
||||
testRequires(t, ExecSupport)
|
||||
defer deleteAllContainers()
|
||||
cmd := exec.Command(dockerBinary, "run", "-i", "-d", "--name", "parent", "busybox", "top")
|
||||
out, _, err := runCommandWithOutput(cmd)
|
||||
@@ -476,39 +475,6 @@ 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()
|
||||
@@ -3237,35 +3203,6 @@ func TestRunNetHost(t *testing.T) {
|
||||
logDone("run - net host mode")
|
||||
}
|
||||
|
||||
func TestRunNetContainerWhichHost(t *testing.T) {
|
||||
testRequires(t, SameHostDaemon)
|
||||
defer deleteAllContainers()
|
||||
|
||||
hostNet, err := os.Readlink("/proc/1/ns/net")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cmd := exec.Command(dockerBinary, "run", "-d", "--net=host", "--name=test", "busybox", "top")
|
||||
out, _, err := runCommandWithOutput(cmd)
|
||||
if err != nil {
|
||||
t.Fatal(err, out)
|
||||
}
|
||||
|
||||
cmd = exec.Command(dockerBinary, "run", "--net=container:test", "busybox", "readlink", "/proc/self/ns/net")
|
||||
out, _, err = runCommandWithOutput(cmd)
|
||||
if err != nil {
|
||||
t.Fatal(err, out)
|
||||
}
|
||||
|
||||
out = strings.Trim(out, "\n")
|
||||
if hostNet != out {
|
||||
t.Fatalf("Container should have host network namespace")
|
||||
}
|
||||
|
||||
logDone("run - net container mode, where container in host mode")
|
||||
}
|
||||
|
||||
func TestRunAllowPortRangeThroughPublish(t *testing.T) {
|
||||
defer deleteAllContainers()
|
||||
|
||||
@@ -3411,28 +3348,3 @@ func TestRunVolumesFromRestartAfterRemoved(t *testing.T) {
|
||||
|
||||
logDone("run - can restart a volumes-from container after producer is removed")
|
||||
}
|
||||
|
||||
func TestRunPidHostWithChildIsKillable(t *testing.T) {
|
||||
defer deleteAllContainers()
|
||||
name := "ibuildthecloud"
|
||||
if out, err := exec.Command(dockerBinary, "run", "-d", "--pid=host", "--name", name, "busybox", "sh", "-c", "sleep 30; echo hi").CombinedOutput(); err != nil {
|
||||
t.Fatal(err, out)
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
errchan := make(chan error)
|
||||
go func() {
|
||||
if out, err := exec.Command(dockerBinary, "kill", name).CombinedOutput(); err != nil {
|
||||
errchan <- fmt.Errorf("%v:\n%s", err, out)
|
||||
}
|
||||
close(errchan)
|
||||
}()
|
||||
select {
|
||||
case err := <-errchan:
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatal("Kill container timed out")
|
||||
}
|
||||
logDone("run - can kill container with pid-host and some childs of pid 1")
|
||||
}
|
||||
|
||||
@@ -17,13 +17,11 @@ var (
|
||||
privateRegistryURL = "127.0.0.1:5000"
|
||||
|
||||
dockerBasePath = "/var/lib/docker"
|
||||
execDriverPath = dockerBasePath + "/execdriver/native"
|
||||
volumesConfigPath = dockerBasePath + "/volumes"
|
||||
volumesStoragePath = dockerBasePath + "/vfs/dir"
|
||||
containerStoragePath = dockerBasePath + "/containers"
|
||||
|
||||
runtimePath = "/var/run/docker"
|
||||
execDriverPath = runtimePath + "/execdriver/native"
|
||||
|
||||
workingDirectory string
|
||||
)
|
||||
|
||||
|
||||
@@ -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.WriteString(line)
|
||||
w.buf.Write([]byte(line))
|
||||
break
|
||||
}
|
||||
for stream, writers := range w.streams {
|
||||
|
||||
@@ -64,7 +64,6 @@ 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()
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/pkg/term/winconsole"
|
||||
)
|
||||
|
||||
@@ -22,49 +21,34 @@ type Winsize struct {
|
||||
y uint16
|
||||
}
|
||||
|
||||
func StdStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) {
|
||||
switch {
|
||||
case os.Getenv("ConEmuANSI") == "ON":
|
||||
// The ConEmu shell emulates ANSI well by default.
|
||||
return os.Stdin, os.Stdout, os.Stderr
|
||||
case os.Getenv("MSYSTEM") != "":
|
||||
// MSYS (mingw) does not emulate ANSI well.
|
||||
return winconsole.WinConsoleStreams()
|
||||
default:
|
||||
return winconsole.WinConsoleStreams()
|
||||
}
|
||||
}
|
||||
|
||||
// GetFdInfo returns file descriptor and bool indicating whether the file is a terminal.
|
||||
func GetFdInfo(in interface{}) (uintptr, bool) {
|
||||
return winconsole.GetHandleInfo(in)
|
||||
}
|
||||
|
||||
// GetWinsize retrieves the window size of the terminal connected to the passed file descriptor.
|
||||
// GetWinsize gets the window size of the given terminal
|
||||
func GetWinsize(fd uintptr) (*Winsize, error) {
|
||||
ws := &Winsize{}
|
||||
var info *winconsole.CONSOLE_SCREEN_BUFFER_INFO
|
||||
info, err := winconsole.GetConsoleScreenBufferInfo(fd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO(azlinux): Set the pixel width / height of the console (currently unused by any caller)
|
||||
return &Winsize{
|
||||
Width: uint16(info.Window.Right - info.Window.Left + 1),
|
||||
Height: uint16(info.Window.Bottom - info.Window.Top + 1),
|
||||
x: 0,
|
||||
y: 0}, nil
|
||||
ws.Width = uint16(info.Window.Right - info.Window.Left + 1)
|
||||
ws.Height = uint16(info.Window.Bottom - info.Window.Top + 1)
|
||||
|
||||
ws.x = 0 // todo azlinux -- this is the pixel size of the Window, and not currently used by any caller
|
||||
ws.y = 0
|
||||
|
||||
return ws, nil
|
||||
}
|
||||
|
||||
// SetWinsize sets the size of the given terminal connected to the passed file descriptor.
|
||||
// SetWinsize sets the terminal connected to the given file descriptor to a
|
||||
// given size.
|
||||
func SetWinsize(fd uintptr, ws *Winsize) error {
|
||||
// TODO(azlinux): Implement SetWinsize
|
||||
logrus.Debugf("[windows] SetWinsize: WARNING -- Unsupported method invoked")
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||
func IsTerminal(fd uintptr) bool {
|
||||
return winconsole.IsConsole(fd)
|
||||
_, e := winconsole.GetConsoleMode(fd)
|
||||
return e == nil
|
||||
}
|
||||
|
||||
// RestoreTerminal restores the terminal connected to the given file descriptor to a
|
||||
@@ -73,7 +57,7 @@ func RestoreTerminal(fd uintptr, state *State) error {
|
||||
return winconsole.SetConsoleMode(fd, state.mode)
|
||||
}
|
||||
|
||||
// SaveState saves the state of the terminal connected to the given file descriptor.
|
||||
// SaveState saves the state of the given console
|
||||
func SaveState(fd uintptr) (*State, error) {
|
||||
mode, e := winconsole.GetConsoleMode(fd)
|
||||
if e != nil {
|
||||
@@ -82,57 +66,72 @@ func SaveState(fd uintptr) (*State, error) {
|
||||
return &State{mode}, nil
|
||||
}
|
||||
|
||||
// DisableEcho disables echo for the terminal connected to the given file descriptor.
|
||||
// -- See http://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx
|
||||
// DisableEcho disbales the echo for given file descriptor and returns previous state
|
||||
// see http://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx for these flag settings
|
||||
func DisableEcho(fd uintptr, state *State) error {
|
||||
mode := state.mode
|
||||
mode &^= winconsole.ENABLE_ECHO_INPUT
|
||||
mode |= winconsole.ENABLE_PROCESSED_INPUT | winconsole.ENABLE_LINE_INPUT
|
||||
// TODO(azlinux): Core code registers a goroutine to catch os.Interrupt and reset the terminal state.
|
||||
return winconsole.SetConsoleMode(fd, mode)
|
||||
state.mode &^= (winconsole.ENABLE_ECHO_INPUT)
|
||||
state.mode |= (winconsole.ENABLE_PROCESSED_INPUT | winconsole.ENABLE_LINE_INPUT)
|
||||
return winconsole.SetConsoleMode(fd, state.mode)
|
||||
}
|
||||
|
||||
// SetRawTerminal puts the terminal connected to the given file descriptor into raw
|
||||
// mode and returns the previous state of the terminal so that it can be
|
||||
// restored.
|
||||
func SetRawTerminal(fd uintptr) (*State, error) {
|
||||
state, err := MakeRaw(fd)
|
||||
oldState, err := MakeRaw(fd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// TODO(azlinux): Core code registers a goroutine to catch os.Interrupt and reset the terminal state.
|
||||
return state, err
|
||||
// TODO (azlinux): implement handling interrupt and restore state of terminal
|
||||
return oldState, err
|
||||
}
|
||||
|
||||
// MakeRaw puts the terminal connected to the given file descriptor into raw
|
||||
// mode and returns the previous state of the terminal so that it can be
|
||||
// restored.
|
||||
func MakeRaw(fd uintptr) (*State, error) {
|
||||
var state *State
|
||||
state, err := SaveState(fd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// See
|
||||
// -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx
|
||||
// -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx
|
||||
mode := state.mode
|
||||
|
||||
// Disable these modes
|
||||
mode &^= winconsole.ENABLE_ECHO_INPUT
|
||||
mode &^= winconsole.ENABLE_LINE_INPUT
|
||||
mode &^= winconsole.ENABLE_MOUSE_INPUT
|
||||
mode &^= winconsole.ENABLE_WINDOW_INPUT
|
||||
mode &^= winconsole.ENABLE_PROCESSED_INPUT
|
||||
|
||||
// Enable these modes
|
||||
mode |= winconsole.ENABLE_EXTENDED_FLAGS
|
||||
mode |= winconsole.ENABLE_INSERT_MODE
|
||||
mode |= winconsole.ENABLE_QUICK_EDIT_MODE
|
||||
|
||||
err = winconsole.SetConsoleMode(fd, mode)
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx
|
||||
// All three input modes, along with processed output mode, are designed to work together.
|
||||
// It is best to either enable or disable all of these modes as a group.
|
||||
// When all are enabled, the application is said to be in "cooked" mode, which means that most of the processing is handled for the application.
|
||||
// When all are disabled, the application is in "raw" mode, which means that input is unfiltered and any processing is left to the application.
|
||||
state.mode = 0
|
||||
err = winconsole.SetConsoleMode(fd, state.mode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return state, nil
|
||||
}
|
||||
|
||||
// GetFdInfo returns file descriptor and bool indicating whether the file is a terminal
|
||||
func GetFdInfo(in interface{}) (uintptr, bool) {
|
||||
return winconsole.GetHandleInfo(in)
|
||||
}
|
||||
|
||||
func StdStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) {
|
||||
var shouldEmulateANSI bool
|
||||
switch {
|
||||
case os.Getenv("ConEmuANSI") == "ON":
|
||||
// ConEmu shell, ansi emulated by default and ConEmu does an extensively
|
||||
// good emulation.
|
||||
shouldEmulateANSI = false
|
||||
case os.Getenv("MSYSTEM") != "":
|
||||
// MSYS (mingw) cannot fully emulate well and still shows escape characters
|
||||
// mostly because it's still running on cmd.exe window.
|
||||
shouldEmulateANSI = true
|
||||
default:
|
||||
shouldEmulateANSI = true
|
||||
}
|
||||
|
||||
if shouldEmulateANSI {
|
||||
return winconsole.StdStreams()
|
||||
}
|
||||
|
||||
return os.Stdin, os.Stdout, os.Stderr
|
||||
}
|
||||
|
||||
@@ -12,22 +12,18 @@ import (
|
||||
"sync"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
// Consts for Get/SetConsoleMode function
|
||||
// -- See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx
|
||||
ENABLE_PROCESSED_INPUT = 0x0001
|
||||
ENABLE_LINE_INPUT = 0x0002
|
||||
// see http://msdn.microsoft.com/en-us/library/windows/desktop/ms683167(v=vs.85).aspx
|
||||
ENABLE_ECHO_INPUT = 0x0004
|
||||
ENABLE_WINDOW_INPUT = 0x0008
|
||||
ENABLE_MOUSE_INPUT = 0x0010
|
||||
ENABLE_INSERT_MODE = 0x0020
|
||||
ENABLE_LINE_INPUT = 0x0002
|
||||
ENABLE_MOUSE_INPUT = 0x0010
|
||||
ENABLE_PROCESSED_INPUT = 0x0001
|
||||
ENABLE_QUICK_EDIT_MODE = 0x0040
|
||||
ENABLE_EXTENDED_FLAGS = 0x0080
|
||||
|
||||
ENABLE_WINDOW_INPUT = 0x0008
|
||||
// If parameter is a screen buffer handle, additional values
|
||||
ENABLE_PROCESSED_OUTPUT = 0x0001
|
||||
ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002
|
||||
@@ -101,27 +97,27 @@ const (
|
||||
VK_HOME = 0x24 // HOME key
|
||||
VK_LEFT = 0x25 // LEFT ARROW key
|
||||
VK_UP = 0x26 // UP ARROW key
|
||||
VK_RIGHT = 0x27 // RIGHT ARROW key
|
||||
VK_DOWN = 0x28 // DOWN ARROW key
|
||||
VK_SELECT = 0x29 // SELECT key
|
||||
VK_PRINT = 0x2A // PRINT key
|
||||
VK_EXECUTE = 0x2B // EXECUTE key
|
||||
VK_SNAPSHOT = 0x2C // PRINT SCREEN key
|
||||
VK_INSERT = 0x2D // INS key
|
||||
VK_DELETE = 0x2E // DEL key
|
||||
VK_HELP = 0x2F // HELP key
|
||||
VK_F1 = 0x70 // F1 key
|
||||
VK_F2 = 0x71 // F2 key
|
||||
VK_F3 = 0x72 // F3 key
|
||||
VK_F4 = 0x73 // F4 key
|
||||
VK_F5 = 0x74 // F5 key
|
||||
VK_F6 = 0x75 // F6 key
|
||||
VK_F7 = 0x76 // F7 key
|
||||
VK_F8 = 0x77 // F8 key
|
||||
VK_F9 = 0x78 // F9 key
|
||||
VK_F10 = 0x79 // F10 key
|
||||
VK_F11 = 0x7A // F11 key
|
||||
VK_F12 = 0x7B // F12 key
|
||||
VK_RIGHT = 0x27 //RIGHT ARROW key
|
||||
VK_DOWN = 0x28 //DOWN ARROW key
|
||||
VK_SELECT = 0x29 //SELECT key
|
||||
VK_PRINT = 0x2A //PRINT key
|
||||
VK_EXECUTE = 0x2B //EXECUTE key
|
||||
VK_SNAPSHOT = 0x2C //PRINT SCREEN key
|
||||
VK_INSERT = 0x2D //INS key
|
||||
VK_DELETE = 0x2E //DEL key
|
||||
VK_HELP = 0x2F //HELP key
|
||||
VK_F1 = 0x70 //F1 key
|
||||
VK_F2 = 0x71 //F2 key
|
||||
VK_F3 = 0x72 //F3 key
|
||||
VK_F4 = 0x73 //F4 key
|
||||
VK_F5 = 0x74 //F5 key
|
||||
VK_F6 = 0x75 //F6 key
|
||||
VK_F7 = 0x76 //F7 key
|
||||
VK_F8 = 0x77 //F8 key
|
||||
VK_F9 = 0x78 //F9 key
|
||||
VK_F10 = 0x79 //F10 key
|
||||
VK_F11 = 0x7A //F11 key
|
||||
VK_F12 = 0x7B //F12 key
|
||||
)
|
||||
|
||||
var kernel32DLL = syscall.NewLazyDLL("kernel32.dll")
|
||||
@@ -144,12 +140,7 @@ var (
|
||||
// types for calling various windows API
|
||||
// see http://msdn.microsoft.com/en-us/library/windows/desktop/ms682093(v=vs.85).aspx
|
||||
type (
|
||||
SHORT int16
|
||||
BOOL int32
|
||||
WORD uint16
|
||||
WCHAR uint16
|
||||
DWORD uint32
|
||||
|
||||
SHORT int16
|
||||
SMALL_RECT struct {
|
||||
Left SHORT
|
||||
Top SHORT
|
||||
@@ -162,6 +153,11 @@ type (
|
||||
Y SHORT
|
||||
}
|
||||
|
||||
BOOL int32
|
||||
WORD uint16
|
||||
WCHAR uint16
|
||||
DWORD uint32
|
||||
|
||||
CONSOLE_SCREEN_BUFFER_INFO struct {
|
||||
Size COORD
|
||||
CursorPosition COORD
|
||||
@@ -196,10 +192,6 @@ type (
|
||||
}
|
||||
)
|
||||
|
||||
// TODO(azlinux): Basic type clean-up
|
||||
// -- Convert all uses of uintptr to syscall.Handle to be consistent with Windows syscall
|
||||
// -- Convert, as appropriate, types to use defined Windows types (e.g., DWORD instead of uint32)
|
||||
|
||||
// Implements the TerminalEmulator interface
|
||||
type WindowsTerminal struct {
|
||||
outMutex sync.Mutex
|
||||
@@ -219,14 +211,14 @@ func getStdHandle(stdhandle int) uintptr {
|
||||
return uintptr(handle)
|
||||
}
|
||||
|
||||
func WinConsoleStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) {
|
||||
func StdStreams() (stdIn io.ReadCloser, stdOut io.Writer, stdErr io.Writer) {
|
||||
handler := &WindowsTerminal{
|
||||
inputBuffer: make([]byte, MAX_INPUT_BUFFER),
|
||||
inputEscapeSequence: []byte(KEY_ESC_CSI),
|
||||
inputEvents: make([]INPUT_RECORD, MAX_INPUT_EVENTS),
|
||||
}
|
||||
|
||||
if IsConsole(os.Stdin.Fd()) {
|
||||
if IsTerminal(os.Stdin.Fd()) {
|
||||
stdIn = &terminalReader{
|
||||
wrappedReader: os.Stdin,
|
||||
emulator: handler,
|
||||
@@ -237,7 +229,7 @@ func WinConsoleStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) {
|
||||
stdIn = os.Stdin
|
||||
}
|
||||
|
||||
if IsConsole(os.Stdout.Fd()) {
|
||||
if IsTerminal(os.Stdout.Fd()) {
|
||||
stdoutHandle := getStdHandle(syscall.STD_OUTPUT_HANDLE)
|
||||
|
||||
// Save current screen buffer info
|
||||
@@ -249,6 +241,8 @@ func WinConsoleStreams() (stdIn io.ReadCloser, stdOut, 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{
|
||||
@@ -261,7 +255,7 @@ func WinConsoleStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) {
|
||||
stdOut = os.Stdout
|
||||
}
|
||||
|
||||
if IsConsole(os.Stderr.Fd()) {
|
||||
if IsTerminal(os.Stderr.Fd()) {
|
||||
stdErr = &terminalWriter{
|
||||
wrappedWriter: os.Stderr,
|
||||
emulator: handler,
|
||||
@@ -275,21 +269,19 @@ func WinConsoleStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) {
|
||||
return stdIn, stdOut, stdErr
|
||||
}
|
||||
|
||||
// GetHandleInfo returns file descriptor and bool indicating whether the file is a console.
|
||||
// GetHandleInfo returns file descriptor and bool indicating whether the file is a terminal
|
||||
func GetHandleInfo(in interface{}) (uintptr, bool) {
|
||||
var inFd uintptr
|
||||
var isTerminalIn bool
|
||||
|
||||
switch t := in.(type) {
|
||||
case *terminalReader:
|
||||
in = t.wrappedReader
|
||||
case *terminalWriter:
|
||||
in = t.wrappedWriter
|
||||
}
|
||||
|
||||
if file, ok := in.(*os.File); ok {
|
||||
inFd = file.Fd()
|
||||
isTerminalIn = IsConsole(inFd)
|
||||
isTerminalIn = IsTerminal(inFd)
|
||||
}
|
||||
if tr, ok := in.(*terminalReader); ok {
|
||||
if file, ok := tr.wrappedReader.(*os.File); ok {
|
||||
inFd = file.Fd()
|
||||
isTerminalIn = IsTerminal(inFd)
|
||||
}
|
||||
}
|
||||
return inFd, isTerminalIn
|
||||
}
|
||||
@@ -322,12 +314,12 @@ func SetConsoleMode(handle uintptr, mode uint32) error {
|
||||
// SetCursorVisible sets the cursor visbility
|
||||
// http://msdn.microsoft.com/en-us/library/windows/desktop/ms686019(v=vs.85).aspx
|
||||
func SetCursorVisible(handle uintptr, isVisible BOOL) (bool, error) {
|
||||
var cursorInfo *CONSOLE_CURSOR_INFO = &CONSOLE_CURSOR_INFO{}
|
||||
if err := getError(getConsoleCursorInfoProc.Call(handle, uintptr(unsafe.Pointer(cursorInfo)), 0)); err != nil {
|
||||
var cursorInfo CONSOLE_CURSOR_INFO
|
||||
if err := getError(getConsoleCursorInfoProc.Call(handle, uintptr(unsafe.Pointer(&cursorInfo)), 0)); err != nil {
|
||||
return false, err
|
||||
}
|
||||
cursorInfo.Visible = isVisible
|
||||
if err := getError(setConsoleCursorInfoProc.Call(handle, uintptr(unsafe.Pointer(cursorInfo)), 0)); err != nil {
|
||||
if err := getError(setConsoleCursorInfoProc.Call(handle, uintptr(unsafe.Pointer(&cursorInfo)), 0)); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
@@ -412,25 +404,25 @@ func getNumberOfChars(fromCoord COORD, toCoord COORD, screenSize COORD) uint32 {
|
||||
|
||||
var buffer []CHAR_INFO
|
||||
|
||||
func clearDisplayRect(handle uintptr, attributes WORD, fromCoord COORD, toCoord COORD) (uint32, error) {
|
||||
func clearDisplayRect(handle uintptr, fillChar rune, attributes WORD, fromCoord COORD, toCoord COORD, windowSize COORD) (uint32, error) {
|
||||
var writeRegion SMALL_RECT
|
||||
writeRegion.Left = fromCoord.X
|
||||
writeRegion.Top = fromCoord.Y
|
||||
writeRegion.Left = fromCoord.X
|
||||
writeRegion.Right = toCoord.X
|
||||
writeRegion.Bottom = toCoord.Y
|
||||
|
||||
// allocate and initialize buffer
|
||||
width := toCoord.X - fromCoord.X + 1
|
||||
height := toCoord.Y - fromCoord.Y + 1
|
||||
size := uint32(width) * uint32(height)
|
||||
size := width * height
|
||||
if size > 0 {
|
||||
buffer := make([]CHAR_INFO, size)
|
||||
for i := range buffer {
|
||||
buffer[i] = CHAR_INFO{WCHAR(' '), attributes}
|
||||
for i := 0; i < int(size); i++ {
|
||||
buffer[i].UnicodeChar = WCHAR(fillChar)
|
||||
buffer[i].Attributes = attributes
|
||||
}
|
||||
|
||||
// Write to buffer
|
||||
r, err := writeConsoleOutput(handle, buffer, COORD{X: width, Y: height}, COORD{X: 0, Y: 0}, &writeRegion)
|
||||
r, err := writeConsoleOutput(handle, buffer[:size], windowSize, COORD{X: 0, Y: 0}, &writeRegion)
|
||||
if !r {
|
||||
if err != nil {
|
||||
return 0, err
|
||||
@@ -441,18 +433,18 @@ func clearDisplayRect(handle uintptr, attributes WORD, fromCoord COORD, toCoord
|
||||
return uint32(size), nil
|
||||
}
|
||||
|
||||
func clearDisplayRange(handle uintptr, attributes WORD, fromCoord COORD, toCoord COORD) (uint32, error) {
|
||||
func clearDisplayRange(handle uintptr, fillChar rune, attributes WORD, fromCoord COORD, toCoord COORD, windowSize COORD) (uint32, error) {
|
||||
nw := uint32(0)
|
||||
// start and end on same line
|
||||
if fromCoord.Y == toCoord.Y {
|
||||
return clearDisplayRect(handle, attributes, fromCoord, toCoord)
|
||||
return clearDisplayRect(handle, fillChar, attributes, fromCoord, toCoord, windowSize)
|
||||
}
|
||||
// TODO(azlinux): if full screen, optimize
|
||||
|
||||
// spans more than one line
|
||||
if fromCoord.Y < toCoord.Y {
|
||||
// from start position till end of line for first line
|
||||
n, err := clearDisplayRect(handle, attributes, fromCoord, COORD{X: toCoord.X, Y: fromCoord.Y})
|
||||
n, err := clearDisplayRect(handle, fillChar, attributes, fromCoord, COORD{X: windowSize.X - 1, Y: fromCoord.Y}, windowSize)
|
||||
if err != nil {
|
||||
return nw, err
|
||||
}
|
||||
@@ -460,14 +452,14 @@ func clearDisplayRange(handle uintptr, attributes WORD, fromCoord COORD, toCoord
|
||||
// lines between
|
||||
linesBetween := toCoord.Y - fromCoord.Y - 1
|
||||
if linesBetween > 0 {
|
||||
n, err = clearDisplayRect(handle, attributes, COORD{X: 0, Y: fromCoord.Y + 1}, COORD{X: toCoord.X, Y: toCoord.Y - 1})
|
||||
n, err = clearDisplayRect(handle, fillChar, attributes, COORD{X: 0, Y: fromCoord.Y + 1}, COORD{X: windowSize.X - 1, Y: toCoord.Y - 1}, windowSize)
|
||||
if err != nil {
|
||||
return nw, err
|
||||
}
|
||||
nw += n
|
||||
}
|
||||
// lines at end
|
||||
n, err = clearDisplayRect(handle, attributes, COORD{X: 0, Y: toCoord.Y}, toCoord)
|
||||
n, err = clearDisplayRect(handle, fillChar, attributes, COORD{X: 0, Y: toCoord.Y}, toCoord, windowSize)
|
||||
if err != nil {
|
||||
return nw, err
|
||||
}
|
||||
@@ -497,7 +489,7 @@ func setConsoleCursorPosition(handle uintptr, isRelative bool, column int16, lin
|
||||
|
||||
// http://msdn.microsoft.com/en-us/library/windows/desktop/ms683207(v=vs.85).aspx
|
||||
func getNumberOfConsoleInputEvents(handle uintptr) (uint16, error) {
|
||||
var n DWORD
|
||||
var n WORD
|
||||
if err := getError(getNumberOfConsoleInputEventsProc.Call(handle, uintptr(unsafe.Pointer(&n)))); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@@ -506,7 +498,7 @@ func getNumberOfConsoleInputEvents(handle uintptr) (uint16, error) {
|
||||
|
||||
//http://msdn.microsoft.com/en-us/library/windows/desktop/ms684961(v=vs.85).aspx
|
||||
func readConsoleInputKey(handle uintptr, inputBuffer []INPUT_RECORD) (int, error) {
|
||||
var nr DWORD
|
||||
var nr WORD
|
||||
if err := getError(readConsoleInputProc.Call(handle, uintptr(unsafe.Pointer(&inputBuffer[0])), uintptr(len(inputBuffer)), uintptr(unsafe.Pointer(&nr)))); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@@ -595,7 +587,6 @@ func (term *WindowsTerminal) HandleOutputCommand(handle uintptr, command []byte)
|
||||
n = len(command)
|
||||
|
||||
parsedCommand := parseAnsiCommand(command)
|
||||
logrus.Debugf("[windows] HandleOutputCommand: %v", parsedCommand)
|
||||
|
||||
// console settings changes need to happen in atomic way
|
||||
term.outMutex.Lock()
|
||||
@@ -641,17 +632,16 @@ func (term *WindowsTerminal) HandleOutputCommand(handle uintptr, command []byte)
|
||||
return n, err
|
||||
}
|
||||
if line > int16(screenBufferInfo.Window.Bottom) {
|
||||
line = int16(screenBufferInfo.Window.Bottom) + 1
|
||||
line = int16(screenBufferInfo.Window.Bottom)
|
||||
}
|
||||
column, err := parseInt16OrDefault(parsedCommand.getParam(1), 1)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
if column > int16(screenBufferInfo.Window.Right) {
|
||||
column = int16(screenBufferInfo.Window.Right) + 1
|
||||
column = int16(screenBufferInfo.Window.Right)
|
||||
}
|
||||
// The numbers are not 0 based, but 1 based
|
||||
logrus.Debugf("[windows] HandleOutputCommmand: Moving cursor to (%v,%v)", column-1, line-1)
|
||||
if err := setConsoleCursorPosition(handle, false, column-1, line-1); err != nil {
|
||||
return n, err
|
||||
}
|
||||
@@ -719,9 +709,9 @@ func (term *WindowsTerminal) HandleOutputCommand(handle uintptr, command []byte)
|
||||
switch value {
|
||||
case 0:
|
||||
start = screenBufferInfo.CursorPosition
|
||||
// end of the buffer
|
||||
end.X = screenBufferInfo.Size.X - 1
|
||||
end.Y = screenBufferInfo.Size.Y - 1
|
||||
// end of the screen
|
||||
end.X = screenBufferInfo.MaximumWindowSize.X - 1
|
||||
end.Y = screenBufferInfo.MaximumWindowSize.Y - 1
|
||||
// cursor
|
||||
cursor = screenBufferInfo.CursorPosition
|
||||
case 1:
|
||||
@@ -737,21 +727,20 @@ func (term *WindowsTerminal) HandleOutputCommand(handle uintptr, command []byte)
|
||||
// start of the screen
|
||||
start.X = 0
|
||||
start.Y = 0
|
||||
// end of the buffer
|
||||
end.X = screenBufferInfo.Size.X - 1
|
||||
end.Y = screenBufferInfo.Size.Y - 1
|
||||
// end of the screen
|
||||
end.X = screenBufferInfo.MaximumWindowSize.X - 1
|
||||
end.Y = screenBufferInfo.MaximumWindowSize.Y - 1
|
||||
// cursor
|
||||
cursor.X = 0
|
||||
cursor.Y = 0
|
||||
}
|
||||
if _, err := clearDisplayRange(uintptr(handle), term.screenBufferInfo.Attributes, start, end); err != nil {
|
||||
if _, err := clearDisplayRange(uintptr(handle), ' ', term.screenBufferInfo.Attributes, start, end, screenBufferInfo.MaximumWindowSize); err != nil {
|
||||
return n, err
|
||||
}
|
||||
// remember the the cursor position is 1 based
|
||||
if err := setConsoleCursorPosition(handle, false, int16(cursor.X), int16(cursor.Y)); err != nil {
|
||||
return n, err
|
||||
}
|
||||
|
||||
case "K":
|
||||
// [K
|
||||
// Clears all characters from the cursor position to the end of the line (including the character at the cursor position).
|
||||
@@ -771,7 +760,7 @@ func (term *WindowsTerminal) HandleOutputCommand(handle uintptr, command []byte)
|
||||
// start is where cursor is
|
||||
start = screenBufferInfo.CursorPosition
|
||||
// end of line
|
||||
end.X = screenBufferInfo.Size.X - 1
|
||||
end.X = screenBufferInfo.MaximumWindowSize.X - 1
|
||||
end.Y = screenBufferInfo.CursorPosition.Y
|
||||
// cursor remains the same
|
||||
cursor = screenBufferInfo.CursorPosition
|
||||
@@ -787,15 +776,15 @@ func (term *WindowsTerminal) HandleOutputCommand(handle uintptr, command []byte)
|
||||
case 2:
|
||||
// start of the line
|
||||
start.X = 0
|
||||
start.Y = screenBufferInfo.CursorPosition.Y - 1
|
||||
start.Y = screenBufferInfo.MaximumWindowSize.Y - 1
|
||||
// end of the line
|
||||
end.X = screenBufferInfo.Size.X - 1
|
||||
end.Y = screenBufferInfo.CursorPosition.Y - 1
|
||||
end.X = screenBufferInfo.MaximumWindowSize.X - 1
|
||||
end.Y = screenBufferInfo.MaximumWindowSize.Y - 1
|
||||
// cursor
|
||||
cursor.X = 0
|
||||
cursor.Y = screenBufferInfo.CursorPosition.Y - 1
|
||||
cursor.Y = screenBufferInfo.MaximumWindowSize.Y - 1
|
||||
}
|
||||
if _, err := clearDisplayRange(uintptr(handle), term.screenBufferInfo.Attributes, start, end); err != nil {
|
||||
if _, err := clearDisplayRange(uintptr(handle), ' ', term.screenBufferInfo.Attributes, start, end, screenBufferInfo.MaximumWindowSize); err != nil {
|
||||
return n, err
|
||||
}
|
||||
// remember the the cursor position is 1 based
|
||||
@@ -1042,12 +1031,12 @@ func (term *WindowsTerminal) HandleInputSequence(fd uintptr, command []byte) (n
|
||||
}
|
||||
|
||||
func marshal(c COORD) uintptr {
|
||||
return uintptr(*((*DWORD)(unsafe.Pointer(&c))))
|
||||
// works only on intel-endian machines
|
||||
return uintptr(uint32(uint32(uint16(c.Y))<<16 | uint32(uint16(c.X))))
|
||||
}
|
||||
|
||||
// IsConsole returns true if the given file descriptor is a terminal.
|
||||
// -- The code assumes that GetConsoleMode will return an error for file descriptors that are not a console.
|
||||
func IsConsole(fd uintptr) bool {
|
||||
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||
func IsTerminal(fd uintptr) bool {
|
||||
_, e := GetConsoleMode(fd)
|
||||
return e == nil
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package winconsole
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -207,21 +206,6 @@ func (c *ansiCommand) getParam(index int) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (ac *ansiCommand) String() string {
|
||||
return fmt.Sprintf("0x%v \"%v\" (\"%v\")",
|
||||
bytesToHex(ac.CommandBytes),
|
||||
ac.Command,
|
||||
strings.Join(ac.Parameters, "\",\""))
|
||||
}
|
||||
|
||||
func bytesToHex(b []byte) string {
|
||||
hex := make([]string, len(b))
|
||||
for i, ch := range b {
|
||||
hex[i] = fmt.Sprintf("%X", ch)
|
||||
}
|
||||
return strings.Join(hex, "")
|
||||
}
|
||||
|
||||
func parseInt16OrDefault(s string, defaultValue int16) (n int16, err error) {
|
||||
if s == "" {
|
||||
return defaultValue, nil
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package registry
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
@@ -70,7 +71,21 @@ func (auth *RequestAuthorization) getToken() (string, error) {
|
||||
return auth.tokenCache, nil
|
||||
}
|
||||
|
||||
client := auth.registryEndpoint.HTTPClient()
|
||||
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,
|
||||
}
|
||||
factory := HTTPRequestFactory(nil)
|
||||
|
||||
for _, challenge := range auth.registryEndpoint.AuthChallenges {
|
||||
@@ -237,10 +252,16 @@ 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 = registryEndpoint.HTTPClient()
|
||||
status string
|
||||
reqBody []byte
|
||||
err error
|
||||
client = &http.Client{
|
||||
Transport: &http.Transport{
|
||||
DisableKeepAlives: true,
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
},
|
||||
CheckRedirect: AddRequiredHeadersToRedirectedRequests,
|
||||
}
|
||||
reqStatusCode = 0
|
||||
serverAddress = authConfig.ServerAddress
|
||||
)
|
||||
@@ -264,7 +285,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 := client.Post(serverAddress+"users/", "application/json; charset=utf-8", b)
|
||||
req1, err := http.Post(serverAddress+"users/", "application/json; charset=utf-8", b)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Server Error: %s", err)
|
||||
}
|
||||
@@ -350,10 +371,26 @@ 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,7 +1,6 @@
|
||||
package registry
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
@@ -263,20 +262,3 @@ 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,10 +511,6 @@ 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,6 +33,7 @@ type Config struct {
|
||||
NetworkDisabled bool
|
||||
MacAddress string
|
||||
OnBuild []string
|
||||
SecurityOpt []string
|
||||
Labels map[string]string
|
||||
}
|
||||
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
bundles
|
||||
nsinit/nsinit
|
||||
|
||||
@@ -29,5 +29,3 @@ local:
|
||||
validate:
|
||||
hack/validate.sh
|
||||
|
||||
binary: all
|
||||
docker run --rm --privileged -v $(CURDIR)/bundles:/go/bin dockercore/libcontainer make direct-install
|
||||
|
||||
@@ -141,9 +141,6 @@ container.Resume()
|
||||
It is able to spawn new containers or join existing containers. A root
|
||||
filesystem must be provided for use along with a container configuration file.
|
||||
|
||||
To build `nsinit`, run `make binary`. It will save the binary into
|
||||
`bundles/nsinit`.
|
||||
|
||||
To use `nsinit`, cd into a Linux rootfs and copy a `container.json` file into
|
||||
the directory with your specified configuration. Environment, networking,
|
||||
and different capabilities for the container are specified in this file.
|
||||
|
||||
@@ -67,12 +67,12 @@ func generateProfile(out io.Writer) error {
|
||||
data := &data{
|
||||
Name: "docker-default",
|
||||
}
|
||||
if tunablesExists() {
|
||||
if tuntablesExists() {
|
||||
data.Imports = append(data.Imports, "#include <tunables/global>")
|
||||
} else {
|
||||
data.Imports = append(data.Imports, "@{PROC}=/proc/")
|
||||
}
|
||||
if abstractionsExists() {
|
||||
if abstrctionsEsists() {
|
||||
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 tunablesExists() bool {
|
||||
func tuntablesExists() bool {
|
||||
_, err := os.Stat("/etc/apparmor.d/tunables/global")
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// check if abstractions/base exist
|
||||
func abstractionsExists() bool {
|
||||
func abstrctionsEsists() bool {
|
||||
_, err := os.Stat("/etc/apparmor.d/abstractions/base")
|
||||
return err == nil
|
||||
}
|
||||
|
||||
@@ -173,6 +173,9 @@ func (m *Manager) Freeze(state configs.FreezerState) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !cgroups.PathExists(dir) {
|
||||
return cgroups.NewNotFoundError("freezer")
|
||||
}
|
||||
|
||||
prevState := m.Cgroups.Freezer
|
||||
m.Cgroups.Freezer = state
|
||||
@@ -197,6 +200,9 @@ func (m *Manager) GetPids() ([]int, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !cgroups.PathExists(dir) {
|
||||
return nil, cgroups.NewNotFoundError("devices")
|
||||
}
|
||||
|
||||
return cgroups.ReadProcsFile(dir)
|
||||
}
|
||||
@@ -220,16 +226,16 @@ func getCgroupData(c *configs.Cgroup, pid int) (*data, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (raw *data) parent(subsystem, mountpoint string) (string, error) {
|
||||
func (raw *data) parent(subsystem string) (string, error) {
|
||||
initPath, err := cgroups.GetInitCgroupDir(subsystem)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return filepath.Join(mountpoint, initPath), nil
|
||||
return filepath.Join(raw.root, subsystem, initPath), nil
|
||||
}
|
||||
|
||||
func (raw *data) path(subsystem string) (string, error) {
|
||||
mnt, err := cgroups.FindCgroupMountpoint(subsystem)
|
||||
_, 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 +246,7 @@ func (raw *data) path(subsystem string) (string, error) {
|
||||
return filepath.Join(raw.root, subsystem, raw.cgroup), nil
|
||||
}
|
||||
|
||||
parent, err := raw.parent(subsystem, mnt)
|
||||
parent, err := raw.parent(subsystem)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package fs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -42,10 +41,6 @@ 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
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
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")
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
package systemd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
@@ -42,10 +43,6 @@ var subsystems = map[string]subsystem{
|
||||
"freezer": &fs.FreezerGroup{},
|
||||
}
|
||||
|
||||
const (
|
||||
testScopeWait = 4
|
||||
)
|
||||
|
||||
var (
|
||||
connLock sync.Mutex
|
||||
theConn *systemd.Conn
|
||||
@@ -89,41 +86,16 @@ 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(scope, "replace", ddf); err != nil {
|
||||
if _, err := theConn.StartTransientUnit("docker-systemd-test-default-dependencies.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
|
||||
}
|
||||
@@ -217,7 +189,16 @@ func (m *Manager) Apply(pid int) error {
|
||||
}
|
||||
|
||||
paths := make(map[string]string)
|
||||
for sysname := range subsystems {
|
||||
for _, sysname := range []string{
|
||||
"devices",
|
||||
"memory",
|
||||
"cpu",
|
||||
"cpuset",
|
||||
"cpuacct",
|
||||
"blkio",
|
||||
"perf_event",
|
||||
"freezer",
|
||||
} {
|
||||
subsystemPath, err := getSubsystemPath(m.Cgroups, sysname)
|
||||
if err != nil {
|
||||
// Don't fail if a cgroup hierarchy was not found, just skip this subsystem
|
||||
@@ -246,21 +227,6 @@ func writeFile(dir, file, data string) error {
|
||||
return ioutil.WriteFile(filepath.Join(dir, file), []byte(data), 0700)
|
||||
}
|
||||
|
||||
func join(c *configs.Cgroup, subsystem string, pid int) (string, error) {
|
||||
path, err := getSubsystemPath(c, subsystem)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := os.MkdirAll(path, 0755); err != nil && !os.IsExist(err) {
|
||||
return "", err
|
||||
}
|
||||
if err := writeFile(path, "cgroup.procs", strconv.Itoa(pid)); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return path, nil
|
||||
}
|
||||
|
||||
func joinCpu(c *configs.Cgroup, pid int) error {
|
||||
path, err := getSubsystemPath(c, "cpu")
|
||||
if err != nil {
|
||||
@@ -280,11 +246,16 @@ func joinCpu(c *configs.Cgroup, pid int) error {
|
||||
}
|
||||
|
||||
func joinFreezer(c *configs.Cgroup, pid int) error {
|
||||
if _, err := join(c, "freezer", pid); err != nil {
|
||||
path, err := getSubsystemPath(c, "freezer")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
if err := os.MkdirAll(path, 0755); err != nil && !os.IsExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
return ioutil.WriteFile(filepath.Join(path, "cgroup.procs"), []byte(strconv.Itoa(pid)), 0700)
|
||||
}
|
||||
|
||||
func getSubsystemPath(c *configs.Cgroup, subsystem string) (string, error) {
|
||||
@@ -312,15 +283,21 @@ func (m *Manager) Freeze(state configs.FreezerState) error {
|
||||
return err
|
||||
}
|
||||
|
||||
prevState := m.Cgroups.Freezer
|
||||
m.Cgroups.Freezer = state
|
||||
|
||||
freezer := subsystems["freezer"]
|
||||
err = freezer.Set(path, m.Cgroups)
|
||||
if err != nil {
|
||||
m.Cgroups.Freezer = prevState
|
||||
if err := ioutil.WriteFile(filepath.Join(path, "freezer.state"), []byte(state), 0); err != nil {
|
||||
return err
|
||||
}
|
||||
for {
|
||||
state_, err := ioutil.ReadFile(filepath.Join(path, "freezer.state"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if string(state) == string(bytes.TrimSpace(state_)) {
|
||||
break
|
||||
}
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
}
|
||||
|
||||
m.Cgroups.Freezer = state
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -369,16 +346,29 @@ func getUnitName(c *configs.Cgroup) string {
|
||||
// because systemd will re-write the device settings if it needs to re-apply the cgroup context.
|
||||
// This happens at least for v208 when any sibling unit is started.
|
||||
func joinDevices(c *configs.Cgroup, pid int) error {
|
||||
path, err := join(c, "devices", pid)
|
||||
path, err := getSubsystemPath(c, "devices")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
devices := subsystems["devices"]
|
||||
if err := devices.Set(path, c); err != nil {
|
||||
if err := os.MkdirAll(path, 0755); err != nil && !os.IsExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(filepath.Join(path, "cgroup.procs"), []byte(strconv.Itoa(pid)), 0700); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !c.AllowAllDevices {
|
||||
if err := writeFile(path, "devices.deny", "a"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, dev := range c.AllowedDevices {
|
||||
if err := writeFile(path, "devices.allow", dev.CgroupString()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -16,17 +16,6 @@ const (
|
||||
NEWUSER NamespaceType = "NEWUSER"
|
||||
)
|
||||
|
||||
func NamespaceTypes() []NamespaceType {
|
||||
return []NamespaceType{
|
||||
NEWNET,
|
||||
NEWPID,
|
||||
NEWNS,
|
||||
NEWUTS,
|
||||
NEWIPC,
|
||||
NEWUSER,
|
||||
}
|
||||
}
|
||||
|
||||
// Namespace defines configuration for each namespace. It specifies an
|
||||
// alternate path that is able to be joined via setns.
|
||||
type Namespace struct {
|
||||
|
||||
@@ -140,9 +140,7 @@ func (c *linuxContainer) commandTemplate(p *Process, childPipe *os.File) (*exec.
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{}
|
||||
}
|
||||
cmd.ExtraFiles = []*os.File{childPipe}
|
||||
// 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.
|
||||
cmd.SysProcAttr.Pdeathsig = syscall.SIGKILL
|
||||
if c.config.ParentDeathSignal > 0 {
|
||||
cmd.SysProcAttr.Pdeathsig = syscall.Signal(c.config.ParentDeathSignal)
|
||||
}
|
||||
@@ -195,13 +193,12 @@ 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,
|
||||
Capabilities: process.Capabilities,
|
||||
Config: c.config,
|
||||
Args: process.Args,
|
||||
Env: process.Env,
|
||||
User: process.User,
|
||||
Cwd: process.Cwd,
|
||||
Console: process.consolePath,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -306,11 +303,5 @@ func (c *linuxContainer) currentState() (*State, error) {
|
||||
for _, ns := range c.config.Namespaces {
|
||||
state.NamespacePaths[ns.Type] = ns.GetPath(c.initProcess.pid())
|
||||
}
|
||||
for _, nsType := range configs.NamespaceTypes() {
|
||||
if _, ok := state.NamespacePaths[nsType]; !ok {
|
||||
ns := configs.Namespace{Type: nsType}
|
||||
state.NamespacePaths[ns.Type] = ns.GetPath(c.initProcess.pid())
|
||||
}
|
||||
}
|
||||
return state, nil
|
||||
}
|
||||
|
||||
@@ -130,8 +130,7 @@ func TestGetContainerState(t *testing.T) {
|
||||
{Type: configs.NEWNS},
|
||||
{Type: configs.NEWNET, Path: expectedNetworkPath},
|
||||
{Type: configs.NEWUTS},
|
||||
// emulate host for IPC
|
||||
//{Type: configs.NEWIPC},
|
||||
{Type: configs.NEWIPC},
|
||||
},
|
||||
},
|
||||
initProcess: &mockProcess{
|
||||
|
||||
@@ -40,14 +40,13 @@ 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"`
|
||||
Capabilities []string `json:"capabilities"`
|
||||
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"`
|
||||
User string `json:"user"`
|
||||
Config *configs.Config `json:"config"`
|
||||
Console string `json:"console"`
|
||||
Networks []*network `json:"network"`
|
||||
}
|
||||
|
||||
type initer interface {
|
||||
@@ -69,8 +68,7 @@ func newContainerInit(t initType, pipe *os.File) (initer, error) {
|
||||
}, nil
|
||||
case initStandard:
|
||||
return &linuxStandardInit{
|
||||
parentPid: syscall.Getppid(),
|
||||
config: config,
|
||||
config: config,
|
||||
}, nil
|
||||
}
|
||||
return nil, fmt.Errorf("unknown init type %q", t)
|
||||
@@ -93,7 +91,7 @@ func populateProcessEnvironment(env []string) error {
|
||||
|
||||
// finalizeNamespace drops the caps, sets the correct user
|
||||
// and working dir, and closes any leaked file descriptors
|
||||
// before executing the command inside the namespace
|
||||
// before execing the command inside the namespace
|
||||
func finalizeNamespace(config *initConfig) error {
|
||||
// Ensure that all non-standard fds we may have accidentally
|
||||
// inherited are marked close-on-exec so they stay out of the
|
||||
@@ -101,12 +99,7 @@ func finalizeNamespace(config *initConfig) error {
|
||||
if err := utils.CloseExecFrom(3); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
capabilities := config.Config.Capabilities
|
||||
if config.Capabilities != nil {
|
||||
capabilities = config.Capabilities
|
||||
}
|
||||
w, err := newCapWhitelist(capabilities)
|
||||
w, err := newCapWhitelist(config.Config.Capabilities)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -4,12 +4,10 @@ import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/libcontainer"
|
||||
"github.com/docker/libcontainer/cgroups/systemd"
|
||||
"github.com/docker/libcontainer/configs"
|
||||
)
|
||||
|
||||
@@ -397,102 +395,7 @@ 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) {
|
||||
testFreeze(t, false)
|
||||
}
|
||||
|
||||
func TestSystemdFreeze(t *testing.T) {
|
||||
if !systemd.UseSystemd() {
|
||||
t.Skip("Systemd is unsupported")
|
||||
}
|
||||
testFreeze(t, true)
|
||||
}
|
||||
|
||||
func testFreeze(t *testing.T, systemd bool) {
|
||||
if testing.Short() {
|
||||
return
|
||||
}
|
||||
@@ -509,9 +412,6 @@ func testFreeze(t *testing.T, systemd bool) {
|
||||
defer remove(rootfs)
|
||||
|
||||
config := newTemplateConfig(rootfs)
|
||||
if systemd {
|
||||
config.Cgroups.Slice = "system.slice"
|
||||
}
|
||||
|
||||
factory, err := libcontainer.New(root, libcontainer.Cgroupfs)
|
||||
if err != nil {
|
||||
@@ -574,77 +474,3 @@ func testFreeze(t *testing.T, systemd bool) {
|
||||
t.Fatal(s.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestContainerState(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)
|
||||
|
||||
l, err := os.Readlink("/proc/1/ns/ipc")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
config := newTemplateConfig(rootfs)
|
||||
config.Namespaces = configs.Namespaces([]configs.Namespace{
|
||||
{Type: configs.NEWNS},
|
||||
{Type: configs.NEWUTS},
|
||||
// host for IPC
|
||||
//{Type: configs.NEWIPC},
|
||||
{Type: configs.NEWPID},
|
||||
{Type: configs.NEWNET},
|
||||
})
|
||||
|
||||
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()
|
||||
|
||||
stdinR, stdinW, err := os.Pipe()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
p := &libcontainer.Process{
|
||||
Args: []string{"cat"},
|
||||
Env: standardEnvironment,
|
||||
Stdin: stdinR,
|
||||
}
|
||||
err = container.Start(p)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
stdinR.Close()
|
||||
defer p.Signal(os.Kill)
|
||||
|
||||
st, err := container.State()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
l1, err := os.Readlink(st.NamespacePaths[configs.NEWIPC])
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if l1 != l {
|
||||
t.Fatal("Container using non-host ipc namespace")
|
||||
}
|
||||
stdinW.Close()
|
||||
p.Wait()
|
||||
}
|
||||
|
||||
@@ -68,14 +68,9 @@ func copyBusybox(dest string) error {
|
||||
}
|
||||
|
||||
func newContainer(config *configs.Config) (libcontainer.Container, error) {
|
||||
cgm := libcontainer.Cgroupfs
|
||||
if config.Cgroups != nil && config.Cgroups.Slice == "system.slice" {
|
||||
cgm = libcontainer.SystemdCgroups
|
||||
}
|
||||
|
||||
factory, err := libcontainer.New(".",
|
||||
libcontainer.InitArgs(os.Args[0], "init", "--"),
|
||||
cgm,
|
||||
libcontainer.Cgroupfs,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
## 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.
|
||||
@@ -13,7 +13,7 @@ func main() {
|
||||
app.Version = "2"
|
||||
app.Author = "libcontainer maintainers"
|
||||
app.Flags = []cli.Flag{
|
||||
cli.StringFlag{Name: "root", Value: "/var/run/nsinit", Usage: "root directory for containers"},
|
||||
cli.StringFlag{Name: "root", Value: ".", Usage: "root directory for containers"},
|
||||
cli.StringFlag{Name: "log-file", Value: "", Usage: "set the log file to output logs to"},
|
||||
cli.BoolFlag{Name: "debug", Usage: "enable debug output in the logs"},
|
||||
}
|
||||
|
||||
@@ -41,10 +41,6 @@ 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,7 +4,6 @@ package libcontainer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -45,12 +44,8 @@ func (p *setnsProcess) startTime() (string, error) {
|
||||
return system.GetProcessStartTime(p.pid())
|
||||
}
|
||||
|
||||
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) signal(s os.Signal) error {
|
||||
return p.cmd.Process.Signal(s)
|
||||
}
|
||||
|
||||
func (p *setnsProcess) start() (err error) {
|
||||
@@ -240,10 +235,6 @@ func (p *initProcess) createNetworkInterfaces() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
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)
|
||||
func (p *initProcess) signal(s os.Signal) error {
|
||||
return p.cmd.Process.Signal(s)
|
||||
}
|
||||
|
||||
@@ -186,9 +186,7 @@ func reOpenDevNull(rootfs string) error {
|
||||
func createDevices(config *configs.Config) error {
|
||||
oldMask := syscall.Umask(0000)
|
||||
for _, node := range config.Devices {
|
||||
// containers running in a user namespace are not allowed to mknod
|
||||
// devices so we can just bind mount it from the host.
|
||||
if err := createDeviceNode(config.Rootfs, node, config.Namespaces.Contains(configs.NEWUSER)); err != nil {
|
||||
if err := createDeviceNode(config.Rootfs, node); err != nil {
|
||||
syscall.Umask(oldMask)
|
||||
return err
|
||||
}
|
||||
@@ -198,13 +196,20 @@ func createDevices(config *configs.Config) error {
|
||||
}
|
||||
|
||||
// Creates the device node in the rootfs of the container.
|
||||
func createDeviceNode(rootfs string, node *configs.Device, bind bool) error {
|
||||
func createDeviceNode(rootfs string, node *configs.Device) error {
|
||||
dest := filepath.Join(rootfs, node.Path)
|
||||
if err := os.MkdirAll(filepath.Dir(dest), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if bind {
|
||||
if err := mknodDevice(dest, node); err != nil {
|
||||
if os.IsExist(err) {
|
||||
return nil
|
||||
}
|
||||
if err != syscall.EPERM {
|
||||
return err
|
||||
}
|
||||
// containers running in a user namespace are not allowed to mknod
|
||||
// devices so we can just bind mount it from the host.
|
||||
f, err := os.Create(dest)
|
||||
if err != nil && !os.IsExist(err) {
|
||||
return err
|
||||
@@ -214,12 +219,6 @@ func createDeviceNode(rootfs string, node *configs.Device, bind bool) error {
|
||||
}
|
||||
return syscall.Mount(node.Path, dest, "bind", syscall.MS_BIND, "")
|
||||
}
|
||||
if err := mknodDevice(dest, node); err != nil {
|
||||
if os.IsExist(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||