Compare commits

..

42 Commits

Author SHA1 Message Date
Solomon Hykes a4fc52305a Bumping version to 0.1.3 2013-04-04 23:05:03 -07:00
Solomon Hykes 2aad4a3478 Choose which TCP frontend port to allocate with '-p :PORT' 2013-04-04 22:58:01 -07:00
Solomon Hykes a5fb1d6c01 Refactored PortAllocator to allow for same-frontend constraint 2013-04-04 22:56:12 -07:00
Solomon Hykes f344212b93 Renamed PortAllocator.populate() to run() 2013-04-04 19:49:32 -07:00
Solomon Hykes 0424998f38 Print a less confusing error message when lxcbr0 doesn't exist 2013-04-04 19:14:10 -07:00
Solomon Hykes 8bfbdd7afa Add versioning to docker image format. IMPORTANT: the format versioning is pegged to docker's versioning, so changes to the format MUST trigger an increment in version number. 2013-04-04 18:38:43 -07:00
Solomon Hykes 3de51b7bfe Merge branch 'master' of ssh://github.com/dotcloud/docker 2013-04-04 17:00:59 -07:00
Solomon Hykes a58cd8c616 Merge pull request #322 from sa2ajj/port-map-nitpick
change option description to reflect the semantics
2013-04-04 16:39:54 -07:00
Solomon Hykes 586a79cca0 Merge remote-tracking branch 'dominikh/minor-code-touchups' 2013-04-04 16:20:37 -07:00
Solomon Hykes 349edf1bea Merge pull request #331 from lynaghk/master
Fix broken link on documentation website between examples.
2013-04-04 16:18:27 -07:00
Solomon Hykes 677908910c Merge pull request #320 from sa2ajj/cli-docs
move each command description into a separate document
2013-04-04 16:17:53 -07:00
Solomon Hykes 6b5fe8c2ec Merge remote-tracking branch 'origin/257-container_real_running_state-fix' 2013-04-04 15:34:18 -07:00
Solomon Hykes 26088a72b3 Merge remote-tracking branch 'dominikh/improve-attachopts' 2013-04-04 15:18:41 -07:00
Solomon Hykes ebc837957f Continue cleaning up iptables rules from previous version, to avoid crashing after an upgrade 2013-04-04 15:16:42 -07:00
Solomon Hykes c4d3da5871 Merge remote-tracking branch 'unclejack/137-fix-nat' 2013-04-04 15:06:20 -07:00
unclejack 32f5811476 stop looping remote:port from host to containers 2013-04-04 23:07:10 +03:00
Solomon Hykes a7f191d51d Merge remote-tracking branch 'origin/328-i_o_error_uncloced_connection-fix' 2013-04-04 11:03:39 -07:00
Kevin J. Lynagh 92186d7cf7 Fix broken link in doc site.
Broken link was from python_web_app to nonexistent "base commands page"; updated to point to next item in examples menu, running_ssh_service screencast.
2013-04-03 22:23:17 -07:00
Solomon Hykes 5d3c0767da Simplified Graph.Delete() - no more garbage collecting, just atomic deregister then os.RemoveAll 2013-04-03 22:14:28 -07:00
unclejack 3b65be9127 Fix NAT problem with ports looping back to containers 2013-04-04 01:32:46 +03:00
shin- ad0183e419 Check WaitTimeout return in test, replaced lock initialization in runtime.Register() with call to initLock() 2013-04-03 10:48:02 -07:00
Dominik Honnef 4f36039e7b clean up AttachOpts type
Primarily, there is no reason to have a pointer to a map. Furthermore,
make() can be used on AttachOpts directly.
2013-04-03 16:06:35 +02:00
Mikhail Sobolev b74d1c9247 change option description to reflect the semantics
At least, for me, 'map' means that there are two values and one is "mapped" to
another.

