Compare commits

...

32 Commits

Author SHA1 Message Date
Jessica Frazelle 20d4e6f29e Bump version to v1.6.0
Docker-DCO-1.1-Signed-off-by: Jessie Frazelle <princess@docker.com> (github: jfrazelle)
2015-03-31 16:43:25 -07:00
Alexander Morozov 45ad064150 Fix panic in integration tests
Closing activationLock only if it's not closed already. This is needed
only because integration tests using docker code directly and doesn't
care about global state.

Signed-off-by: Alexander Morozov <lk4d4@docker.com>
(cherry picked from commit c717475714)

Docker-DCO-1.1-Signed-off-by: Jessie Frazelle <jess@docker.com> (github: jfrazelle)

Docker-DCO-1.1-Signed-off-by: Jessie Frazelle <princess@docker.com> (github: jfrazelle)
2015-03-31 16:43:16 -07:00
Darren Shepherd 72e14a1566 Avoid ServeApi race condition
If job "acceptconnections" is called before "serveapi" the API Accept()
method will hang forever waiting for activation.  This is due to the fact
that when "acceptconnections" ran the activation channel was nil.

Signed-off-by: Darren Shepherd <darren@rancher.com>
(cherry picked from commit 8f6a14452d)

Docker-DCO-1.1-Signed-off-by: Jessie Frazelle <princess@docker.com> (github: jfrazelle)

Docker-DCO-1.1-Signed-off-by: Jessie Frazelle <hugs@docker.com> (github: jfrazelle)
2015-03-31 16:43:16 -07:00
Vincent Batts 7d7bec86c9 graphdriver: promote overlay above vfs
It's about time to let folks not hit 'vfs', when 'overlay' is supported
on their kernel. Especially now that v3.18.y is a long-term kernel.

Signed-off-by: Vincent Batts <vbatts@redhat.com>
(cherry picked from commit 2c72ff1dbf)

Docker-DCO-1.1-Signed-off-by: Jessie Frazelle <hugs@docker.com> (github: jfrazelle)

Docker-DCO-1.1-Signed-off-by: Jessie Frazelle <jess@docker.com> (github: jfrazelle)
2015-03-31 14:46:09 -07:00
Derek McGowan a39d49d676 Fix progress reader output on close
Currently the progress reader won't close properly by not setting the close size.

fixes #11849

Signed-off-by: Derek McGowan <derek@mcgstyle.net> (github: dmcgowan)
(cherry picked from commit aa3083f577)

Docker-DCO-1.1-Signed-off-by: Jessie Frazelle <jess@docker.com> (github: jfrazelle)

Docker-DCO-1.1-Signed-off-by: Jessie Frazelle <princess@docker.com> (github: jfrazelle)
2015-03-31 14:18:42 -07:00
Brian Goff 5bf15a013b Use getResourcePath instead
Also cleans up tests to not shell out for file creation.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>
(cherry picked from commit 63708dca8a)

Docker-DCO-1.1-Signed-off-by: Jessie Frazelle <princess@docker.com> (github: jfrazelle)

Docker-DCO-1.1-Signed-off-by: Jessie Frazelle <jess@docker.com> (github: jfrazelle)
2015-03-31 14:06:32 -07:00
Lei Jitang 9461967eec Fix create volume in a directory which is a symbolic link
Signed-off-by: Lei Jitang <leijitang@huawei.com>
(cherry picked from commit 7583b49125)

Docker-DCO-1.1-Signed-off-by: Jessie Frazelle <princess@docker.com> (github: jfrazelle)
2015-03-31 14:06:32 -07:00
Alexander Morozov 3be7d11cee Initialize portMapper in RequestPort too
Api requesting port for daemon before init_networkdriver called.
Problem is that now initialization of api depends on initialization of
daemon and their intializations runs in parallel. Proper fix will be
just do it sequentially. For now I don't want refactor it, because it
can bring additional problems in 1.6.0.

Signed-off-by: Alexander Morozov <lk4d4@docker.com>
(cherry picked from commit 584180fce7)

Docker-DCO-1.1-Signed-off-by: Jessie Frazelle <princess@docker.com> (github: jfrazelle)

Docker-DCO-1.1-Signed-off-by: Jessie Frazelle <jess@docker.com> (github: jfrazelle)
2015-03-31 14:00:02 -07:00
Vivek Goyal d9910b8fd8 container: Do not remove contianer if any of the resource failed cleanup
Do not remove container if any of the resource could not be cleaned up. We
don't want to leak resources.

Two new states have been created. RemovalInProgress and Dead. Once container
is Dead, it can not be started/restarted. Dead container signifies the
container where we tried to remove it but removal failed. User now needs to
figure out what went wrong, corrent the situation and try cleanup again.

RemovalInProgress signifies that container is already being removed. Only
one removal can be in progress.

Also, do not allow start of a container if it is already dead or removal is
in progress.

Also extend existing force option (-f) to docker rm to not return an error
and remove container from user view even if resource cleanup failed.
This will allow a user to get back to old behavior where resources
might leak but atleast user will be able to make progress.

Signed-off-by: Vivek Goyal <vgoyal@redhat.com>
(cherry picked from commit 40945fc186)

Docker-DCO-1.1-Signed-off-by: Jessie Frazelle <jess@docker.com> (github: jfrazelle)

Docker-DCO-1.1-Signed-off-by: Jessie Frazelle <hugs@docker.com> (github: jfrazelle)
2015-03-31 14:00:02 -07:00
Michael Crosby f115c32f6b Ensure that bridge driver does not use global mappers
This has a few hacks in it but it ensures that the bridge driver does
not use global state in the mappers, atleast as much as possible at this
point without further refactoring.  Some of the exported fields are
hacks to handle the daemon port mapping but this results in a much
cleaner approach and completely remove the global state from the mapper
and allocator.

Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
(cherry picked from commit d8c628cf08)

