Compare commits

...

73 Commits

Author SHA1 Message Date
Solomon Hykes dce5a06aba Bumped version to 0.1.2 2013-04-03 15:35:32 -07:00
Solomon Hykes 15c3096e89 Merged attachStdin 2013-04-02 20:46:32 -07:00
Solomon Hykes 22d1622926 Merge remote-tracking branch 'robryk/writebroadcaster-stuff' 2013-04-02 20:35:13 -07:00
Solomon Hykes 35c68944c7 Merged shawnsi/git-version 2013-04-02 20:26:22 -07:00
Solomon Hykes b1fa26bb76 TestRunHostname checks that 'docker run -h' correctly sets a custom hostname 2013-04-02 19:00:05 -07:00
Solomon Hykes d6fb2a0836 Merge remote-tracking branch 'sa2ajj/hostname-parameter' 2013-04-02 18:41:14 -07:00
Solomon Hykes 65aa3dda85 Merged dhrp/docs 2013-04-02 18:39:39 -07:00
Solomon Hykes a05af3ebf3 Fix a bug which caused 'docker push' to fail with 411-lenght-required in Go 1.0 2013-04-02 18:37:29 -07:00
Solomon Hykes 0e1781af26 Merge remote-tracking branch 'unclejack/194-high-image-compression' 2013-04-02 18:10:07 -07:00
Solomon Hykes 14d9f51bbe Merge branch 'master' into attach-stdin 2013-04-02 18:07:45 -07:00
Solomon Hykes c04af2a330 docker run [-a [stdin|stdout|stderr] [...]]: choose which streams to attach to when running a command. Fixes #234. 2013-04-02 18:07:16 -07:00
Solomon Hykes c77063afcd Add test for attaching only stdin at run with 'docker run -i -a=stdin' 2013-04-02 18:05:19 -07:00
Solomon Hykes 2db358146f 'docker run' in attached mode no longer waits for the process to exit. Take this into account in the tests. 2013-04-02 11:07:49 -07:00
Solomon Hykes 1cc1cb099e When simulating disconnects in the tests, make sure that the command returns - but don't check for a specific return value 2013-04-02 11:06:42 -07:00
Solomon Hykes aea2675f7b Activate Config.StdinOnce at argument parsing 2013-04-02 11:02:19 -07:00
Solomon Hykes 8f9e454241 [unit tests] Cleanly kill all containers before nuking a temporary runtime 2013-04-02 11:00:14 -07:00
Solomon Hykes 7666307931 Use an environment variable to activate debug mode, instead of a package-specific flag 2013-04-02 10:58:16 -07:00
Shawn Siefkas 5471f5b2ee Implementing dirty git checkout indicator 2013-04-02 09:58:19 -05:00
Joffrey F 927308cfd9 Merge pull request #317 from dotcloud/307-loginfix
Fix for #307
2013-04-02 03:01:23 -07:00
shin- 8fed600077 Fix for #307 2013-04-02 03:00:21 -07:00
Robert Obryk a83d87abd4 Renamed writeBroadcaster.Close() to CloseWriters(). 2013-04-02 10:45:17 +02:00
Solomon Hykes c808940c04 Refactored CmdRun and CmdAttach to use Container.Attach 2013-04-01 23:52:20 -07:00
Thatcher Peskens 81874ad442 Fixed a lot of language and small things. 2013-04-01 19:11:09 -07:00
Solomon Hykes 3478d6f706 Merge pull request #288 from titanous/expand-contributing
Expand the contributing guidelines
2013-04-01 18:45:18 -07:00
Solomon Hykes 13b6309138 Merge pull request #312 from sa2ajj/devenv-doc-fix
fix code block formatting
2013-04-01 18:40:29 -07:00
Mikhail Sobolev 90db9e7517 fix code block formatting 2013-04-02 04:35:54 +03:00
Thatcher Peskens f15889461d Merge remote-tracking branch 'dotcloud/master' into dhrp/docs 2013-04-01 18:11:46 -07:00
Guillaume J. Charmes dea4194f8b Merge pull request #286 from titanous/285-close-bodies
Close HTTP response bodies
2013-04-01 17:59:22 -07:00
Solomon Hykes e331f1ee0e Merge pull request #305 from fsouza/master
Makefile: simplify "fmt" target
2013-04-01 16:28:29 -07:00
unclejack 0ddc6867fb use xz compression when pushing images to the registry
This makes the xz compression the default.
2013-04-02 02:19:34 +03:00
unclejack 3c5d2e4661 add support for xz compression
This adds xz as a compression option.
xz is alread supported by bsdtar without the external xz program.
2013-04-02 02:16:28 +03:00
Solomon Hykes ff5cb8e864 Images can be removed by short-hand ID. Solves #306. 2013-04-01 16:04:44 -07:00
Solomon Hykes 7ad2e022fb Add test to reproduce issue #306 2013-04-01 16:02:02 -07:00
Mikhail Sobolev f65fc1e766 add host name parameter to "run" command
fixes #243
2013-04-02 01:26:17 +03:00
Mikhail Sobolev dcc9dfb27d make complex options to stand out 2013-04-02 01:26:17 +03:00
Francisco Souza 650dff73bd makefile: simplify "fmt" target, and include -s flag 2013-04-01 18:50:25 -03:00
Solomon Hykes 6d00145076 Merge pull request #304 from paulhammond/mkimage-busybox-fixes
Fix mkimage-busybox
2013-04-01 14:38:00 -07:00
Paul Hammond cc2558bf10 Fix mkimage-busybox 2013-04-01 14:34:12 -07:00
Shawn Siefkas e566b89a5f Shortening the git commit used in the version command 2013-04-01 16:31:33 -05:00
Solomon Hykes 47c4c2abd4 Merge remote-tracking branch 'origin/progress_bar_push_pull' 2013-04-01 13:49:07 -07:00
Solomon Hykes fa5bb5acf1 Merge branch 'master' of ssh://github.com/dotcloud/docker 2013-04-01 13:44:19 -07:00
Solomon Hykes 50500cfcb6 Merge pull request #300 from cespare/make-fmt
Add a 'fmt' target to the Makefile.
2013-04-01 13:41:53 -07:00
Guillaume J. Charmes 9e81ab65cb Merge pull request #301 from dotcloud/container_test_improvment-1
Avoid destroy() timeout by closing stdin in TestStart()
2013-04-01 13:36:02 -07:00
Guillaume J. Charmes ff26493fd5 Merge pull request #298 from cespare/cleanup-1
Some style, text, and comment cleanup.
2013-04-01 13:26:40 -07:00
Caleb Spare 9b13d21fc9 Add a 'fmt' target to the Makefile.
A convenience for gofmting all the code, including subpackages.
2013-04-01 13:05:00 -07:00
Caleb Spare 887f509d1d Don't use an interface{} where a string will do. 2013-04-01 12:56:59 -07:00
Caleb Spare 13d2b08638 A few spelling/grammar corrections. 2013-04-01 12:56:50 -07:00
Caleb Spare c298a91f95 Use a *println or *print function instead of *printf where appropriate. 2013-04-01 12:17:00 -07:00
Shawn Siefkas 37a78902db Adding git commit to the version output
The Makefile must be used in order to inject the git commit
via -ldflags.
2013-04-01 13:52:45 -05:00
Caleb Spare 15b3088157 Don't convert []byte to string unnecessarily. 2013-04-01 11:15:40 -07:00
Caleb Spare 7830cf9166 Don't use a strings.Reader where a bytes.Reader will do.
There are several places where a []byte is converted to a string
and then fed into strings.NewReader().
2013-04-01 11:15:10 -07:00
Jonathan Rudenberg 321d94b17e Expand the contributing guidelines 2013-04-01 12:35:04 -04:00
Jonathan Rudenberg 6b59cc8a10 Close HTTP response bodies
Fixes #285.
2013-04-01 09:51:56 -04:00
Guillaume J. Charmes 91b1f9eee9 Avoid destroy() timeout by closing stdin in TestStart() 2013-03-31 22:42:10 -07:00
Solomon Hykes c9a13147fe Merge pull request #238 from johncosta/42-contribution-guidelines
Contribution guidelines
2013-03-31 22:14:18 -07:00
Solomon Hykes 1632566ecb Show shorthand image IDs for convenience. Shorthand IDs (or any non-conflicting prefix) can be used to lookup images 2013-03-31 22:11:55 -07:00
Solomon Hykes a52a28b609 Temporarily disable a broken test (waiting for @creack to fix it), and silence a warning which pollutes unit tests but is complicated to fix 2013-03-31 22:05:14 -07:00
Solomon Hykes 54443c092c gofmt 2013-03-31 22:04:59 -07:00
Guillaume J. Charmes ad1e8a9b0f Add unit test for CmdAttach 2013-03-31 21:48:18 -07:00
Solomon Hykes 29b7ecb017 Merge pull request #272 from dominikh/improve/lazy-ip-allocator
Make IP allocator lazy
2013-03-31 21:04:35 -07:00
Dominik Honnef 6f9a67a7c7 Make IP allocator lazy
Instead of allocating all possible IPs in advance, generate them as
needed.