In this case, just one value is provided (container's port), the other value is
automatically obtained (host's port) and the actual mapping can be seen using
``docker port`` command.
2013-04-03 16:37:56 +03:00
Mikhail Sobolev cf8b8c1969 move each command description into a separate document 2013-04-03 15:55:18 +03:00
shin- d1767bbf67 Moved resetLock() to the Load() method ; changed resetLock() to initLock() and changed behavior to not modify the lock if it was already set (not nil) 2013-04-03 05:39:39 -07:00
shin- 7b74b9cab5 Integrated @creack's feedback on TestRestore 2013-04-03 05:37:45 -07:00
Dominik Honnef 14d3880daf remove superfluous panic 2013-04-03 11:19:48 +02:00
Dominik Honnef 22f1cc955d replace unreachable returns with panics
Not only is this a more common idiom, it'll make finding bugs easier,
and it'll make porting to Go 1.1 easier.

Go 1.1 will not require the final return or panic because it has a
notion of terminating statements.
2013-04-03 11:18:23 +02:00
Dominik Honnef cab31fd512 use wg.Done() isntead of wg.Add(-1) 2013-04-03 11:11:34 +02:00
Dominik Honnef 1fc55c2bb9 kill the right containers in runtime_test 2013-04-03 11:11:06 +02:00
Dominik Honnef 5ecd940a59 remove dead code in CmdPush 2013-04-03 11:08:32 +02:00
Dominik Honnef 3b8c2417fb use fmt.Fprintf instead of fmt.Fprint
fmt.Fprint does not allow format strings
2013-04-03 11:04:33 +02:00
Guillaume J. Charmes a19a9e3ca8 Discarding errors in CmdRun 2013-04-02 12:21:35 -07:00
Guillaume J. Charmes ad2bbe23be Close the broadcaster once they are not needed anymore 2013-04-02 12:19:01 -07:00
Guillaume J. Charmes 6882c78ce4 Add a stdincloser to container.Attach in order to close the client connection when needed 2013-04-02 12:18:20 -07:00
shin- 791ca6fde4 Better crash simulation in TestRestore ; force state lock creation when loading a container from disk 2013-04-02 10:06:49 -07:00
Guillaume J. Charmes 43484e8b50 Add a TestRunExit, make sure cmdRun returns after process dies 2013-04-02 09:22:30 -07:00
shin- 02c211a0dc variable names 2013-04-02 07:13:42 -07:00
shin- c780ff5ae7 More thorough test case, use container.Stop() instead of lxc-kill,
use setStopped() during the restore step
2013-04-02 07:01:43 -07:00
shin- 8edf0ca7f3 Merge branch 'master' into 257-container_real_running_state-fix 2013-04-02 06:37:50 -07:00
Guillaume J. Charmes 8c36e6920a Working in progress: add unit tests for the running state check 2013-03-31 20:14:54 -07:00
Guillaume J. Charmes 3dcaf20d6b Check if the containers are really running when starting docker 2013-03-31 17:40:39 -07:00
41 changed files with 594 additions and 447 deletions
+1
View File
@@ -21,6 +21,7 @@ Jonathan Rudenberg <jonathan@titanous.com>
Julien Barbier <write0@gmail.com>
Jérôme Petazzoni <jerome.petazzoni@dotcloud.com>
Ken Cochrane <kencochrane@gmail.com>
Kevin J. Lynagh <kevin@keminglabs.com>
Louis Opter <kalessin@kalessin.fr>
Mikhail Sobolev <mss@mawhrin.net>
Nelson Chen <crazysim@gmail.com>
+28 -19
View File
@@ -18,7 +18,7 @@ import (
"unicode"
)
const VERSION = "0.1.2"
const VERSION = "0.1.3"
var GIT_COMMIT string
@@ -99,7 +99,7 @@ func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout io.Writer, args ...strin
}
if err != nil {
if err != io.EOF {
fmt.Fprint(stdout, "Read error: %v\n", err)
fmt.Fprintf(stdout, "Read error: %v\n", err)
}
break
}
@@ -512,10 +512,9 @@ func (srv *Server) CmdPush(stdin io.ReadCloser, stdout io.Writer, args ...string
return err
}
return nil
} else {
return err
}
return nil
return err
}
err = srv.runtime.graph.PushImage(stdout, img, srv.runtime.authConfig)
if err != nil {
@@ -799,7 +798,8 @@ func (srv *Server) CmdAttach(stdin io.ReadCloser, stdout io.Writer, args ...stri
if container == nil {
return fmt.Errorf("No such container: %s", name)
}
return <-container.Attach(stdin, stdout, stdout)
return <-container.Attach(stdin, nil, stdout, stdout)
}
// Ports type - Used to parse multiple -p flags
@@ -833,25 +833,25 @@ func (opts *ListOpts) Set(value string) error {
// AttachOpts stores arguments to 'docker run -a', eg. which streams to attach to
type AttachOpts map[string]bool
func NewAttachOpts() *AttachOpts {
opts := make(map[string]bool)
return (*AttachOpts)(&opts)
func NewAttachOpts() AttachOpts {
return make(AttachOpts)
}
func (opts *AttachOpts) String() string {
return fmt.Sprint(*opts)
func (opts AttachOpts) String() string {
// Cast to underlying map type to avoid infinite recursion
return fmt.Sprintf("%v", map[string]bool(opts))
}
func (opts *AttachOpts) Set(val string) error {
func (opts AttachOpts) Set(val string) error {
if val != "stdin" && val != "stdout" && val != "stderr" {
return fmt.Errorf("Unsupported stream name: %s", val)
}
(*opts)[val] = true
opts[val] = true
return nil
}
func (opts *AttachOpts) Get(val string) bool {
if res, exists := (*opts)[val]; exists {
func (opts AttachOpts) Get(val string) bool {
if res, exists := opts[val]; exists {
return res
}
return false
@@ -901,11 +901,17 @@ func (srv *Server) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...string)
}
}
var (
cStdin io.Reader
cStdin io.ReadCloser
cStdout, cStderr io.Writer
)
if config.AttachStdin {
cStdin = stdin
r, w := io.Pipe()
go func() {
defer w.Close()
defer Debugf("Closing buffered stdin pipe")
io.Copy(w, stdin)
}()
cStdin = r
}
if config.AttachStdout {
cStdout = stdout
@@ -913,7 +919,8 @@ func (srv *Server) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...string)
if config.AttachStderr {
cStderr = stdout // FIXME: rcli can't differentiate stdout from stderr
}
attachErr := container.Attach(cStdin, cStdout, cStderr)
attachErr := container.Attach(cStdin, stdin, cStdout, cStderr)
Debugf("Starting\n")
if err := container.Start(); err != nil {
return err
@@ -922,7 +929,9 @@ func (srv *Server) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...string)
fmt.Fprintln(stdout, container.ShortId())
}
Debugf("Waiting for attach to return\n")
return <-attachErr
<-attachErr
// Expecting I/O pipe error, discarding
return nil
}
func NewServer() (*Server, error) {
+52
View File
@@ -80,6 +80,57 @@ func TestRunHostname(t *testing.T) {
}
}
func TestRunExit(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
defer nuke(runtime)
srv := &Server{runtime: runtime}
stdin, stdinPipe := io.Pipe()
stdout, stdoutPipe := io.Pipe()
c1 := make(chan struct{})
go func() {
srv.CmdRun(stdin, stdoutPipe, "-i", GetTestImage(runtime).Id, "/bin/cat")
close(c1)
}()
setTimeout(t, "Read/Write assertion timed out", 2*time.Second, func() {
if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
t.Fatal(err)
}
})
container := runtime.List()[0]
// Closing /bin/cat stdin, expect it to exit
p, err := container.StdinPipe()
if err != nil {
t.Fatal(err)
}
if err := p.Close(); err != nil {
t.Fatal(err)
}
// as the process exited, CmdRun must finish and unblock. Wait for it
setTimeout(t, "Waiting for CmdRun timed out", 2*time.Second, func() {
<-c1
})
// Make sure that the client has been disconnected
setTimeout(t, "The client should have been disconnected once the remote process exited.", 2*time.Second, func() {
// Expecting pipe i/o error, just check that read does not block
stdin.Read([]byte{})
})
// Cleanup pipes
if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
t.Fatal(err)
}
}
// Expected behaviour: the process dies when the client disconnects
func TestRunDisconnect(t *testing.T) {
runtime, err := newTestRuntime()
@@ -237,6 +288,7 @@ func TestAttachDisconnect(t *testing.T) {
setTimeout(t, "Waiting for CmdAttach timed out", 2*time.Second, func() {
<-c1
})
// We closed stdin, expect /bin/cat to still be running
// Wait a little bit to make sure container.monitor() did his thing
err = container.WaitTimeout(500 * time.Millisecond)
+42 -16
View File
@@ -55,7 +55,7 @@ type Config struct {
AttachStdin bool
AttachStdout bool
AttachStderr bool
Ports []int
PortSpecs []string
Tty bool // Attach standard streams to a tty, including stdin if it is not closed.
OpenStdin bool // Open stdin
StdinOnce bool // If true, close stdin after the 1 attached client disconnects.
@@ -79,8 +79,8 @@ func ParseRun(args []string, stdout io.Writer) (*Config, error) {
flTty := cmd.Bool("t", false, "Allocate a pseudo-tty")
flMemory := cmd.Int64("m", 0, "Memory limit (in bytes)")
var flPorts ports
cmd.Var(&flPorts, "p", "Map a network port to the container")
var flPorts ListOpts
cmd.Var(&flPorts, "p", "Expose a container's port to the host (use 'docker port' to see the actual mapping)")
var flEnv ListOpts
cmd.Var(&flEnv, "e", "Set environment variables")
@@ -88,11 +88,11 @@ func ParseRun(args []string, stdout io.Writer) (*Config, error) {
if err := cmd.Parse(args); err != nil {
return nil, err
}
if *flDetach && len(*flAttach) > 0 {
if *flDetach && len(flAttach) > 0 {
return nil, fmt.Errorf("Conflicting options: -a and -d")
}
// If neither -d or -a are set, attach to everything by default
if len(*flAttach) == 0 && !*flDetach {
if len(flAttach) == 0 && !*flDetach {
if !*flDetach {
flAttach.Set("stdout")
flAttach.Set("stderr")
@@ -112,7 +112,7 @@ func ParseRun(args []string, stdout io.Writer) (*Config, error) {
}
config := &Config{
Hostname: *flHostname,
Ports: flPorts,
PortSpecs: flPorts,
User: *flUser,
Tty: *flTty,
OpenStdin: *flStdin,
@@ -257,9 +257,9 @@ func (container *Container) start() error {
return container.cmd.Start()
}
func (container *Container) Attach(stdin io.Reader, stdout io.Writer, stderr io.Writer) chan error {
var cStdout io.ReadCloser
var cStderr io.ReadCloser
func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, stdout io.Writer, stderr io.Writer) chan error {
var cStdout, cStderr io.ReadCloser
var nJobs int
errors := make(chan error, 3)
if stdin != nil && container.Config.OpenStdin {
@@ -269,15 +269,23 @@ func (container *Container) Attach(stdin io.Reader, stdout io.Writer, stderr io.
} else {
go func() {
Debugf("[start] attach stdin\n")
defer Debugf("[end] attach stdin\n")
defer Debugf("[end] attach stdin\n")
// No matter what, when stdin is closed (io.Copy unblock), close stdout and stderr
if cStdout != nil {
defer cStdout.Close()
}
if cStderr != nil {
defer cStderr.Close()
}
if container.Config.StdinOnce {
defer cStdin.Close()
}
_, err := io.Copy(cStdin, stdin)
if err != nil {
Debugf("[error] attach stdout: %s\n", err)
Debugf("[error] attach stdin: %s\n", err)
}
errors <- err
// Discard error, expecting pipe error
errors <- nil
}()
}
}
@@ -290,6 +298,15 @@ func (container *Container) Attach(stdin io.Reader, stdout io.Writer, stderr io.
go func() {
Debugf("[start] attach stdout\n")
defer Debugf("[end] attach stdout\n")
// If we are in StdinOnce mode, then close stdin
if container.Config.StdinOnce {
if stdin != nil {
defer stdin.Close()
}
if stdinCloser != nil {
defer stdinCloser.Close()
}
}
_, err := io.Copy(stdout, cStdout)
if err != nil {
Debugf("[error] attach stdout: %s\n", err)
@@ -307,6 +324,15 @@ func (container *Container) Attach(stdin io.Reader, stdout io.Writer, stderr io.
go func() {
Debugf("[start] attach stderr\n")
defer Debugf("[end] attach stderr\n")
// If we are in StdinOnce mode, then close stdin
if container.Config.StdinOnce {
if stdin != nil {
defer stdin.Close()
}
if stdinCloser != nil {
defer stdinCloser.Close()
}
}
_, err := io.Copy(stderr, cStderr)
if err != nil {
Debugf("[error] attach stderr: %s\n", err)
@@ -456,12 +482,12 @@ func (container *Container) allocateNetwork() error {
return err
}
container.NetworkSettings.PortMapping = make(map[string]string)
for _, port := range container.Config.Ports {
if extPort, err := iface.AllocatePort(port); err != nil {
for _, spec := range container.Config.PortSpecs {
if nat, err := iface.AllocatePort(spec); err != nil {
iface.Release()
return err
} else {
container.NetworkSettings.PortMapping[strconv.Itoa(port)] = strconv.Itoa(extPort)
container.NetworkSettings.PortMapping[strconv.Itoa(nat.Backend)] = strconv.Itoa(nat.Frontend)
}
}
container.network = iface
@@ -626,7 +652,7 @@ func (container *Container) WaitTimeout(timeout time.Duration) error {
case <-done:
return nil
}
return nil
panic("unreachable")
}
func (container *Container) EnsureMounted() error {
+35 -303
View File
@@ -10,312 +10,44 @@ Command Line Interface
Docker Usage
~~~~~~~~~~~~
::
To list available commands, either run ``docker`` with no parameters or execute
``docker help``::
$ docker
Usage: docker COMMAND [arg...]
A self-sufficient runtime for linux containers.
Commands:
attach Attach to a running container
commit Create a new image from a container's changes
diff Inspect changes on a container's filesystem
export Stream the contents of a container as a tar archive
history Show the history of an image
images List images
import Create a new filesystem image from the contents of a tarball
info Display system-wide information
inspect Return low-level information on a container
kill Kill a running container
login Register or Login to the docker registry server
logs Fetch the logs of a container
port Lookup the public-facing port which is NAT-ed to PRIVATE_PORT
ps List containers
pull Pull an image or a repository to the docker registry server
push Push an image or a repository to the docker registry server
restart Restart a running container
rm Remove a container
rmi Remove an image
run Run a command in a new container
start Start a stopped container
stop Stop a running container
tag Tag an image into a repository
version Show the docker version information
wait Block until a container stops, then print its exit code
attach
~~~~~~
::
Usage: docker attach [OPTIONS]
Attach to a running container
-e=true: Attach to stderr
-i=false: Attach to stdin
-o=true: Attach to stdout
commit
~~~~~~
::
Usage: docker commit [OPTIONS] CONTAINER [DEST]
Create a new image from a container's changes
-m="": Commit message
diff
~~~~
::
Usage: docker diff CONTAINER [OPTIONS]
Inspect changes on a container's filesystem
export
~~~~~~
::
Usage: docker export CONTAINER
Export the contents of a filesystem as a tar archive
history
~~~~~~~
::
Usage: docker history [OPTIONS] IMAGE
Show the history of an image
images
~~~~~~
::
Usage: docker images [OPTIONS] [NAME]
List images
-a=false: show all images
-q=false: only show numeric IDs
import
~~~~~~
::
Usage: docker import [OPTIONS] URL|- [REPOSITORY [TAG]]
Create a new filesystem image from the contents of a tarball
info
~~~~
::
Usage: docker info
Display system-wide information.
inspect
~~~~~~~
::
Usage: docker inspect [OPTIONS] CONTAINER
Return low-level information on a container
kill
~~~~
::
Usage: docker kill [OPTIONS] CONTAINER [CONTAINER...]
Kill a running container
login
~~~~~
::
Usage: docker login
Register or Login to the docker registry server
logs
~~~~
::
Usage: docker logs [OPTIONS] CONTAINER
Fetch the logs of a container
port
~~~~
::
Usage: docker port [OPTIONS] CONTAINER PRIVATE_PORT
Lookup the public-facing port which is NAT-ed to PRIVATE_PORT
ps
~~
::
Usage: docker ps [OPTIONS]
List containers
-a=false: Show all containers. Only running containers are shown by default.
-notrunc=false: Don't truncate output
-q=false: Only display numeric IDs
pull
~~~~
::
Usage: docker pull NAME
Pull an image or a repository from the registry
push
~~~~
::
Usage: docker push NAME
Push an image or a repository to the registry
restart
~~~~~~~
::
Usage: docker restart [OPTIONS] NAME
Restart a running container
rm
~~
::
Usage: docker rm [OPTIONS] CONTAINER
Remove a container
rmi
~~~
::
Usage: docker rmi [OPTIONS] IMAGE
Remove an image
-a=false: Use IMAGE as a path and remove ALL images in this path
-r=false: Use IMAGE as a regular expression instead of an exact name
run
~~~
::
Usage: docker run [OPTIONS] IMAGE COMMAND [ARG...]
Run a command in a new container
-c="": Comment
-i=false: Keep stdin open even if not attached
-m=0: Memory limit (in bytes)
-p=[]: Map a network port to the container
-t=false: Allocate a pseudo-tty
-h="": Container host name
-u="": Username or UID
start
~~~~~
::
Usage: docker start [OPTIONS] NAME
Start a stopped container
stop
~~~~
::
Usage: docker stop [OPTIONS] NAME
Stop a running container
tag
~~~
::
Usage: docker tag [OPTIONS] IMAGE REPOSITORY [TAG]
Tag an image into a repository
-f=false: Force
version
~~~~~~~
::
Usage: docker version
Show the docker version information
wait
~~~~
::
Usage: docker wait [OPTIONS] NAME
Block until a container stops, then print its exit code.
...
Available Commands
~~~~~~~~~~~~~~~~~~
.. toctree::
:maxdepth: 1
command/attach
command/commit
command/diff
command/export
command/history
command/images
command/import
command/info
command/inspect
command/kill
command/login
command/logs
command/port
command/ps
command/pull
command/push
command/restart
command/rm
command/rmi
command/run
command/start
command/stop
command/tag
command/version
command/wait
@@ -0,0 +1,9 @@
===========================================
``attach`` -- Attach to a running container
===========================================
::
Usage: docker attach CONTAINER
Attach to a running container
@@ -0,0 +1,11 @@
===========================================================
``commit`` -- Create a new image from a container's changes
===========================================================
::
Usage: docker commit [OPTIONS] CONTAINER [REPOSITORY [TAG]]
Create a new image from a container's changes
-m="": Commit message
@@ -0,0 +1,9 @@
=======================================================
``diff`` -- Inspect changes on a container's filesystem
=======================================================
::
Usage: docker diff CONTAINER [OPTIONS]
Inspect changes on a container's filesystem
@@ -0,0 +1,9 @@
=================================================================
``export`` -- Stream the contents of a container as a tar archive
=================================================================
::
Usage: docker export CONTAINER
Export the contents of a filesystem as a tar archive
@@ -0,0 +1,9 @@
===========================================
``history`` -- Show the history of an image
===========================================
::
Usage: docker history [OPTIONS] IMAGE
Show the history of an image
@@ -0,0 +1,12 @@
=========================
``images`` -- List images
=========================
::
Usage: docker images [OPTIONS] [NAME]
List images
-a=false: show all images
-q=false: only show numeric IDs
@@ -0,0 +1,9 @@
==========================================================================
``import`` -- Create a new filesystem image from the contents of a tarball
==========================================================================
::
Usage: docker import [OPTIONS] URL|- [REPOSITORY [TAG]]
Create a new filesystem image from the contents of a tarball
@@ -0,0 +1,9 @@
===========================================
``info`` -- Display system-wide information
===========================================
::
Usage: docker info
Display system-wide information.
@@ -0,0 +1,9 @@
==========================================================
``inspect`` -- Return low-level information on a container
==========================================================
::
Usage: docker inspect [OPTIONS] CONTAINER
Return low-level information on a container
@@ -0,0 +1,9 @@
====================================
``kill`` -- Kill a running container
====================================
::
Usage: docker kill [OPTIONS] CONTAINER [CONTAINER...]
Kill a running container
@@ -0,0 +1,9 @@
============================================================
``login`` -- Register or Login to the docker registry server
============================================================
::
Usage: docker login
Register or Login to the docker registry server
@@ -0,0 +1,9 @@
=========================================
``logs`` -- Fetch the logs of a container
=========================================
::
Usage: docker logs [OPTIONS] CONTAINER
Fetch the logs of a container
@@ -0,0 +1,9 @@
=========================================================================
``port`` -- Lookup the public-facing port which is NAT-ed to PRIVATE_PORT
=========================================================================
::
Usage: docker port [OPTIONS] CONTAINER PRIVATE_PORT
Lookup the public-facing port which is NAT-ed to PRIVATE_PORT
+13
View File
@@ -0,0 +1,13 @@
=========================
``ps`` -- List containers
=========================
::
Usage: docker ps [OPTIONS]
List containers
-a=false: Show all containers. Only running containers are shown by default.
-notrunc=false: Don't truncate output
-q=false: Only display numeric IDs
@@ -0,0 +1,9 @@
=========================================================================
``pull`` -- Pull an image or a repository from the docker registry server
=========================================================================
::
Usage: docker pull NAME
Pull an image or a repository from the registry
@@ -0,0 +1,9 @@
=======================================================================
``push`` -- Push an image or a repository to the docker registry server
=======================================================================
::
Usage: docker push NAME
Push an image or a repository to the registry
@@ -0,0 +1,9 @@
==========================================
``restart`` -- Restart a running container
==========================================
::
Usage: docker restart [OPTIONS] NAME
Restart a running container
+9
View File
@@ -0,0 +1,9 @@
============================
``rm`` -- Remove a container
============================
::
Usage: docker rm [OPTIONS] CONTAINER
Remove a container
+9
View File
@@ -0,0 +1,9 @@
==========================
``rmi`` -- Remove an image
==========================
::
Usage: docker rmimage [OPTIONS] IMAGE
Remove an image
+19
View File
@@ -0,0 +1,19 @@
===========================================
``run`` -- Run a command in a new container
===========================================
::
Usage: docker run [OPTIONS] IMAGE COMMAND [ARG...]
Run a command in a new container
-a=map[]: Attach to stdin, stdout or stderr.
-d=false: Detached mode: leave the container running in the background
-e=[]: Set environment variables
-h="": Container host name
-i=false: Keep stdin open even if not attached
-m=0: Memory limit (in bytes)
-p=[]: Map a network port to the container
-t=false: Allocate a pseudo-tty
-u="": Username or UID
@@ -0,0 +1,9 @@
======================================
``start`` -- Start a stopped container
======================================
::
Usage: docker start [OPTIONS] NAME
Start a stopped container
@@ -0,0 +1,9 @@
====================================
``stop`` -- Stop a running container
====================================
::
Usage: docker stop [OPTIONS] NAME
Stop a running container
+11
View File
@@ -0,0 +1,11 @@
=========================================
``tag`` -- Tag an image into a repository
=========================================
::
Usage: docker tag [OPTIONS] IMAGE REPOSITORY [TAG]
Tag an image into a repository
-f=false: Force
@@ -0,0 +1,3 @@
==================================================
``version`` -- Show the docker version information
==================================================
@@ -0,0 +1,9 @@
===================================================================
``wait`` -- Block until a container stops, then print its exit code
===================================================================
::
Usage: docker wait [OPTIONS] NAME
Block until a container stops, then print its exit code.
+1 -1
View File
@@ -15,4 +15,4 @@ Contents:
hello_world
hello_world_daemon
python_web_app
runningsshservice
running_ssh_service
+1 -3
View File
@@ -65,6 +65,4 @@ See the example in action
<iframe width="720" height="350" src="http://ascii.io/a/2573/raw" frameborder="0"></iframe>
</div>
Continue to the `base commands`_
.. _base commands: ../commandline/basecommands.html
Continue to :ref:`running_ssh_service`.
@@ -1,3 +1,8 @@
:title: Running an SSH service
:description: A screencast of installing and running an sshd service
:keywords: docker, example, package installation, networking
.. _running_ssh_service:
Create an ssh daemon service
============================
+10 -51
View File
@@ -85,9 +85,10 @@ func (graph *Graph) Get(name string) (*Image, error) {
// Create creates a new image and registers it in the graph.
func (graph *Graph) Create(layerData Archive, container *Container, comment string) (*Image, error) {
img := &Image{
Id: GenerateId(),
Comment: comment,
Created: time.Now(),
Id: GenerateId(),
Comment: comment,
Created: time.Now(),
DockerVersion: VERSION,
}
if container != nil {
img.Parent = container.Image
@@ -129,6 +130,9 @@ func (graph *Graph) Register(layerData Archive, img *Image) error {
// Mktemp creates a temporary sub-directory inside the graph's filesystem.
func (graph *Graph) Mktemp(id string) (string, error) {
if id == "" {
id = GenerateId()
}
tmp, err := NewGraph(path.Join(graph.Root, ":tmp:"))
if err != nil {
return "", fmt.Errorf("Couldn't create temp: %s", err)
@@ -139,13 +143,6 @@ func (graph *Graph) Mktemp(id string) (string, error) {
return tmp.imageRoot(id), nil
}
// Garbage returns the "garbage", a staging area for deleted images.
// This allows images to be deleted atomically by os.Rename(), instead of
// os.RemoveAll() which is prone to race conditions.
func (graph *Graph) Garbage() (*Graph, error) {
return NewGraph(path.Join(graph.Root, ":garbage:"))
}
// Check if given error is "not empty".
// Note: this is the way golang does it internally with os.IsNotExists.
func isNotEmpty(err error) bool {
@@ -166,54 +163,16 @@ func (graph *Graph) Delete(name string) error {
if err != nil {
return err
}
garbage, err := graph.Garbage()
tmp, err := graph.Mktemp("")
if err != nil {
return err
}
graph.idIndex.Delete(id)
err = os.Rename(graph.imageRoot(id), garbage.imageRoot(id))
if err != nil {
// FIXME: this introduces a race condition in Delete() if the image is already present
// in garbage. Let's store at random names in grabage instead.
if isNotEmpty(err) {
Debugf("The image %s is already present in garbage. Removing it.", id)
if err = os.RemoveAll(garbage.imageRoot(id)); err != nil {
Debugf("Error while removing the image %s from garbage: %s\n", id, err)
return err
}
Debugf("Image %s removed from garbage", id)
if err = os.Rename(graph.imageRoot(id), garbage.imageRoot(id)); err != nil {
return err
}
Debugf("Image %s put in the garbage", id)
} else {
Debugf("Error putting the image %s to garbage: %s\n", id, err)
}
return err
}
return nil
}
// Undelete moves an image back from the garbage to the main graph.
func (graph *Graph) Undelete(id string) error {
garbage, err := graph.Garbage()
err = os.Rename(graph.imageRoot(id), tmp)
if err != nil {
return err
}
if err := os.Rename(garbage.imageRoot(id), graph.imageRoot(id)); err != nil {
return err
}
graph.idIndex.Add(id)
return nil
}
// GarbageCollect definitely deletes all images moved to the garbage.
func (graph *Graph) GarbageCollect() error {
garbage, err := graph.Garbage()
if err != nil {
return err
}
return os.RemoveAll(garbage.Root)
return os.RemoveAll(tmp)
}
// Map returns a list of all images in the graph, addressable by ID.
+3
View File
@@ -45,6 +45,9 @@ func TestGraphCreate(t *testing.T) {
if image.Comment != "Testing" {
t.Fatalf("Wrong comment: should be '%s', not '%s'", "Testing", image.Comment)
}
if image.DockerVersion != VERSION {
t.Fatalf("Wrong docker_version: should be '%s', not '%s'", VERSION, image.DockerVersion)
}
if images, err := graph.All(); err != nil {
t.Fatal(err)
} else if l := len(images); l != 1 {
+1
View File
@@ -20,6 +20,7 @@ type Image struct {
Created time.Time `json:"created"`
Container string `json:"container,omitempty"`
ContainerConfig Config `json:"container_config,omitempty"`
DockerVersion string `json:"docker_version,omitempty"`
graph *Graph
}
+89 -36
View File
@@ -9,6 +9,7 @@ import (
"os/exec"
"strconv"
"strings"
"sync"
)
const (
@@ -98,6 +99,9 @@ type PortMapper struct {
func (mapper *PortMapper) cleanup() error {
// Ignore errors - This could mean the chains were never set up
iptables("-t", "nat", "-D", "PREROUTING", "-m", "addrtype", "--dst-type", "LOCAL", "-j", "DOCKER")
iptables("-t", "nat", "-D", "OUTPUT", "-m", "addrtype", "--dst-type", "LOCAL", "-j", "DOCKER")
// Also cleanup rules created by older versions, or -X might fail.
iptables("-t", "nat", "-D", "PREROUTING", "-j", "DOCKER")
iptables("-t", "nat", "-D", "OUTPUT", "-j", "DOCKER")
iptables("-t", "nat", "-F", "DOCKER")
@@ -110,10 +114,10 @@ func (mapper *PortMapper) setup() error {
if err := iptables("-t", "nat", "-N", "DOCKER"); err != nil {
return fmt.Errorf("Failed to create DOCKER chain: %s", err)
}
if err := iptables("-t", "nat", "-A", "PREROUTING", "-j", "DOCKER"); err != nil {
if err := iptables("-t", "nat", "-A", "PREROUTING", "-m", "addrtype", "--dst-type", "LOCAL", "-j", "DOCKER"); err != nil {
return fmt.Errorf("Failed to inject docker in PREROUTING chain: %s", err)
}
if err := iptables("-t", "nat", "-A", "OUTPUT", "-j", "DOCKER"); err != nil {
if err := iptables("-t", "nat", "-A", "OUTPUT", "-m", "addrtype", "--dst-type", "LOCAL", "-j", "DOCKER"); err != nil {
return fmt.Errorf("Failed to inject docker in OUTPUT chain: %s", err)
}
return nil
@@ -157,39 +161,55 @@ func newPortMapper() (*PortMapper, error) {
// Port allocator: Atomatically allocate and release networking ports
type PortAllocator struct {
ports chan (int)
inUse map[int]struct{}
fountain chan (int)
lock sync.Mutex
}
func (alloc *PortAllocator) populate(start, end int) {
alloc.ports = make(chan int, end-start)
for port := start; port < end; port++ {
alloc.ports <- port
func (alloc *PortAllocator) runFountain() {
if alloc.fountain != nil {
return
}
alloc.fountain = make(chan int)
for {
for port := portRangeStart; port < portRangeEnd; port++ {
alloc.fountain <- port
}
}
}
func (alloc *PortAllocator) Acquire() (int, error) {
select {
case port := <-alloc.ports:
return port, nil
default:
return -1, errors.New("No more ports available")
}
return -1, nil
}
// FIXME: Release can no longer fail, change its prototype to reflect that.
func (alloc *PortAllocator) Release(port int) error {
select {
case alloc.ports <- port:
return nil
default:
return errors.New("Too many ports have been released")
}
alloc.lock.Lock()
delete(alloc.inUse, port)
alloc.lock.Unlock()
return nil
}
func newPortAllocator(start, end int) (*PortAllocator, error) {
allocator := &PortAllocator{}
allocator.populate(start, end)
func (alloc *PortAllocator) Acquire(port int) (int, error) {
if port == 0 {
// Allocate a port from the fountain
for port := range alloc.fountain {
if _, err := alloc.Acquire(port); err == nil {
return port, nil
}
}
return -1, fmt.Errorf("Port generator ended unexpectedly")
}
alloc.lock.Lock()
defer alloc.lock.Unlock()
if _, inUse := alloc.inUse[port]; inUse {
return -1, fmt.Errorf("Port already in use: %d", port)
}
alloc.inUse[port] = struct{}{}
return port, nil
}
func newPortAllocator() (*PortAllocator, error) {
allocator := &PortAllocator{
inUse: make(map[int]struct{}),
}
go allocator.runFountain()
return allocator, nil
}
@@ -298,17 +318,50 @@ type NetworkInterface struct {
}
// Allocate an external TCP port and map it to the interface
func (iface *NetworkInterface) AllocatePort(port int) (int, error) {
extPort, err := iface.manager.portAllocator.Acquire()
func (iface *NetworkInterface) AllocatePort(spec string) (*Nat, error) {
nat, err := parseNat(spec)
if err != nil {
return -1, err
return nil, err
}
if err := iface.manager.portMapper.Map(extPort, net.TCPAddr{IP: iface.IPNet.IP, Port: port}); err != nil {
iface.manager.portAllocator.Release(extPort)
return -1, err
// Allocate a random port if Frontend==0
if extPort, err := iface.manager.portAllocator.Acquire(nat.Frontend); err != nil {
return nil, err
} else {
nat.Frontend = extPort
}
iface.extPorts = append(iface.extPorts, extPort)
return extPort, nil
if err := iface.manager.portMapper.Map(nat.Frontend, net.TCPAddr{IP: iface.IPNet.IP, Port: nat.Backend}); err != nil {
iface.manager.portAllocator.Release(nat.Frontend)
return nil, err
}
iface.extPorts = append(iface.extPorts, nat.Frontend)
return nat, nil
}
type Nat struct {
Proto string
Frontend int
Backend int
}
func parseNat(spec string) (*Nat, error) {
var nat Nat
// If spec starts with ':', external and internal ports must be the same.
// This might fail if the requested external port is not available.
var sameFrontend bool
if spec[0] == ':' {
sameFrontend = true
spec = spec[1:]
}
port, err := strconv.ParseUint(spec, 10, 16)
if err != nil {
return nil, err
}
nat.Backend = int(port)
if sameFrontend {
nat.Frontend = nat.Backend
}
nat.Proto = "tcp"
return &nat, nil
}
// Release: Network cleanup - release all resources
@@ -354,13 +407,13 @@ func (manager *NetworkManager) Allocate() (*NetworkInterface, error) {
func newNetworkManager(bridgeIface string) (*NetworkManager, error) {
addr, err := getIfaceAddr(bridgeIface)
if err != nil {
return nil, err
return nil, fmt.Errorf("Couldn't find bridge interface %s (%s).\nPlease create it with 'ip link add lxcbr0 type bridge; ip addr add ADDRESS/MASK dev lxcbr0'", bridgeIface, err)
}
network := addr.(*net.IPNet)
ipAllocator := newIPAllocator(network)
portAllocator, err := newPortAllocator(portRangeStart, portRangeEnd)
portAllocator, err := newPortAllocator()
if err != nil {
return nil, err
}
+21 -5
View File
@@ -7,9 +7,10 @@ import (
"io"
"io/ioutil"
"os"
"os/exec"
"path"
"sort"
"sync"
"strings"
"time"
)
@@ -115,6 +116,7 @@ func (runtime *Runtime) Load(id string) (*Container, error) {
if err := container.FromDisk(); err != nil {
return nil, err
}
container.State.initLock()
if container.Id != id {
return container, fmt.Errorf("Container %s is stored at %s", container.Id, id)
}
@@ -132,11 +134,26 @@ func (runtime *Runtime) Register(container *Container) error {
if err := validateId(container.Id); err != nil {
return err
}
// FIXME: if the container is supposed to be running but is not, auto restart it?
// If the container is supposed to be running, make sure of it
if container.State.Running {
if output, err := exec.Command("lxc-info", "-n", container.Id).CombinedOutput(); err != nil {
return err
} else {
if !strings.Contains(string(output), "RUNNING") {
Debugf("Container %s was supposed to be running be is not.", container.Id)
container.State.setStopped(-127)
if err := container.ToDisk(); err != nil {
return err
}
}
}
}
container.runtime = runtime
// Setup state lock (formerly in newState()
lock := new(sync.Mutex)
container.State.stateChangeLock = lock
container.State.stateChangeCond = sync.NewCond(lock)
container.State.initLock()
// Attach to stdout and stderr
container.stderr = newWriteBroadcaster()
container.stdout = newWriteBroadcaster()
@@ -259,7 +276,6 @@ func NewRuntimeFromDirectory(root string) (*Runtime, error) {
// If the auth file does not exist, keep going
return nil, err
}
runtime := &Runtime{
root: root,
repository: runtimeRepo,
+57 -12
View File
@@ -8,6 +8,7 @@ import (
"os/user"
"sync"
"testing"
"time"
)
// FIXME: this is no longer needed
@@ -21,10 +22,10 @@ func nuke(runtime *Runtime) error {
var wg sync.WaitGroup
for _, container := range runtime.List() {
wg.Add(1)
go func() {
container.Kill()
wg.Add(-1)
}()
go func(c *Container) {
c.Kill()
wg.Done()
}(container)
}
wg.Wait()
return os.RemoveAll(runtime.root)
@@ -90,7 +91,6 @@ func newTestRuntime() (*Runtime, error) {
return nil, err
}
if err := CopyDirectory(unitTestStoreBase, root); err != nil {
panic(err)
return nil, err
}
@@ -289,13 +289,48 @@ func TestRestore(t *testing.T) {
t.Fatal(err)
}
defer runtime1.Destroy(container1)
if len(runtime1.List()) != 1 {
t.Errorf("Expected 1 container, %v found", len(runtime1.List()))
// Create a second container meant to be killed
container2, err := runtime1.Create(&Config{
Image: GetTestImage(runtime1).Id,
Cmd: []string{"/bin/cat"},
OpenStdin: true,
},
)
if err != nil {
t.Fatal(err)
}
defer runtime1.Destroy(container2)
// Start the container non blocking
if err := container2.Start(); err != nil {
t.Fatal(err)
}
if !container2.State.Running {
t.Fatalf("Container %v should appear as running but isn't", container2.Id)
}
// Simulate a crash/manual quit of dockerd: process dies, states stays 'Running'
cStdin, _ := container2.StdinPipe()
cStdin.Close()
if err := container2.WaitTimeout(time.Second); err != nil {
t.Fatal(err)
}
container2.State.Running = true
container2.ToDisk()
if len(runtime1.List()) != 2 {
t.Errorf("Expected 2 container, %v found", len(runtime1.List()))
}
if err := container1.Run(); err != nil {
t.Fatal(err)
}
if !container2.State.Running {
t.Fatalf("Container %v should appear as running but isn't", container2.Id)
}
// Here are are simulating a docker restart - that is, reloading all containers
// from scratch
runtime2, err := NewRuntimeFromDirectory(root)
@@ -303,14 +338,24 @@ func TestRestore(t *testing.T) {
t.Fatal(err)
}
defer nuke(runtime2)
if len(runtime2.List()) != 1 {
t.Errorf("Expected 1 container, %v found", len(runtime2.List()))
if len(runtime2.List()) != 2 {
t.Errorf("Expected 2 container, %v found", len(runtime2.List()))
}
container2 := runtime2.Get(container1.Id)
if container2 == nil {
runningCount := 0
for _, c := range runtime2.List() {
if c.State.Running {
t.Errorf("Running container found: %v (%v)", c.Id, c.Path)
runningCount++
}
}
if runningCount != 0 {
t.Fatalf("Expected 0 container alive, %d found", runningCount)
}
container3 := runtime2.Get(container1.Id)
if container3 == nil {
t.Fatal("Unable to Get container")
}
if err := container2.Run(); err != nil {
if err := container3.Run(); err != nil {
t.Fatal(err)
}
}
+7
View File
@@ -39,6 +39,13 @@ func (s *State) setStopped(exitCode int) {
s.broadcast()
}
func (s *State) initLock() {
if s.stateChangeLock == nil {
s.stateChangeLock = &sync.Mutex{}
s.stateChangeCond = sync.NewCond(s.stateChangeLock)
}
}
func (s *State) broadcast() {
s.stateChangeLock.Lock()
s.stateChangeCond.Broadcast()
+1 -1
View File
@@ -202,7 +202,7 @@ func (r *bufReader) Read(p []byte) (n int, err error) {
}
r.wait.Wait()
}
return
panic("unreachable")
}
func (r *bufReader) Close() error {