Docker-DCO-1.1-Signed-off-by: Jessie Frazelle <princess@docker.com> (github: jfrazelle)

Docker-DCO-1.1-Signed-off-by: Jessie Frazelle <jess@docker.com> (github: jfrazelle)
2015-03-31 10:53:09 -07:00
Michael Crosby 57939badc3 Refactor port allocator to not have ANY global state
Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
(cherry picked from commit 43a50b0618)

Docker-DCO-1.1-Signed-off-by: Jessie Frazelle <princess@docker.com> (github: jfrazelle)

Docker-DCO-1.1-Signed-off-by: Jessie Frazelle <hugs@docker.com> (github: jfrazelle)
2015-03-31 10:53:09 -07:00
Michael Crosby 51ee02d478 Refactor portmapper to remove ALL global state
Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
(cherry picked from commit 62522c9853)

Docker-DCO-1.1-Signed-off-by: Jessie Frazelle <hugs@docker.com> (github: jfrazelle)

Docker-DCO-1.1-Signed-off-by: Jessie Frazelle <princess@docker.com> (github: jfrazelle)
2015-03-31 10:53:09 -07:00
Paul Bellamy c92860748c Refactor global portallocator and portmapper state
Continuation of: #11660, working on issue #11626.

Wrapped portmapper global state into a struct. Now portallocator and
portmapper have no global state (except configuration, and a default
instance).

Unfortunately, removing the global default instances will break
```api/server/server.go:1539```, and ```daemon/daemon.go:832```, which
both call the global portallocator directly. Fixing that would be a much
bigger change, so for now, have postponed that.

Signed-off-by: Paul Bellamy <paul.a.bellamy@gmail.com>
(cherry picked from commit 87df5ab41b)

Docker-DCO-1.1-Signed-off-by: Jessie Frazelle <hugs@docker.com> (github: jfrazelle)

Docker-DCO-1.1-Signed-off-by: Jessie Frazelle <jess@docker.com> (github: jfrazelle)
2015-03-31 10:53:09 -07:00
Paul Bellamy f582f9717f Refactor global portallocator state into a global struct
Signed-off-by: Paul Bellamy <paul.a.bellamy@gmail.com>
(cherry picked from commit 1257679876)

Docker-DCO-1.1-Signed-off-by: Jessie Frazelle <hugs@docker.com> (github: jfrazelle)
2015-03-31 10:53:09 -07:00
Ahmet Alp Balkan ebcb36a8d2 windows: monitorTtySize correctly by polling
This change makes `monitorTtySize` work correctly on windows by polling
into win32 API to get terminal size (because there's no SIGWINCH on
windows) and send it to the engine over Remove API properly.

Average getttysize syscall takes around 30-40 ms on an average windows
machine as far as I can tell, therefore in a `for` loop, checking every
250ms if size has changed or not.

I'm not sure if there's a better way to do it on windows, if so,
somebody please send a link 'cause I could not find.

Signed-off-by: Ahmet Alp Balkan <ahmetalpbalkan@gmail.com>
(cherry picked from commit ebbceea8a7)

Docker-DCO-1.1-Signed-off-by: Jessie Frazelle <jess@docker.com> (github: jfrazelle)

Docker-DCO-1.1-Signed-off-by: Jessie Frazelle <princess@docker.com> (github: jfrazelle)
2015-03-31 10:39:59 -07:00
Michael Crosby e6e8f2d717 Update libcontainer to c8512754166539461fd860451ff
Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
(cherry picked from commit 17ecbcf8ff)

Docker-DCO-1.1-Signed-off-by: Jessie Frazelle <jess@docker.com> (github: jfrazelle)
2015-03-30 17:19:49 -07:00
Alexander Morozov 317a510261 Use proper wait function for --pid=host
Signed-off-by: Alexander Morozov <lk4d4@docker.com>
(cherry picked from commit 489ab77f4a)

Docker-DCO-1.1-Signed-off-by: Jessie Frazelle <hugs@docker.com> (github: jfrazelle)
2015-03-30 17:19:48 -07:00
Alexander Morozov 5d3a080178 Get child processes before main process die
Signed-off-by: Alexander Morozov <lk4d4@docker.com>

Docker-DCO-1.1-Signed-off-by: Jessie Frazelle <jess@docker.com> (github: jfrazelle)

Docker-DCO-1.1-Signed-off-by: Jessie Frazelle <princess@docker.com> (github: jfrazelle)
2015-03-30 17:19:48 -07:00
Alexander Morozov 542c84c2d2 Do not mask *exec.ExitError
Fix #11764

Signed-off-by: Alexander Morozov <lk4d4@docker.com>
(cherry picked from commit f468bbb7e8)

Docker-DCO-1.1-Signed-off-by: Jessie Frazelle <hugs@docker.com> (github: jfrazelle)

Docker-DCO-1.1-Signed-off-by: Jessie Frazelle <princess@docker.com> (github: jfrazelle)
2015-03-30 17:19:48 -07:00
Harald Albers f1df74d09d Add missing filters to bash completion for docker images and docker ps
Signed-off-by: Harald Albers <github@albersweb.de>
(cherry picked from commit cf438a542e)

Docker-DCO-1.1-Signed-off-by: Jessie Frazelle <hugs@docker.com> (github: jfrazelle)

