mirror of
https://github.com/clearlinux/docker.git
synced 2026-08-19 20:26:54 +00:00
Compare commits
115 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a4fc52305a | |||
| 2aad4a3478 | |||
| a5fb1d6c01 | |||
| f344212b93 | |||
| 0424998f38 | |||
| 8bfbdd7afa | |||
| 3de51b7bfe | |||
| a58cd8c616 | |||
| 586a79cca0 | |||
| 349edf1bea | |||
| 677908910c | |||
| 6b5fe8c2ec | |||
| 26088a72b3 | |||
| ebc837957f | |||
| c4d3da5871 | |||
| 32f5811476 | |||
| a7f191d51d | |||
| 92186d7cf7 | |||
| 5d3c0767da | |||
| 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 .
|
||||
|
||||
@@ -192,11 +192,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 +205,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 {
|
||||
|
||||
+80
-116
@@ -13,13 +13,14 @@ import (
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
const VERSION = "0.1.1"
|
||||
const VERSION = "0.1.3"
|
||||
|
||||
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"},
|
||||
@@ -65,7 +66,7 @@ func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout io.Writer, args ...strin
|
||||
// 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
|
||||
}
|
||||
@@ -146,12 +147,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 +180,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 +209,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 +371,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, " "),
|
||||
)
|
||||
@@ -438,7 +440,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,7 +460,7 @@ 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
|
||||
}
|
||||
|
||||
@@ -510,10 +512,9 @@ func (srv *Server) CmdPush(stdin io.ReadCloser, stdout io.Writer, args ...string
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
return err
|
||||
}
|
||||
err = srv.runtime.graph.PushImage(stdout, img, srv.runtime.authConfig)
|
||||
if err != nil {
|
||||
@@ -533,6 +534,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 +566,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 +593,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 +605,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 +616,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 +628,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 +649,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 +702,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
|
||||
}
|
||||
|
||||
@@ -797,55 +799,7 @@ 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
|
||||
}
|
||||
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
|
||||
return <-container.Attach(stdin, nil, stdout, stdout)
|
||||
}
|
||||
|
||||
// Ports type - Used to parse multiple -p flags
|
||||
@@ -876,6 +830,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")
|
||||
@@ -919,54 +900,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
|
||||
}
|
||||
|
||||
|
||||
+231
-57
@@ -2,8 +2,10 @@ package docker
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"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,244 @@ 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}
|
||||
var stdin, stdout bytes.Buffer
|
||||
setTimeout(t, "CmdRun timed out", 2*time.Second, func() {
|
||||
if err := srv.CmdRun(ioutil.NopCloser(&stdin), &nopWriteCloser{&stdout}, "-h", "foobar", GetTestImage(runtime).Id, "hostname"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
if output := string(stdout.Bytes()); output != "foobar\n" {
|
||||
t.Fatalf("'hostname' should display '%s', not '%s'", "foobar\n", output)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}()
|
||||
func TestRunExit(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
stdin, stdinPipe := io.Pipe()
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
c1 := make(chan struct{})
|
||||
go func() {
|
||||
srv.CmdRun(stdin, stdoutPipe, "-i", GetTestImage(runtime).Id, "/bin/cat")
|
||||
close(c1)
|
||||
}()
|
||||
|
||||
setTimeout(t, "Read/Write assertion timed out", 2*time.Second, func() {
|
||||
if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
// Close pipes (simulate disconnect)
|
||||
if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
container := runtime.List()[0]
|
||||
|
||||
container := runtime.containers.Back().Value.(*Container)
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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
|
||||
// as the process exited, CmdRun must finish and unblock. Wait for it
|
||||
setTimeout(t, "Waiting for CmdRun timed out", 2*time.Second, func() {
|
||||
<-c1
|
||||
<-c2
|
||||
})
|
||||
|
||||
// Finished, no timeout
|
||||
timeout <- false
|
||||
// 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, 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")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 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 TestAttachStdin(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(runtime)
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
stdinR, stdinW := io.Pipe()
|
||||
var stdout bytes.Buffer
|
||||
|
||||
ch := make(chan struct{})
|
||||
go func() {
|
||||
srv.CmdRun(stdinR, &stdout, "-i", "-a", "stdin", GetTestImage(runtime).Id, "sh", "-c", "echo hello; cat")
|
||||
close(ch)
|
||||
}()
|
||||
|
||||
// Send input to the command, close stdin, wait for CmdRun to return
|
||||
setTimeout(t, "Read/Write timed out", 2*time.Second, func() {
|
||||
if _, err := stdinW.Write([]byte("hi there\n")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
stdinW.Close()
|
||||
<-ch
|
||||
})
|
||||
|
||||
// Check output
|
||||
cmdOutput := string(stdout.Bytes())
|
||||
container := runtime.List()[0]
|
||||
if cmdOutput != container.ShortId()+"\n" {
|
||||
t.Fatalf("Wrong output: should be '%s', not '%s'\n", container.ShortId()+"\n", cmdOutput)
|
||||
}
|
||||
|
||||
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, 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()
|
||||
}
|
||||
|
||||
+180
-44
@@ -48,17 +48,20 @@ type Container 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
|
||||
}
|
||||
@@ -167,14 +195,14 @@ func (container *Container) startPty() error {
|
||||
|
||||
// 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)
|
||||
Debugf("[startPty] End of stdout pipe")
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer container.stderr.Close()
|
||||
defer container.stderr.CloseWriters()
|
||||
Debugf("[startPty] Begin of stderr pipe")
|
||||
io.Copy(container.stderr, stderrMaster)
|
||||
Debugf("[startPty] End of stderr pipe")
|
||||
@@ -229,7 +257,116 @@ 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 {
|
||||
defer cStdin.Close()
|
||||
}
|
||||
_, 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 {
|
||||
if container.State.Running {
|
||||
return fmt.Errorf("The container %s is already running.", container.Id)
|
||||
}
|
||||
if err := container.EnsureMounted(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -345,12 +482,12 @@ func (container *Container) allocateNetwork() error {
|
||||
return err
|
||||
}
|
||||
container.NetworkSettings.PortMapping = make(map[string]string)
|
||||
for _, port := range container.Config.Ports {
|
||||
if extPort, err := iface.AllocatePort(port); err != nil {
|
||||
for _, spec := range container.Config.PortSpecs {
|
||||
if nat, err := iface.AllocatePort(spec); err != nil {
|
||||
iface.Release()
|
||||
return err
|
||||
} else {
|
||||
container.NetworkSettings.PortMapping[strconv.Itoa(port)] = strconv.Itoa(extPort)
|
||||
container.NetworkSettings.PortMapping[strconv.Itoa(nat.Backend)] = strconv.Itoa(nat.Frontend)
|
||||
}
|
||||
}
|
||||
container.network = iface
|
||||
@@ -360,11 +497,10 @@ func (container *Container) allocateNetwork() error {
|
||||
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,18 +515,16 @@ 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)
|
||||
}
|
||||
|
||||
@@ -422,7 +556,13 @@ func (container *Container) monitor() {
|
||||
// Report status back
|
||||
container.State.setStopped(exitCode)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -452,8 +592,8 @@ func (container *Container) Stop() error {
|
||||
|
||||
// 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")
|
||||
log.Print(string(output))
|
||||
log.Print("Failed to send SIGTERM to the process, force killing")
|
||||
if err := container.Kill(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -512,7 +652,7 @@ func (container *Container) WaitTimeout(timeout time.Duration) error {
|
||||
case <-done:
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
func (container *Container) EnsureMounted() error {
|
||||
@@ -560,11 +700,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 {
|
||||
|
||||
+40
-2
@@ -227,10 +227,48 @@ 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()
|
||||
}
|
||||
|
||||
func TestRun(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
@@ -851,7 +889,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.
|
||||
|
||||
+6
-1
@@ -11,6 +11,8 @@ import (
|
||||
"os/signal"
|
||||
)
|
||||
|
||||
var GIT_COMMIT string
|
||||
|
||||
func main() {
|
||||
if docker.SelfPath() == "/sbin/init" {
|
||||
// Running in init mode
|
||||
@@ -21,7 +23,10 @@ func main() {
|
||||
flDaemon := flag.Bool("d", false, "Daemon mode")
|
||||
flDebug := flag.Bool("D", false, "Debug mode")
|
||||
flag.Parse()
|
||||
rcli.DEBUG_FLAG = *flDebug
|
||||
if *flDebug {
|
||||
os.Setenv("DEBUG", "1")
|
||||
}
|
||||
docker.GIT_COMMIT = GIT_COMMIT
|
||||
if *flDaemon {
|
||||
if flag.NArg() != 0 {
|
||||
flag.Usage()
|
||||
|
||||
@@ -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,100 @@
|
||||
: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.
|
||||
echo hello world | nc $(hostname) $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.
|
||||
|
||||
@@ -6,9 +6,7 @@
|
||||
|
||||
Hello World
|
||||
===========
|
||||
This is the most basic example available for using docker
|
||||
|
||||
This example assumes you have Docker installed.
|
||||
This is the most basic example available for using Docker. The example assumes you have Docker installed.
|
||||
|
||||
|
||||
Download the base container
|
||||
|
||||
@@ -79,11 +79,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``
|
||||
|
||||
@@ -15,3 +15,4 @@ Contents:
|
||||
hello_world
|
||||
hello_world_daemon
|
||||
python_web_app
|
||||
running_ssh_service
|
||||
|
||||
@@ -65,6 +65,4 @@ See the example in action
|
||||
<iframe width="720" height="350" src="http://ascii.io/a/2573/raw" frameborder="0"></iframe>
|
||||
</div>
|
||||
|
||||
Continue to the `base commands`_
|
||||
|
||||
.. _base commands: ../commandline/basecommands.html
|
||||
Continue to :ref:`running_ssh_service`.
|
||||
|
||||
@@ -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
|
||||
============================
|
||||
|
||||
|
||||
|
||||
|
||||
**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``
|
||||
@@ -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")
|
||||
|
||||
+180
-114
@@ -1,7 +1,6 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -10,6 +9,7 @@ import (
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -30,40 +30,25 @@ func networkRange(network *net.IPNet) (net.IP, net.IP) {
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
return n, nil
|
||||
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 n + 1, nil
|
||||
|
||||
return int32(binary.BigEndian.Uint32(m)) + 1
|
||||
}
|
||||
|
||||
// Wrapper around the iptables command
|
||||
@@ -114,6 +99,9 @@ type PortMapper struct {
|
||||
|
||||
func (mapper *PortMapper) cleanup() error {
|
||||
// Ignore errors - This could mean the chains were never set up
|
||||
iptables("-t", "nat", "-D", "PREROUTING", "-m", "addrtype", "--dst-type", "LOCAL", "-j", "DOCKER")
|
||||
iptables("-t", "nat", "-D", "OUTPUT", "-m", "addrtype", "--dst-type", "LOCAL", "-j", "DOCKER")
|
||||
// Also cleanup rules created by older versions, or -X might fail.
|
||||
iptables("-t", "nat", "-D", "PREROUTING", "-j", "DOCKER")
|
||||
iptables("-t", "nat", "-D", "OUTPUT", "-j", "DOCKER")
|
||||
iptables("-t", "nat", "-F", "DOCKER")
|
||||
@@ -126,10 +114,10 @@ func (mapper *PortMapper) setup() error {
|
||||
if err := iptables("-t", "nat", "-N", "DOCKER"); err != nil {
|
||||
return fmt.Errorf("Failed to create DOCKER chain: %s", err)
|
||||
}
|
||||
if err := iptables("-t", "nat", "-A", "PREROUTING", "-j", "DOCKER"); err != nil {
|
||||
if err := iptables("-t", "nat", "-A", "PREROUTING", "-m", "addrtype", "--dst-type", "LOCAL", "-j", "DOCKER"); err != nil {
|
||||
return fmt.Errorf("Failed to inject docker in PREROUTING chain: %s", err)
|
||||
}
|
||||
if err := iptables("-t", "nat", "-A", "OUTPUT", "-j", "DOCKER"); err != nil {
|
||||
if err := iptables("-t", "nat", "-A", "OUTPUT", "-m", "addrtype", "--dst-type", "LOCAL", "-j", "DOCKER"); err != nil {
|
||||
return fmt.Errorf("Failed to inject docker in OUTPUT chain: %s", err)
|
||||
}
|
||||
return nil
|
||||
@@ -173,104 +161,151 @@ func newPortMapper() (*PortMapper, error) {
|
||||
|
||||
// Port allocator: Atomatically allocate and release networking ports
|
||||
type PortAllocator struct {
|
||||
ports chan (int)
|
||||
inUse map[int]struct{}
|
||||
fountain chan (int)
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
func (alloc *PortAllocator) populate(start, end int) {
|
||||
alloc.ports = make(chan int, end-start)
|
||||
for port := start; port < end; port++ {
|
||||
alloc.ports <- port
|
||||
func (alloc *PortAllocator) runFountain() {
|
||||
if alloc.fountain != nil {
|
||||
return
|
||||
}
|
||||
alloc.fountain = make(chan int)
|
||||
for {
|
||||
for port := portRangeStart; port < portRangeEnd; port++ {
|
||||
alloc.fountain <- port
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (alloc *PortAllocator) Acquire() (int, error) {
|
||||
select {
|
||||
case port := <-alloc.ports:
|
||||
return port, nil
|
||||
default:
|
||||
return -1, errors.New("No more ports available")
|
||||
}
|
||||
return -1, nil
|
||||
}
|
||||
|
||||
// FIXME: Release can no longer fail, change its prototype to reflect that.
|
||||
func (alloc *PortAllocator) Release(port int) error {
|
||||
select {
|
||||
case alloc.ports <- port:
|
||||
return nil
|
||||
default:
|
||||
return errors.New("Too many ports have been released")
|
||||
}
|
||||
alloc.lock.Lock()
|
||||
delete(alloc.inUse, port)
|
||||
alloc.lock.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func newPortAllocator(start, end int) (*PortAllocator, error) {
|
||||
allocator := &PortAllocator{}
|
||||
allocator.populate(start, end)
|
||||
func (alloc *PortAllocator) Acquire(port int) (int, error) {
|
||||
if port == 0 {
|
||||
// Allocate a port from the fountain
|
||||
for port := range alloc.fountain {
|
||||
if _, err := alloc.Acquire(port); err == nil {
|
||||
return port, nil
|
||||
}
|
||||
}
|
||||
return -1, fmt.Errorf("Port generator ended unexpectedly")
|
||||
}
|
||||
alloc.lock.Lock()
|
||||
defer alloc.lock.Unlock()
|
||||
if _, inUse := alloc.inUse[port]; inUse {
|
||||
return -1, fmt.Errorf("Port already in use: %d", port)
|
||||
}
|
||||
alloc.inUse[port] = struct{}{}
|
||||
return port, nil
|
||||
}
|
||||
|
||||
func newPortAllocator() (*PortAllocator, error) {
|
||||
allocator := &PortAllocator{
|
||||
inUse: make(map[int]struct{}),
|
||||
}
|
||||
go allocator.runFountain()
|
||||
return allocator, nil
|
||||
}
|
||||
|
||||
// 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 +318,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 +375,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 +407,13 @@ func (manager *NetworkManager) Allocate() (*NetworkInterface, error) {
|
||||
func newNetworkManager(bridgeIface string) (*NetworkManager, error) {
|
||||
addr, err := getIfaceAddr(bridgeIface)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("Couldn't find bridge interface %s (%s).\nPlease create it with 'ip link add lxcbr0 type bridge; ip addr add ADDRESS/MASK dev lxcbr0'", bridgeIface, err)
|
||||
}
|
||||
network := addr.(*net.IPNet)
|
||||
|
||||
ipAllocator, 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
|
||||
}
|
||||
|
||||
+108
-32
@@ -28,8 +28,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 +41,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 +64,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 +77,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 +90,130 @@ 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)
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ func ListenAndServeHTTP(addr string, service Service) error {
|
||||
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")
|
||||
fmt.Fprintln(w, "Error:", err.Error())
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
+2
-2
@@ -51,8 +51,8 @@ func ListenAndServe(proto, addr string, service Service) error {
|
||||
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()
|
||||
}()
|
||||
|
||||
+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:
|
||||
|
||||
+21
-5
@@ -7,9 +7,10 @@ import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"sort"
|
||||
"sync"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -115,6 +116,7 @@ func (runtime *Runtime) Load(id string) (*Container, error) {
|
||||
if err := container.FromDisk(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
container.State.initLock()
|
||||
if container.Id != id {
|
||||
return container, fmt.Errorf("Container %s is stored at %s", container.Id, id)
|
||||
}
|
||||
@@ -132,11 +134,26 @@ func (runtime *Runtime) Register(container *Container) error {
|
||||
if err := validateId(container.Id); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// FIXME: if the container is supposed to be running but is not, auto restart it?
|
||||
// If the container is supposed to be running, make sure of it
|
||||
if container.State.Running {
|
||||
if output, err := exec.Command("lxc-info", "-n", container.Id).CombinedOutput(); err != nil {
|
||||
return err
|
||||
} else {
|
||||
if !strings.Contains(string(output), "RUNNING") {
|
||||
Debugf("Container %s was supposed to be running be is not.", container.Id)
|
||||
container.State.setStopped(-127)
|
||||
if err := container.ToDisk(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
container.runtime = runtime
|
||||
// Setup state lock (formerly in newState()
|
||||
lock := new(sync.Mutex)
|
||||
container.State.stateChangeLock = lock
|
||||
container.State.stateChangeCond = sync.NewCond(lock)
|
||||
container.State.initLock()
|
||||
// Attach to stdout and stderr
|
||||
container.stderr = newWriteBroadcaster()
|
||||
container.stdout = newWriteBroadcaster()
|
||||
@@ -259,7 +276,6 @@ func NewRuntimeFromDirectory(root string) (*Runtime, error) {
|
||||
// If the auth file does not exist, keep going
|
||||
return nil, err
|
||||
}
|
||||
|
||||
runtime := &Runtime{
|
||||
root: root,
|
||||
repository: runtimeRepo,
|
||||
|
||||
+63
-8
@@ -6,7 +6,9 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// FIXME: this is no longer needed
|
||||
@@ -17,6 +19,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)
|
||||
}
|
||||
|
||||
@@ -80,7 +91,6 @@ func newTestRuntime() (*Runtime, error) {
|
||||
return nil, err
|
||||
}
|
||||
if err := CopyDirectory(unitTestStoreBase, root); err != nil {
|
||||
panic(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -279,13 +289,48 @@ func TestRestore(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer runtime1.Destroy(container1)
|
||||
if len(runtime1.List()) != 1 {
|
||||
t.Errorf("Expected 1 container, %v found", len(runtime1.List()))
|
||||
|
||||
// Create a second container meant to be killed
|
||||
container2, err := runtime1.Create(&Config{
|
||||
Image: GetTestImage(runtime1).Id,
|
||||
Cmd: []string{"/bin/cat"},
|
||||
OpenStdin: true,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer runtime1.Destroy(container2)
|
||||
|
||||
// Start the container non blocking
|
||||
if err := container2.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !container2.State.Running {
|
||||
t.Fatalf("Container %v should appear as running but isn't", container2.Id)
|
||||
}
|
||||
|
||||
// Simulate a crash/manual quit of dockerd: process dies, states stays 'Running'
|
||||
cStdin, _ := container2.StdinPipe()
|
||||
cStdin.Close()
|
||||
if err := container2.WaitTimeout(time.Second); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
container2.State.Running = true
|
||||
container2.ToDisk()
|
||||
|
||||
if len(runtime1.List()) != 2 {
|
||||
t.Errorf("Expected 2 container, %v found", len(runtime1.List()))
|
||||
}
|
||||
if err := container1.Run(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !container2.State.Running {
|
||||
t.Fatalf("Container %v should appear as running but isn't", container2.Id)
|
||||
}
|
||||
|
||||
// Here are are simulating a docker restart - that is, reloading all containers
|
||||
// from scratch
|
||||
runtime2, err := NewRuntimeFromDirectory(root)
|
||||
@@ -293,14 +338,24 @@ func TestRestore(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(runtime2)
|
||||
if len(runtime2.List()) != 1 {
|
||||
t.Errorf("Expected 1 container, %v found", len(runtime2.List()))
|
||||
if len(runtime2.List()) != 2 {
|
||||
t.Errorf("Expected 2 container, %v found", len(runtime2.List()))
|
||||
}
|
||||
container2 := runtime2.Get(container1.Id)
|
||||
if container2 == nil {
|
||||
runningCount := 0
|
||||
for _, c := range runtime2.List() {
|
||||
if c.State.Running {
|
||||
t.Errorf("Running container found: %v (%v)", c.Id, c.Path)
|
||||
runningCount++
|
||||
}
|
||||
}
|
||||
if runningCount != 0 {
|
||||
t.Fatalf("Expected 0 container alive, %d found", runningCount)
|
||||
}
|
||||
container3 := runtime2.Get(container1.Id)
|
||||
if container3 == nil {
|
||||
t.Fatal("Unable to Get container")
|
||||
}
|
||||
if err := container2.Run(); err != nil {
|
||||
if err := container3.Run(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,13 @@ func (s *State) setStopped(exitCode int) {
|
||||
s.broadcast()
|
||||
}
|
||||
|
||||
func (s *State) initLock() {
|
||||
if s.stateChangeLock == nil {
|
||||
s.stateChangeLock = &sync.Mutex{}
|
||||
s.stateChangeCond = sync.NewCond(s.stateChangeLock)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *State) broadcast() {
|
||||
s.stateChangeLock.Lock()
|
||||
s.stateChangeCond.Broadcast()
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,15 @@ 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]
|
||||
}
|
||||
|
||||
+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