A loop will cycle through all possible IPs in sequential order,
allocating them as needed and marking them as in use. Once the loop
exhausts all IPs, it will wrap back to the beginning. IPs that are
already in use will be skipped. When an IP is released, it will be
cleared and be available for allocation again.

Two decisions went into this design:

1) Minimize memory footprint by only allocating IPs that are actually
in use

2) Minimize reuse of released IP addresses to avoid sending traffic to
the wrong containers

As a side effect, the functions for IP/Mask<->int conversion have been
rewritten to never be able to fail in order to reduce the amount of
error returns.

Fixes gh-231
2013-04-01 06:02:44 +02:00
Guillaume J. Charmes cfeed391d7 Change the commands unit tests in order to reflect the proper behaviour of CmdRun 2013-03-31 20:52:35 -07:00
Guillaume J. Charmes d949e2804a Add a check to avoid double start (resulting in dockerd to panic) and unit test for it 2013-03-31 14:15:10 -07:00
Guillaume J. Charmes 1fc9405537 Add progress bar on docker push 2013-03-31 13:53:47 -07:00
Guillaume J. Charmes b64dfdd8cd Add a progress bar to docker pull 2013-03-31 13:04:41 -07:00
Solomon Hykes a6779bcae2 Revert regression introduced in 81eac415ad, which caused 'docker run -i' to never close stdin 2013-03-31 02:44:56 -07:00
Robert Obryk 75ba07cb3a Swapped a map for a list in writeBroadcaster. 2013-03-30 16:04:19 +01:00
Robert Obryk 0bdfcfaa33 Make writeBroadcaster safe for concurrent use. 2013-03-30 16:02:22 +01:00
Thatcher Peskens e9d17b1f91 Redundant message in basics. 2013-03-29 17:38:31 -07:00
Thatcher Peskens 6437648176 Added a lot of basic instructions on various topics 2013-03-29 17:30:10 -07:00
John Costa bd3c6793a1 remove dulicated text and correct contributing link 2013-03-29 07:06:58 -04:00
John Costa 760736b3f3 add contributing guidlines md file 2013-03-28 20:04:21 -04:00
John Costa a5054184a1 move documentation changes to reStructuredText docs under website. https://github.com/dotcloud/docker/issues/42 2013-03-28 16:32:31 -04:00
43 changed files with 1410 additions and 539 deletions
+1
View File
@@ -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>
+93
View File
@@ -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.
+9 -1
View File
@@ -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 .
+1 -22
View File
@@ -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
----------------------------
+3
View File
@@ -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
View File
@@ -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 {
+69 -114
View File
@@ -13,13 +13,14 @@ import (
"runtime"
"strconv"
"strings"
"sync"
"text/tabwriter"
"time"
"unicode"
)
const VERSION = "0.1.1"
const VERSION = "0.1.2"
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
@@ -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
}
@@ -533,6 +535,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 +567,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 +594,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 +606,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 +617,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 +629,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 +650,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 +703,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
}
@@ -796,56 +799,7 @@ func (srv *Server) CmdAttach(stdin io.ReadCloser, stdout io.Writer, args ...stri
if container == nil {
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, 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 {
opts := make(map[string]bool)
return (*AttachOpts)(&opts)
}
func (opts *AttachOpts) String() string {
return fmt.Sprint(*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,55 +900,29 @@ 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.Reader
cStdout, cStderr io.Writer
)
if config.AttachStdin {
cStdin = stdin
}
// 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, cStdout, cStderr)
Debugf("Starting\n")
if err := container.Start(); err != nil {
return err
}
if cStdout == nil && cStderr == nil {
fmt.Fprintln(stdout, container.ShortId())
}
return nil
Debugf("Waiting for attach to return\n")
return <-attachErr
}
func NewServer() (*Server, error) {
+180 -58
View File
@@ -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,192 @@ 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)
}()
// 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)
}
// Close pipes (simulate disconnect)
if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
t.Fatal(err)
}
container := runtime.containers.Back().Value.(*Container)
// Recreate the pipes
stdin, stdinPipe = io.Pipe()
stdout, stdoutPipe = io.Pipe()
// Attach to it
c2 := make(chan struct{})
go func() {
if err := srv.CmdAttach(stdin, stdoutPipe, container.Id); err != nil {
t.Fatal(err)
}
close(c2)
}()
if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
t.Fatal(err)
}
// Close pipes
if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
t.Fatal(err)
}
// FIXME: when #230 will be finished, send SIGINT instead of SIGTERM
// we expect cat to stay alive so SIGTERM will have no effect
// and Stop will timeout
if err := container.Stop(); err != nil {
t.Fatal(err)
}
// Wait for run and attach to finish
// 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
<-c2
})
// Finished, no timeout
timeout <- false
// 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()
}
+149 -39
View File
@@ -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
Ports []int
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
var flPorts ports
cmd.Var(&flPorts, "p", "Map a network port to the container")
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,
Ports: 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,90 @@ func (container *Container) start() error {
return container.cmd.Start()
}
func (container *Container) Attach(stdin io.Reader, stdout io.Writer, stderr io.Writer) chan error {
var cStdout io.ReadCloser
var cStderr io.ReadCloser
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")
if container.Config.StdinOnce {
defer cStdin.Close()
}
_, err := io.Copy(cStdin, stdin)
if err != nil {
Debugf("[error] attach stdout: %s\n", err)
}
errors <- err
}()
}
}
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")
_, 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")
_, 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
}
@@ -360,11 +471,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 +489,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 +530,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 +566,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
}
@@ -560,11 +674,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
View File
@@ -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)
+2 -3
View File
@@ -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
View File
@@ -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()
-67
View File
@@ -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
+100
View File
@@ -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`
+4 -3
View File
@@ -2,6 +2,7 @@
:description: Docker's CLI command description and usage
:keywords: Docker, Docker documentation, CLI, command line
.. _cli:
Command Line Interface
======================
@@ -118,9 +119,9 @@ import
::
Usage: docker import [OPTIONS] URL|- [REPOSITORY [TAG]]
Usage: docker import [OPTIONS] URL|- [REPOSITORY [TAG]]
Create a new filesystem image from the contents of a tarball
Create a new filesystem image from the contents of a tarball
info
@@ -258,12 +259,12 @@ run
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
-h="": Container host name
-u="": Username or UID
+2 -1
View File
@@ -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>
+25
View File
@@ -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
+26 -10
View File
@@ -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)
+2 -1
View File
@@ -12,5 +12,6 @@ Contents:
.. toctree::
:maxdepth: 1
containers
introduction
buildingblocks
+127
View File
@@ -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
^^^^^^^^
+46 -3
View File
@@ -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.
+4 -2
View File
@@ -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.
+1 -3
View File
@@ -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``
+1
View File
@@ -15,3 +15,4 @@ Contents:
hello_world
hello_world_daemon
python_web_app
runningsshservice
@@ -0,0 +1,27 @@
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
View File
@@ -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.
+1
View File
@@ -16,3 +16,4 @@ Contents:
macos
windows
amazon
upgrading
+9 -1
View File
@@ -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.
+40
View File
@@ -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``
+51 -17
View File
@@ -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 {
@@ -101,6 +123,7 @@ func (graph *Graph) Register(layerData Archive, img *Image) error {
return err
}
img.graph = graph
graph.idIndex.Add(img.Id)
return nil
}
@@ -117,14 +140,14 @@ func (graph *Graph) Mktemp(id string) (string, error) {
}
// 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
// This allows images to be deleted atomically by os.Rename(), instead of
// os.RemoveAll() which is prone to race conditions.
func (graph *Graph) Garbage() (*Graph, error) {
return NewGraph(path.Join(graph.Root, ":garbage:"))
}
// Check if given error is "not empty"
// Note: this is the way golang 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,13 +161,20 @@ func isNotEmpty(err error) bool {
}
// Delete atomically removes an image from the graph.
func (graph *Graph) Delete(id string) error {
func (graph *Graph) Delete(name string) error {
id, err := graph.idIndex.Get(name)
if err != nil {
return err
}
garbage, err := graph.Garbage()
if err != nil {
return err
}
graph.idIndex.Delete(id)
err = os.Rename(graph.imageRoot(id), garbage.imageRoot(id))
if err != nil {
// FIXME: this introduces a race condition in Delete() if the image is already present
// in garbage. Let's store at random names in grabage instead.
if isNotEmpty(err) {
Debugf("The image %s is already present in garbage. Removing it.", id)
if err = os.RemoveAll(garbage.imageRoot(id)); err != nil {
@@ -164,16 +194,20 @@ func (graph *Graph) Delete(id string) error {
return nil
}
// Undelete moves an image back from the garbage to the main graph
// 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))
if err := os.Rename(garbage.imageRoot(id), graph.imageRoot(id)); err != nil {
return err
}
graph.idIndex.Add(id)
return nil
}
// GarbageCollect definitely deletes all images moved to the garbage
// GarbageCollect definitely deletes all images moved to the garbage.
func (graph *Graph) GarbageCollect() error {
garbage, err := graph.Garbage()
if err != nil {
@@ -182,7 +216,7 @@ func (graph *Graph) GarbageCollect() error {
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 +230,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) {
+23
View File
@@ -120,6 +120,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)
+4
View File
@@ -150,6 +150,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")
+91 -78
View File
@@ -1,7 +1,6 @@
package docker
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
@@ -30,40 +29,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
@@ -211,66 +195,97 @@ func newPortAllocator(start, end int) (*PortAllocator, error) {
// 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
@@ -297,7 +312,7 @@ func (iface *NetworkInterface) AllocatePort(port int) (int, error) {
}
// 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 +322,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
@@ -342,10 +358,7 @@ func newNetworkManager(bridgeIface string) (*NetworkManager, error) {
}
network := addr.(*net.IPNet)
ipAllocator, err := newIPAllocator(network)
if err != nil {
return nil, err
}
ipAllocator := newIPAllocator(network)
portAllocator, err := newPortAllocator(portRangeStart, portRangeEnd)
if err != nil {
+108 -32
View File
@@ -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.2127.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
View File
@@ -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
View File
@@ -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
View File
@@ -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:
+10
View File
@@ -6,6 +6,7 @@ import (
"os"
"os/exec"
"os/user"
"sync"
"testing"
)
@@ -17,6 +18,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() {
container.Kill()
wg.Add(-1)
}()
}
wg.Wait()
return os.RemoveAll(runtime.root)
}
+1 -1
View File
@@ -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 {
+32 -25
View File
@@ -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)
@@ -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
View File
@@ -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.