Docker-DCO-1.1-Signed-off-by: Jessie Frazelle <jess@docker.com> (github: jfrazelle)
2015-03-30 16:42:53 -07:00
Derek McGowan 4ddbc7a62f Compress layers on push to a v2 registry
When buffering to file add support for compressing the tar contents. Since digest should be computed while writing buffer, include digest creation during buffer.

Signed-off-by: Derek McGowan <derek@mcgstyle.net> (github: dmcgowan)
(cherry picked from commit 851c64725d)

Docker-DCO-1.1-Signed-off-by: Jessie Frazelle <hugs@docker.com> (github: jfrazelle)
2015-03-30 15:42:14 -07:00
Harald Albers f72b2c02b8 Do not complete --cgroup-parent as _filedir
This is a follow-up on PR 11708, as suggested by tianon.

Signed-off-by: Harald Albers <github@albersweb.de>
(cherry picked from commit a09cc935c3)

Docker-DCO-1.1-Signed-off-by: Jessie Frazelle <jess@docker.com> (github: jfrazelle)
2015-03-30 12:02:48 -07:00
Daniel, Dao Quang Minh af9dab70f8 aufs: apply dirperm1 by default if supported
Automatically detect support for aufs `dirperm1` option and apply it.
`dirperm1` tells aufs to check the permission bits of the directory on the
topmost branch and ignore the permission bits on all lower branches.
It can be used to fix aufs' permission bug (i.e., upper layer having
broader mask than the lower layer).

More information about the bug can be found at https://github.com/docker/docker/issues/783
`dirperm1` man page is at: http://aufs.sourceforge.net/aufs3/man.html

Signed-off-by: Daniel, Dao Quang Minh <dqminh89@gmail.com>
(cherry picked from commit 281abd2c8a)

Docker-DCO-1.1-Signed-off-by: Jessie Frazelle <jess@docker.com> (github: jfrazelle)
2015-03-30 12:02:48 -07:00
Daniel, Dao Quang Minh 10425e83f2 document dirperm1 fix for #783 in known issues
Since `dirperm1` requires a more recent aufs patch than many current OS release,
we cant remove #783 completely. This documents that docker will apply `dirperm1`
automatically for systems that support it

Signed-off-by: Daniel, Dao Quang Minh <dqminh89@gmail.com>
(cherry picked from commit d7bbe2fcb5)

Docker-DCO-1.1-Signed-off-by: Jessie Frazelle <jess@docker.com> (github: jfrazelle)
2015-03-30 12:02:48 -07:00
Daniel, Dao Quang Minh 9c528dca85 print dirperm1 supported status in docker info
It's easier for users to check if their systems support dirperm1 just by using
docker info

Signed-off-by: Daniel, Dao Quang Minh <dqminh89@gmail.com>
(cherry picked from commit d68d5f2e4b)

Docker-DCO-1.1-Signed-off-by: Jessie Frazelle <princess@docker.com> (github: jfrazelle)
2015-03-30 12:02:48 -07:00
Ahmet Alp Balkan cb2c25ad2d docs: remove unused windows images
These images was just sitting around and referenced from
nowhere, nor they seemed any useful.

Signed-off-by: Ahmet Alp Balkan <ahmetalpbalkan@gmail.com>
(cherry picked from commit 986ae5d52a)

Docker-DCO-1.1-Signed-off-by: Jessie Frazelle <hugs@docker.com> (github: jfrazelle)

Docker-DCO-1.1-Signed-off-by: Jessie Frazelle <princess@docker.com> (github: jfrazelle)
2015-03-30 10:46:16 -07:00
Ahmet Alp Balkan 962dec81ec Update boot2docker on Windows documentation
Boot2Docker experience is updated now that we have a Docker
client on Windows. Instead of running `boot2docker ssh`, users
can also use boot2docker on Windows Command Prompt (`cmd.exe`)
and PowerShell.

Updated documentation and screenshots, added a few details,
reorganized sections by importance, fixed a few errors.

Remaining: the video link in the Demonstration section needs
to be updated once I shoot a new video.

Signed-off-by: Ahmet Alp Balkan <ahmetalpbalkan@gmail.com>
(cherry picked from commit de09c55394)

Docker-DCO-1.1-Signed-off-by: Jessie Frazelle <princess@docker.com> (github: jfrazelle)

Docker-DCO-1.1-Signed-off-by: Jessie Frazelle <hugs@docker.com> (github: jfrazelle)
2015-03-30 10:46:16 -07:00
unclejack 1eae925a3d pkg/broadcastwriter: avoid alloc w/ WriteString
Signed-off-by: Cristian Staretu <cristian.staretu@gmail.com>
(cherry picked from commit db877d8a42)

Docker-DCO-1.1-Signed-off-by: Jessie Frazelle <hugs@docker.com> (github: jfrazelle)

Docker-DCO-1.1-Signed-off-by: Jessie Frazelle <jess@docker.com> (github: jfrazelle)
2015-03-30 10:46:16 -07:00
Lei Jitang 3ce2cc8ee7 Add some run option to bash completion
Signed-off-by: Lei Jitang <leijitang@huawei.com>
(cherry picked from commit 7d70736015)

Docker-DCO-1.1-Signed-off-by: Jessie Frazelle <jess@docker.com> (github: jfrazelle)

Docker-DCO-1.1-Signed-off-by: Jessie Frazelle <hugs@docker.com> (github: jfrazelle)
2015-03-27 15:11:59 -07:00
Ahmet Alp Balkan 054acc4bee term/winconsole: Identify tty correctly, fix resize problem
This change fixes a bug where stdout/stderr handles are not identified
correctly.

Previously we used to set the window size to fixed size to fit the default
tty size on the host (80x24). Now the attach/exec commands can correctly
get the terminal size from windows.

