mirror of
https://github.com/clearlinux/docker.git
synced 2026-08-20 20:56:42 +00:00
Compare commits
179 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 40ebe78bb1 | |||
| 1b7115a337 | |||
| 2e6a5bc7ee | |||
| 72cef46e5e | |||
| 626bfd87a7 | |||
| 8f41f1fa60 | |||
| faa8843650 | |||
| 0d9e54367f | |||
| 1f70b1e15d | |||
| 3f63b87807 | |||
| 9c3d2b6a4e | |||
| 1716fccbcc | |||
| 9043e4c757 | |||
| 2e9a73c5d8 | |||
| 1eaaa6b744 | |||
| cb54e9c659 | |||
| 7c2b085d1a | |||
| d063d52cce | |||
| 64c1b6d9cd | |||
| 329f4449dc | |||
| 0767916ade | |||
| 10923c7890 | |||
| 2832ea0cfe | |||
| a7299a3f26 | |||
| 1601366cb6 | |||
| e9a68801ba | |||
| f73401fb9a | |||
| dcf4572a69 | |||
| d530d581f7 | |||
| bdf05d8368 | |||
| b71b226cc1 | |||
| 80f6b4587b | |||
| e6e9c1cd62 | |||
| 246eed52de | |||
| b306a60738 | |||
| 7d0ab3858e | |||
| 4e5001b46a | |||
| b8f9803459 | |||
| 0c018d3697 | |||
| 72fdb41069 | |||
| 6eb8a74ff9 | |||
| 81ebf4fcf6 | |||
| 9875a9b1f1 | |||
| 27feba4594 | |||
| c83393a541 | |||
| 7e1e7d14fa | |||
| 99b5bec069 | |||
| 7d8895545e | |||
| 33a5fe3bd4 | |||
| 847a8f45a4 | |||
| 8cf30395a1 | |||
| 22adb52c0a | |||
| 793c1ad990 | |||
| febaeebfb8 | |||
| d32f184696 | |||
| 20085794f0 | |||
| a4fc52305a | |||
| 2aad4a3478 | |||
| a5fb1d6c01 | |||
| b76b329ef0 | |||
| bae6f95830 | |||
| cda9cf1539 | |||
| f344212b93 | |||
| 0424998f38 | |||
| 8bfbdd7afa | |||
| 3de51b7bfe | |||
| a58cd8c616 | |||
| 586a79cca0 | |||
| 349edf1bea | |||
| 677908910c | |||
| 6b5fe8c2ec | |||
| 26088a72b3 | |||
| ebc837957f | |||
| c4d3da5871 | |||
| 32f5811476 | |||
| a7f191d51d | |||
| 1b370f9d8d | |||
| 92186d7cf7 | |||
| 5d3c0767da | |||
| aa4bf4284b | |||
| d9a9bfc9c7 | |||
| 90a6e310fe | |||
| f39af7e05d | |||
| dce5a06aba | |||
| 3b65be9127 | |||
| ad0183e419 | |||
| 4f36039e7b | |||
| b74d1c9247 | |||
| cf8b8c1969 | |||
| d1767bbf67 | |||
| 7b74b9cab5 | |||
| 14d3880daf | |||
| 22f1cc955d | |||
| cab31fd512 | |||
| 1fc55c2bb9 | |||
| 5ecd940a59 | |||
| 3b8c2417fb | |||
| 15c3096e89 | |||
| 22d1622926 | |||
| 35c68944c7 | |||
| b1fa26bb76 | |||
| d6fb2a0836 | |||
| 65aa3dda85 | |||
| a05af3ebf3 | |||
| 0e1781af26 | |||
| 14d9f51bbe | |||
| c04af2a330 | |||
| c77063afcd | |||
| a19a9e3ca8 | |||
| ad2bbe23be | |||
| 6882c78ce4 | |||
| 2db358146f | |||
| 1cc1cb099e | |||
| aea2675f7b | |||
| 8f9e454241 | |||
| 7666307931 | |||
| 791ca6fde4 | |||
| 43484e8b50 | |||
| 5471f5b2ee | |||
| 02c211a0dc | |||
| c780ff5ae7 | |||
| 8edf0ca7f3 | |||
| 927308cfd9 | |||
| 8fed600077 | |||
| a83d87abd4 | |||
| c808940c04 | |||
| 81874ad442 | |||
| 3478d6f706 | |||
| 13b6309138 | |||
| 90db9e7517 | |||
| f15889461d | |||
| dea4194f8b | |||
| e331f1ee0e | |||
| 0ddc6867fb | |||
| 3c5d2e4661 | |||
| ff5cb8e864 | |||
| 7ad2e022fb | |||
| f65fc1e766 | |||
| dcc9dfb27d | |||
| 650dff73bd | |||
| 6d00145076 | |||
| cc2558bf10 | |||
| e566b89a5f | |||
| 47c4c2abd4 | |||
| fa5bb5acf1 | |||
| 50500cfcb6 | |||
| 9e81ab65cb | |||
| ff26493fd5 | |||
| 9b13d21fc9 | |||
| 887f509d1d | |||
| 13d2b08638 | |||
| c298a91f95 | |||
| 37a78902db | |||
| 15b3088157 | |||
| 7830cf9166 | |||
| 321d94b17e | |||
| 6b59cc8a10 | |||
| 91b1f9eee9 | |||
| c9a13147fe | |||
| 1632566ecb | |||
| a52a28b609 | |||
| 54443c092c | |||
| ad1e8a9b0f | |||
| 29b7ecb017 | |||
| 6f9a67a7c7 | |||
| cfeed391d7 | |||
| 8c36e6920a | |||
| 3dcaf20d6b | |||
| d949e2804a | |||
| 1fc9405537 | |||
| b64dfdd8cd | |||
| a6779bcae2 | |||
| 75ba07cb3a | |||
| 0bdfcfaa33 | |||
| e9d17b1f91 | |||
| 6437648176 | |||
| bd3c6793a1 | |||
| 760736b3f3 | |||
| a5054184a1 |
@@ -7,6 +7,7 @@ Caleb Spare <cespare@gmail.com>
|
||||
Charles Hooper <charles.hooper@dotcloud.com>
|
||||
Daniel Mizyrycki <daniel.mizyrycki@dotcloud.com>
|
||||
Daniel Robinson <gottagetmac@gmail.com>
|
||||
Dominik Honnef <dominik@honnef.co>
|
||||
Don Spaulding <donspauldingii@gmail.com>
|
||||
ezbercih <cem.ezberci@gmail.com>
|
||||
Frederick F. Kautz IV <fkautz@alumni.cmu.edu>
|
||||
@@ -20,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>
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
# Contributing to Docker
|
||||
|
||||
Want to hack on Docker? Awesome! There are instructions to get you
|
||||
started on the website: http://docker.io/gettingstarted.html
|
||||
|
||||
They are probably not perfect, please let us know if anything feels
|
||||
wrong or incomplete.
|
||||
|
||||
## Contribution guidelines
|
||||
|
||||
### Pull requests are always welcome
|
||||
|
||||
We are always thrilled to receive pull requests, and do our best to
|
||||
process them as fast as possible. Not sure if that typo is worth a pull
|
||||
request? Do it! We will appreciate it.
|
||||
|
||||
If your pull request is not accepted on the first try, don't be
|
||||
discouraged! If there's a problem with the implementation, hopefully you
|
||||
received feedback on what to improve.
|
||||
|
||||
We're trying very hard to keep Docker lean and focused. We don't want it
|
||||
to do everything for everybody. This means that we might decide against
|
||||
incorporating a new feature. However, there might be a way to implement
|
||||
that feature *on top of* docker.
|
||||
|
||||
### Discuss your design on the mailing list
|
||||
|
||||
We recommend discussing your plans [on the mailing
|
||||
list](https://groups.google.com/forum/?fromgroups#!forum/docker-club)
|
||||
before starting to code - especially for more ambitious contributions.
|
||||
This gives other contributors a chance to point you in the right
|
||||
direction, give feedback on your design, and maybe point out if someone
|
||||
else is working on the same thing.
|
||||
|
||||
### Create issues...
|
||||
|
||||
Any significant improvement should be documented as [a github
|
||||
issue](https://github.com/dotcloud/docker/issues) before anybody
|
||||
starts working on it.
|
||||
|
||||
### ...but check for existing issues first!
|
||||
|
||||
Please take a moment to check that an issue doesn't already exist
|
||||
documenting your bug report or improvement proposal. If it does, it
|
||||
never hurts to add a quick "+1" or "I have this problem too". This will
|
||||
help prioritize the most common problems and requests.
|
||||
|
||||
### Conventions
|
||||
|
||||
Fork the repo and make changes on your fork in a feature branch:
|
||||
|
||||
- If it's a bugfix branch, name it XXX-something where XXX is the number of the
|
||||
issue
|
||||
- If it's a feature branch, create an enhancement issue to announce your
|
||||
intentions, and name it XXX-something where XXX is the number of the issue.
|
||||
|
||||
Submit unit tests for your changes. Go has a great test framework built in; use
|
||||
it! Take a look at existing tests for inspiration. Run the full test suite on
|
||||
your branch before submitting a pull request.
|
||||
|
||||
Make sure you include relevant updates or additions to documentation when
|
||||
creating or modifying features.
|
||||
|
||||
Write clean code. Universally formatted code promotes ease of writing, reading,
|
||||
and maintenance. Always run `go fmt` before committing your changes. Most
|
||||
editors have plugins that do this automatically, and there's also a git
|
||||
pre-commit hook:
|
||||
|
||||
```
|
||||
curl -o .git/hooks/pre-commit https://raw.github.com/edsrzf/gofmt-git-hook/master/fmt-check && chmod +x .git/hooks/pre-commit
|
||||
```
|
||||
|
||||
Pull requests descriptions should be as clear as possible and include a
|
||||
reference to all the issues that they address.
|
||||
|
||||
Code review comments may be added to your pull request. Discuss, then make the
|
||||
suggested modifications and push additional commits to your feature branch. Be
|
||||
sure to post a comment after pushing. The new commits will show up in the pull
|
||||
request automatically, but the reviewers will not be notified unless you
|
||||
comment.
|
||||
|
||||
Before the pull request is merged, make sure that you squash your commits into
|
||||
logical units of work using `git rebase -i` and `git push -f`. After every
|
||||
commit the test suite should be passing. Include documentation changes in the
|
||||
same commit so that a revert would remove all traces of the feature or fix.
|
||||
|
||||
Commits that fix or close an issue should include a reference like `Closes #XXX`
|
||||
or `Fixes #XXX`, which will automatically close the issue when merged.
|
||||
|
||||
Add your name to the AUTHORS file, but make sure the list is sorted and your
|
||||
name and email address match your git configuration. The AUTHORS file is
|
||||
regenerated occasionally from the git commit history, so a mismatch may result
|
||||
in your changes being overwritten.
|
||||
@@ -10,6 +10,11 @@ ifeq ($(VERBOSE), 1)
|
||||
GO_OPTIONS += -v
|
||||
endif
|
||||
|
||||
GIT_COMMIT = $(shell git rev-parse --short HEAD)
|
||||
GIT_STATUS = $(shell test -n "`git status --porcelain`" && echo "+CHANGES")
|
||||
|
||||
BUILD_OPTIONS = -ldflags "-X main.GIT_COMMIT $(GIT_COMMIT)$(GIT_STATUS)"
|
||||
|
||||
SRC_DIR := $(GOPATH)/src
|
||||
|
||||
DOCKER_DIR := $(SRC_DIR)/$(DOCKER_PACKAGE)
|
||||
@@ -24,7 +29,7 @@ all: $(DOCKER_BIN)
|
||||
|
||||
$(DOCKER_BIN): $(DOCKER_DIR)
|
||||
@mkdir -p $(dir $@)
|
||||
@(cd $(DOCKER_MAIN); go get $(GO_OPTIONS); go build $(GO_OPTIONS) -o $@)
|
||||
@(cd $(DOCKER_MAIN); go get $(GO_OPTIONS); go build $(GO_OPTIONS) $(BUILD_OPTIONS) -o $@)
|
||||
@echo $(DOCKER_BIN_RELATIVE) is created.
|
||||
|
||||
$(DOCKER_DIR):
|
||||
@@ -41,3 +46,6 @@ endif
|
||||
|
||||
test: all
|
||||
@(cd $(DOCKER_DIR); sudo -E go test $(GO_OPTIONS))
|
||||
|
||||
fmt:
|
||||
@gofmt -s -l -w .
|
||||
|
||||
@@ -134,6 +134,12 @@ docker pull base
|
||||
docker run -i -t base /bin/bash
|
||||
```
|
||||
|
||||
Detaching from the interactive shell
|
||||
------------------------------------
|
||||
```
|
||||
# In order to detach without killing the shell, you can use the escape sequence Ctrl-p + Ctrl-q
|
||||
# Note: this works only in tty mode (run with -t option).
|
||||
```
|
||||
|
||||
Starting a long-running worker process
|
||||
--------------------------------------
|
||||
@@ -183,7 +189,9 @@ JOB=$(docker run -d -p 4444 base /bin/nc -l -p 4444)
|
||||
PORT=$(docker port $JOB 4444)
|
||||
|
||||
# Connect to the public port via the host's public address
|
||||
echo hello world | nc $(hostname) $PORT
|
||||
# Please note that because of how routing works connecting to localhost or 127.0.0.1 $PORT will not work.
|
||||
IP=$(ifconfig eth0 | perl -n -e 'if (m/inet addr:([\d\.]+)/g) { print $1 }')
|
||||
echo hello world | nc $IP $PORT
|
||||
|
||||
# Verify that the network connection worked
|
||||
echo "Daemon received: $(docker logs $JOB)"
|
||||
@@ -192,11 +200,10 @@ echo "Daemon received: $(docker logs $JOB)"
|
||||
Contributing to Docker
|
||||
======================
|
||||
|
||||
Want to hack on Docker? Awesome! There are instructions to get you started on the website: http://docker.io/documentation/contributing/contributing.html
|
||||
Want to hack on Docker? Awesome! There are instructions to get you started on the website: http://docs.docker.io/en/latest/contributing/contributing/
|
||||
|
||||
They are probably not perfect, please let us know if anything feels wrong or incomplete.
|
||||
|
||||
### Pull requests are always welcome
|
||||
|
||||
Note
|
||||
----
|
||||
@@ -206,26 +213,6 @@ Please find it under docs/sources/ and read more about it https://github.com/dot
|
||||
|
||||
Please feel free to fix / update the documentation and send us pull requests. More tutorials are also welcome.
|
||||
|
||||
### Discuss your design on the mailing list
|
||||
|
||||
We recommend discussing your plans [on the mailing list](https://groups.google.com/forum/?fromgroups#!forum/docker-club) before starting to code - especially for more ambitious contributions. This gives other contributors a chance to point
|
||||
you in the right direction, give feedback on your design, and maybe point out if someone else is working on the same thing.
|
||||
|
||||
### Create issues...
|
||||
|
||||
Any significant improvement should be documented as [a github issue](https://github.com/dotcloud/docker/issues) before anybody starts working on it.
|
||||
|
||||
### ...but check for existing issues first!
|
||||
|
||||
Please take a moment to check that an issue doesn't already exist documenting your bug report or improvement proposal.
|
||||
If it does, it never hurts to add a quick "+1" or "I have this problem too". This will help prioritize the most common problems and requests.
|
||||
|
||||
|
||||
### Write tests
|
||||
|
||||
Golang has a great testing suite built in: use it! Take a look at existing tests for inspiration.
|
||||
|
||||
|
||||
|
||||
Setting up a dev environment
|
||||
----------------------------
|
||||
|
||||
@@ -15,6 +15,7 @@ const (
|
||||
Uncompressed Compression = iota
|
||||
Bzip2
|
||||
Gzip
|
||||
Xz
|
||||
)
|
||||
|
||||
func (compression *Compression) Flag() string {
|
||||
@@ -23,6 +24,8 @@ func (compression *Compression) Flag() string {
|
||||
return "j"
|
||||
case Gzip:
|
||||
return "z"
|
||||
case Xz:
|
||||
return "J"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
+4
-2
@@ -111,6 +111,7 @@ func Login(authConfig *AuthConfig) (string, error) {
|
||||
return "", errors.New(errMsg)
|
||||
}
|
||||
|
||||
// using `bytes.NewReader(jsonBody)` here causes the server to respond with a 411 status.
|
||||
b := strings.NewReader(string(jsonBody))
|
||||
req1, err := http.Post(REGISTRY_SERVER+"/v1/users", "application/json; charset=utf-8", b)
|
||||
if err != nil {
|
||||
@@ -130,6 +131,7 @@ func Login(authConfig *AuthConfig) (string, error) {
|
||||
status = "Account Created\n"
|
||||
storeConfig = true
|
||||
} else if reqStatusCode == 400 {
|
||||
// FIXME: This should be 'exists', not 'exist'. Need to change on the server first.
|
||||
if string(reqBody) == "Username or email already exist" {
|
||||
client := &http.Client{}
|
||||
req, err := http.NewRequest("GET", REGISTRY_SERVER+"/v1/users", nil)
|
||||
@@ -151,11 +153,11 @@ func Login(authConfig *AuthConfig) (string, error) {
|
||||
return "", errors.New(status)
|
||||
}
|
||||
} else {
|
||||
status = fmt.Sprintf("Registration: %s", string(reqBody))
|
||||
status = fmt.Sprintf("Registration: %s", reqBody)
|
||||
return "", errors.New(status)
|
||||
}
|
||||
} else {
|
||||
status = fmt.Sprintf("[%s] : %s", reqStatusCode, string(reqBody))
|
||||
status = fmt.Sprintf("[%s] : %s", reqStatusCode, reqBody)
|
||||
return "", errors.New(status)
|
||||
}
|
||||
if storeConfig {
|
||||
|
||||
+99
-120
@@ -13,13 +13,14 @@ import (
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
const VERSION = "0.1.1"
|
||||
const VERSION = "0.1.4"
|
||||
|
||||
var GIT_COMMIT string
|
||||
|
||||
func (srv *Server) Name() string {
|
||||
return "docker"
|
||||
@@ -28,7 +29,7 @@ func (srv *Server) Name() string {
|
||||
// FIXME: Stop violating DRY by repeating usage here and in Subcmd declarations
|
||||
func (srv *Server) Help() string {
|
||||
help := "Usage: docker COMMAND [arg...]\n\nA self-sufficient runtime for linux containers.\n\nCommands:\n"
|
||||
for _, cmd := range [][]interface{}{
|
||||
for _, cmd := range [][]string{
|
||||
{"attach", "Attach to a running container"},
|
||||
{"commit", "Create a new image from a container's changes"},
|
||||
{"diff", "Inspect changes on a container's filesystem"},
|
||||
@@ -61,11 +62,11 @@ func (srv *Server) Help() string {
|
||||
}
|
||||
|
||||
// 'docker login': login / register a user to registry service.
|
||||
func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||
func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
|
||||
// Read a line on raw terminal with support for simple backspace
|
||||
// sequences and echo.
|
||||
//
|
||||
// This function is necessary because the login command must be done a
|
||||
// This function is necessary because the login command must be done in a
|
||||
// raw terminal for two reasons:
|
||||
// - we have to read a password (without echoing it);
|
||||
// - the rcli "protocol" only supports cannonical and raw modes and you
|
||||
@@ -98,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
|
||||
}
|
||||
@@ -112,6 +113,8 @@ func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout io.Writer, args ...strin
|
||||
return readStringOnRawTerminal(stdin, stdout, false)
|
||||
}
|
||||
|
||||
stdout.SetOptionRawTerminal()
|
||||
|
||||
cmd := rcli.Subcmd(stdout, "login", "", "Register or Login to the docker registry server")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
@@ -146,12 +149,12 @@ func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout io.Writer, args ...strin
|
||||
newAuthConfig := auth.NewAuthConfig(username, password, email, srv.runtime.root)
|
||||
status, err := auth.Login(newAuthConfig)
|
||||
if err != nil {
|
||||
fmt.Fprintf(stdout, "Error : %s\n", err)
|
||||
fmt.Fprintln(stdout, "Error:", err)
|
||||
} else {
|
||||
srv.runtime.authConfig = newAuthConfig
|
||||
}
|
||||
if status != "" {
|
||||
fmt.Fprintf(stdout, status)
|
||||
fmt.Fprint(stdout, status)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -179,6 +182,7 @@ func (srv *Server) CmdWait(stdin io.ReadCloser, stdout io.Writer, args ...string
|
||||
// 'docker version': show version information
|
||||
func (srv *Server) CmdVersion(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||
fmt.Fprintf(stdout, "Version:%s\n", VERSION)
|
||||
fmt.Fprintf(stdout, "Git Commit:%s\n", GIT_COMMIT)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -207,7 +211,7 @@ func (srv *Server) CmdInfo(stdin io.ReadCloser, stdout io.Writer, args ...string
|
||||
if !rcli.DEBUG_FLAG {
|
||||
return nil
|
||||
}
|
||||
fmt.Fprintf(stdout, "debug mode enabled\n")
|
||||
fmt.Fprintln(stdout, "debug mode enabled")
|
||||
fmt.Fprintf(stdout, "fds: %d\ngoroutines: %d\n", getTotalUsedFds(), runtime.NumGoroutine())
|
||||
return nil
|
||||
}
|
||||
@@ -369,10 +373,10 @@ func (srv *Server) CmdHistory(stdin io.ReadCloser, stdout io.Writer, args ...str
|
||||
}
|
||||
w := tabwriter.NewWriter(stdout, 20, 1, 3, ' ', 0)
|
||||
defer w.Flush()
|
||||
fmt.Fprintf(w, "ID\tCREATED\tCREATED BY\n")
|
||||
fmt.Fprintln(w, "ID\tCREATED\tCREATED BY")
|
||||
return image.WalkHistory(func(img *Image) error {
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\n",
|
||||
srv.runtime.repositories.ImageName(img.Id),
|
||||
srv.runtime.repositories.ImageName(img.ShortId()),
|
||||
HumanDuration(time.Now().Sub(img.Created))+" ago",
|
||||
strings.Join(img.ContainerConfig.Cmd, " "),
|
||||
)
|
||||
@@ -415,7 +419,8 @@ func (srv *Server) CmdKill(stdin io.ReadCloser, stdout io.Writer, args ...string
|
||||
return nil
|
||||
}
|
||||
|
||||
func (srv *Server) CmdImport(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||
func (srv *Server) CmdImport(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
|
||||
stdout.Flush()
|
||||
cmd := rcli.Subcmd(stdout, "import", "[OPTIONS] URL|- [REPOSITORY [TAG]]", "Create a new filesystem image from the contents of a tarball")
|
||||
var archive io.Reader
|
||||
var resp *http.Response
|
||||
@@ -438,7 +443,7 @@ func (srv *Server) CmdImport(stdin io.ReadCloser, stdout io.Writer, args ...stri
|
||||
u.Host = src
|
||||
u.Path = ""
|
||||
}
|
||||
fmt.Fprintf(stdout, "Downloading from %s\n", u.String())
|
||||
fmt.Fprintln(stdout, "Downloading from", u)
|
||||
// Download with curl (pretty progress bar)
|
||||
// If curl is not available, fallback to http.Get()
|
||||
resp, err = Download(u.String(), stdout)
|
||||
@@ -458,11 +463,11 @@ func (srv *Server) CmdImport(stdin io.ReadCloser, stdout io.Writer, args ...stri
|
||||
return err
|
||||
}
|
||||
}
|
||||
fmt.Fprintln(stdout, img.Id)
|
||||
fmt.Fprintln(stdout, img.ShortId())
|
||||
return nil
|
||||
}
|
||||
|
||||
func (srv *Server) CmdPush(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||
func (srv *Server) CmdPush(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
|
||||
cmd := rcli.Subcmd(stdout, "push", "NAME", "Push an image or a repository to the registry")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
@@ -510,10 +515,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 {
|
||||
@@ -533,6 +537,7 @@ func (srv *Server) CmdPull(stdin io.ReadCloser, stdout io.Writer, args ...string
|
||||
return nil
|
||||
}
|
||||
|
||||
// FIXME: CmdPull should be a wrapper around Runtime.Pull()
|
||||
if srv.runtime.graph.LookupRemoteImage(remote, srv.runtime.authConfig) {
|
||||
if err := srv.runtime.graph.PullImage(stdout, remote, srv.runtime.authConfig); err != nil {
|
||||
return err
|
||||
@@ -564,7 +569,7 @@ func (srv *Server) CmdImages(stdin io.ReadCloser, stdout io.Writer, args ...stri
|
||||
}
|
||||
w := tabwriter.NewWriter(stdout, 20, 1, 3, ' ', 0)
|
||||
if !*quiet {
|
||||
fmt.Fprintf(w, "REPOSITORY\tTAG\tID\tCREATED\tPARENT\n")
|
||||
fmt.Fprintln(w, "REPOSITORY\tTAG\tID\tCREATED\tPARENT")
|
||||
}
|
||||
var allImages map[string]*Image
|
||||
var err error
|
||||
@@ -591,7 +596,7 @@ func (srv *Server) CmdImages(stdin io.ReadCloser, stdout io.Writer, args ...stri
|
||||
for idx, field := range []string{
|
||||
/* REPOSITORY */ name,
|
||||
/* TAG */ tag,
|
||||
/* ID */ id,
|
||||
/* ID */ TruncateId(id),
|
||||
/* CREATED */ HumanDuration(time.Now().Sub(image.Created)) + " ago",
|
||||
/* PARENT */ srv.runtime.repositories.ImageName(image.Parent),
|
||||
} {
|
||||
@@ -603,7 +608,7 @@ func (srv *Server) CmdImages(stdin io.ReadCloser, stdout io.Writer, args ...stri
|
||||
}
|
||||
w.Write([]byte{'\n'})
|
||||
} else {
|
||||
stdout.Write([]byte(image.Id + "\n"))
|
||||
stdout.Write([]byte(image.ShortId() + "\n"))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -614,7 +619,7 @@ func (srv *Server) CmdImages(stdin io.ReadCloser, stdout io.Writer, args ...stri
|
||||
for idx, field := range []string{
|
||||
/* REPOSITORY */ "<none>",
|
||||
/* TAG */ "<none>",
|
||||
/* ID */ id,
|
||||
/* ID */ TruncateId(id),
|
||||
/* CREATED */ HumanDuration(time.Now().Sub(image.Created)) + " ago",
|
||||
/* PARENT */ srv.runtime.repositories.ImageName(image.Parent),
|
||||
} {
|
||||
@@ -626,7 +631,7 @@ func (srv *Server) CmdImages(stdin io.ReadCloser, stdout io.Writer, args ...stri
|
||||
}
|
||||
w.Write([]byte{'\n'})
|
||||
} else {
|
||||
stdout.Write([]byte(image.Id + "\n"))
|
||||
stdout.Write([]byte(image.ShortId() + "\n"))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -647,7 +652,7 @@ func (srv *Server) CmdPs(stdin io.ReadCloser, stdout io.Writer, args ...string)
|
||||
}
|
||||
w := tabwriter.NewWriter(stdout, 12, 1, 3, ' ', 0)
|
||||
if !*quiet {
|
||||
fmt.Fprintf(w, "ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tCOMMENT\n")
|
||||
fmt.Fprintln(w, "ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tCOMMENT")
|
||||
}
|
||||
for _, container := range srv.runtime.List() {
|
||||
if !container.State.Running && !*flAll {
|
||||
@@ -700,7 +705,7 @@ func (srv *Server) CmdCommit(stdin io.ReadCloser, stdout io.Writer, args ...stri
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintln(stdout, img.Id)
|
||||
fmt.Fprintln(stdout, img.ShortId())
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -782,7 +787,7 @@ func (srv *Server) CmdLogs(stdin io.ReadCloser, stdout io.Writer, args ...string
|
||||
return fmt.Errorf("No such container: %s", cmd.Arg(0))
|
||||
}
|
||||
|
||||
func (srv *Server) CmdAttach(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||
func (srv *Server) CmdAttach(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
|
||||
cmd := rcli.Subcmd(stdout, "attach", "CONTAINER", "Attach to a running container")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
@@ -797,55 +802,12 @@ func (srv *Server) CmdAttach(stdin io.ReadCloser, stdout io.Writer, args ...stri
|
||||
return fmt.Errorf("No such container: %s", name)
|
||||
}
|
||||
|
||||
cStdout, err := container.StdoutPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
if container.Config.Tty {
|
||||
stdout.SetOptionRawTerminal()
|
||||
}
|
||||
cStderr, err := container.StderrPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
if container.Config.OpenStdin {
|
||||
cStdin, err := container.StdinPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
Debugf("Begin stdin pipe [attach]")
|
||||
io.Copy(cStdin, stdin)
|
||||
|
||||
// When stdin get closed, it means the client has been detached
|
||||
// Make sure all pipes are closed.
|
||||
if err := cStdout.Close(); err != nil {
|
||||
Debugf("Error closing stdin pipe: %s", err)
|
||||
}
|
||||
if err := cStderr.Close(); err != nil {
|
||||
Debugf("Error closing stderr pipe: %s", err)
|
||||
}
|
||||
|
||||
wg.Add(-1)
|
||||
Debugf("End of stdin pipe [attach]")
|
||||
}()
|
||||
}
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
Debugf("Begin stdout pipe [attach]")
|
||||
io.Copy(stdout, cStdout)
|
||||
wg.Add(-1)
|
||||
Debugf("End of stdout pipe [attach]")
|
||||
}()
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
Debugf("Begin stderr pipe [attach]")
|
||||
io.Copy(stdout, cStderr)
|
||||
wg.Add(-1)
|
||||
Debugf("End of stderr pipe [attach]")
|
||||
}()
|
||||
wg.Wait()
|
||||
return nil
|
||||
// Flush the options to make sure the client sets the raw mode
|
||||
stdout.Flush()
|
||||
return <-container.Attach(stdin, nil, stdout, stdout)
|
||||
}
|
||||
|
||||
// Ports type - Used to parse multiple -p flags
|
||||
@@ -876,6 +838,33 @@ func (opts *ListOpts) Set(value string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// AttachOpts stores arguments to 'docker run -a', eg. which streams to attach to
|
||||
type AttachOpts map[string]bool
|
||||
|
||||
func NewAttachOpts() AttachOpts {
|
||||
return make(AttachOpts)
|
||||
}
|
||||
|
||||
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 {
|
||||
if val != "stdin" && val != "stdout" && val != "stderr" {
|
||||
return fmt.Errorf("Unsupported stream name: %s", val)
|
||||
}
|
||||
opts[val] = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (opts AttachOpts) Get(val string) bool {
|
||||
if res, exists := opts[val]; exists {
|
||||
return res
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (srv *Server) CmdTag(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||
cmd := rcli.Subcmd(stdout, "tag", "[OPTIONS] IMAGE REPOSITORY [TAG]", "Tag an image into a repository")
|
||||
force := cmd.Bool("f", false, "Force")
|
||||
@@ -889,7 +878,7 @@ func (srv *Server) CmdTag(stdin io.ReadCloser, stdout io.Writer, args ...string)
|
||||
return srv.runtime.repositories.Set(cmd.Arg(1), cmd.Arg(2), cmd.Arg(0), *force)
|
||||
}
|
||||
|
||||
func (srv *Server) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||
func (srv *Server) CmdRun(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
|
||||
config, err := ParseRun(args, stdout)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -903,6 +892,13 @@ func (srv *Server) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...string)
|
||||
return fmt.Errorf("Command not specified")
|
||||
}
|
||||
|
||||
if config.Tty {
|
||||
stdout.SetOptionRawTerminal()
|
||||
}
|
||||
// Flush the options to make sure the client sets the raw mode
|
||||
// or tell the client there is no options
|
||||
stdout.Flush()
|
||||
|
||||
// Create new container
|
||||
container, err := srv.runtime.Create(config)
|
||||
if err != nil {
|
||||
@@ -919,54 +915,37 @@ func (srv *Server) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...string)
|
||||
return err
|
||||
}
|
||||
}
|
||||
if config.OpenStdin {
|
||||
cmdStdin, err := container.StdinPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !config.Detach {
|
||||
Go(func() error {
|
||||
_, err := io.Copy(cmdStdin, stdin)
|
||||
return err
|
||||
})
|
||||
}
|
||||
var (
|
||||
cStdin io.ReadCloser
|
||||
cStdout, cStderr io.Writer
|
||||
)
|
||||
if config.AttachStdin {
|
||||
r, w := io.Pipe()
|
||||
go func() {
|
||||
defer w.Close()
|
||||
defer Debugf("Closing buffered stdin pipe")
|
||||
io.Copy(w, stdin)
|
||||
}()
|
||||
cStdin = r
|
||||
}
|
||||
// Run the container
|
||||
if !config.Detach {
|
||||
cmdStderr, err := container.StderrPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmdStdout, err := container.StdoutPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := container.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
sendingStdout := Go(func() error {
|
||||
_, err := io.Copy(stdout, cmdStdout)
|
||||
return err
|
||||
})
|
||||
sendingStderr := Go(func() error {
|
||||
_, err := io.Copy(stdout, cmdStderr)
|
||||
return err
|
||||
})
|
||||
errSendingStdout := <-sendingStdout
|
||||
errSendingStderr := <-sendingStderr
|
||||
if errSendingStdout != nil {
|
||||
return errSendingStdout
|
||||
}
|
||||
if errSendingStderr != nil {
|
||||
return errSendingStderr
|
||||
}
|
||||
container.Wait()
|
||||
} else {
|
||||
if err := container.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
if config.AttachStdout {
|
||||
cStdout = stdout
|
||||
}
|
||||
if config.AttachStderr {
|
||||
cStderr = stdout // FIXME: rcli can't differentiate stdout from stderr
|
||||
}
|
||||
|
||||
attachErr := container.Attach(cStdin, stdin, cStdout, cStderr)
|
||||
Debugf("Starting\n")
|
||||
if err := container.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
if cStdout == nil && cStderr == nil {
|
||||
fmt.Fprintln(stdout, container.ShortId())
|
||||
}
|
||||
Debugf("Waiting for attach to return\n")
|
||||
<-attachErr
|
||||
// Expecting I/O pipe error, discarding
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
+305
-63
@@ -3,7 +3,9 @@ package docker
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/rcli"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -24,7 +26,7 @@ func closeWrap(args ...io.Closer) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func setTimeout(t *testing.T, msg string, d time.Duration, f func(chan bool)) {
|
||||
func setTimeout(t *testing.T, msg string, d time.Duration, f func()) {
|
||||
c := make(chan bool)
|
||||
|
||||
// Make sure we are not too long
|
||||
@@ -32,9 +34,12 @@ func setTimeout(t *testing.T, msg string, d time.Duration, f func(chan bool)) {
|
||||
time.Sleep(d)
|
||||
c <- true
|
||||
}()
|
||||
go f(c)
|
||||
if timeout := <-c; timeout {
|
||||
t.Fatalf("Timeout: %s", msg)
|
||||
go func() {
|
||||
f()
|
||||
c <- false
|
||||
}()
|
||||
if <-c {
|
||||
t.Fatal(msg)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,75 +59,312 @@ func assertPipe(input, output string, r io.Reader, w io.Writer, count int) error
|
||||
return nil
|
||||
}
|
||||
|
||||
// Test the behavior of a client disconnection.
|
||||
// We expect a client disconnect to leave the stdin of the container open
|
||||
// Therefore a process will keep his stdin open when a client disconnects
|
||||
func TestReattachAfterDisconnect(t *testing.T) {
|
||||
// TestRunHostname checks that 'docker run -h' correctly sets a custom hostname
|
||||
func TestRunHostname(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(runtime)
|
||||
|
||||
// FIXME: low down the timeout (after #230)
|
||||
setTimeout(t, "TestReattachAfterDisconnect", 12*time.Second, func(timeout chan bool) {
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
stdin, _ := io.Pipe()
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
|
||||
stdin, stdinPipe := io.Pipe()
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
c1 := make(chan struct{})
|
||||
go func() {
|
||||
if err := srv.CmdRun(stdin, stdoutPipe, "-i", GetTestImage(runtime).Id, "/bin/cat"); err == nil {
|
||||
t.Fatal("CmdRun should generate a read/write on closed pipe error. No error found.")
|
||||
}
|
||||
close(c1)
|
||||
}()
|
||||
|
||||
if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
|
||||
c := make(chan struct{})
|
||||
go func() {
|
||||
if err := srv.CmdRun(stdin, rcli.NewDockerLocalConn(stdoutPipe), "-h", "foobar", GetTestImage(runtime).Id, "hostname"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
close(c)
|
||||
}()
|
||||
cmdOutput, err := bufio.NewReader(stdout).ReadString('\n')
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if cmdOutput != "foobar\n" {
|
||||
t.Fatalf("'hostname' should display '%s', not '%s'", "foobar\n", cmdOutput)
|
||||
}
|
||||
|
||||
// Close pipes (simulate disconnect)
|
||||
if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
container := runtime.containers.Back().Value.(*Container)
|
||||
|
||||
// Recreate the pipes
|
||||
stdin, stdinPipe = io.Pipe()
|
||||
stdout, stdoutPipe = io.Pipe()
|
||||
|
||||
// Attach to it
|
||||
c2 := make(chan struct{})
|
||||
go func() {
|
||||
if err := srv.CmdAttach(stdin, stdoutPipe, container.Id); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
close(c2)
|
||||
}()
|
||||
|
||||
if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Close pipes
|
||||
if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// FIXME: when #230 will be finished, send SIGINT instead of SIGTERM
|
||||
// we expect cat to stay alive so SIGTERM will have no effect
|
||||
// and Stop will timeout
|
||||
if err := container.Stop(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Wait for run and attach to finish
|
||||
<-c1
|
||||
<-c2
|
||||
|
||||
// Finished, no timeout
|
||||
timeout <- false
|
||||
setTimeout(t, "CmdRun timed out", 2*time.Second, func() {
|
||||
<-c
|
||||
})
|
||||
}
|
||||
|
||||
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, rcli.NewDockerLocalConn(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()
|
||||
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() {
|
||||
// We're simulating a disconnect so the return value doesn't matter. What matters is the
|
||||
// fact that CmdRun returns.
|
||||
srv.CmdRun(stdin, rcli.NewDockerLocalConn(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)
|
||||
}
|
||||
})
|
||||
|
||||
// Close pipes (simulate disconnect)
|
||||
if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// as the pipes are close, we expect the process to die,
|
||||
// therefore CmdRun to unblock. Wait for CmdRun
|
||||
setTimeout(t, "Waiting for CmdRun timed out", 2*time.Second, func() {
|
||||
<-c1
|
||||
})
|
||||
|
||||
// Client disconnect after run -i should cause stdin to be closed, which should
|
||||
// cause /bin/cat to exit.
|
||||
setTimeout(t, "Waiting for /bin/cat to exit timed out", 2*time.Second, func() {
|
||||
container := runtime.List()[0]
|
||||
container.Wait()
|
||||
if container.State.Running {
|
||||
t.Fatalf("/bin/cat is still running after closing stdin")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Expected behaviour: the process dies when the client disconnects
|
||||
func TestRunDisconnectTty(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() {
|
||||
// We're simulating a disconnect so the return value doesn't matter. What matters is the
|
||||
// fact that CmdRun returns.
|
||||
srv.CmdRun(stdin, rcli.NewDockerLocalConn(stdoutPipe), "-i", "-t", 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)
|
||||
}
|
||||
})
|
||||
|
||||
// Close pipes (simulate disconnect)
|
||||
if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// as the pipes are close, we expect the process to die,
|
||||
// therefore CmdRun to unblock. Wait for CmdRun
|
||||
setTimeout(t, "Waiting for CmdRun timed out", 2*time.Second, func() {
|
||||
<-c1
|
||||
})
|
||||
|
||||
// Client disconnect after run -i should keep stdin out in TTY mode
|
||||
container := runtime.List()[0]
|
||||
// Give some time to monitor to do his thing
|
||||
container.WaitTimeout(500 * time.Millisecond)
|
||||
if !container.State.Running {
|
||||
t.Fatalf("/bin/cat should still be running after closing stdin (tty mode)")
|
||||
}
|
||||
}
|
||||
|
||||
// TestAttachStdin checks attaching to stdin without stdout and stderr.
|
||||
// 'docker run -i -a stdin' should sends the client's stdin to the command,
|
||||
// then detach from it and print the container id.
|
||||
func TestRunAttachStdin(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()
|
||||
|
||||
ch := make(chan struct{})
|
||||
go func() {
|
||||
srv.CmdRun(stdin, rcli.NewDockerLocalConn(stdoutPipe), "-i", "-a", "stdin", GetTestImage(runtime).Id, "sh", "-c", "echo hello; cat")
|
||||
close(ch)
|
||||
}()
|
||||
|
||||
// Send input to the command, close stdin
|
||||
setTimeout(t, "Write timed out", 2*time.Second, func() {
|
||||
if _, err := stdinPipe.Write([]byte("hi there\n")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := stdinPipe.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
container := runtime.List()[0]
|
||||
|
||||
// Check output
|
||||
cmdOutput, err := bufio.NewReader(stdout).ReadString('\n')
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if cmdOutput != container.ShortId()+"\n" {
|
||||
t.Fatalf("Wrong output: should be '%s', not '%s'\n", container.ShortId()+"\n", cmdOutput)
|
||||
}
|
||||
|
||||
// wait for CmdRun to return
|
||||
setTimeout(t, "Waiting for CmdRun timed out", 2*time.Second, func() {
|
||||
<-ch
|
||||
})
|
||||
|
||||
setTimeout(t, "Waiting for command to exit timed out", 2*time.Second, func() {
|
||||
container.Wait()
|
||||
})
|
||||
|
||||
// Check logs
|
||||
if cmdLogs, err := container.ReadLog("stdout"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
if output, err := ioutil.ReadAll(cmdLogs); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
expectedLog := "hello\nhi there\n"
|
||||
if string(output) != expectedLog {
|
||||
t.Fatalf("Unexpected logs: should be '%s', not '%s'\n", expectedLog, output)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Expected behaviour, the process stays alive when the client disconnects
|
||||
func TestAttachDisconnect(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
container, err := runtime.Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).Id,
|
||||
Memory: 33554432,
|
||||
Cmd: []string{"/bin/cat"},
|
||||
OpenStdin: true,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer runtime.Destroy(container)
|
||||
|
||||
// Start the process
|
||||
if err := container.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
stdin, stdinPipe := io.Pipe()
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
|
||||
// Attach to it
|
||||
c1 := make(chan struct{})
|
||||
go func() {
|
||||
// We're simulating a disconnect so the return value doesn't matter. What matters is the
|
||||
// fact that CmdAttach returns.
|
||||
srv.CmdAttach(stdin, rcli.NewDockerLocalConn(stdoutPipe), container.Id)
|
||||
close(c1)
|
||||
}()
|
||||
|
||||
setTimeout(t, "First read/write assertion timed out", 2*time.Second, func() {
|
||||
if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
// Close pipes (client disconnects)
|
||||
if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Wait for attach to finish, the client disconnected, therefore, Attach finished his job
|
||||
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)
|
||||
if err == nil || !container.State.Running {
|
||||
t.Fatalf("/bin/cat is not running after closing stdin")
|
||||
}
|
||||
|
||||
// Try to avoid the timeoout in destroy. Best effort, don't check error
|
||||
cStdin, _ := container.StdinPipe()
|
||||
cStdin.Close()
|
||||
}
|
||||
|
||||
+218
-104
@@ -40,25 +40,28 @@ type Container struct {
|
||||
stdin io.ReadCloser
|
||||
stdinPipe io.WriteCloser
|
||||
|
||||
ptyStdinMaster io.Closer
|
||||
ptyStdoutMaster io.Closer
|
||||
ptyStderrMaster io.Closer
|
||||
ptyMaster io.Closer
|
||||
|
||||
runtime *Runtime
|
||||
|
||||
waitLock chan struct{}
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Hostname string
|
||||
User string
|
||||
Memory int64 // Memory limit (in bytes)
|
||||
MemorySwap int64 // Total memory usage (memory + swap); set `-1' to disable swap
|
||||
Detach bool
|
||||
Ports []int
|
||||
Tty bool // Attach standard streams to a tty, including stdin if it is not closed.
|
||||
OpenStdin bool // Open stdin
|
||||
Env []string
|
||||
Cmd []string
|
||||
Image string // Name of the image as it was passed by the operator (eg. could be symbolic)
|
||||
Hostname string
|
||||
User string
|
||||
Memory int64 // Memory limit (in bytes)
|
||||
MemorySwap int64 // Total memory usage (memory + swap); set `-1' to disable swap
|
||||
AttachStdin bool
|
||||
AttachStdout bool
|
||||
AttachStderr bool
|
||||
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.
|
||||
Env []string
|
||||
Cmd []string
|
||||
Image string // Name of the image as it was passed by the operator (eg. could be symbolic)
|
||||
}
|
||||
|
||||
func ParseRun(args []string, stdout io.Writer) (*Config, error) {
|
||||
@@ -67,19 +70,37 @@ func ParseRun(args []string, stdout io.Writer) (*Config, error) {
|
||||
cmd.SetOutput(ioutil.Discard)
|
||||
}
|
||||
|
||||
flHostname := cmd.String("h", "", "Container host name")
|
||||
flUser := cmd.String("u", "", "Username or UID")
|
||||
flDetach := cmd.Bool("d", false, "Detached mode: leave the container running in the background")
|
||||
flAttach := NewAttachOpts()
|
||||
cmd.Var(flAttach, "a", "Attach to stdin, stdout or stderr.")
|
||||
flStdin := cmd.Bool("i", false, "Keep stdin open even if not attached")
|
||||
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")
|
||||
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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 !*flDetach {
|
||||
flAttach.Set("stdout")
|
||||
flAttach.Set("stderr")
|
||||
if *flStdin {
|
||||
flAttach.Set("stdin")
|
||||
}
|
||||
}
|
||||
}
|
||||
parsedArgs := cmd.Args()
|
||||
runCmd := []string{}
|
||||
image := ""
|
||||
@@ -90,15 +111,22 @@ func ParseRun(args []string, stdout io.Writer) (*Config, error) {
|
||||
runCmd = parsedArgs[1:]
|
||||
}
|
||||
config := &Config{
|
||||
Ports: flPorts,
|
||||
User: *flUser,
|
||||
Tty: *flTty,
|
||||
OpenStdin: *flStdin,
|
||||
Memory: *flMemory,
|
||||
Detach: *flDetach,
|
||||
Env: flEnv,
|
||||
Cmd: runCmd,
|
||||
Image: image,
|
||||
Hostname: *flHostname,
|
||||
PortSpecs: flPorts,
|
||||
User: *flUser,
|
||||
Tty: *flTty,
|
||||
OpenStdin: *flStdin,
|
||||
Memory: *flMemory,
|
||||
AttachStdin: flAttach.Get("stdin"),
|
||||
AttachStdout: flAttach.Get("stdout"),
|
||||
AttachStderr: flAttach.Get("stderr"),
|
||||
Env: flEnv,
|
||||
Cmd: runCmd,
|
||||
Image: image,
|
||||
}
|
||||
// When allocating stdin in attached mode, close stdin at client disconnect
|
||||
if config.OpenStdin && config.AttachStdin {
|
||||
config.StdinOnce = true
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
@@ -107,6 +135,7 @@ type NetworkSettings struct {
|
||||
IpAddress string
|
||||
IpPrefixLen int
|
||||
Gateway string
|
||||
Bridge string
|
||||
PortMapping map[string]string
|
||||
}
|
||||
|
||||
@@ -151,63 +180,37 @@ func (container *Container) generateLXCConfig() error {
|
||||
}
|
||||
|
||||
func (container *Container) startPty() error {
|
||||
stdoutMaster, stdoutSlave, err := pty.Open()
|
||||
ptyMaster, ptySlave, err := pty.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
container.ptyStdoutMaster = stdoutMaster
|
||||
container.cmd.Stdout = stdoutSlave
|
||||
|
||||
stderrMaster, stderrSlave, err := pty.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
container.ptyStderrMaster = stderrMaster
|
||||
container.cmd.Stderr = stderrSlave
|
||||
container.ptyMaster = ptyMaster
|
||||
container.cmd.Stdout = ptySlave
|
||||
container.cmd.Stderr = ptySlave
|
||||
|
||||
// Copy the PTYs to our broadcasters
|
||||
go func() {
|
||||
defer container.stdout.Close()
|
||||
defer container.stdout.CloseWriters()
|
||||
Debugf("[startPty] Begin of stdout pipe")
|
||||
io.Copy(container.stdout, stdoutMaster)
|
||||
io.Copy(container.stdout, ptyMaster)
|
||||
Debugf("[startPty] End of stdout pipe")
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer container.stderr.Close()
|
||||
Debugf("[startPty] Begin of stderr pipe")
|
||||
io.Copy(container.stderr, stderrMaster)
|
||||
Debugf("[startPty] End of stderr pipe")
|
||||
}()
|
||||
|
||||
// stdin
|
||||
var stdinSlave io.ReadCloser
|
||||
if container.Config.OpenStdin {
|
||||
var stdinMaster io.WriteCloser
|
||||
stdinMaster, stdinSlave, err = pty.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
container.ptyStdinMaster = stdinMaster
|
||||
container.cmd.Stdin = stdinSlave
|
||||
// FIXME: The following appears to be broken.
|
||||
// "cannot set terminal process group (-1): Inappropriate ioctl for device"
|
||||
// container.cmd.SysProcAttr = &syscall.SysProcAttr{Setctty: true, Setsid: true}
|
||||
container.cmd.Stdin = ptySlave
|
||||
container.cmd.SysProcAttr = &syscall.SysProcAttr{Setctty: true, Setsid: true}
|
||||
go func() {
|
||||
defer container.stdin.Close()
|
||||
Debugf("[startPty] Begin of stdin pipe")
|
||||
io.Copy(stdinMaster, container.stdin)
|
||||
io.Copy(ptyMaster, container.stdin)
|
||||
Debugf("[startPty] End of stdin pipe")
|
||||
}()
|
||||
}
|
||||
if err := container.cmd.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
stdoutSlave.Close()
|
||||
stderrSlave.Close()
|
||||
if stdinSlave != nil {
|
||||
stdinSlave.Close()
|
||||
}
|
||||
ptySlave.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -229,7 +232,123 @@ func (container *Container) start() error {
|
||||
return container.cmd.Start()
|
||||
}
|
||||
|
||||
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 {
|
||||
nJobs += 1
|
||||
if cStdin, err := container.StdinPipe(); err != nil {
|
||||
errors <- err
|
||||
} else {
|
||||
go func() {
|
||||
Debugf("[start] 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 && !container.Config.Tty {
|
||||
defer cStdin.Close()
|
||||
}
|
||||
if container.Config.Tty {
|
||||
_, err = CopyEscapable(cStdin, stdin)
|
||||
} else {
|
||||
_, err = io.Copy(cStdin, stdin)
|
||||
}
|
||||
if err != nil {
|
||||
Debugf("[error] attach stdin: %s\n", err)
|
||||
}
|
||||
// Discard error, expecting pipe error
|
||||
errors <- nil
|
||||
}()
|
||||
}
|
||||
}
|
||||
if stdout != nil {
|
||||
nJobs += 1
|
||||
if p, err := container.StdoutPipe(); err != nil {
|
||||
errors <- err
|
||||
} else {
|
||||
cStdout = p
|
||||
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)
|
||||
}
|
||||
errors <- err
|
||||
}()
|
||||
}
|
||||
}
|
||||
if stderr != nil {
|
||||
nJobs += 1
|
||||
if p, err := container.StderrPipe(); err != nil {
|
||||
errors <- err
|
||||
} else {
|
||||
cStderr = p
|
||||
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)
|
||||
}
|
||||
errors <- err
|
||||
}()
|
||||
}
|
||||
}
|
||||
return Go(func() error {
|
||||
if cStdout != nil {
|
||||
defer cStdout.Close()
|
||||
}
|
||||
if cStderr != nil {
|
||||
defer cStderr.Close()
|
||||
}
|
||||
// FIXME: how do clean up the stdin goroutine without the unwanted side effect
|
||||
// of closing the passed stdin? Add an intermediary io.Pipe?
|
||||
for i := 0; i < nJobs; i += 1 {
|
||||
Debugf("Waiting for job %d/%d\n", i+1, nJobs)
|
||||
if err := <-errors; err != nil {
|
||||
Debugf("Job %d returned error %s. Aborting all jobs\n", i+1, err)
|
||||
return err
|
||||
}
|
||||
Debugf("Job %d completed successfully\n", i+1)
|
||||
}
|
||||
Debugf("All jobs completed successfully\n")
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (container *Container) Start() error {
|
||||
container.State.lock()
|
||||
defer container.State.unlock()
|
||||
|
||||
if container.State.Running {
|
||||
return fmt.Errorf("The container %s is already running.", container.Id)
|
||||
}
|
||||
if err := container.EnsureMounted(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -293,6 +412,9 @@ func (container *Container) Start() error {
|
||||
// FIXME: save state on disk *first*, then converge
|
||||
// this way disk state is used as a journal, eg. we can restore after crash etc.
|
||||
container.State.setRunning(container.cmd.Process.Pid)
|
||||
|
||||
// Init the lock
|
||||
container.waitLock = make(chan struct{})
|
||||
container.ToDisk()
|
||||
go container.monitor()
|
||||
return nil
|
||||
@@ -345,26 +467,26 @@ 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
|
||||
container.NetworkSettings.Bridge = container.runtime.networkManager.bridgeIface
|
||||
container.NetworkSettings.IpAddress = iface.IPNet.IP.String()
|
||||
container.NetworkSettings.IpPrefixLen, _ = iface.IPNet.Mask.Size()
|
||||
container.NetworkSettings.Gateway = iface.Gateway.String()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (container *Container) releaseNetwork() error {
|
||||
err := container.network.Release()
|
||||
func (container *Container) releaseNetwork() {
|
||||
container.network.Release()
|
||||
container.network = nil
|
||||
container.NetworkSettings = &NetworkSettings{}
|
||||
return err
|
||||
}
|
||||
|
||||
func (container *Container) monitor() {
|
||||
@@ -379,34 +501,22 @@ func (container *Container) monitor() {
|
||||
exitCode := container.cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus()
|
||||
|
||||
// Cleanup
|
||||
if err := container.releaseNetwork(); err != nil {
|
||||
log.Printf("%v: Failed to release network: %v", container.Id, err)
|
||||
}
|
||||
container.releaseNetwork()
|
||||
if container.Config.OpenStdin {
|
||||
if err := container.stdin.Close(); err != nil {
|
||||
Debugf("%s: Error close stdin: %s", container.Id, err)
|
||||
}
|
||||
}
|
||||
if err := container.stdout.Close(); err != nil {
|
||||
if err := container.stdout.CloseWriters(); err != nil {
|
||||
Debugf("%s: Error close stdout: %s", container.Id, err)
|
||||
}
|
||||
if err := container.stderr.Close(); err != nil {
|
||||
if err := container.stderr.CloseWriters(); err != nil {
|
||||
Debugf("%s: Error close stderr: %s", container.Id, err)
|
||||
}
|
||||
|
||||
if container.ptyStdinMaster != nil {
|
||||
if err := container.ptyStdinMaster.Close(); err != nil {
|
||||
Debugf("%s: Error close pty stdin master: %s", container.Id, err)
|
||||
}
|
||||
}
|
||||
if container.ptyStdoutMaster != nil {
|
||||
if err := container.ptyStdoutMaster.Close(); err != nil {
|
||||
Debugf("%s: Error close pty stdout master: %s", container.Id, err)
|
||||
}
|
||||
}
|
||||
if container.ptyStderrMaster != nil {
|
||||
if err := container.ptyStderrMaster.Close(); err != nil {
|
||||
Debugf("%s: Error close pty stderr master: %s", container.Id, err)
|
||||
if container.ptyMaster != nil {
|
||||
if err := container.ptyMaster.Close(); err != nil {
|
||||
Debugf("%s: Error closing Pty master: %s", container.Id, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -421,13 +531,23 @@ func (container *Container) monitor() {
|
||||
|
||||
// Report status back
|
||||
container.State.setStopped(exitCode)
|
||||
|
||||
// Release the lock
|
||||
close(container.waitLock)
|
||||
|
||||
if err := container.ToDisk(); err != nil {
|
||||
log.Printf("%s: Failed to dump configuration to the disk: %s", container.Id, err)
|
||||
// FIXME: there is a race condition here which causes this to fail during the unit tests.
|
||||
// If another goroutine was waiting for Wait() to return before removing the container's root
|
||||
// from the filesystem... At this point it may already have done so.
|
||||
// This is because State.setStopped() has already been called, and has caused Wait()
|
||||
// to return.
|
||||
// FIXME: why are we serializing running state to disk in the first place?
|
||||
//log.Printf("%s: Failed to dump configuration to the disk: %s", container.Id, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (container *Container) kill() error {
|
||||
if container.cmd == nil {
|
||||
if !container.State.Running || container.cmd == nil {
|
||||
return nil
|
||||
}
|
||||
if err := container.cmd.Process.Kill(); err != nil {
|
||||
@@ -439,22 +559,23 @@ func (container *Container) kill() error {
|
||||
}
|
||||
|
||||
func (container *Container) Kill() error {
|
||||
if !container.State.Running {
|
||||
return nil
|
||||
}
|
||||
container.State.lock()
|
||||
defer container.State.unlock()
|
||||
return container.kill()
|
||||
}
|
||||
|
||||
func (container *Container) Stop() error {
|
||||
container.State.lock()
|
||||
defer container.State.unlock()
|
||||
if !container.State.Running {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 1. Send a SIGTERM
|
||||
if output, err := exec.Command("lxc-kill", "-n", container.Id, "15").CombinedOutput(); err != nil {
|
||||
log.Printf(string(output))
|
||||
log.Printf("Failed to send SIGTERM to the process, force killing")
|
||||
if err := container.Kill(); err != nil {
|
||||
log.Print(string(output))
|
||||
log.Print("Failed to send SIGTERM to the process, force killing")
|
||||
if err := container.kill(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -462,7 +583,7 @@ func (container *Container) Stop() error {
|
||||
// 2. Wait for the process to exit on its own
|
||||
if err := container.WaitTimeout(10 * time.Second); err != nil {
|
||||
log.Printf("Container %v failed to exit within 10 seconds of SIGTERM - using the force", container.Id)
|
||||
if err := container.Kill(); err != nil {
|
||||
if err := container.kill(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -481,10 +602,7 @@ func (container *Container) Restart() error {
|
||||
|
||||
// Wait blocks until the container stops running, then returns its exit code.
|
||||
func (container *Container) Wait() int {
|
||||
|
||||
for container.State.Running {
|
||||
container.State.wait()
|
||||
}
|
||||
<-container.waitLock
|
||||
return container.State.ExitCode
|
||||
}
|
||||
|
||||
@@ -512,7 +630,7 @@ func (container *Container) WaitTimeout(timeout time.Duration) error {
|
||||
case <-done:
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
func (container *Container) EnsureMounted() error {
|
||||
@@ -560,11 +678,7 @@ func (container *Container) Unmount() error {
|
||||
// In case of a collision a lookup with Runtime.Get() will fail, and the caller
|
||||
// will need to use a langer prefix, or the full-length container Id.
|
||||
func (container *Container) ShortId() string {
|
||||
shortLen := 12
|
||||
if len(container.Id) < shortLen {
|
||||
shortLen = len(container.Id)
|
||||
}
|
||||
return container.Id[:shortLen]
|
||||
return TruncateId(container.Id)
|
||||
}
|
||||
|
||||
func (container *Container) logPath(name string) string {
|
||||
|
||||
+41
-2
@@ -227,10 +227,49 @@ func TestCommitRun(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(output) != "hello\n" {
|
||||
t.Fatalf("Unexpected output. Expected %s, received: %s (err: %s)", "hello\n", string(output), string(output2))
|
||||
t.Fatalf("Unexpected output. Expected %s, received: %s (err: %s)", "hello\n", output, output2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStart(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(runtime)
|
||||
container, err := runtime.Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).Id,
|
||||
Memory: 33554432,
|
||||
Cmd: []string{"/bin/cat"},
|
||||
OpenStdin: true,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer runtime.Destroy(container)
|
||||
|
||||
if err := container.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Give some time to the process to start
|
||||
container.WaitTimeout(500 * time.Millisecond)
|
||||
|
||||
if !container.State.Running {
|
||||
t.Errorf("Container should be running")
|
||||
}
|
||||
if err := container.Start(); err == nil {
|
||||
t.Fatalf("A running containter should be able to be started")
|
||||
}
|
||||
|
||||
// Try to avoid the timeoout in destroy. Best effort, don't check error
|
||||
cStdin, _ := container.StdinPipe()
|
||||
cStdin.Close()
|
||||
container.WaitTimeout(2 * time.Second)
|
||||
}
|
||||
|
||||
func TestRun(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
@@ -851,7 +890,7 @@ func BenchmarkRunSequencial(b *testing.B) {
|
||||
b.Fatal(err)
|
||||
}
|
||||
if string(output) != "foo" {
|
||||
b.Fatalf("Unexecpted output: %v", string(output))
|
||||
b.Fatalf("Unexpected output: %s", output)
|
||||
}
|
||||
if err := runtime.Destroy(container); err != nil {
|
||||
b.Fatal(err)
|
||||
|
||||
@@ -35,6 +35,5 @@ do
|
||||
cp -a /dev/$X dev
|
||||
done
|
||||
|
||||
tar -cf- . | docker put busybox
|
||||
docker run -i -a -u root busybox /bin/echo Success.
|
||||
|
||||
tar -cf- . | docker import - busybox
|
||||
docker run -i -u root busybox /bin/echo Success.
|
||||
|
||||
+25
-24
@@ -8,9 +8,10 @@ import (
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
)
|
||||
|
||||
var GIT_COMMIT string
|
||||
|
||||
func main() {
|
||||
if docker.SelfPath() == "/sbin/init" {
|
||||
// Running in init mode
|
||||
@@ -20,8 +21,17 @@ func main() {
|
||||
// FIXME: Switch d and D ? (to be more sshd like)
|
||||
flDaemon := flag.Bool("d", false, "Daemon mode")
|
||||
flDebug := flag.Bool("D", false, "Debug mode")
|
||||
bridgeName := flag.String("b", "", "Attach containers to a pre-existing network bridge")
|
||||
flag.Parse()
|
||||
rcli.DEBUG_FLAG = *flDebug
|
||||
if *bridgeName != "" {
|
||||
docker.NetworkBridgeIface = *bridgeName
|
||||
} else {
|
||||
docker.NetworkBridgeIface = docker.DefaultNetworkBridge
|
||||
}
|
||||
if *flDebug {
|
||||
os.Setenv("DEBUG", "1")
|
||||
}
|
||||
docker.GIT_COMMIT = GIT_COMMIT
|
||||
if *flDaemon {
|
||||
if flag.NArg() != 0 {
|
||||
flag.Usage()
|
||||
@@ -46,29 +56,21 @@ func daemon() error {
|
||||
}
|
||||
|
||||
func runCommand(args []string) error {
|
||||
var oldState *term.State
|
||||
var err error
|
||||
if term.IsTerminal(int(os.Stdin.Fd())) && os.Getenv("NORAW") == "" {
|
||||
oldState, err = term.MakeRaw(int(os.Stdin.Fd()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer term.Restore(int(os.Stdin.Fd()), oldState)
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, os.Interrupt)
|
||||
go func() {
|
||||
for _ = range c {
|
||||
term.Restore(int(os.Stdin.Fd()), oldState)
|
||||
log.Printf("\nSIGINT received\n")
|
||||
os.Exit(0)
|
||||
}
|
||||
}()
|
||||
}
|
||||
// FIXME: we want to use unix sockets here, but net.UnixConn doesn't expose
|
||||
// CloseWrite(), which we need to cleanly signal that stdin is closed without
|
||||
// closing the connection.
|
||||
// See http://code.google.com/p/go/issues/detail?id=3345
|
||||
if conn, err := rcli.Call("tcp", "127.0.0.1:4242", args...); err == nil {
|
||||
options := conn.GetOptions()
|
||||
if options.RawTerminal &&
|
||||
term.IsTerminal(int(os.Stdin.Fd())) &&
|
||||
os.Getenv("NORAW") == "" {
|
||||
if oldState, err := rcli.SetRawTerminal(); err != nil {
|
||||
return err
|
||||
} else {
|
||||
defer rcli.RestoreTerminal(oldState)
|
||||
}
|
||||
}
|
||||
receiveStdout := docker.Go(func() error {
|
||||
_, err := io.Copy(os.Stdout, conn)
|
||||
return err
|
||||
@@ -93,12 +95,11 @@ func runCommand(args []string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := rcli.LocalCall(service, os.Stdin, os.Stdout, args...); err != nil {
|
||||
dockerConn := rcli.NewDockerLocalConn(os.Stdout)
|
||||
defer dockerConn.Close()
|
||||
if err := rcli.LocalCall(service, os.Stdin, dockerConn, args...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if oldState != nil {
|
||||
term.Restore(int(os.Stdin.Fd()), oldState)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
+33
-1
@@ -39,4 +39,36 @@ Notes
|
||||
* The index.html and gettingstarted.html files are copied from the source dir to the output dir without modification.
|
||||
So changes to those pages should be made directly in html
|
||||
* For the template the css is compiled from less. When changes are needed they can be compiled using
|
||||
lessc ``lessc main.less`` or watched using watch-lessc ``watch-lessc -i main.less -o main.css``
|
||||
lessc ``lessc main.less`` or watched using watch-lessc ``watch-lessc -i main.less -o main.css``
|
||||
|
||||
|
||||
Guides on using sphinx
|
||||
----------------------
|
||||
* To make links to certain pages create a link target like so:
|
||||
|
||||
```
|
||||
.. _hello_world:
|
||||
|
||||
Hello world
|
||||
===========
|
||||
|
||||
This is.. (etc.)
|
||||
```
|
||||
|
||||
The ``_hello_world:`` will make it possible to link to this position (page and marker) from all other pages.
|
||||
|
||||
* Notes, warnings and alarms
|
||||
|
||||
```
|
||||
# a note (use when something is important)
|
||||
.. note::
|
||||
|
||||
# a warning (orange)
|
||||
.. warning::
|
||||
|
||||
# danger (red, use sparsely)
|
||||
.. danger::
|
||||
|
||||
* Code examples
|
||||
|
||||
Start without $, so it's easy to copy and paste.
|
||||
@@ -1,67 +0,0 @@
|
||||
:title: Base commands
|
||||
:description: Common usage and commands
|
||||
:keywords: Examples, Usage
|
||||
|
||||
|
||||
Base commands
|
||||
=============
|
||||
|
||||
|
||||
Running an interactive shell
|
||||
----------------------------
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Download a base image
|
||||
docker pull base
|
||||
|
||||
# Run an interactive shell in the base image,
|
||||
# allocate a tty, attach stdin and stdout
|
||||
docker run -i -t base /bin/bash
|
||||
|
||||
|
||||
Starting a long-running worker process
|
||||
--------------------------------------
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Run docker in daemon mode
|
||||
(sudo docker -d || echo "Docker daemon already running") &
|
||||
|
||||
# Start a very useful long-running process
|
||||
JOB=$(docker run -d base /bin/sh -c "while true; do echo Hello world; sleep 1; done")
|
||||
|
||||
# Collect the output of the job so far
|
||||
docker logs $JOB
|
||||
|
||||
# Kill the job
|
||||
docker kill $JOB
|
||||
|
||||
|
||||
Listing all running containers
|
||||
------------------------------
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker ps
|
||||
|
||||
Expose a service on a TCP port
|
||||
------------------------------
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Expose port 4444 of this container, and tell netcat to listen on it
|
||||
JOB=$(docker run -d -p 4444 base /bin/nc -l -p 4444)
|
||||
|
||||
# Which public port is NATed to my container?
|
||||
PORT=$(docker port $JOB 4444)
|
||||
|
||||
# Connect to the public port via the host's public address
|
||||
echo hello world | nc $(hostname) $PORT
|
||||
|
||||
# Verify that the network connection worked
|
||||
echo "Daemon received: $(docker logs $JOB)"
|
||||
|
||||
Continue to the complete `Command Line Interface`_
|
||||
|
||||
.. _Command Line Interface: ../commandline/cli.html
|
||||
@@ -0,0 +1,101 @@
|
||||
:title: Base commands
|
||||
:description: Common usage and commands
|
||||
:keywords: Examples, Usage
|
||||
|
||||
|
||||
The basics
|
||||
=============
|
||||
|
||||
Starting Docker
|
||||
---------------
|
||||
|
||||
If you have used one of the quick install paths', Docker may have been installed with upstart, Ubuntu's
|
||||
system for starting processes at boot time. You should be able to run ``docker help`` and get output.
|
||||
|
||||
If you get ``docker: command not found`` or something like ``/var/lib/docker/repositories: permission denied``
|
||||
you will need to specify the path to it and manually start it.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Run docker in daemon mode
|
||||
sudo <path to>/docker -d &
|
||||
|
||||
|
||||
Running an interactive shell
|
||||
----------------------------
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Download a base image
|
||||
docker pull base
|
||||
|
||||
# Run an interactive shell in the base image,
|
||||
# allocate a tty, attach stdin and stdout
|
||||
docker run -i -t base /bin/bash
|
||||
|
||||
|
||||
Starting a long-running worker process
|
||||
--------------------------------------
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Start a very useful long-running process
|
||||
JOB=$(docker run -d base /bin/sh -c "while true; do echo Hello world; sleep 1; done")
|
||||
|
||||
# Collect the output of the job so far
|
||||
docker logs $JOB
|
||||
|
||||
# Kill the job
|
||||
docker kill $JOB
|
||||
|
||||
|
||||
Listing all running containers
|
||||
------------------------------
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker ps
|
||||
|
||||
Expose a service on a TCP port
|
||||
------------------------------
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Expose port 4444 of this container, and tell netcat to listen on it
|
||||
JOB=$(docker run -d -p 4444 base /bin/nc -l -p 4444)
|
||||
|
||||
# Which public port is NATed to my container?
|
||||
PORT=$(docker port $JOB 4444)
|
||||
|
||||
# Connect to the public port via the host's public address
|
||||
# Please note that because of how routing works connecting to localhost or 127.0.0.1 $PORT will not work.
|
||||
IP=$(ifconfig eth0 | perl -n -e 'if (m/inet addr:([\d\.]+)/g) { print $1 }')
|
||||
echo hello world | nc $IP $PORT
|
||||
|
||||
# Verify that the network connection worked
|
||||
echo "Daemon received: $(docker logs $JOB)"
|
||||
|
||||
|
||||
Committing (saving) an image
|
||||
-----------------------------
|
||||
|
||||
Save your containers state to a container image, so the state can be re-used.
|
||||
|
||||
When you commit your container only the differences between the image the container was created from
|
||||
and the current state of the container will be stored (as a diff). See which images you already have
|
||||
using ``docker images``
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Commit your container to a new named image
|
||||
docker commit <container_id> <some_name>
|
||||
|
||||
# List your containers
|
||||
docker images
|
||||
|
||||
You now have a image state from which you can create new instances.
|
||||
|
||||
|
||||
|
||||
Read more about :ref:`working_with_the_repository` or continue to the complete :ref:`cli`
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
:description: Docker's CLI command description and usage
|
||||
:keywords: Docker, Docker documentation, CLI, command line
|
||||
|
||||
.. _cli:
|
||||
|
||||
Command Line Interface
|
||||
======================
|
||||
@@ -9,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
|
||||
|
||||
-a=false: Attach stdin and stdout
|
||||
-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
|
||||
-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
|
||||
@@ -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
|
||||
@@ -0,0 +1,9 @@
|
||||
============================
|
||||
``rm`` -- Remove a container
|
||||
============================
|
||||
|
||||
::
|
||||
|
||||
Usage: docker rm [OPTIONS] CONTAINER
|
||||
|
||||
Remove a container
|
||||
@@ -0,0 +1,9 @@
|
||||
==========================
|
||||
``rmi`` -- Remove an image
|
||||
==========================
|
||||
|
||||
::
|
||||
|
||||
Usage: docker rmimage [OPTIONS] IMAGE
|
||||
|
||||
Remove an image
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -11,5 +11,6 @@ Contents:
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
basecommands
|
||||
basics
|
||||
workingwithrepository
|
||||
cli
|
||||
@@ -0,0 +1,42 @@
|
||||
.. _working_with_the_repository:
|
||||
|
||||
Working with the repository
|
||||
============================
|
||||
|
||||
Connecting to the repository
|
||||
----------------------------
|
||||
|
||||
You create a user on the central docker repository by running
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker login
|
||||
|
||||
|
||||
If your username does not exist it will prompt you to also enter a password and your e-mail address. It will then
|
||||
automatically log you in.
|
||||
|
||||
|
||||
Committing a container to a named image
|
||||
---------------------------------------
|
||||
|
||||
In order to commit to the repository it is required to have committed your container to an image with your namespace.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# for example docker commit $CONTAINER_ID dhrp/kickassapp
|
||||
docker commit <container_id> <your username>/<some_name>
|
||||
|
||||
|
||||
Pushing a container to the repository
|
||||
-----------------------------------------
|
||||
|
||||
In order to push an image to the repository you need to have committed your container to a named image (see above)
|
||||
|
||||
Now you can commit this image to the repository
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# for example docker push dhrp/kickassapp
|
||||
docker push <image-name>
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
:title: Building blocks
|
||||
:description: An introduction to docker and standard containers?
|
||||
:keywords: containers, lxc, concepts, explanation
|
||||
|
||||
|
||||
Building blocks
|
||||
===============
|
||||
|
||||
.. _images:
|
||||
|
||||
Images
|
||||
------
|
||||
An original container image. These are stored on disk and are comparable with what you normally expect from a stoppped virtual machine image. Images are stored (and retrieved from) repository
|
||||
|
||||
Images are stored on your local file system under /var/lib/docker/images
|
||||
|
||||
|
||||
.. _containers:
|
||||
|
||||
Containers
|
||||
----------
|
||||
A container is a local version of an image. It can be running or stopped, The equivalent would be a virtual machine instance.
|
||||
|
||||
Containers are stored on your local file system under /var/lib/docker/containers
|
||||
|
||||
@@ -1,11 +1,27 @@
|
||||
:title: Containers
|
||||
:description: What are standard containers?
|
||||
:title: Introduction
|
||||
:description: An introduction to docker and standard containers?
|
||||
:keywords: containers, lxc, concepts, explanation
|
||||
|
||||
|
||||
:note: This version of the introduction is temporary, just to make sure we don't break the links from the website when the documentation is updated
|
||||
|
||||
|
||||
Introduction
|
||||
============
|
||||
|
||||
Docker - The Linux container runtime
|
||||
------------------------------------
|
||||
|
||||
Docker complements LXC with a high-level API which operates at the process level. It runs unix processes with strong guarantees of isolation and repeatability across servers.
|
||||
|
||||
Docker is a great building block for automating distributed systems: large-scale web deployments, database clusters, continuous deployment systems, private PaaS, service-oriented architectures, etc.
|
||||
|
||||
|
||||
- **Heterogeneous payloads** Any combination of binaries, libraries, configuration files, scripts, virtualenvs, jars, gems, tarballs, you name it. No more juggling between domain-specific tools. Docker can deploy and run them all.
|
||||
- **Any server** Docker can run on any x64 machine with a modern linux kernel - whether it's a laptop, a bare metal server or a VM. This makes it perfect for multi-cloud deployments.
|
||||
- **Isolation** docker isolates processes from each other and from the underlying host, using lightweight containers.
|
||||
- **Repeatability** Because containers are isolated in their own filesystem, they behave the same regardless of where, when, and alongside what they run.
|
||||
|
||||
Standard Containers
|
||||
===================
|
||||
|
||||
|
||||
What is a Standard Container?
|
||||
@@ -19,25 +35,25 @@ The spec for Standard Containers is currently work in progress, but it is very s
|
||||
A great analogy for this is the shipping container. Just like Standard Containers are a fundamental unit of software delivery, shipping containers (http://bricks.argz.com/ins/7823-1/12) are a fundamental unit of physical delivery.
|
||||
|
||||
Standard operations
|
||||
-----------------------
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Just like shipping containers, Standard Containers define a set of STANDARD OPERATIONS. Shipping containers can be lifted, stacked, locked, loaded, unloaded and labelled. Similarly, standard containers can be started, stopped, copied, snapshotted, downloaded, uploaded and tagged.
|
||||
|
||||
|
||||
Content-agnostic
|
||||
---------------------
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Just like shipping containers, Standard Containers are CONTENT-AGNOSTIC: all standard operations have the same effect regardless of the contents. A shipping container will be stacked in exactly the same way whether it contains Vietnamese powder coffee or spare Maserati parts. Similarly, Standard Containers are started or uploaded in the same way whether they contain a postgres database, a php application with its dependencies and application server, or Java build artifacts.
|
||||
|
||||
|
||||
Infrastructure-agnostic
|
||||
--------------------------
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Both types of containers are INFRASTRUCTURE-AGNOSTIC: they can be transported to thousands of facilities around the world, and manipulated by a wide variety of equipment. A shipping container can be packed in a factory in Ukraine, transported by truck to the nearest routing center, stacked onto a train, loaded into a German boat by an Australian-built crane, stored in a warehouse at a US facility, etc. Similarly, a standard container can be bundled on my laptop, uploaded to S3, downloaded, run and snapshotted by a build server at Equinix in Virginia, uploaded to 10 staging servers in a home-made Openstack cluster, then sent to 30 production instances across 3 EC2 regions.
|
||||
|
||||
|
||||
Designed for automation
|
||||
--------------------------
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Because they offer the same standard operations regardless of content and infrastructure, Standard Containers, just like their physical counterpart, are extremely well-suited for automation. In fact, you could say automation is their secret weapon.
|
||||
|
||||
@@ -47,7 +63,7 @@ Similarly, before Standard Containers, by the time a software component ran in p
|
||||
|
||||
|
||||
Industrial-grade delivery
|
||||
----------------------------
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
There are 17 million shipping containers in existence, packed with every physical good imaginable. Every single one of them can be loaded on the same boats, by the same cranes, in the same facilities, and sent anywhere in the World with incredible efficiency. It is embarrassing to think that a 30 ton shipment of coffee can safely travel half-way across the World in *less time* than it takes a software team to deliver its code from one datacenter to another sitting 10 miles away.
|
||||
|
||||
@@ -55,7 +71,7 @@ With Standard Containers we can put an end to that embarrassment, by making INDU
|
||||
|
||||
|
||||
Standard Container Specification
|
||||
--------------------------------
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
(TODO)
|
||||
|
||||
|
||||
@@ -12,5 +12,6 @@ Contents:
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
containers
|
||||
introduction
|
||||
buildingblocks
|
||||
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
:title: Introduction
|
||||
:description: An introduction to docker and standard containers?
|
||||
:keywords: containers, lxc, concepts, explanation
|
||||
|
||||
|
||||
|
||||
Introduction
|
||||
============
|
||||
|
||||
Docker - The Linux container runtime
|
||||
------------------------------------
|
||||
|
||||
Docker complements LXC with a high-level API which operates at the process level. It runs unix processes with strong guarantees of isolation and repeatability across servers.
|
||||
|
||||
Docker is a great building block for automating distributed systems: large-scale web deployments, database clusters, continuous deployment systems, private PaaS, service-oriented architectures, etc.
|
||||
|
||||
|
||||
- **Heterogeneous payloads** Any combination of binaries, libraries, configuration files, scripts, virtualenvs, jars, gems, tarballs, you name it. No more juggling between domain-specific tools. Docker can deploy and run them all.
|
||||
- **Any server** Docker can run on any x64 machine with a modern linux kernel - whether it's a laptop, a bare metal server or a VM. This makes it perfect for multi-cloud deployments.
|
||||
- **Isolation** docker isolates processes from each other and from the underlying host, using lightweight containers.
|
||||
- **Repeatability** Because containers are isolated in their own filesystem, they behave the same regardless of where, when, and alongside what they run.
|
||||
|
||||
.. image:: http://www.docker.io/_static/lego_docker.jpg
|
||||
|
||||
|
||||
What is a Standard Container?
|
||||
-----------------------------
|
||||
|
||||
Docker defines a unit of software delivery called a Standard Container. The goal of a Standard Container is to encapsulate a software component and all its dependencies in
|
||||
a format that is self-describing and portable, so that any compliant runtime can run it without extra dependency, regardless of the underlying machine and the contents of the container.
|
||||
|
||||
The spec for Standard Containers is currently work in progress, but it is very straightforward. It mostly defines 1) an image format, 2) a set of standard operations, and 3) an execution environment.
|
||||
|
||||
A great analogy for this is the shipping container. Just like Standard Containers are a fundamental unit of software delivery, shipping containers (http://bricks.argz.com/ins/7823-1/12) are a fundamental unit of physical delivery.
|
||||
|
||||
Standard operations
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Just like shipping containers, Standard Containers define a set of STANDARD OPERATIONS. Shipping containers can be lifted, stacked, locked, loaded, unloaded and labelled. Similarly, standard containers can be started, stopped, copied, snapshotted, downloaded, uploaded and tagged.
|
||||
|
||||
|
||||
Content-agnostic
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Just like shipping containers, Standard Containers are CONTENT-AGNOSTIC: all standard operations have the same effect regardless of the contents. A shipping container will be stacked in exactly the same way whether it contains Vietnamese powder coffee or spare Maserati parts. Similarly, Standard Containers are started or uploaded in the same way whether they contain a postgres database, a php application with its dependencies and application server, or Java build artifacts.
|
||||
|
||||
|
||||
Infrastructure-agnostic
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Both types of containers are INFRASTRUCTURE-AGNOSTIC: they can be transported to thousands of facilities around the world, and manipulated by a wide variety of equipment. A shipping container can be packed in a factory in Ukraine, transported by truck to the nearest routing center, stacked onto a train, loaded into a German boat by an Australian-built crane, stored in a warehouse at a US facility, etc. Similarly, a standard container can be bundled on my laptop, uploaded to S3, downloaded, run and snapshotted by a build server at Equinix in Virginia, uploaded to 10 staging servers in a home-made Openstack cluster, then sent to 30 production instances across 3 EC2 regions.
|
||||
|
||||
|
||||
Designed for automation
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Because they offer the same standard operations regardless of content and infrastructure, Standard Containers, just like their physical counterpart, are extremely well-suited for automation. In fact, you could say automation is their secret weapon.
|
||||
|
||||
Many things that once required time-consuming and error-prone human effort can now be programmed. Before shipping containers, a bag of powder coffee was hauled, dragged, dropped, rolled and stacked by 10 different people in 10 different locations by the time it reached its destination. 1 out of 50 disappeared. 1 out of 20 was damaged. The process was slow, inefficient and cost a fortune - and was entirely different depending on the facility and the type of goods.
|
||||
|
||||
Similarly, before Standard Containers, by the time a software component ran in production, it had been individually built, configured, bundled, documented, patched, vendored, templated, tweaked and instrumented by 10 different people on 10 different computers. Builds failed, libraries conflicted, mirrors crashed, post-it notes were lost, logs were misplaced, cluster updates were half-broken. The process was slow, inefficient and cost a fortune - and was entirely different depending on the language and infrastructure provider.
|
||||
|
||||
|
||||
Industrial-grade delivery
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
There are 17 million shipping containers in existence, packed with every physical good imaginable. Every single one of them can be loaded on the same boats, by the same cranes, in the same facilities, and sent anywhere in the World with incredible efficiency. It is embarrassing to think that a 30 ton shipment of coffee can safely travel half-way across the World in *less time* than it takes a software team to deliver its code from one datacenter to another sitting 10 miles away.
|
||||
|
||||
With Standard Containers we can put an end to that embarrassment, by making INDUSTRIAL-GRADE DELIVERY of software a reality.
|
||||
|
||||
|
||||
Standard Container Specification
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
(TODO)
|
||||
|
||||
Image format
|
||||
~~~~~~~~~~~~
|
||||
|
||||
Standard operations
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- Copy
|
||||
- Run
|
||||
- Stop
|
||||
- Wait
|
||||
- Commit
|
||||
- Attach standard streams
|
||||
- List filesystem changes
|
||||
- ...
|
||||
|
||||
Execution environment
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Root filesystem
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
Environment variables
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Process arguments
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
Networking
|
||||
^^^^^^^^^^
|
||||
|
||||
Process namespacing
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Resource limits
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
Process monitoring
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Logging
|
||||
^^^^^^^
|
||||
|
||||
Signals
|
||||
^^^^^^^
|
||||
|
||||
Pseudo-terminal allocation
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Security
|
||||
^^^^^^^^
|
||||
|
||||
@@ -51,8 +51,51 @@ documenting your bug report or improvement proposal. If it does, it
|
||||
never hurts to add a quick "+1" or "I have this problem too". This will
|
||||
help prioritize the most common problems and requests.
|
||||
|
||||
Write tests
|
||||
Conventions
|
||||
~~~~~~~~~~~
|
||||
|
||||
Golang has a great testing suite built in: use it! Take a look at
|
||||
existing tests for inspiration.
|
||||
Fork the repo and make changes on your fork in a feature branch:
|
||||
|
||||
- If it's a bugfix branch, name it XXX-something where XXX is the number of the
|
||||
issue
|
||||
- If it's a feature branch, create an enhancement issue to announce your
|
||||
intentions, and name it XXX-something where XXX is the number of the issue.
|
||||
|
||||
Submit unit tests for your changes. Go has a great test framework built in; use
|
||||
it! Take a look at existing tests for inspiration. Run the full test suite on
|
||||
your branch before submitting a pull request.
|
||||
|
||||
Make sure you include relevant updates or additions to documentation when
|
||||
creating or modifying features.
|
||||
|
||||
Write clean code. Universally formatted code promotes ease of writing, reading,
|
||||
and maintenance. Always run ``go fmt`` before committing your changes. Most
|
||||
editors have plugins that do this automatically, and there's also a git
|
||||
pre-commit hook:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl -o .git/hooks/pre-commit https://raw.github.com/edsrzf/gofmt-git-hook/master/fmt-check && chmod +x .git/hooks/pre-commit
|
||||
|
||||
|
||||
Pull requests descriptions should be as clear as possible and include a
|
||||
reference to all the issues that they address.
|
||||
|
||||
Code review comments may be added to your pull request. Discuss, then make the
|
||||
suggested modifications and push additional commits to your feature branch. Be
|
||||
sure to post a comment after pushing. The new commits will show up in the pull
|
||||
request automatically, but the reviewers will not be notified unless you
|
||||
comment.
|
||||
|
||||
Before the pull request is merged, make sure that you squash your commits into
|
||||
logical units of work using ``git rebase -i`` and ``git push -f``. After every
|
||||
commit the test suite should be passing. Include documentation changes in the
|
||||
same commit so that a revert would remove all traces of the feature or fix.
|
||||
|
||||
Commits that fix or close an issue should include a reference like ``Closes #XXX``
|
||||
or ``Fixes #XXX``, which will automatically close the issue when merged.
|
||||
|
||||
Add your name to the AUTHORS file, but make sure the list is sorted and your
|
||||
name and email address match your git configuration. The AUTHORS file is
|
||||
regenerated occasionally from the git commit history, so a mismatch may result
|
||||
in your changes being overwritten.
|
||||
|
||||
@@ -7,7 +7,7 @@ Setting up a dev environment
|
||||
|
||||
Instructions that have been verified to work on Ubuntu 12.10,
|
||||
|
||||
.. code:: bash
|
||||
.. code-block:: bash
|
||||
|
||||
sudo apt-get -y install lxc wget bsdtar curl golang git
|
||||
|
||||
@@ -22,10 +22,12 @@ Instructions that have been verified to work on Ubuntu 12.10,
|
||||
go get -v github.com/dotcloud/docker/...
|
||||
go install -v github.com/dotcloud/docker/...
|
||||
|
||||
|
||||
Then run the docker daemon,
|
||||
|
||||
.. code:: bash
|
||||
.. code-block:: bash
|
||||
|
||||
sudo $GOPATH/bin/docker -d
|
||||
|
||||
|
||||
Run the ``go install`` command (above) to recompile docker.
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
|
||||
.. note::
|
||||
|
||||
This example assumes you have Docker running in daemon mode. For more information please see :ref:`running_examples`
|
||||
@@ -6,10 +6,10 @@
|
||||
|
||||
Hello World
|
||||
===========
|
||||
This is the most basic example available for using docker
|
||||
|
||||
This example assumes you have Docker installed.
|
||||
.. include:: example_header.inc
|
||||
|
||||
This is the most basic example available for using Docker.
|
||||
|
||||
Download the base container
|
||||
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
|
||||
Hello World Daemon
|
||||
==================
|
||||
|
||||
.. include:: example_header.inc
|
||||
|
||||
The most boring daemon ever written.
|
||||
|
||||
This example assumes you have Docker installed and with the base image already imported ``docker pull base``.
|
||||
@@ -18,7 +21,7 @@ out every second. It will continue to do this until we stop it.
|
||||
|
||||
CONTAINER_ID=$(docker run -d base /bin/sh -c "while true; do echo hello world; sleep 1; done")
|
||||
|
||||
We are going to run a simple hello world daemon in a new container made from the busybox daemon.
|
||||
We are going to run a simple hello world daemon in a new container made from the base image.
|
||||
|
||||
- **"docker run -d "** run a command in a new container. We pass "-d" so it runs as a daemon.
|
||||
- **"base"** is the image we want to run the command inside of.
|
||||
@@ -79,11 +82,3 @@ See the example in action
|
||||
</div>
|
||||
|
||||
Continue to the :ref:`python_web_app` example.
|
||||
|
||||
|
||||
Notes:
|
||||
------
|
||||
|
||||
- **Docker daemon** The docker daemon is started by ``sudo docker -d``, Vagrant may have started
|
||||
the Docker daemon for you, but you will need to restart it this way if it was terminated. Otherwise
|
||||
it may give you ``Couldn't create Tag store: open /var/lib/docker/repositories: permission denied``
|
||||
|
||||
@@ -12,6 +12,8 @@ Contents:
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
running_examples
|
||||
hello_world
|
||||
hello_world_daemon
|
||||
python_web_app
|
||||
running_ssh_service
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
|
||||
Building a python web app
|
||||
=========================
|
||||
|
||||
.. include:: example_header.inc
|
||||
|
||||
The goal of this example is to show you how you can author your own docker images using a parent image, making changes to it, and then saving the results as a new image. We will do that by making a simple hello flask web application image.
|
||||
|
||||
**Steps:**
|
||||
@@ -45,6 +48,11 @@ Save the changed we just made in the container to a new image called "_/builds/g
|
||||
|
||||
WEB_WORKER=$(docker run -d -p 5000 $BUILD_IMG /usr/local/bin/runapp)
|
||||
|
||||
- **"docker run -d "** run a command in a new container. We pass "-d" so it runs as a daemon.
|
||||
**"-p 5000"* the web app is going to listen on this port, so it must be mapped from the container to the host system.
|
||||
- **"$BUILD_IMG"** is the image we want to run the command inside of.
|
||||
- **/usr/local/bin/runapp** is the command which starts the web app.
|
||||
|
||||
Use the new image we just created and create a new container with network port 5000, and return the container id and store in the WEB_WORKER variable.
|
||||
|
||||
.. code-block:: bash
|
||||
@@ -54,6 +62,18 @@ Use the new image we just created and create a new container with network port 5
|
||||
|
||||
view the logs for the new container using the WEB_WORKER variable, and if everything worked as planned you should see the line "Running on http://0.0.0.0:5000/" in the log output.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
WEB_PORT=$(docker port $WEB_WORKER 5000)
|
||||
|
||||
lookup the public-facing port which is NAT-ed store the private port used by the container and store it inside of the WEB_PORT variable.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl http://`hostname`:$WEB_PORT
|
||||
Hello world!
|
||||
|
||||
access the web app using curl. If everything worked as planned you should see the line "Hello world!" inside of your console.
|
||||
|
||||
**Video:**
|
||||
|
||||
@@ -65,6 +85,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`.
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
:title: Running the Examples
|
||||
:description: An overview on how to run the docker examples
|
||||
:keywords: docker, examples, how to
|
||||
|
||||
.. _running_examples:
|
||||
|
||||
Running The Examples
|
||||
--------------------
|
||||
|
||||
There are two ways to run docker, daemon mode and standalone mode.
|
||||
|
||||
When you run the docker command it will first check if there is a docker daemon running in the background it can connect to.
|
||||
|
||||
* If it exists it will use that daemon to run all of the commands.
|
||||
* If it does not exist docker will run in standalone mode (docker will exit after each command).
|
||||
|
||||
Docker needs to be run from a privileged account (root).
|
||||
|
||||
1. The most common (and recommended) way is to run a docker daemon as root in the background, and then connect to it from the docker client from any account.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# starting docker daemon in the background
|
||||
sudo docker -d &
|
||||
|
||||
# now you can run docker commands from any account.
|
||||
docker <command>
|
||||
|
||||
2. Standalone: You need to run every command as root, or using sudo
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo docker <command>
|
||||
@@ -0,0 +1,32 @@
|
||||
: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
|
||||
============================
|
||||
|
||||
.. include:: example_header.inc
|
||||
|
||||
|
||||
**Video:**
|
||||
|
||||
I've create a little screencast to show how to create a sshd service and connect to it. It is something like 11
|
||||
minutes and not entirely smooth, but gives you a good idea.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<div style="margin-top:10px;">
|
||||
<iframe width="800" height="400" src="http://ascii.io/a/2637/raw" frameborder="0"></iframe>
|
||||
</div>
|
||||
|
||||
|
||||
You can also get this sshd container by using
|
||||
::
|
||||
|
||||
docker pull dhrp/sshd
|
||||
|
||||
|
||||
The password is 'screencast'
|
||||
|
||||
+23
-23
@@ -5,43 +5,43 @@ FAQ
|
||||
Most frequently asked questions.
|
||||
--------------------------------
|
||||
|
||||
**1. How much does Docker cost?**
|
||||
1. **How much does Docker cost?**
|
||||
|
||||
Docker is 100% free, it is open source, so you can use it without paying.
|
||||
Docker is 100% free, it is open source, so you can use it without paying.
|
||||
|
||||
**2. What open source license are you using?**
|
||||
2. **What open source license are you using?**
|
||||
|
||||
We are using the Apache License Version 2.0, see it here: https://github.com/dotcloud/docker/blob/master/LICENSE
|
||||
We are using the Apache License Version 2.0, see it here: https://github.com/dotcloud/docker/blob/master/LICENSE
|
||||
|
||||
**3. Does Docker run on Mac OS X or Windows?**
|
||||
3. **Does Docker run on Mac OS X or Windows?**
|
||||
|
||||
Not at this time, Docker currently only runs on Linux, but you can use VirtualBox to run Docker in a virtual machine on your box, and get the best of both worlds. Check out the MacOSX_ and Windows_ intallation guides.
|
||||
Not at this time, Docker currently only runs on Linux, but you can use VirtualBox to run Docker in a virtual machine on your box, and get the best of both worlds. Check out the MacOSX_ and Windows_ intallation guides.
|
||||
|
||||
**4. How do containers compare to virtual machines?**
|
||||
4. **How do containers compare to virtual machines?**
|
||||
|
||||
They are complementary. VMs are best used to allocate chunks of hardware resources. Containers operate at the process level, which makes them very lightweight and perfect as a unit of software delivery.
|
||||
They are complementary. VMs are best used to allocate chunks of hardware resources. Containers operate at the process level, which makes them very lightweight and perfect as a unit of software delivery.
|
||||
|
||||
**5. Can I help by adding some questions and answers?**
|
||||
5. **Can I help by adding some questions and answers?**
|
||||
|
||||
Definitely! You can fork `the repo`_ and edit the documentation sources.
|
||||
Definitely! You can fork `the repo`_ and edit the documentation sources.
|
||||
|
||||
|
||||
**42. Where can I find more answers?**
|
||||
42. **Where can I find more answers?**
|
||||
|
||||
You can find more answers on:
|
||||
You can find more answers on:
|
||||
|
||||
* `IRC: docker on freenode`_
|
||||
* `Github`_
|
||||
* `Ask questions on Stackoverflow`_
|
||||
* `Join the conversation on Twitter`_
|
||||
* `IRC: docker on freenode`_
|
||||
* `Github`_
|
||||
* `Ask questions on Stackoverflow`_
|
||||
* `Join the conversation on Twitter`_
|
||||
|
||||
.. _Windows: ../documentation/installation/windows.html
|
||||
.. _MacOSX: ../documentation/installation/macos.html
|
||||
.. _the repo: http://www.github.com/dotcloud/docker
|
||||
.. _IRC\: docker on freenode: irc://chat.freenode.net#docker
|
||||
.. _Github: http://www.github.com/dotcloud/docker
|
||||
.. _Ask questions on Stackoverflow: http://stackoverflow.com/search?q=docker
|
||||
.. _Join the conversation on Twitter: http://twitter.com/getdocker
|
||||
.. _Windows: ../documentation/installation/windows.html
|
||||
.. _MacOSX: ../documentation/installation/macos.html
|
||||
.. _the repo: http://www.github.com/dotcloud/docker
|
||||
.. _IRC\: docker on freenode: irc://chat.freenode.net#docker
|
||||
.. _Github: http://www.github.com/dotcloud/docker
|
||||
.. _Ask questions on Stackoverflow: http://stackoverflow.com/search?q=docker
|
||||
.. _Join the conversation on Twitter: http://twitter.com/getdocker
|
||||
|
||||
|
||||
Looking for something else to read? Checkout the :ref:`hello_world` example.
|
||||
|
||||
@@ -16,3 +16,4 @@ Contents:
|
||||
macos
|
||||
windows
|
||||
amazon
|
||||
upgrading
|
||||
|
||||
@@ -42,7 +42,15 @@ Run your first container!
|
||||
|
||||
sudo ./docker run -i -t base /bin/bash
|
||||
|
||||
|
||||
To run docker as a daemon, in the background, and allow non-root users to run ``docker`` start
|
||||
docker -d
|
||||
|
||||
::
|
||||
|
||||
sudo ./docker -d &
|
||||
|
||||
|
||||
Consider adding docker to your PATH for simplicity.
|
||||
|
||||
|
||||
Continue with the :ref:`hello_world` example.
|
||||
@@ -0,0 +1,40 @@
|
||||
.. _upgrading:
|
||||
|
||||
Upgrading
|
||||
============
|
||||
|
||||
We assume you are upgrading from within the operating system which runs your docker daemon.
|
||||
|
||||
|
||||
Get the latest docker binary:
|
||||
|
||||
::
|
||||
|
||||
wget http://get.docker.io/builds/$(uname -s)/$(uname -m)/docker-master.tgz
|
||||
|
||||
|
||||
|
||||
Unpack it to your current dir
|
||||
|
||||
::
|
||||
|
||||
tar -xf docker-master.tgz
|
||||
|
||||
|
||||
Stop your current daemon. How you stop your daemon depends on how you started it.
|
||||
|
||||
- If you started the daemon manually (``sudo docker -d``), you can just kill the process: ``killall docker``
|
||||
- If the process was started using upstart (the ubuntu startup daemon), you may need to use that to stop it
|
||||
|
||||
|
||||
Start docker in daemon mode (-d) and disconnect (&) starting ./docker will start the version in your current dir rather
|
||||
than the one in your PATH.
|
||||
|
||||
Now start the daemon
|
||||
|
||||
::
|
||||
|
||||
sudo ./docker -d &
|
||||
|
||||
|
||||
Alternatively you can replace the docker binary in ``/usr/local/bin``
|
||||
+22
-1
@@ -82,7 +82,7 @@ h4 {
|
||||
.btn-custom {
|
||||
background-color: #292929 !important;
|
||||
background-repeat: repeat-x;
|
||||
filter: progid:dximagetransform.microsoft.gradient(startColorstr="#515151", endColorstr="#282828");
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr="#515151", endColorstr="#282828");
|
||||
background-image: -khtml-gradient(linear, left top, left bottom, from(#515151), to(#282828));
|
||||
background-image: -moz-linear-gradient(top, #515151, #282828);
|
||||
background-image: -ms-linear-gradient(top, #515151, #282828);
|
||||
@@ -131,6 +131,27 @@ section.header {
|
||||
margin: 15px 15px 15px 0;
|
||||
border: 2px solid gray;
|
||||
}
|
||||
.admonition {
|
||||
padding: 10px;
|
||||
border: 1px solid grey;
|
||||
margin-bottom: 10px;
|
||||
margin-top: 10px;
|
||||
-webkit-border-radius: 4px;
|
||||
-moz-border-radius: 4px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.admonition .admonition-title {
|
||||
font-weight: bold;
|
||||
}
|
||||
.admonition.note {
|
||||
background-color: #f1ebba;
|
||||
}
|
||||
.admonition.warning {
|
||||
background-color: #eed9af;
|
||||
}
|
||||
.admonition.danger {
|
||||
background-color: #e9bcab;
|
||||
}
|
||||
/* ===================
|
||||
left navigation
|
||||
===================== */
|
||||
|
||||
+26
@@ -179,7 +179,33 @@ section.header {
|
||||
border: 2px solid gray;
|
||||
}
|
||||
|
||||
.admonition {
|
||||
padding: 10px;
|
||||
border: 1px solid grey;
|
||||
|
||||
margin-bottom: 10px;
|
||||
margin-top: 10px;
|
||||
|
||||
-webkit-border-radius: 4px;
|
||||
-moz-border-radius: 4px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.admonition .admonition-title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.admonition.note {
|
||||
background-color: rgb(241, 235, 186);
|
||||
}
|
||||
|
||||
.admonition.warning {
|
||||
background-color: rgb(238, 217, 175);
|
||||
}
|
||||
|
||||
.admonition.danger {
|
||||
background-color: rgb(233, 188, 171);
|
||||
}
|
||||
|
||||
/* ===================
|
||||
left navigation
|
||||
|
||||
@@ -10,12 +10,13 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// A Graph is a store for versioned filesystem images, and the relationship between them.
|
||||
// A Graph is a store for versioned filesystem images and the relationship between them.
|
||||
type Graph struct {
|
||||
Root string
|
||||
Root string
|
||||
idIndex *TruncIndex
|
||||
}
|
||||
|
||||
// NewGraph instanciates a new graph at the given root path in the filesystem.
|
||||
// NewGraph instantiates a new graph at the given root path in the filesystem.
|
||||
// `root` will be created if it doesn't exist.
|
||||
func NewGraph(root string) (*Graph, error) {
|
||||
abspath, err := filepath.Abs(root)
|
||||
@@ -26,9 +27,26 @@ func NewGraph(root string) (*Graph, error) {
|
||||
if err := os.Mkdir(root, 0700); err != nil && !os.IsExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
return &Graph{
|
||||
Root: abspath,
|
||||
}, nil
|
||||
graph := &Graph{
|
||||
Root: abspath,
|
||||
idIndex: NewTruncIndex(),
|
||||
}
|
||||
if err := graph.restore(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return graph, nil
|
||||
}
|
||||
|
||||
func (graph *Graph) restore() error {
|
||||
dir, err := ioutil.ReadDir(graph.Root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, v := range dir {
|
||||
id := v.Name()
|
||||
graph.idIndex.Add(id)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FIXME: Implement error subclass instead of looking at the error text
|
||||
@@ -47,7 +65,11 @@ func (graph *Graph) Exists(id string) bool {
|
||||
}
|
||||
|
||||
// Get returns the image with the given id, or an error if the image doesn't exist.
|
||||
func (graph *Graph) Get(id string) (*Image, error) {
|
||||
func (graph *Graph) Get(name string) (*Image, error) {
|
||||
id, err := graph.idIndex.Get(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// FIXME: return nil when the image doesn't exist, instead of an error
|
||||
img, err := LoadImage(graph.imageRoot(id))
|
||||
if err != nil {
|
||||
@@ -63,9 +85,10 @@ func (graph *Graph) Get(id 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
|
||||
@@ -101,11 +124,15 @@ func (graph *Graph) Register(layerData Archive, img *Image) error {
|
||||
return err
|
||||
}
|
||||
img.graph = graph
|
||||
graph.idIndex.Add(img.Id)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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)
|
||||
@@ -116,15 +143,8 @@ 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 ot 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 do it internally with os.IsNotExists
|
||||
// Check if given error is "not empty".
|
||||
// Note: this is the way golang does it internally with os.IsNotExists.
|
||||
func isNotEmpty(err error) bool {
|
||||
switch pe := err.(type) {
|
||||
case nil:
|
||||
@@ -138,51 +158,24 @@ func isNotEmpty(err error) bool {
|
||||
}
|
||||
|
||||
// Delete atomically removes an image from the graph.
|
||||
func (graph *Graph) Delete(id string) error {
|
||||
garbage, err := graph.Garbage()
|
||||
func (graph *Graph) Delete(name string) error {
|
||||
id, err := graph.idIndex.Get(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.Rename(graph.imageRoot(id), garbage.imageRoot(id))
|
||||
tmp, err := graph.Mktemp("")
|
||||
if err != nil {
|
||||
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
|
||||
graph.idIndex.Delete(id)
|
||||
err = os.Rename(graph.imageRoot(id), tmp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.RemoveAll(tmp)
|
||||
}
|
||||
|
||||
// Undelete moves an image back from the garbage to the main graph
|
||||
func (graph *Graph) Undelete(id string) error {
|
||||
garbage, err := graph.Garbage()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Rename(garbage.imageRoot(id), graph.imageRoot(id))
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// Map returns a list of all images in the graph, addressable by ID
|
||||
// Map returns a list of all images in the graph, addressable by ID.
|
||||
func (graph *Graph) Map() (map[string]*Image, error) {
|
||||
// FIXME: this should replace All()
|
||||
all, err := graph.All()
|
||||
@@ -196,7 +189,7 @@ func (graph *Graph) Map() (map[string]*Image, error) {
|
||||
return images, nil
|
||||
}
|
||||
|
||||
// All returns a list of all images in the graph
|
||||
// All returns a list of all images in the graph.
|
||||
func (graph *Graph) All() ([]*Image, error) {
|
||||
var images []*Image
|
||||
err := graph.WalkAll(func(image *Image) {
|
||||
|
||||
@@ -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 {
|
||||
@@ -120,6 +123,29 @@ func TestMount(t *testing.T) {
|
||||
}()
|
||||
}
|
||||
|
||||
// Test that an image can be deleted by its shorthand prefix
|
||||
func TestDeletePrefix(t *testing.T) {
|
||||
graph := tempGraph(t)
|
||||
defer os.RemoveAll(graph.Root)
|
||||
img := createTestImage(graph, t)
|
||||
if err := graph.Delete(TruncateId(img.Id)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertNImages(graph, t, 0)
|
||||
}
|
||||
|
||||
func createTestImage(graph *Graph, t *testing.T) *Image {
|
||||
archive, err := fakeTar()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
img, err := graph.Create(archive, nil, "Test image")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return img
|
||||
}
|
||||
|
||||
func TestDelete(t *testing.T) {
|
||||
graph := tempGraph(t)
|
||||
defer os.RemoveAll(graph.Root)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -150,6 +151,10 @@ func (image *Image) Changes(rw string) ([]Change, error) {
|
||||
return Changes(layers, rw)
|
||||
}
|
||||
|
||||
func (image *Image) ShortId() string {
|
||||
return TruncateId(image.Id)
|
||||
}
|
||||
|
||||
func ValidateId(id string) error {
|
||||
if id == "" {
|
||||
return fmt.Errorf("Image id can't be empty")
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ lxc.utsname = {{.Id}}
|
||||
# network configuration
|
||||
lxc.network.type = veth
|
||||
lxc.network.flags = up
|
||||
lxc.network.link = lxcbr0
|
||||
lxc.network.link = {{.NetworkSettings.Bridge}}
|
||||
lxc.network.name = eth0
|
||||
lxc.network.mtu = 1500
|
||||
lxc.network.ipv4 = {{.NetworkSettings.IpAddress}}/{{.NetworkSettings.IpPrefixLen}}
|
||||
|
||||
+276
-116
@@ -1,7 +1,6 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -10,12 +9,15 @@ import (
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var NetworkBridgeIface string
|
||||
|
||||
const (
|
||||
networkBridgeIface = "lxcbr0"
|
||||
portRangeStart = 49153
|
||||
portRangeEnd = 65535
|
||||
DefaultNetworkBridge = "docker0"
|
||||
portRangeStart = 49153
|
||||
portRangeEnd = 65535
|
||||
)
|
||||
|
||||
// Calculates the first and last IP addresses in an IPNet
|
||||
@@ -29,41 +31,52 @@ func networkRange(network *net.IPNet) (net.IP, net.IP) {
|
||||
return firstIP, lastIP
|
||||
}
|
||||
|
||||
// Converts a 4 bytes IP into a 32 bit integer
|
||||
func ipToInt(ip net.IP) (int32, error) {
|
||||
buf := bytes.NewBuffer(ip.To4())
|
||||
var n int32
|
||||
if err := binary.Read(buf, binary.BigEndian, &n); err != nil {
|
||||
return 0, err
|
||||
// Detects overlap between one IPNet and another
|
||||
func networkOverlaps(netX *net.IPNet, netY *net.IPNet) bool {
|
||||
firstIP, _ := networkRange(netX)
|
||||
if netY.Contains(firstIP) {
|
||||
return true
|
||||
}
|
||||
return n, nil
|
||||
firstIP, _ = networkRange(netY)
|
||||
if netX.Contains(firstIP) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Converts a 4 bytes IP into a 32 bit integer
|
||||
func ipToInt(ip net.IP) int32 {
|
||||
return int32(binary.BigEndian.Uint32(ip.To4()))
|
||||
}
|
||||
|
||||
// Converts 32 bit integer into a 4 bytes IP address
|
||||
func intToIp(n int32) (net.IP, error) {
|
||||
var buf bytes.Buffer
|
||||
if err := binary.Write(&buf, binary.BigEndian, &n); err != nil {
|
||||
return net.IP{}, err
|
||||
}
|
||||
ip := net.IPv4(0, 0, 0, 0).To4()
|
||||
for i := 0; i < net.IPv4len; i++ {
|
||||
ip[i] = buf.Bytes()[i]
|
||||
}
|
||||
return ip, nil
|
||||
func intToIp(n int32) net.IP {
|
||||
b := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(b, uint32(n))
|
||||
return net.IP(b)
|
||||
}
|
||||
|
||||
// Given a netmask, calculates the number of available hosts
|
||||
func networkSize(mask net.IPMask) (int32, error) {
|
||||
func networkSize(mask net.IPMask) int32 {
|
||||
m := net.IPv4Mask(0, 0, 0, 0)
|
||||
for i := 0; i < net.IPv4len; i++ {
|
||||
m[i] = ^mask[i]
|
||||
}
|
||||
buf := bytes.NewBuffer(m)
|
||||
var n int32
|
||||
if err := binary.Read(buf, binary.BigEndian, &n); err != nil {
|
||||
return 0, err
|
||||
|
||||
return int32(binary.BigEndian.Uint32(m)) + 1
|
||||
}
|
||||
|
||||
//Wrapper around the ip command
|
||||
func ip(args ...string) (string, error) {
|
||||
path, err := exec.LookPath("ip")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("command not found: ip")
|
||||
}
|
||||
return n + 1, nil
|
||||
output, err := exec.Command(path, args...).CombinedOutput()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("ip failed: ip %v", strings.Join(args, " "))
|
||||
}
|
||||
return string(output), nil
|
||||
}
|
||||
|
||||
// Wrapper around the iptables command
|
||||
@@ -78,6 +91,66 @@ func iptables(args ...string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkRouteOverlaps(dockerNetwork *net.IPNet) error {
|
||||
output, err := ip("route")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
Debugf("Routes:\n\n%s", output)
|
||||
for _, line := range strings.Split(output, "\n") {
|
||||
if strings.Trim(line, "\r\n\t ") == "" || strings.Contains(line, "default") {
|
||||
continue
|
||||
}
|
||||
if _, network, err := net.ParseCIDR(strings.Split(line, " ")[0]); err != nil {
|
||||
return fmt.Errorf("Unexpected ip route output: %s (%s)", err, line)
|
||||
} else if networkOverlaps(dockerNetwork, network) {
|
||||
return fmt.Errorf("Network %s is already routed: '%s'", dockerNetwork.String(), line)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func CreateBridgeIface(ifaceName string) error {
|
||||
// FIXME: try more IP ranges
|
||||
// FIXME: try bigger ranges! /24 is too small.
|
||||
addrs := []string{"172.16.42.1/24", "10.0.42.1/24", "192.168.42.1/24"}
|
||||
|
||||
var ifaceAddr string
|
||||
for _, addr := range addrs {
|
||||
_, dockerNetwork, err := net.ParseCIDR(addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := checkRouteOverlaps(dockerNetwork); err == nil {
|
||||
ifaceAddr = addr
|
||||
break
|
||||
} else {
|
||||
Debugf("%s: %s", addr, err)
|
||||
}
|
||||
}
|
||||
if ifaceAddr == "" {
|
||||
return fmt.Errorf("Could not find a free IP address range for interface '%s'. Please configure its address manually and run 'docker -b %s'", ifaceName, ifaceName)
|
||||
} else {
|
||||
Debugf("Creating bridge %s with network %s", ifaceName, ifaceAddr)
|
||||
}
|
||||
|
||||
if output, err := ip("link", "add", ifaceName, "type", "bridge"); err != nil {
|
||||
return fmt.Errorf("Error creating bridge: %s (output: %s)", err, output)
|
||||
}
|
||||
|
||||
if output, err := ip("addr", "add", ifaceAddr, "dev", ifaceName); err != nil {
|
||||
return fmt.Errorf("Unable to add private network: %s (%s)", err, output)
|
||||
}
|
||||
if output, err := ip("link", "set", ifaceName, "up"); err != nil {
|
||||
return fmt.Errorf("Unable to start network bridge: %s (%s)", err, output)
|
||||
}
|
||||
if err := iptables("-t", "nat", "-A", "POSTROUTING", "-s", ifaceAddr,
|
||||
"!", "-d", ifaceAddr, "-j", "MASQUERADE"); err != nil {
|
||||
return fmt.Errorf("Unable to enable network bridge NAT: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Return the IPv4 address of a network interface
|
||||
func getIfaceAddr(name string) (net.Addr, error) {
|
||||
iface, err := net.InterfaceByName(name)
|
||||
@@ -114,6 +187,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")
|
||||
@@ -126,10 +202,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
|
||||
@@ -173,104 +249,150 @@ 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() {
|
||||
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")
|
||||
}
|
||||
Debugf("Releasing %d", port)
|
||||
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) {
|
||||
Debugf("Acquiring %d", port)
|
||||
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{}),
|
||||
fountain: make(chan int),
|
||||
}
|
||||
go allocator.runFountain()
|
||||
return allocator, nil
|
||||
}
|
||||
|
||||
// IP allocator: Atomatically allocate and release networking ports
|
||||
type IPAllocator struct {
|
||||
network *net.IPNet
|
||||
queue chan (net.IP)
|
||||
network *net.IPNet
|
||||
queueAlloc chan allocatedIP
|
||||
queueReleased chan net.IP
|
||||
inUse map[int32]struct{}
|
||||
}
|
||||
|
||||
func (alloc *IPAllocator) populate() error {
|
||||
type allocatedIP struct {
|
||||
ip net.IP
|
||||
err error
|
||||
}
|
||||
|
||||
func (alloc *IPAllocator) run() {
|
||||
firstIP, _ := networkRange(alloc.network)
|
||||
size, err := networkSize(alloc.network.Mask)
|
||||
if err != nil {
|
||||
return err
|
||||
ipNum := ipToInt(firstIP)
|
||||
ownIP := ipToInt(alloc.network.IP)
|
||||
size := networkSize(alloc.network.Mask)
|
||||
|
||||
pos := int32(1)
|
||||
max := size - 2 // -1 for the broadcast address, -1 for the gateway address
|
||||
for {
|
||||
var (
|
||||
newNum int32
|
||||
inUse bool
|
||||
)
|
||||
|
||||
// Find first unused IP, give up after one whole round
|
||||
for attempt := int32(0); attempt < max; attempt++ {
|
||||
newNum = ipNum + pos
|
||||
|
||||
pos = pos%max + 1
|
||||
|
||||
// The network's IP is never okay to use
|
||||
if newNum == ownIP {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, inUse = alloc.inUse[newNum]; !inUse {
|
||||
// We found an unused IP
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
ip := allocatedIP{ip: intToIp(newNum)}
|
||||
if inUse {
|
||||
ip.err = errors.New("No unallocated IP available")
|
||||
}
|
||||
|
||||
select {
|
||||
case alloc.queueAlloc <- ip:
|
||||
alloc.inUse[newNum] = struct{}{}
|
||||
case released := <-alloc.queueReleased:
|
||||
r := ipToInt(released)
|
||||
delete(alloc.inUse, r)
|
||||
|
||||
if inUse {
|
||||
// If we couldn't allocate a new IP, the released one
|
||||
// will be the only free one now, so instantly use it
|
||||
// next time
|
||||
pos = r - ipNum
|
||||
} else {
|
||||
// Use same IP as last time
|
||||
if pos == 1 {
|
||||
pos = max
|
||||
} else {
|
||||
pos--
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// The queue size should be the network size - 3
|
||||
// -1 for the network address, -1 for the broadcast address and
|
||||
// -1 for the gateway address
|
||||
alloc.queue = make(chan net.IP, size-3)
|
||||
for i := int32(1); i < size-1; i++ {
|
||||
ipNum, err := ipToInt(firstIP)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ip, err := intToIp(ipNum + int32(i))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Discard the network IP (that's the host IP address)
|
||||
if ip.Equal(alloc.network.IP) {
|
||||
continue
|
||||
}
|
||||
alloc.queue <- ip
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (alloc *IPAllocator) Acquire() (net.IP, error) {
|
||||
select {
|
||||
case ip := <-alloc.queue:
|
||||
return ip, nil
|
||||
default:
|
||||
return net.IP{}, errors.New("No more IP addresses available")
|
||||
}
|
||||
return net.IP{}, nil
|
||||
ip := <-alloc.queueAlloc
|
||||
return ip.ip, ip.err
|
||||
}
|
||||
|
||||
func (alloc *IPAllocator) Release(ip net.IP) error {
|
||||
select {
|
||||
case alloc.queue <- ip:
|
||||
return nil
|
||||
default:
|
||||
return errors.New("Too many IP addresses have been released")
|
||||
}
|
||||
return nil
|
||||
func (alloc *IPAllocator) Release(ip net.IP) {
|
||||
alloc.queueReleased <- ip
|
||||
}
|
||||
|
||||
func newIPAllocator(network *net.IPNet) (*IPAllocator, error) {
|
||||
func newIPAllocator(network *net.IPNet) *IPAllocator {
|
||||
alloc := &IPAllocator{
|
||||
network: network,
|
||||
network: network,
|
||||
queueAlloc: make(chan allocatedIP),
|
||||
queueReleased: make(chan net.IP),
|
||||
inUse: make(map[int32]struct{}),
|
||||
}
|
||||
if err := alloc.populate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return alloc, nil
|
||||
|
||||
go alloc.run()
|
||||
|
||||
return alloc
|
||||
}
|
||||
|
||||
// Network interface represents the networking stack of a container
|
||||
@@ -283,21 +405,54 @@ 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
|
||||
func (iface *NetworkInterface) Release() error {
|
||||
func (iface *NetworkInterface) Release() {
|
||||
for _, port := range iface.extPorts {
|
||||
if err := iface.manager.portMapper.Unmap(port); err != nil {
|
||||
log.Printf("Unable to unmap port %v: %v", port, err)
|
||||
@@ -307,7 +462,8 @@ func (iface *NetworkInterface) Release() error {
|
||||
}
|
||||
|
||||
}
|
||||
return iface.manager.ipAllocator.Release(iface.IPNet.IP)
|
||||
|
||||
iface.manager.ipAllocator.Release(iface.IPNet.IP)
|
||||
}
|
||||
|
||||
// Network Manager manages a set of network interfaces
|
||||
@@ -338,16 +494,20 @@ func (manager *NetworkManager) Allocate() (*NetworkInterface, error) {
|
||||
func newNetworkManager(bridgeIface string) (*NetworkManager, error) {
|
||||
addr, err := getIfaceAddr(bridgeIface)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// If the iface is not found, try to create it
|
||||
if err := CreateBridgeIface(bridgeIface); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addr, err = getIfaceAddr(bridgeIface)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
network := addr.(*net.IPNet)
|
||||
|
||||
ipAllocator, err := newIPAllocator(network)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ipAllocator := newIPAllocator(network)
|
||||
|
||||
portAllocator, err := newPortAllocator(portRangeStart, portRangeEnd)
|
||||
portAllocator, err := newPortAllocator()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
+179
-32
@@ -18,6 +18,42 @@ func TestIptables(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPortAllocation(t *testing.T) {
|
||||
allocator, err := newPortAllocator()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if port, err := allocator.Acquire(80); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if port != 80 {
|
||||
t.Fatalf("Acquire(80) should return 80, not %d", port)
|
||||
}
|
||||
port, err := allocator.Acquire(0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if port <= 0 {
|
||||
t.Fatalf("Acquire(0) should return a non-zero port")
|
||||
}
|
||||
if _, err := allocator.Acquire(port); err == nil {
|
||||
t.Fatalf("Acquiring a port already in use should return an error")
|
||||
}
|
||||
if newPort, err := allocator.Acquire(0); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if newPort == port {
|
||||
t.Fatalf("Acquire(0) allocated the same port twice: %d", port)
|
||||
}
|
||||
if _, err := allocator.Acquire(80); err == nil {
|
||||
t.Fatalf("Acquiring a port already in use should return an error")
|
||||
}
|
||||
if err := allocator.Release(80); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := allocator.Acquire(80); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNetworkRange(t *testing.T) {
|
||||
// Simple class C test
|
||||
_, network, _ := net.ParseCIDR("192.168.0.1/24")
|
||||
@@ -28,8 +64,8 @@ func TestNetworkRange(t *testing.T) {
|
||||
if !last.Equal(net.ParseIP("192.168.0.255")) {
|
||||
t.Error(last.String())
|
||||
}
|
||||
if size, err := networkSize(network.Mask); err != nil || size != 256 {
|
||||
t.Error(size, err)
|
||||
if size := networkSize(network.Mask); size != 256 {
|
||||
t.Error(size)
|
||||
}
|
||||
|
||||
// Class A test
|
||||
@@ -41,8 +77,8 @@ func TestNetworkRange(t *testing.T) {
|
||||
if !last.Equal(net.ParseIP("10.255.255.255")) {
|
||||
t.Error(last.String())
|
||||
}
|
||||
if size, err := networkSize(network.Mask); err != nil || size != 16777216 {
|
||||
t.Error(size, err)
|
||||
if size := networkSize(network.Mask); size != 16777216 {
|
||||
t.Error(size)
|
||||
}
|
||||
|
||||
// Class A, random IP address
|
||||
@@ -64,8 +100,8 @@ func TestNetworkRange(t *testing.T) {
|
||||
if !last.Equal(net.ParseIP("10.1.2.3")) {
|
||||
t.Error(last.String())
|
||||
}
|
||||
if size, err := networkSize(network.Mask); err != nil || size != 1 {
|
||||
t.Error(size, err)
|
||||
if size := networkSize(network.Mask); size != 1 {
|
||||
t.Error(size)
|
||||
}
|
||||
|
||||
// 31bit mask
|
||||
@@ -77,8 +113,8 @@ func TestNetworkRange(t *testing.T) {
|
||||
if !last.Equal(net.ParseIP("10.1.2.3")) {
|
||||
t.Error(last.String())
|
||||
}
|
||||
if size, err := networkSize(network.Mask); err != nil || size != 2 {
|
||||
t.Error(size, err)
|
||||
if size := networkSize(network.Mask); size != 2 {
|
||||
t.Error(size)
|
||||
}
|
||||
|
||||
// 26bit mask
|
||||
@@ -90,54 +126,165 @@ func TestNetworkRange(t *testing.T) {
|
||||
if !last.Equal(net.ParseIP("10.1.2.63")) {
|
||||
t.Error(last.String())
|
||||
}
|
||||
if size, err := networkSize(network.Mask); err != nil || size != 64 {
|
||||
t.Error(size, err)
|
||||
if size := networkSize(network.Mask); size != 64 {
|
||||
t.Error(size)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConversion(t *testing.T) {
|
||||
ip := net.ParseIP("127.0.0.1")
|
||||
i, err := ipToInt(ip)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
i := ipToInt(ip)
|
||||
if i == 0 {
|
||||
t.Fatal("converted to zero")
|
||||
}
|
||||
conv, err := intToIp(i)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
conv := intToIp(i)
|
||||
if !ip.Equal(conv) {
|
||||
t.Error(conv.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestIPAllocator(t *testing.T) {
|
||||
gwIP, n, _ := net.ParseCIDR("127.0.0.1/29")
|
||||
alloc, err := newIPAllocator(&net.IPNet{IP: gwIP, Mask: n.Mask})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
expectedIPs := []net.IP{
|
||||
0: net.IPv4(127, 0, 0, 2),
|
||||
1: net.IPv4(127, 0, 0, 3),
|
||||
2: net.IPv4(127, 0, 0, 4),
|
||||
3: net.IPv4(127, 0, 0, 5),
|
||||
4: net.IPv4(127, 0, 0, 6),
|
||||
}
|
||||
var lastIP net.IP
|
||||
|
||||
gwIP, n, _ := net.ParseCIDR("127.0.0.1/29")
|
||||
alloc := newIPAllocator(&net.IPNet{IP: gwIP, Mask: n.Mask})
|
||||
// Pool after initialisation (f = free, u = used)
|
||||
// 2(f) - 3(f) - 4(f) - 5(f) - 6(f)
|
||||
// ↑
|
||||
|
||||
// Check that we get 5 IPs, from 127.0.0.2–127.0.0.6, in that
|
||||
// order.
|
||||
for i := 0; i < 5; i++ {
|
||||
ip, err := alloc.Acquire()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
lastIP = ip
|
||||
|
||||
assertIPEquals(t, expectedIPs[i], ip)
|
||||
}
|
||||
ip, err := alloc.Acquire()
|
||||
// Before loop begin
|
||||
// 2(f) - 3(f) - 4(f) - 5(f) - 6(f)
|
||||
// ↑
|
||||
|
||||
// After i = 0
|
||||
// 2(u) - 3(f) - 4(f) - 5(f) - 6(f)
|
||||
// ↑
|
||||
|
||||
// After i = 1
|
||||
// 2(u) - 3(u) - 4(f) - 5(f) - 6(f)
|
||||
// ↑
|
||||
|
||||
// After i = 2
|
||||
// 2(u) - 3(u) - 4(u) - 5(f) - 6(f)
|
||||
// ↑
|
||||
|
||||
// After i = 3
|
||||
// 2(u) - 3(u) - 4(u) - 5(u) - 6(f)
|
||||
// ↑
|
||||
|
||||
// After i = 4
|
||||
// 2(u) - 3(u) - 4(u) - 5(u) - 6(u)
|
||||
// ↑
|
||||
|
||||
// Check that there are no more IPs
|
||||
_, err := alloc.Acquire()
|
||||
if err == nil {
|
||||
t.Fatal("There shouldn't be any IP addresses at this point")
|
||||
}
|
||||
// Release 1 IP
|
||||
alloc.Release(lastIP)
|
||||
ip, err = alloc.Acquire()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
// Release some IPs in non-sequential order
|
||||
alloc.Release(expectedIPs[3])
|
||||
// 2(u) - 3(u) - 4(u) - 5(f) - 6(u)
|
||||
// ↑
|
||||
|
||||
alloc.Release(expectedIPs[2])
|
||||
// 2(u) - 3(u) - 4(f) - 5(f) - 6(u)
|
||||
// ↑
|
||||
|
||||
alloc.Release(expectedIPs[4])
|
||||
// 2(u) - 3(u) - 4(f) - 5(f) - 6(f)
|
||||
// ↑
|
||||
|
||||
// Make sure that IPs are reused in sequential order, starting
|
||||
// with the first released IP
|
||||
newIPs := make([]net.IP, 3)
|
||||
for i := 0; i < 3; i++ {
|
||||
ip, err := alloc.Acquire()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
newIPs[i] = ip
|
||||
}
|
||||
if !ip.Equal(lastIP) {
|
||||
t.Fatal(ip.String())
|
||||
// Before loop begin
|
||||
// 2(u) - 3(u) - 4(f) - 5(f) - 6(f)
|
||||
// ↑
|
||||
|
||||
// After i = 0
|
||||
// 2(u) - 3(u) - 4(f) - 5(u) - 6(f)
|
||||
// ↑
|
||||
|
||||
// After i = 1
|
||||
// 2(u) - 3(u) - 4(f) - 5(u) - 6(u)
|
||||
// ↑
|
||||
|
||||
// After i = 2
|
||||
// 2(u) - 3(u) - 4(u) - 5(u) - 6(u)
|
||||
// ↑
|
||||
|
||||
assertIPEquals(t, expectedIPs[3], newIPs[0])
|
||||
assertIPEquals(t, expectedIPs[4], newIPs[1])
|
||||
assertIPEquals(t, expectedIPs[2], newIPs[2])
|
||||
|
||||
_, err = alloc.Acquire()
|
||||
if err == nil {
|
||||
t.Fatal("There shouldn't be any IP addresses at this point")
|
||||
}
|
||||
}
|
||||
|
||||
func assertIPEquals(t *testing.T, ip1, ip2 net.IP) {
|
||||
if !ip1.Equal(ip2) {
|
||||
t.Fatalf("Expected IP %s, got %s", ip1, ip2)
|
||||
}
|
||||
}
|
||||
|
||||
func AssertOverlap(CIDRx string, CIDRy string, t *testing.T) {
|
||||
_, netX, _ := net.ParseCIDR(CIDRx)
|
||||
_, netY, _ := net.ParseCIDR(CIDRy)
|
||||
if !networkOverlaps(netX, netY) {
|
||||
t.Errorf("%v and %v should overlap", netX, netY)
|
||||
}
|
||||
}
|
||||
|
||||
func AssertNoOverlap(CIDRx string, CIDRy string, t *testing.T) {
|
||||
_, netX, _ := net.ParseCIDR(CIDRx)
|
||||
_, netY, _ := net.ParseCIDR(CIDRy)
|
||||
if networkOverlaps(netX, netY) {
|
||||
t.Errorf("%v and %v should not overlap", netX, netY)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNetworkOverlaps(t *testing.T) {
|
||||
//netY starts at same IP and ends within netX
|
||||
AssertOverlap("172.16.0.1/24", "172.16.0.1/25", t)
|
||||
//netY starts within netX and ends at same IP
|
||||
AssertOverlap("172.16.0.1/24", "172.16.0.128/25", t)
|
||||
//netY starts and ends within netX
|
||||
AssertOverlap("172.16.0.1/24", "172.16.0.64/25", t)
|
||||
//netY starts at same IP and ends outside of netX
|
||||
AssertOverlap("172.16.0.1/24", "172.16.0.1/23", t)
|
||||
//netY starts before and ends at same IP of netX
|
||||
AssertOverlap("172.16.1.1/24", "172.16.0.1/23", t)
|
||||
//netY starts before and ends outside of netX
|
||||
AssertOverlap("172.16.1.1/24", "172.16.0.1/23", t)
|
||||
//netY starts and ends before netX
|
||||
AssertNoOverlap("172.16.1.1/25", "172.16.0.1/24", t)
|
||||
//netX starts and ends before netY
|
||||
AssertNoOverlap("172.16.1.1/25", "172.16.2.1/24", t)
|
||||
}
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
package rcli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
)
|
||||
|
||||
// Use this key to encode an RPC call into an URL,
|
||||
// eg. domain.tld/path/to/method?q=get_user&q=gordon
|
||||
const ARG_URL_KEY = "q"
|
||||
|
||||
func URLToCall(u *url.URL) (method string, args []string) {
|
||||
return path.Base(u.Path), u.Query()[ARG_URL_KEY]
|
||||
}
|
||||
|
||||
func ListenAndServeHTTP(addr string, service Service) error {
|
||||
return http.ListenAndServe(addr, http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
cmd, args := URLToCall(r.URL)
|
||||
if err := call(service, r.Body, &AutoFlush{w}, append([]string{cmd}, args...)...); err != nil {
|
||||
fmt.Fprintf(w, "Error: "+err.Error()+"\n")
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
type AutoFlush struct {
|
||||
http.ResponseWriter
|
||||
}
|
||||
|
||||
func (w *AutoFlush) Write(data []byte) (int, error) {
|
||||
ret, err := w.ResponseWriter.Write(data)
|
||||
if flusher, ok := w.ResponseWriter.(http.Flusher); ok {
|
||||
flusher.Flush()
|
||||
}
|
||||
return ret, err
|
||||
}
|
||||
+98
-6
@@ -2,6 +2,7 @@ package rcli
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -15,22 +16,109 @@ import (
|
||||
var DEBUG_FLAG bool = false
|
||||
var CLIENT_SOCKET io.Writer = nil
|
||||
|
||||
type DockerTCPConn struct {
|
||||
conn *net.TCPConn
|
||||
options *DockerConnOptions
|
||||
optionsBuf *[]byte
|
||||
handshaked bool
|
||||
client bool
|
||||
}
|
||||
|
||||
func NewDockerTCPConn(conn *net.TCPConn, client bool) *DockerTCPConn {
|
||||
return &DockerTCPConn{
|
||||
conn: conn,
|
||||
options: &DockerConnOptions{},
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *DockerTCPConn) SetOptionRawTerminal() {
|
||||
c.options.RawTerminal = true
|
||||
}
|
||||
|
||||
func (c *DockerTCPConn) GetOptions() *DockerConnOptions {
|
||||
if c.client && !c.handshaked {
|
||||
// Attempt to parse options encoded as a JSON dict and store
|
||||
// the reminder of what we read from the socket in a buffer.
|
||||
//
|
||||
// bufio (and its ReadBytes method) would have been nice here,
|
||||
// but if json.Unmarshal() fails (which will happen if we speak
|
||||
// to a version of docker that doesn't send any option), then
|
||||
// we can't put the data back in it for the next Read().
|
||||
c.handshaked = true
|
||||
buf := make([]byte, 4096)
|
||||
if n, _ := c.conn.Read(buf); n > 0 {
|
||||
buf = buf[:n]
|
||||
if nl := bytes.IndexByte(buf, '\n'); nl != -1 {
|
||||
if err := json.Unmarshal(buf[:nl], c.options); err == nil {
|
||||
buf = buf[nl+1:]
|
||||
}
|
||||
}
|
||||
c.optionsBuf = &buf
|
||||
}
|
||||
}
|
||||
|
||||
return c.options
|
||||
}
|
||||
|
||||
func (c *DockerTCPConn) Read(b []byte) (int, error) {
|
||||
if c.optionsBuf != nil {
|
||||
// Consume what we buffered in GetOptions() first:
|
||||
optionsBuf := *c.optionsBuf
|
||||
optionsBuflen := len(optionsBuf)
|
||||
copied := copy(b, optionsBuf)
|
||||
if copied < optionsBuflen {
|
||||
optionsBuf = optionsBuf[copied:]
|
||||
c.optionsBuf = &optionsBuf
|
||||
return copied, nil
|
||||
}
|
||||
c.optionsBuf = nil
|
||||
return copied, nil
|
||||
}
|
||||
return c.conn.Read(b)
|
||||
}
|
||||
|
||||
func (c *DockerTCPConn) Write(b []byte) (int, error) {
|
||||
optionsLen := 0
|
||||
if !c.client && !c.handshaked {
|
||||
c.handshaked = true
|
||||
options, _ := json.Marshal(c.options)
|
||||
options = append(options, '\n')
|
||||
if optionsLen, err := c.conn.Write(options); err != nil {
|
||||
return optionsLen, err
|
||||
}
|
||||
}
|
||||
n, err := c.conn.Write(b)
|
||||
return n + optionsLen, err
|
||||
}
|
||||
|
||||
func (c *DockerTCPConn) Flush() error {
|
||||
_, err := c.Write([]byte{})
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *DockerTCPConn) Close() error { return c.conn.Close() }
|
||||
|
||||
func (c *DockerTCPConn) CloseWrite() error { return c.conn.CloseWrite() }
|
||||
|
||||
func (c *DockerTCPConn) CloseRead() error { return c.conn.CloseRead() }
|
||||
|
||||
// Connect to a remote endpoint using protocol `proto` and address `addr`,
|
||||
// issue a single call, and return the result.
|
||||
// `proto` may be "tcp", "unix", etc. See the `net` package for available protocols.
|
||||
func Call(proto, addr string, args ...string) (*net.TCPConn, error) {
|
||||
func Call(proto, addr string, args ...string) (DockerConn, error) {
|
||||
cmd, err := json.Marshal(args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conn, err := net.Dial(proto, addr)
|
||||
conn, err := dialDocker(proto, addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := fmt.Fprintln(conn, string(cmd)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return conn.(*net.TCPConn), nil
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
// Listen on `addr`, using protocol `proto`, for incoming rcli calls,
|
||||
@@ -46,13 +134,17 @@ func ListenAndServe(proto, addr string, service Service) error {
|
||||
if conn, err := listener.Accept(); err != nil {
|
||||
return err
|
||||
} else {
|
||||
conn, err := newDockerServerConn(conn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
if DEBUG_FLAG {
|
||||
CLIENT_SOCKET = conn
|
||||
}
|
||||
if err := Serve(conn, service); err != nil {
|
||||
log.Printf("Error: " + err.Error() + "\n")
|
||||
fmt.Fprintf(conn, "Error: "+err.Error()+"\n")
|
||||
log.Println("Error:", err.Error())
|
||||
fmt.Fprintln(conn, "Error:", err.Error())
|
||||
}
|
||||
conn.Close()
|
||||
}()
|
||||
@@ -63,7 +155,7 @@ func ListenAndServe(proto, addr string, service Service) error {
|
||||
|
||||
// Parse an rcli call on a new connection, and pass it to `service` if it
|
||||
// is valid.
|
||||
func Serve(conn io.ReadWriter, service Service) error {
|
||||
func Serve(conn DockerConn, service Service) error {
|
||||
r := bufio.NewReader(conn)
|
||||
var args []string
|
||||
if line, err := r.ReadString('\n'); err != nil {
|
||||
|
||||
+89
-5
@@ -8,15 +8,99 @@ package rcli
|
||||
// are the usual suspects.
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/term"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type DockerConnOptions struct {
|
||||
RawTerminal bool
|
||||
}
|
||||
|
||||
type DockerConn interface {
|
||||
io.ReadWriteCloser
|
||||
CloseWrite() error
|
||||
CloseRead() error
|
||||
GetOptions() *DockerConnOptions
|
||||
SetOptionRawTerminal()
|
||||
Flush() error
|
||||
}
|
||||
|
||||
type DockerLocalConn struct {
|
||||
writer io.WriteCloser
|
||||
savedState *term.State
|
||||
}
|
||||
|
||||
func NewDockerLocalConn(w io.WriteCloser) *DockerLocalConn {
|
||||
return &DockerLocalConn{
|
||||
writer: w,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *DockerLocalConn) Read(b []byte) (int, error) {
|
||||
return 0, fmt.Errorf("DockerLocalConn does not implement Read()")
|
||||
}
|
||||
|
||||
func (c *DockerLocalConn) Write(b []byte) (int, error) { return c.writer.Write(b) }
|
||||
|
||||
func (c *DockerLocalConn) Close() error {
|
||||
if c.savedState != nil {
|
||||
RestoreTerminal(c.savedState)
|
||||
c.savedState = nil
|
||||
}
|
||||
return c.writer.Close()
|
||||
}
|
||||
|
||||
func (c *DockerLocalConn) Flush() error { return nil }
|
||||
|
||||
func (c *DockerLocalConn) CloseWrite() error { return nil }
|
||||
|
||||
func (c *DockerLocalConn) CloseRead() error { return nil }
|
||||
|
||||
func (c *DockerLocalConn) GetOptions() *DockerConnOptions { return nil }
|
||||
|
||||
func (c *DockerLocalConn) SetOptionRawTerminal() {
|
||||
if state, err := SetRawTerminal(); err != nil {
|
||||
if os.Getenv("DEBUG") != "" {
|
||||
log.Printf("Can't set the terminal in raw mode: %s", err)
|
||||
}
|
||||
} else {
|
||||
c.savedState = state
|
||||
}
|
||||
}
|
||||
|
||||
var UnknownDockerProto = fmt.Errorf("Only TCP is actually supported by Docker at the moment")
|
||||
|
||||
func dialDocker(proto string, addr string) (DockerConn, error) {
|
||||
conn, err := net.Dial(proto, addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch i := conn.(type) {
|
||||
case *net.TCPConn:
|
||||
return NewDockerTCPConn(i, true), nil
|
||||
}
|
||||
return nil, UnknownDockerProto
|
||||
}
|
||||
|
||||
func newDockerFromConn(conn net.Conn, client bool) (DockerConn, error) {
|
||||
switch i := conn.(type) {
|
||||
case *net.TCPConn:
|
||||
return NewDockerTCPConn(i, client), nil
|
||||
}
|
||||
return nil, UnknownDockerProto
|
||||
}
|
||||
|
||||
func newDockerServerConn(conn net.Conn) (DockerConn, error) {
|
||||
return newDockerFromConn(conn, false)
|
||||
}
|
||||
|
||||
type Service interface {
|
||||
Name() string
|
||||
Help() string
|
||||
@@ -26,11 +110,11 @@ type Cmd func(io.ReadCloser, io.Writer, ...string) error
|
||||
type CmdMethod func(Service, io.ReadCloser, io.Writer, ...string) error
|
||||
|
||||
// FIXME: For reverse compatibility
|
||||
func call(service Service, stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||
func call(service Service, stdin io.ReadCloser, stdout DockerConn, args ...string) error {
|
||||
return LocalCall(service, stdin, stdout, args...)
|
||||
}
|
||||
|
||||
func LocalCall(service Service, stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||
func LocalCall(service Service, stdin io.ReadCloser, stdout DockerConn, args ...string) error {
|
||||
if len(args) == 0 {
|
||||
args = []string{"help"}
|
||||
}
|
||||
@@ -49,7 +133,7 @@ func LocalCall(service Service, stdin io.ReadCloser, stdout io.Writer, args ...s
|
||||
if method != nil {
|
||||
return method(stdin, stdout, flags.Args()[1:]...)
|
||||
}
|
||||
return errors.New("No such command: " + cmd)
|
||||
return fmt.Errorf("No such command: %s", cmd)
|
||||
}
|
||||
|
||||
func getMethod(service Service, name string) Cmd {
|
||||
@@ -59,7 +143,7 @@ func getMethod(service Service, name string) Cmd {
|
||||
stdout.Write([]byte(service.Help()))
|
||||
} else {
|
||||
if method := getMethod(service, args[0]); method == nil {
|
||||
return errors.New("No such command: " + args[0])
|
||||
return fmt.Errorf("No such command: %s", args[0])
|
||||
} else {
|
||||
method(stdin, stdout, "--help")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package rcli
|
||||
|
||||
import (
|
||||
"github.com/dotcloud/docker/term"
|
||||
"os"
|
||||
"os/signal"
|
||||
)
|
||||
|
||||
//FIXME: move these function to utils.go (in rcli to avoid import loop)
|
||||
func SetRawTerminal() (*term.State, error) {
|
||||
oldState, err := term.MakeRaw(int(os.Stdin.Fd()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, os.Interrupt)
|
||||
go func() {
|
||||
_ = <-c
|
||||
term.Restore(int(os.Stdin.Fd()), oldState)
|
||||
os.Exit(0)
|
||||
}()
|
||||
return oldState, err
|
||||
}
|
||||
|
||||
func RestoreTerminal(state *term.State) {
|
||||
term.Restore(int(os.Stdin.Fd()), state)
|
||||
}
|
||||
+27
-18
@@ -20,7 +20,7 @@ func NewImgJson(src []byte) (*Image, error) {
|
||||
ret := &Image{}
|
||||
|
||||
Debugf("Json string: {%s}\n", src)
|
||||
// FIXME: Is there a cleaner way to "puryfy" the input json?
|
||||
// FIXME: Is there a cleaner way to "purify" the input json?
|
||||
if err := json.Unmarshal(src, ret); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -135,7 +135,7 @@ func (graph *Graph) getRemoteImage(stdout io.Writer, imgId string, authConfig *a
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return img, res.Body, nil
|
||||
return img, ProgressReader(res.Body, int(res.ContentLength), stdout), nil
|
||||
}
|
||||
|
||||
func (graph *Graph) PullImage(stdout io.Writer, imgId string, authConfig *auth.AuthConfig) error {
|
||||
@@ -183,10 +183,10 @@ func (graph *Graph) PullRepository(stdout io.Writer, remote, askedTag string, re
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != 200 {
|
||||
return fmt.Errorf("HTTP code: %d", res.StatusCode)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
rawJson, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -237,6 +237,7 @@ func (graph *Graph) PushImage(stdout io.Writer, imgOrig *Image, authConfig *auth
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to upload metadata: %s", err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != 200 {
|
||||
switch res.StatusCode {
|
||||
case 204:
|
||||
@@ -256,9 +257,13 @@ func (graph *Graph) PushImage(stdout io.Writer, imgOrig *Image, authConfig *auth
|
||||
req2, err := http.NewRequest("PUT", REGISTRY_ENDPOINT+"/images/"+img.Id+"/layer", nil)
|
||||
req2.SetBasicAuth(authConfig.Username, authConfig.Password)
|
||||
res2, err := client.Do(req2)
|
||||
if err != nil || res2.StatusCode != 307 {
|
||||
if err != nil {
|
||||
return fmt.Errorf("Registry returned error: %s", err)
|
||||
}
|
||||
res2.Body.Close()
|
||||
if res2.StatusCode != 307 {
|
||||
return fmt.Errorf("Registry returned unexpected HTTP status code %d, expected 307", res2.StatusCode)
|
||||
}
|
||||
url, err := res2.Location()
|
||||
if err != nil || url == nil {
|
||||
return fmt.Errorf("Failed to retrieve layer upload location: %s", err)
|
||||
@@ -266,26 +271,29 @@ func (graph *Graph) PushImage(stdout io.Writer, imgOrig *Image, authConfig *auth
|
||||
|
||||
// FIXME: Don't do this :D. Check the S3 requierement and implement chunks of 5MB
|
||||
// FIXME2: I won't stress it enough, DON'T DO THIS! very high priority
|
||||
layerData2, err := Tar(path.Join(graph.Root, img.Id, "layer"), Gzip)
|
||||
layerData, err := Tar(path.Join(graph.Root, img.Id, "layer"), Gzip)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to generate layer archive: %s", err)
|
||||
}
|
||||
req3, err := http.NewRequest("PUT", url.String(), layerData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
layerData2, err := Tar(path.Join(graph.Root, img.Id, "layer"), Xz)
|
||||
tmp, err := ioutil.ReadAll(layerData2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req3.ContentLength = int64(len(tmp))
|
||||
layerLength := len(tmp)
|
||||
|
||||
layerData, err := Tar(path.Join(graph.Root, img.Id, "layer"), Xz)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to generate layer archive: %s", err)
|
||||
}
|
||||
req3, err := http.NewRequest("PUT", url.String(), ProgressReader(layerData.(io.ReadCloser), layerLength, stdout))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req3.ContentLength = int64(layerLength)
|
||||
|
||||
req3.TransferEncoding = []string{"none"}
|
||||
res3, err := client.Do(req3)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to upload layer: %s", err)
|
||||
}
|
||||
res3.Body.Close()
|
||||
if res3.StatusCode != 200 {
|
||||
return fmt.Errorf("Received HTTP code %d while uploading layer", res3.StatusCode)
|
||||
}
|
||||
@@ -315,12 +323,13 @@ func (graph *Graph) pushTag(remote, revision, tag string, authConfig *auth.AuthC
|
||||
req.Header.Add("Content-type", "application/json")
|
||||
req.SetBasicAuth(authConfig.Username, authConfig.Password)
|
||||
res, err := client.Do(req)
|
||||
if err != nil || (res.StatusCode != 200 && res.StatusCode != 201) {
|
||||
if res != nil {
|
||||
return fmt.Errorf("Internal server error: %d trying to push tag %s on %s", res.StatusCode, tag, remote)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
res.Body.Close()
|
||||
if res.StatusCode != 200 && res.StatusCode != 201 {
|
||||
return fmt.Errorf("Internal server error: %d trying to push tag %s on %s", res.StatusCode, tag, remote)
|
||||
}
|
||||
Debugf("Result of push tag: %d\n", res.StatusCode)
|
||||
switch res.StatusCode {
|
||||
default:
|
||||
|
||||
+26
-7
@@ -7,9 +7,10 @@ import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"sort"
|
||||
"sync"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -132,11 +133,27 @@ 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 so, then we need to restart monitor and init a new lock
|
||||
// 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.State.initLock()
|
||||
|
||||
container.runtime = runtime
|
||||
// Setup state lock (formerly in newState()
|
||||
lock := new(sync.Mutex)
|
||||
container.State.stateChangeLock = lock
|
||||
container.State.stateChangeCond = sync.NewCond(lock)
|
||||
|
||||
// Attach to stdout and stderr
|
||||
container.stderr = newWriteBroadcaster()
|
||||
container.stdout = newWriteBroadcaster()
|
||||
@@ -250,7 +267,10 @@ func NewRuntimeFromDirectory(root string) (*Runtime, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Couldn't create Tag store: %s", err)
|
||||
}
|
||||
netManager, err := newNetworkManager(networkBridgeIface)
|
||||
if NetworkBridgeIface == "" {
|
||||
NetworkBridgeIface = DefaultNetworkBridge
|
||||
}
|
||||
netManager, err := newNetworkManager(NetworkBridgeIface)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -259,7 +279,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,
|
||||
|
||||
+66
-9
@@ -1,12 +1,15 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"github.com/dotcloud/docker/rcli"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// FIXME: this is no longer needed
|
||||
@@ -17,6 +20,15 @@ var unitTestStoreBase string
|
||||
var srv *Server
|
||||
|
||||
func nuke(runtime *Runtime) error {
|
||||
var wg sync.WaitGroup
|
||||
for _, container := range runtime.List() {
|
||||
wg.Add(1)
|
||||
go func(c *Container) {
|
||||
c.Kill()
|
||||
wg.Done()
|
||||
}(container)
|
||||
}
|
||||
wg.Wait()
|
||||
return os.RemoveAll(runtime.root)
|
||||
}
|
||||
|
||||
@@ -66,7 +78,7 @@ func init() {
|
||||
runtime: runtime,
|
||||
}
|
||||
// Retrieve the Image
|
||||
if err := srv.CmdPull(os.Stdin, os.Stdout, unitTestImageName); err != nil {
|
||||
if err := srv.CmdPull(os.Stdin, rcli.NewDockerLocalConn(os.Stdout), unitTestImageName); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
@@ -80,7 +92,6 @@ func newTestRuntime() (*Runtime, error) {
|
||||
return nil, err
|
||||
}
|
||||
if err := CopyDirectory(unitTestStoreBase, root); err != nil {
|
||||
panic(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -279,13 +290,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(2 * 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)
|
||||
@@ -293,14 +339,25 @@ 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)
|
||||
}
|
||||
container2.State.Running = false
|
||||
}
|
||||
|
||||
@@ -11,9 +11,7 @@ type State struct {
|
||||
Pid int
|
||||
ExitCode int
|
||||
StartedAt time.Time
|
||||
|
||||
stateChangeLock *sync.Mutex
|
||||
stateChangeCond *sync.Cond
|
||||
l *sync.Mutex
|
||||
}
|
||||
|
||||
// String returns a human-readable description of the state
|
||||
@@ -29,24 +27,22 @@ func (s *State) setRunning(pid int) {
|
||||
s.ExitCode = 0
|
||||
s.Pid = pid
|
||||
s.StartedAt = time.Now()
|
||||
s.broadcast()
|
||||
}
|
||||
|
||||
func (s *State) setStopped(exitCode int) {
|
||||
s.Running = false
|
||||
s.Pid = 0
|
||||
s.ExitCode = exitCode
|
||||
s.broadcast()
|
||||
}
|
||||
|
||||
func (s *State) broadcast() {
|
||||
s.stateChangeLock.Lock()
|
||||
s.stateChangeCond.Broadcast()
|
||||
s.stateChangeLock.Unlock()
|
||||
func (s *State) initLock() {
|
||||
s.l = &sync.Mutex{}
|
||||
}
|
||||
|
||||
func (s *State) wait() {
|
||||
s.stateChangeLock.Lock()
|
||||
s.stateChangeCond.Wait()
|
||||
s.stateChangeLock.Unlock()
|
||||
func (s *State) lock() {
|
||||
s.l.Lock()
|
||||
}
|
||||
|
||||
func (s *State) unlock() {
|
||||
s.l.Unlock()
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ func (store *TagStore) ImageName(id string) string {
|
||||
if names, exists := store.ById()[id]; exists && len(names) > 0 {
|
||||
return names[0]
|
||||
}
|
||||
return id
|
||||
return TruncateId(id)
|
||||
}
|
||||
|
||||
func (store *TagStore) Set(repoName, tag, imageName string, force bool) error {
|
||||
|
||||
@@ -15,7 +15,8 @@ void MakeRaw(int fd) {
|
||||
ioctl(fd, TCGETS, &t);
|
||||
|
||||
t.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
|
||||
t.c_lflag &= ~(ECHO | ECHONL | ICANON | IEXTEN);
|
||||
t.c_oflag &= ~OPOST;
|
||||
t.c_lflag &= ~(ECHO | ECHONL | ICANON | IEXTEN | ISIG);
|
||||
t.c_cflag &= ~(CSIZE | PARENB);
|
||||
t.c_cflag |= CS8;
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ package docker
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"container/list"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/rcli"
|
||||
@@ -45,7 +44,7 @@ func Download(url string, stderr io.Writer) (*http.Response, error) {
|
||||
// Debug function, if the debug flag is set, then display. Do nothing otherwise
|
||||
// If Docker is in damon mode, also send the debug info on the socket
|
||||
func Debugf(format string, a ...interface{}) {
|
||||
if rcli.DEBUG_FLAG {
|
||||
if os.Getenv("DEBUG") != "" {
|
||||
|
||||
// Retrieve the stack infos
|
||||
_, file, line, ok := runtime.Caller(1)
|
||||
@@ -203,7 +202,7 @@ func (r *bufReader) Read(p []byte) (n int, err error) {
|
||||
}
|
||||
r.wait.Wait()
|
||||
}
|
||||
return
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
func (r *bufReader) Close() error {
|
||||
@@ -215,52 +214,48 @@ func (r *bufReader) Close() error {
|
||||
}
|
||||
|
||||
type writeBroadcaster struct {
|
||||
writers *list.List
|
||||
mu sync.Mutex
|
||||
writers map[io.WriteCloser]struct{}
|
||||
}
|
||||
|
||||
func (w *writeBroadcaster) AddWriter(writer io.WriteCloser) {
|
||||
w.writers.PushBack(writer)
|
||||
w.mu.Lock()
|
||||
w.writers[writer] = struct{}{}
|
||||
w.mu.Unlock()
|
||||
}
|
||||
|
||||
// FIXME: Is that function used?
|
||||
// FIXME: This relies on the concrete writer type used having equality operator
|
||||
func (w *writeBroadcaster) RemoveWriter(writer io.WriteCloser) {
|
||||
for e := w.writers.Front(); e != nil; e = e.Next() {
|
||||
v := e.Value.(io.Writer)
|
||||
if v == writer {
|
||||
w.writers.Remove(e)
|
||||
return
|
||||
}
|
||||
}
|
||||
w.mu.Lock()
|
||||
delete(w.writers, writer)
|
||||
w.mu.Unlock()
|
||||
}
|
||||
|
||||
func (w *writeBroadcaster) Write(p []byte) (n int, err error) {
|
||||
failed := []*list.Element{}
|
||||
for e := w.writers.Front(); e != nil; e = e.Next() {
|
||||
writer := e.Value.(io.Writer)
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
for writer := range w.writers {
|
||||
if n, err := writer.Write(p); err != nil || n != len(p) {
|
||||
// On error, evict the writer
|
||||
failed = append(failed, e)
|
||||
delete(w.writers, writer)
|
||||
}
|
||||
}
|
||||
// We cannot remove while iterating, so it has to be done in
|
||||
// a separate step
|
||||
for _, e := range failed {
|
||||
w.writers.Remove(e)
|
||||
}
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func (w *writeBroadcaster) Close() error {
|
||||
for e := w.writers.Front(); e != nil; e = e.Next() {
|
||||
writer := e.Value.(io.WriteCloser)
|
||||
func (w *writeBroadcaster) CloseWriters() error {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
for writer := range w.writers {
|
||||
writer.Close()
|
||||
}
|
||||
w.writers.Init()
|
||||
w.writers = make(map[io.WriteCloser]struct{})
|
||||
return nil
|
||||
}
|
||||
|
||||
func newWriteBroadcaster() *writeBroadcaster {
|
||||
return &writeBroadcaster{list.New()}
|
||||
return &writeBroadcaster{writers: make(map[io.WriteCloser]struct{})}
|
||||
}
|
||||
|
||||
func getTotalUsedFds() int {
|
||||
@@ -334,3 +329,58 @@ func (idx *TruncIndex) Get(s string) (string, error) {
|
||||
}
|
||||
return string(idx.bytes[before:after]), err
|
||||
}
|
||||
|
||||
// TruncateId returns a shorthand version of a string identifier for convenience.
|
||||
// A collision with other shorthands is very unlikely, but possible.
|
||||
// In case of a collision a lookup with TruncIndex.Get() will fail, and the caller
|
||||
// will need to use a langer prefix, or the full-length Id.
|
||||
func TruncateId(id string) string {
|
||||
shortLen := 12
|
||||
if len(id) < shortLen {
|
||||
shortLen = len(id)
|
||||
}
|
||||
return id[:shortLen]
|
||||
}
|
||||
|
||||
// Code c/c from io.Copy() modified to handle escape sequence
|
||||
func CopyEscapable(dst io.Writer, src io.ReadCloser) (written int64, err error) {
|
||||
buf := make([]byte, 32*1024)
|
||||
for {
|
||||
nr, er := src.Read(buf)
|
||||
if nr > 0 {
|
||||
// ---- Docker addition
|
||||
// char 16 is C-p
|
||||
if nr == 1 && buf[0] == 16 {
|
||||
nr, er = src.Read(buf)
|
||||
// char 17 is C-q
|
||||
if nr == 1 && buf[0] == 17 {
|
||||
if err := src.Close(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return 0, io.EOF
|
||||
}
|
||||
}
|
||||
// ---- End of docker
|
||||
nw, ew := dst.Write(buf[0:nr])
|
||||
if nw > 0 {
|
||||
written += int64(nw)
|
||||
}
|
||||
if ew != nil {
|
||||
err = ew
|
||||
break
|
||||
}
|
||||
if nr != nw {
|
||||
err = io.ErrShortWrite
|
||||
break
|
||||
}
|
||||
}
|
||||
if er == io.EOF {
|
||||
break
|
||||
}
|
||||
if er != nil {
|
||||
err = er
|
||||
break
|
||||
}
|
||||
}
|
||||
return written, err
|
||||
}
|
||||
|
||||
+23
-1
@@ -122,7 +122,29 @@ func TestWriteBroadcaster(t *testing.T) {
|
||||
t.Errorf("Buffer contains %v", bufferC.String())
|
||||
}
|
||||
|
||||
writer.Close()
|
||||
writer.CloseWriters()
|
||||
}
|
||||
|
||||
type devNullCloser int
|
||||
|
||||
func (d devNullCloser) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d devNullCloser) Write(buf []byte) (int, error) {
|
||||
return len(buf), nil
|
||||
}
|
||||
|
||||
// This test checks for races. It is only useful when run with the race detector.
|
||||
func TestRaceWriteBroadcaster(t *testing.T) {
|
||||
writer := newWriteBroadcaster()
|
||||
c := make(chan bool)
|
||||
go func() {
|
||||
writer.AddWriter(devNullCloser(0))
|
||||
c <- true
|
||||
}()
|
||||
writer.Write([]byte("hello"))
|
||||
<-c
|
||||
}
|
||||
|
||||
// Test the behavior of TruncIndex, an index for querying IDs from a non-conflicting prefix.
|
||||
|
||||
Reference in New Issue
Block a user