We still do not `monitorTtySize()` correctly on windows and update the tty
size on the host-side, in order to fix that we'll provide a
platform-specific `monitorTtySize` implementation in the future.

Signed-off-by: Ahmet Alp Balkan <ahmetalpbalkan@gmail.com>
(cherry picked from commit 0532dcf3dc)

Docker-DCO-1.1-Signed-off-by: Jessie Frazelle <hugs@docker.com> (github: jfrazelle)
2015-03-26 16:24:40 -07:00
Don Kjer 63cb03a55b Fix for issue 9922: private registry search with auth returns 401
Signed-off-by: Don Kjer <don.kjer@gmail.com>
(cherry picked from commit 6b2eeaf896)

Docker-DCO-1.1-Signed-off-by: Jessie Frazelle <princess@docker.com> (github: jfrazelle)

Docker-DCO-1.1-Signed-off-by: Jessie Frazelle <hugs@docker.com> (github: jfrazelle)
2015-03-26 13:03:50 -07:00
Arnaud Porterie 49b6f23696 Remove unused runconfig.Config.SecurityOpt field
Signed-off-by: Arnaud Porterie <arnaud.porterie@docker.com>
(cherry picked from commit e39646d2e1)

Docker-DCO-1.1-Signed-off-by: Jessie Frazelle <jess@docker.com> (github: jfrazelle)

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

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 205 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 248 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 187 KiB

+94 -48
View File
@@ -8,12 +8,17 @@ page_keywords: Docker, Docker documentation, Windows, requirements, virtualbox,
> Your processor needs to support hardware virtualization.
The Docker Engine uses Linux-specific kernel features, so to run it on Windows
we need to use a lightweight virtual machine (vm). You use the Windows Docker client to
control the virtualized Docker Engine to build, run, and manage Docker containers.
we need to use a lightweight virtual machine (VM). You use the **Windows Docker
Client** to control the virtualized Docker Engine to build, run, and manage
Docker containers.
To make this process easier, we've designed a helper application called
[Boot2Docker](https://github.com/boot2docker/boot2docker) that installs the
virtual machine and runs the Docker daemon.
[Boot2Docker](https://github.com/boot2docker/boot2docker) creates a Linux virtual
machine on Windows to run Docker on a Linux operating system.
Although you will be using Windows Docker client, the docker engine hosting the
containers will still be running on Linux. Until the Docker engine for Windows
is developed, you can launch only Linux containers from your Windows machine.
## Demonstration
@@ -21,18 +26,77 @@ virtual machine and runs the Docker daemon.
## Installation
1. Download the latest release of the [Docker for Windows Installer](https://github.com/boot2docker/windows-installer/releases/latest)
2. Run the installer, which will install VirtualBox, MSYS-git, the boot2docker Linux ISO,
and the Boot2Docker management tool.
1. Download the latest release of the
[Docker for Windows Installer](https://github.com/boot2docker/windows-installer/releases/latest).
2. Run the installer, which will install Docker Client or Windows, VirtualBox,
Git for Windows (MSYS-git), the boot2docker Linux ISO, and the Boot2Docker
management tool.
![](/installation/images/windows-installer.png)
3. Run the `Boot2Docker Start` shell script from your Desktop or Program Files > Boot2Docker for Windows.
3. Run the **Boot2Docker Start** shortcut from your Desktop or Program Files
Boot2Docker for Windows”.
The Start script will ask you to enter an ssh key passphrase - the simplest
(but least secure) is to just hit [Enter].
![](/installation/images/windows-boot2docker-start.png)
4. The **Boot2Docker Start** will start a unix shell already configured to manage
Docker running inside the virtual machine. Run `docker version` to see
if it is working correctly:
The `Boot2Docker Start` script will connect you to a shell session in the virtual
machine. If needed, it will initialize a new VM and start it.
![](/installation/images/windows-boot2docker-start.png)
## 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`:
![](/installation/images/windows-boot2docker-cmd.png)
## 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`:
![](/installation/images/windows-boot2docker-powershell.png)
> NOTE: You can alternatively run `boot2docker shellinit | Invoke-Expression`
> command to set the environment variables instead of copying and pasting on
> PowerShell.
# Further Details
The Boot2Docker management tool provides several commands:
$ boot2docker
Usage: boot2docker.exe [<options>] {help|init|up|ssh|save|down|poweroff|reset|restart|config|status|info|ip|shellinit|delete|download|upgrade|version} [<args>]
## Upgrading
@@ -47,46 +111,13 @@ and the Boot2Docker management tool.
boot2docker download
boot2docker start
## Running Docker
{{ include "no-remote-sudo.md" }}
Boot2Docker will log you in automatically so you can start using Docker right away.
Let's try the `hello-world` example image. Run
$ docker run hello-world
This should download the very small `hello-world` image and print a `Hello from Docker.` message.
## Login with PUTTY instead of using the CMD
Boot2Docker generates and uses the public/private key pair in your `%HOMEPATH%\.ssh`
directory so to log in you need to use the private key from this same directory.
The private key needs to be converted into the format PuTTY uses.
You can do this with
[puttygen](http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html):
- Open `puttygen.exe` and load ("File"->"Load" menu) the private key from
`%HOMEPATH%\.ssh\id_boot2docker`
- then click: "Save Private Key".
- Then use the saved file to login with PuTTY using `docker@127.0.0.1:2022`.
# Further Details
The Boot2Docker management tool provides several commands:
$ ./boot2docker
Usage: ./boot2docker [<options>] {help|init|up|ssh|save|down|poweroff|reset|restart|config|status|info|ip|delete|download|version} [<args>]
## Container port redirection
If you are curious, the username for the boot2docker default user is `docker` and the password is `tcuser`.
If you are curious, the username for the boot2docker default user is `docker`
and the password is `tcuser`.
The latest version of `boot2docker` sets up a host only network adaptor which provides access to the container's ports.
The latest version of `boot2docker` sets up a host only network adaptor which
provides access to the container's ports.
If you run a container with an exposed port:
@@ -101,3 +132,18 @@ Typically, it is 192.168.59.103, but it could get changed by Virtualbox's DHCP
implementation.
For further information or to report issues, please see the [Boot2Docker site](http://boot2docker.io)
## Login with PUTTY instead of using the CMD
Boot2Docker generates and uses the public/private key pair in your `%USERPROFILE%\.ssh`
directory so to log in you need to use the private key from this same directory.
The private key needs to be converted into the format PuTTY uses.
You can do this with
[puttygen](http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html):
- Open `puttygen.exe` and load ("File"->"Load" menu) the private key from
`%USERPROFILE%\.ssh\id_boot2docker`
- then click: "Save Private Key".
- Then use the saved file to login with PuTTY using `docker@127.0.0.1:2022`.
+8 -2
View File
@@ -280,8 +280,14 @@ The cache for `RUN` instructions can be invalidated by `ADD` instructions. See
- [Issue 783](https://github.com/docker/docker/issues/783) is about file
permissions problems that can occur when using the AUFS file system. You
might notice it during an attempt to `rm` a file, for example. The issue
describes a workaround.
might notice it during an attempt to `rm` a file, for example.
For systems that have recent aufs version (i.e., `dirperm1` mount option can
be set), docker will attempt to fix the issue automatically by mounting
the layers with `dirperm1` option. More details on `dirperm1` option can be
found at [`aufs` man page](http://aufs.sourceforge.net/aufs3/man.html)
If your system doesnt have support for `dirperm1`, the issue describes a workaround.
## CMD
+8 -1
View File
@@ -57,7 +57,14 @@ impact on users. This list will be updated as issues are resolved.
* **Unexpected File Permissions in Containers**
An idiosyncrasy in AUFS prevents permissions from propagating predictably
between upper and lower layers. This can cause issues with accessing private
keys, database instances, etc. For complete information and workarounds see
keys, database instances, etc.
For systems that have recent aufs version (i.e., `dirperm1` mount option can
be set), docker will attempt to fix the issue automatically by mounting
the layers with `dirperm1` option. More details on `dirperm1` option can be
found at [`aufs` man page](http://aufs.sourceforge.net/aufs3/man.html)
For complete information and workarounds see
[Github Issue 783](https://github.com/docker/docker/issues/783).
* **Docker Hub incompatible with Safari 8**
+18 -6
View File
@@ -1,6 +1,8 @@
package graph
import (
"compress/gzip"
"crypto/sha256"
"fmt"
"io"
"io/ioutil"
@@ -13,6 +15,7 @@ import (
"time"
log "github.com/Sirupsen/logrus"
"github.com/docker/distribution/digest"
"github.com/docker/docker/autogen/dockerversion"
"github.com/docker/docker/daemon/graphdriver"
"github.com/docker/docker/image"
@@ -241,18 +244,27 @@ func (graph *Graph) newTempFile() (*os.File, error) {
return ioutil.TempFile(tmp, "")
}
func bufferToFile(f *os.File, src io.Reader) (int64, error) {
n, err := io.Copy(f, src)
func bufferToFile(f *os.File, src io.Reader) (int64, digest.Digest, error) {
var (
h = sha256.New()
w = gzip.NewWriter(io.MultiWriter(f, h))
)
_, err := io.Copy(w, src)
w.Close()
if err != nil {
return n, err
return 0, "", err
}
if err = f.Sync(); err != nil {
return n, err
return 0, "", err
}
n, err := f.Seek(0, os.SEEK_CUR)
if err != nil {
return 0, "", err
}
if _, err := f.Seek(0, 0); err != nil {
return n, err
return 0, "", err
}
return n, nil
return n, digest.NewDigest("sha256", h), nil
}
// setupInitLayer populates a directory with mountpoints suitable
+1 -8
View File
@@ -1,7 +1,6 @@
package graph
import (
"crypto/sha256"
"encoding/json"
"errors"
"fmt"
@@ -13,7 +12,6 @@ import (
"sync"
log "github.com/Sirupsen/logrus"
"github.com/docker/distribution/digest"
"github.com/docker/docker/engine"
"github.com/docker/docker/image"
"github.com/docker/docker/pkg/common"
@@ -464,12 +462,7 @@ func (s *TagStore) pushV2Image(r *registry.Session, img *image.Image, endpoint *
os.Remove(tf.Name())
}()
h := sha256.New()
size, err := bufferToFile(tf, io.TeeReader(arch, h))
if err != nil {
return "", err
}
dgst := digest.NewDigest("sha256", h)
size, dgst, err := bufferToFile(tf, arch)
// Send the layer
log.Debugf("rendered layer for %s of [%d] size", img.ID, size)
+1 -1
View File
@@ -75,7 +75,7 @@ rm -rf src/github.com/docker/distribution
mkdir -p src/github.com/docker/distribution
mv tmp-digest src/github.com/docker/distribution/digest
clone git github.com/docker/libcontainer a6044b701c166fe538fc760f9e2dcea3d737cd2a
clone git github.com/docker/libcontainer c8512754166539461fd860451ff1a0af7491c197
# 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')"
+33
View File
@@ -476,6 +476,39 @@ func TestRunWithVolumesFromExited(t *testing.T) {
logDone("run - regression test for #4979 - volumes-from on exited container")
}
// Volume path is a symlink which also exists on the host, and the host side is a file not a dir
// But the volume call is just a normal volume, not a bind mount
func TestRunCreateVolumesInSymlinkDir(t *testing.T) {
testRequires(t, SameHostDaemon)
testRequires(t, NativeExecDriver)
defer deleteAllContainers()
name := "test-volume-symlink"
dir, err := ioutil.TempDir("", name)
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
f, err := os.OpenFile(filepath.Join(dir, "test"), os.O_CREATE, 0700)
if err != nil {
t.Fatal(err)
}
f.Close()
dockerFile := fmt.Sprintf("FROM busybox\nRUN mkdir -p %s\nRUN ln -s %s /test", dir, dir)
if _, err := buildImage(name, dockerFile, false); err != nil {
t.Fatal(err)
}
defer deleteImages(name)
if out, _, err := dockerCmd(t, "run", "-v", "/test/test", name); err != nil {
t.Fatal(err, out)
}
logDone("run - create volume in symlink directory")
}
// Regression test for #4830
func TestRunWithRelativePath(t *testing.T) {
defer deleteAllContainers()
+1 -1
View File
@@ -51,7 +51,7 @@ func (w *BroadcastWriter) Write(p []byte) (n int, err error) {
for {
line, err := w.buf.ReadString('\n')
if err != nil {
w.buf.Write([]byte(line))
w.buf.WriteString(line)
break
}
for stream, writers := range w.streams {
+1
View File
@@ -64,6 +64,7 @@ func (config *Config) Read(p []byte) (n int, err error) {
return read, err
}
func (config *Config) Close() error {
config.Current = config.Size
config.Out.Write(config.Formatter.FormatProg(config.ID, config.Action, &JSONProg{Current: config.Current, Total: config.Size}))
return config.In.Close()
}
+6 -2
View File
@@ -241,8 +241,6 @@ func StdStreams() (stdIn io.ReadCloser, stdOut io.Writer, stdErr io.Writer) {
}
handler.screenBufferInfo = screenBufferInfo
// Set the window size
SetWindowSize(stdoutHandle, DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_HEIGHT)
buffer = make([]CHAR_INFO, screenBufferInfo.MaximumWindowSize.X*screenBufferInfo.MaximumWindowSize.Y)
stdOut = &terminalWriter{
@@ -283,6 +281,12 @@ func GetHandleInfo(in interface{}) (uintptr, bool) {
isTerminalIn = IsTerminal(inFd)
}
}
if tr, ok := in.(*terminalWriter); ok {
if file, ok := tr.wrappedWriter.(*os.File); ok {
inFd = file.Fd()
isTerminalIn = IsTerminal(inFd)
}
}
return inFd, isTerminalIn
}
+7 -44
View File
@@ -1,7 +1,6 @@
package registry
import (
"crypto/tls"
"encoding/base64"
"encoding/json"
"errors"
@@ -71,21 +70,7 @@ func (auth *RequestAuthorization) getToken() (string, error) {
return auth.tokenCache, nil
}
tlsConfig := tls.Config{
MinVersion: tls.VersionTLS10,
}
if !auth.registryEndpoint.IsSecure {
tlsConfig.InsecureSkipVerify = true
}
client := &http.Client{
Transport: &http.Transport{
DisableKeepAlives: true,
Proxy: http.ProxyFromEnvironment,
TLSClientConfig: &tlsConfig,
},
CheckRedirect: AddRequiredHeadersToRedirectedRequests,
}
client := auth.registryEndpoint.HTTPClient()
factory := HTTPRequestFactory(nil)
for _, challenge := range auth.registryEndpoint.AuthChallenges {
@@ -252,16 +237,10 @@ func Login(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *utils.HT
// loginV1 tries to register/login to the v1 registry server.
func loginV1(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *utils.HTTPRequestFactory) (string, error) {
var (
status string
reqBody []byte
err error
client = &http.Client{
Transport: &http.Transport{
DisableKeepAlives: true,
Proxy: http.ProxyFromEnvironment,
},
CheckRedirect: AddRequiredHeadersToRedirectedRequests,
}
status string
reqBody []byte
err error
client = registryEndpoint.HTTPClient()
reqStatusCode = 0
serverAddress = authConfig.ServerAddress
)
@@ -285,7 +264,7 @@ func loginV1(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *utils.
// using `bytes.NewReader(jsonBody)` here causes the server to respond with a 411 status.
b := strings.NewReader(string(jsonBody))
req1, err := http.Post(serverAddress+"users/", "application/json; charset=utf-8", b)
req1, err := client.Post(serverAddress+"users/", "application/json; charset=utf-8", b)
if err != nil {
return "", fmt.Errorf("Server Error: %s", err)
}
@@ -371,26 +350,10 @@ func loginV1(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *utils.
// is to be determined.
func loginV2(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *utils.HTTPRequestFactory) (string, error) {
log.Debugf("attempting v2 login to registry endpoint %s", registryEndpoint)
tlsConfig := tls.Config{
MinVersion: tls.VersionTLS10,
}
if !registryEndpoint.IsSecure {
tlsConfig.InsecureSkipVerify = true
}
client := &http.Client{
Transport: &http.Transport{
DisableKeepAlives: true,
Proxy: http.ProxyFromEnvironment,
TLSClientConfig: &tlsConfig,
},
CheckRedirect: AddRequiredHeadersToRedirectedRequests,
}
var (
err error
allErrors []error
client = registryEndpoint.HTTPClient()
)
for _, challenge := range registryEndpoint.AuthChallenges {
+18
View File
@@ -1,6 +1,7 @@
package registry
import (
"crypto/tls"
"encoding/json"
"fmt"
"io/ioutil"
@@ -262,3 +263,20 @@ HeaderLoop:
return RegistryInfo{}, fmt.Errorf("v2 registry endpoint returned status %d: %q", resp.StatusCode, http.StatusText(resp.StatusCode))
}
func (e *Endpoint) HTTPClient() *http.Client {
tlsConfig := tls.Config{
MinVersion: tls.VersionTLS10,
}
if !e.IsSecure {
tlsConfig.InsecureSkipVerify = true
}
return &http.Client{
Transport: &http.Transport{
DisableKeepAlives: true,
Proxy: http.ProxyFromEnvironment,
TLSClientConfig: &tlsConfig,
},
CheckRedirect: AddRequiredHeadersToRedirectedRequests,
}
}
+4
View File
@@ -511,6 +511,10 @@ func (r *Session) PushImageJSONIndex(remote string, imgList []*ImgData, validate
}
defer res.Body.Close()
if res.StatusCode == 401 {
return nil, errLoginRequired
}
var tokens, endpoints []string
if !validate {
if res.StatusCode != 200 && res.StatusCode != 201 {
-1
View File
@@ -33,7 +33,6 @@ type Config struct {
NetworkDisabled bool
MacAddress string
OnBuild []string
SecurityOpt []string
Labels map[string]string
}
@@ -220,16 +220,16 @@ func getCgroupData(c *configs.Cgroup, pid int) (*data, error) {
}, nil
}
func (raw *data) parent(subsystem string) (string, error) {
func (raw *data) parent(subsystem, mountpoint string) (string, error) {
initPath, err := cgroups.GetInitCgroupDir(subsystem)
if err != nil {
return "", err
}
return filepath.Join(raw.root, subsystem, initPath), nil
return filepath.Join(mountpoint, initPath), nil
}
func (raw *data) path(subsystem string) (string, error) {
_, err := cgroups.FindCgroupMountpoint(subsystem)
mnt, err := cgroups.FindCgroupMountpoint(subsystem)
// If we didn't mount the subsystem, there is no point we make the path.
if err != nil {
return "", err
@@ -240,7 +240,7 @@ func (raw *data) path(subsystem string) (string, error) {
return filepath.Join(raw.root, subsystem, raw.cgroup), nil
}
parent, err := raw.parent(subsystem)
parent, err := raw.parent(subsystem, mnt)
if err != nil {
return "", err
}
@@ -43,6 +43,10 @@ var subsystems = map[string]subsystem{
"freezer": &fs.FreezerGroup{},
}
const (
testScopeWait = 4
)
var (
connLock sync.Mutex
theConn *systemd.Conn
@@ -86,16 +90,41 @@ func UseSystemd() bool {
}
}
// Ensure the scope name we use doesn't exist. Use the Pid to
// avoid collisions between multiple libcontainer users on a
// single host.
scope := fmt.Sprintf("libcontainer-%d-systemd-test-default-dependencies.scope", os.Getpid())
testScopeExists := true
for i := 0; i <= testScopeWait; i++ {
if _, err := theConn.StopUnit(scope, "replace"); err != nil {
if dbusError, ok := err.(dbus.Error); ok {
if strings.Contains(dbusError.Name, "org.freedesktop.systemd1.NoSuchUnit") {
testScopeExists = false
break
}
}
}
time.Sleep(time.Millisecond)
}
// Bail out if we can't kill this scope without testing for DefaultDependencies
if testScopeExists {
return hasStartTransientUnit
}
// Assume StartTransientUnit on a scope allows DefaultDependencies
hasTransientDefaultDependencies = true
ddf := newProp("DefaultDependencies", false)
if _, err := theConn.StartTransientUnit("docker-systemd-test-default-dependencies.scope", "replace", ddf); err != nil {
if _, err := theConn.StartTransientUnit(scope, "replace", ddf); err != nil {
if dbusError, ok := err.(dbus.Error); ok {
if strings.Contains(dbusError.Name, "org.freedesktop.DBus.Error.PropertyReadOnly") {
hasTransientDefaultDependencies = false
}
}
}
// Not critical because of the stop unit logic above.
theConn.StopUnit(scope, "replace")
}
return hasStartTransientUnit
}
@@ -193,12 +193,13 @@ func (c *linuxContainer) newSetnsProcess(p *Process, cmd *exec.Cmd, parentPipe,
func (c *linuxContainer) newInitConfig(process *Process) *initConfig {
return &initConfig{
Config: c.config,
Args: process.Args,
Env: process.Env,
User: process.User,
Cwd: process.Cwd,
Console: process.consolePath,
Config: c.config,
Args: process.Args,
Env: process.Env,
User: process.User,
Cwd: process.Cwd,
Console: process.consolePath,
Capabilities: process.Capabilities,
}
}
+14 -8
View File
@@ -40,13 +40,14 @@ type network struct {
// initConfig is used for transferring parameters from Exec() to Init()
type initConfig struct {
Args []string `json:"args"`
Env []string `json:"env"`
Cwd string `json:"cwd"`
User string `json:"user"`
Config *configs.Config `json:"config"`
Console string `json:"console"`
Networks []*network `json:"network"`
Args []string `json:"args"`
Env []string `json:"env"`
Cwd string `json:"cwd"`
Capabilities []string `json:"capabilities"`
User string `json:"user"`
Config *configs.Config `json:"config"`
Console string `json:"console"`
Networks []*network `json:"network"`
}
type initer interface {
@@ -99,7 +100,12 @@ func finalizeNamespace(config *initConfig) error {
if err := utils.CloseExecFrom(3); err != nil {
return err
}
w, err := newCapWhitelist(config.Config.Capabilities)
capabilities := config.Config.Capabilities
if config.Capabilities != nil {
capabilities = config.Capabilities
}
w, err := newCapWhitelist(capabilities)
if err != nil {
return err
}
@@ -4,6 +4,7 @@ import (
"bytes"
"io/ioutil"
"os"
"strconv"
"strings"
"testing"
@@ -395,6 +396,90 @@ func TestProcessEnv(t *testing.T) {
}
}
func TestProcessCaps(t *testing.T) {
if testing.Short() {
return
}
root, err := newTestRoot()
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(root)
rootfs, err := newRootfs()
if err != nil {
t.Fatal(err)
}
defer remove(rootfs)
config := newTemplateConfig(rootfs)
factory, err := libcontainer.New(root, libcontainer.Cgroupfs)
if err != nil {
t.Fatal(err)
}
container, err := factory.Create("test", config)
if err != nil {
t.Fatal(err)
}
defer container.Destroy()
processCaps := append(config.Capabilities, "NET_ADMIN")
var stdout bytes.Buffer
pconfig := libcontainer.Process{
Args: []string{"sh", "-c", "cat /proc/self/status"},
Env: standardEnvironment,
Capabilities: processCaps,
Stdin: nil,
Stdout: &stdout,
}
err = container.Start(&pconfig)
if err != nil {
t.Fatal(err)
}
// Wait for process
waitProcess(&pconfig, t)
outputStatus := string(stdout.Bytes())
if err != nil {
t.Fatal(err)
}
lines := strings.Split(outputStatus, "\n")
effectiveCapsLine := ""
for _, l := range lines {
line := strings.TrimSpace(l)
if strings.Contains(line, "CapEff:") {
effectiveCapsLine = line
break
}
}
if effectiveCapsLine == "" {
t.Fatal("Couldn't find effective caps: ", outputStatus)
}
parts := strings.Split(effectiveCapsLine, ":")
effectiveCapsStr := strings.TrimSpace(parts[1])
effectiveCaps, err := strconv.ParseUint(effectiveCapsStr, 16, 64)
if err != nil {
t.Fatal("Could not parse effective caps", err)
}
var netAdminMask uint64
var netAdminBit uint
netAdminBit = 12 // from capability.h
netAdminMask = 1 << netAdminBit
if effectiveCaps&netAdminMask != netAdminMask {
t.Fatal("CAP_NET_ADMIN is not set as expected")
}
}
func TestFreeze(t *testing.T) {
if testing.Short() {
return
@@ -0,0 +1,57 @@
## nsinit
`nsinit` is a cli application which demonstrates the use of libcontainer.
It is able to spawn new containers or join existing containers.
### How to build?
First to add the `libcontainer/vendor` into your GOPATH. It's because something related with this [issue](https://github.com/docker/libcontainer/issues/210).
```
export GOPATH=$GOPATH:/your/path/to/libcontainer/vendor
```
Then get into the nsinit folder and get the imported file. Use `make` command to make the nsinit binary.
```
cd libcontainer/nsinit
go get
make
```
We have finished compiling the nsinit package, but a root filesystem must be provided for use along with a container configuration file.
Choose a proper place to run your container. For example we use `/busybox`.
```
mkdir /busybox
curl -sSL 'https://github.com/jpetazzo/docker-busybox/raw/buildroot-2014.11/rootfs.tar' | tar -xC /busybox
```
Then you may need to write a configure file named `container.json` in the `/busybox` folder.
Environment, networking, and different capabilities for the container are specified in this file.
The configuration is used for each process executed inside the container
See the `sample_configs` folder for examples of what the container configuration should look like.
```
cp libcontainer/sample_configs/minimal.json /busybox/container.json
cd /busybox
```
Now the nsinit is ready to work.
To execute `/bin/bash` in the current directory as a container just run the following **as root**:
```bash
nsinit exec --tty /bin/bash
```
If you wish to spawn another process inside the container while your
current bash session is running, run the same command again to
get another bash shell (or change the command). If the original
process (PID 1) dies, all other processes spawned inside the container
will be killed and the namespace will be removed.
You can identify if a process is running in a container by
looking to see if `state.json` is in the root of the directory.
You may also specify an alternate root place where
the `container.json` file is read and where the `state.json` file will be saved.
+4
View File
@@ -41,6 +41,10 @@ type Process struct {
// consolePath is the path to the console allocated to the container.
consolePath string
// Capabilities specify the capabilities to keep when executing the process inside the container
// All capbilities not specified will be dropped from the processes capability mask
Capabilities []string
ops processOperations
}
+13 -4
View File
@@ -4,6 +4,7 @@ package libcontainer
import (
"encoding/json"
"errors"
"io"
"os"
"os/exec"
@@ -44,8 +45,12 @@ func (p *setnsProcess) startTime() (string, error) {
return system.GetProcessStartTime(p.pid())
}
func (p *setnsProcess) signal(s os.Signal) error {
return p.cmd.Process.Signal(s)
func (p *setnsProcess) signal(sig os.Signal) error {
s, ok := sig.(syscall.Signal)
if !ok {
return errors.New("os: unsupported signal type")
}
return syscall.Kill(p.cmd.Process.Pid, s)
}
func (p *setnsProcess) start() (err error) {
@@ -235,6 +240,10 @@ func (p *initProcess) createNetworkInterfaces() error {
return nil
}
func (p *initProcess) signal(s os.Signal) error {
return p.cmd.Process.Signal(s)
func (p *initProcess) signal(sig os.Signal) error {
s, ok := sig.(syscall.Signal)
if !ok {
return errors.New("os: unsupported signal type")
}
return syscall.Kill(p.cmd.Process.Pid, s)
}