Conflicts:
docs/sources/api/docker_remote_api.rst
| ... | ... |
@@ -7,6 +7,7 @@ import ( |
| 7 | 7 |
"github.com/dotcloud/docker/utils" |
| 8 | 8 |
"io" |
| 9 | 9 |
"io/ioutil" |
| 10 |
+ "net/url" |
|
| 10 | 11 |
"os" |
| 11 | 12 |
"path" |
| 12 | 13 |
"reflect" |
| ... | ... |
@@ -201,6 +202,24 @@ func (b *buildFile) addRemote(container *Container, orig, dest string) error {
|
| 201 | 201 |
} |
| 202 | 202 |
defer file.Body.Close() |
| 203 | 203 |
|
| 204 |
+ // If the destination is a directory, figure out the filename. |
|
| 205 |
+ if strings.HasSuffix(dest, "/") {
|
|
| 206 |
+ u, err := url.Parse(orig) |
|
| 207 |
+ if err != nil {
|
|
| 208 |
+ return err |
|
| 209 |
+ } |
|
| 210 |
+ path := u.Path |
|
| 211 |
+ if strings.HasSuffix(path, "/") {
|
|
| 212 |
+ path = path[:len(path)-1] |
|
| 213 |
+ } |
|
| 214 |
+ parts := strings.Split(path, "/") |
|
| 215 |
+ filename := parts[len(parts)-1] |
|
| 216 |
+ if filename == "" {
|
|
| 217 |
+ return fmt.Errorf("cannot determine filename from url: %s", u)
|
|
| 218 |
+ } |
|
| 219 |
+ dest = dest + filename |
|
| 220 |
+ } |
|
| 221 |
+ |
|
| 204 | 222 |
return container.Inject(file.Body, dest) |
| 205 | 223 |
} |
| 206 | 224 |
|
| ... | ... |
@@ -208,7 +227,7 @@ func (b *buildFile) addContext(container *Container, orig, dest string) error {
|
| 208 | 208 |
origPath := path.Join(b.context, orig) |
| 209 | 209 |
destPath := path.Join(container.RootfsPath(), dest) |
| 210 | 210 |
// Preserve the trailing '/' |
| 211 |
- if dest[len(dest)-1] == '/' {
|
|
| 211 |
+ if strings.HasSuffix(dest, "/") {
|
|
| 212 | 212 |
destPath = destPath + "/" |
| 213 | 213 |
} |
| 214 | 214 |
fi, err := os.Stat(origPath) |
| ... | ... |
@@ -3,13 +3,17 @@ package docker |
| 3 | 3 |
import ( |
| 4 | 4 |
"fmt" |
| 5 | 5 |
"io/ioutil" |
| 6 |
+ "net" |
|
| 7 |
+ "net/http" |
|
| 8 |
+ "net/http/httptest" |
|
| 9 |
+ "strings" |
|
| 6 | 10 |
"testing" |
| 7 | 11 |
) |
| 8 | 12 |
|
| 9 | 13 |
// mkTestContext generates a build context from the contents of the provided dockerfile. |
| 10 | 14 |
// This context is suitable for use as an argument to BuildFile.Build() |
| 11 | 15 |
func mkTestContext(dockerfile string, files [][2]string, t *testing.T) Archive {
|
| 12 |
- context, err := mkBuildContext(fmt.Sprintf(dockerfile, unitTestImageID), files) |
|
| 16 |
+ context, err := mkBuildContext(dockerfile, files) |
|
| 13 | 17 |
if err != nil {
|
| 14 | 18 |
t.Fatal(err) |
| 15 | 19 |
} |
| ... | ... |
@@ -22,6 +26,8 @@ type testContextTemplate struct {
|
| 22 | 22 |
dockerfile string |
| 23 | 23 |
// Additional files in the context, eg [][2]string{"./passwd", "gordon"}
|
| 24 | 24 |
files [][2]string |
| 25 |
+ // Additional remote files to host on a local HTTP server. |
|
| 26 |
+ remoteFiles [][2]string |
|
| 25 | 27 |
} |
| 26 | 28 |
|
| 27 | 29 |
// A table of all the contexts to build and test. |
| ... | ... |
@@ -29,27 +35,31 @@ type testContextTemplate struct {
|
| 29 | 29 |
var testContexts = []testContextTemplate{
|
| 30 | 30 |
{
|
| 31 | 31 |
` |
| 32 |
-from %s |
|
| 32 |
+from {IMAGE}
|
|
| 33 | 33 |
run sh -c 'echo root:testpass > /tmp/passwd' |
| 34 | 34 |
run mkdir -p /var/run/sshd |
| 35 | 35 |
run [ "$(cat /tmp/passwd)" = "root:testpass" ] |
| 36 | 36 |
run [ "$(ls -d /var/run/sshd)" = "/var/run/sshd" ] |
| 37 | 37 |
`, |
| 38 | 38 |
nil, |
| 39 |
+ nil, |
|
| 39 | 40 |
}, |
| 40 | 41 |
|
| 41 | 42 |
{
|
| 42 | 43 |
` |
| 43 |
-from %s |
|
| 44 |
+from {IMAGE}
|
|
| 44 | 45 |
add foo /usr/lib/bla/bar |
| 45 |
-run [ "$(cat /usr/lib/bla/bar)" = 'hello world!' ] |
|
| 46 |
+run [ "$(cat /usr/lib/bla/bar)" = 'hello' ] |
|
| 47 |
+add http://{SERVERADDR}/baz /usr/lib/baz/quux
|
|
| 48 |
+run [ "$(cat /usr/lib/baz/quux)" = 'world!' ] |
|
| 46 | 49 |
`, |
| 47 |
- [][2]string{{"foo", "hello world!"}},
|
|
| 50 |
+ [][2]string{{"foo", "hello"}},
|
|
| 51 |
+ [][2]string{{"/baz", "world!"}},
|
|
| 48 | 52 |
}, |
| 49 | 53 |
|
| 50 | 54 |
{
|
| 51 | 55 |
` |
| 52 |
-from %s |
|
| 56 |
+from {IMAGE}
|
|
| 53 | 57 |
add f / |
| 54 | 58 |
run [ "$(cat /f)" = "hello" ] |
| 55 | 59 |
add f /abc |
| ... | ... |
@@ -71,38 +81,86 @@ run [ "$(cat /somewheeeere/over/the/rainbooow/ga)" = "bu" ] |
| 71 | 71 |
{"f", "hello"},
|
| 72 | 72 |
{"d/ga", "bu"},
|
| 73 | 73 |
}, |
| 74 |
+ nil, |
|
| 75 |
+ }, |
|
| 76 |
+ |
|
| 77 |
+ {
|
|
| 78 |
+ ` |
|
| 79 |
+from {IMAGE}
|
|
| 80 |
+add http://{SERVERADDR}/x /a/b/c
|
|
| 81 |
+run [ "$(cat /a/b/c)" = "hello" ] |
|
| 82 |
+add http://{SERVERADDR}/x?foo=bar /
|
|
| 83 |
+run [ "$(cat /x)" = "hello" ] |
|
| 84 |
+add http://{SERVERADDR}/x /d/
|
|
| 85 |
+run [ "$(cat /d/x)" = "hello" ] |
|
| 86 |
+add http://{SERVERADDR} /e
|
|
| 87 |
+run [ "$(cat /e)" = "blah" ] |
|
| 88 |
+`, |
|
| 89 |
+ nil, |
|
| 90 |
+ [][2]string{{"/x", "hello"}, {"/", "blah"}},
|
|
| 74 | 91 |
}, |
| 75 | 92 |
|
| 76 | 93 |
{
|
| 77 | 94 |
` |
| 78 |
-from %s |
|
| 95 |
+from {IMAGE}
|
|
| 79 | 96 |
env FOO BAR |
| 80 | 97 |
run [ "$FOO" = "BAR" ] |
| 81 | 98 |
`, |
| 82 | 99 |
nil, |
| 100 |
+ nil, |
|
| 83 | 101 |
}, |
| 84 | 102 |
|
| 85 | 103 |
{
|
| 86 | 104 |
` |
| 87 |
-from %s |
|
| 105 |
+from {IMAGE}
|
|
| 88 | 106 |
ENTRYPOINT /bin/echo |
| 89 | 107 |
CMD Hello world |
| 90 | 108 |
`, |
| 91 | 109 |
nil, |
| 110 |
+ nil, |
|
| 92 | 111 |
}, |
| 93 | 112 |
|
| 94 | 113 |
{
|
| 95 | 114 |
` |
| 96 |
-from %s |
|
| 115 |
+from {IMAGE}
|
|
| 97 | 116 |
VOLUME /test |
| 98 | 117 |
CMD Hello world |
| 99 | 118 |
`, |
| 100 | 119 |
nil, |
| 120 |
+ nil, |
|
| 101 | 121 |
}, |
| 102 | 122 |
} |
| 103 | 123 |
|
| 104 | 124 |
// FIXME: test building with 2 successive overlapping ADD commands |
| 105 | 125 |
|
| 126 |
+func constructDockerfile(template string, ip net.IP, port string) string {
|
|
| 127 |
+ serverAddr := fmt.Sprintf("%s:%s", ip, port)
|
|
| 128 |
+ replacer := strings.NewReplacer("{IMAGE}", unitTestImageID, "{SERVERADDR}", serverAddr)
|
|
| 129 |
+ return replacer.Replace(template) |
|
| 130 |
+} |
|
| 131 |
+ |
|
| 132 |
+func mkTestingFileServer(files [][2]string) (*httptest.Server, error) {
|
|
| 133 |
+ mux := http.NewServeMux() |
|
| 134 |
+ for _, file := range files {
|
|
| 135 |
+ name, contents := file[0], file[1] |
|
| 136 |
+ mux.HandleFunc(name, func(w http.ResponseWriter, r *http.Request) {
|
|
| 137 |
+ w.Write([]byte(contents)) |
|
| 138 |
+ }) |
|
| 139 |
+ } |
|
| 140 |
+ |
|
| 141 |
+ // This is how httptest.NewServer sets up a net.Listener, except that our listener must accept remote |
|
| 142 |
+ // connections (from the container). |
|
| 143 |
+ listener, err := net.Listen("tcp", ":0")
|
|
| 144 |
+ if err != nil {
|
|
| 145 |
+ return nil, err |
|
| 146 |
+ } |
|
| 147 |
+ |
|
| 148 |
+ s := httptest.NewUnstartedServer(mux) |
|
| 149 |
+ s.Listener = listener |
|
| 150 |
+ s.Start() |
|
| 151 |
+ return s, nil |
|
| 152 |
+} |
|
| 153 |
+ |
|
| 106 | 154 |
func TestBuild(t *testing.T) {
|
| 107 | 155 |
for _, ctx := range testContexts {
|
| 108 | 156 |
buildImage(ctx, t) |
| ... | ... |
@@ -121,9 +179,24 @@ func buildImage(context testContextTemplate, t *testing.T) *Image {
|
| 121 | 121 |
pullingPool: make(map[string]struct{}),
|
| 122 | 122 |
pushingPool: make(map[string]struct{}),
|
| 123 | 123 |
} |
| 124 |
- buildfile := NewBuildFile(srv, ioutil.Discard, false) |
|
| 125 | 124 |
|
| 126 |
- id, err := buildfile.Build(mkTestContext(context.dockerfile, context.files, t)) |
|
| 125 |
+ httpServer, err := mkTestingFileServer(context.remoteFiles) |
|
| 126 |
+ if err != nil {
|
|
| 127 |
+ t.Fatal(err) |
|
| 128 |
+ } |
|
| 129 |
+ defer httpServer.Close() |
|
| 130 |
+ |
|
| 131 |
+ idx := strings.LastIndex(httpServer.URL, ":") |
|
| 132 |
+ if idx < 0 {
|
|
| 133 |
+ t.Fatalf("could not get port from test http server address %s", httpServer.URL)
|
|
| 134 |
+ } |
|
| 135 |
+ port := httpServer.URL[idx+1:] |
|
| 136 |
+ |
|
| 137 |
+ ip := runtime.networkManager.bridgeNetwork.IP |
|
| 138 |
+ dockerfile := constructDockerfile(context.dockerfile, ip, port) |
|
| 139 |
+ |
|
| 140 |
+ buildfile := NewBuildFile(srv, ioutil.Discard, false) |
|
| 141 |
+ id, err := buildfile.Build(mkTestContext(dockerfile, context.files, t)) |
|
| 127 | 142 |
if err != nil {
|
| 128 | 143 |
t.Fatal(err) |
| 129 | 144 |
} |
| ... | ... |
@@ -137,10 +210,10 @@ func buildImage(context testContextTemplate, t *testing.T) *Image {
|
| 137 | 137 |
|
| 138 | 138 |
func TestVolume(t *testing.T) {
|
| 139 | 139 |
img := buildImage(testContextTemplate{`
|
| 140 |
- from %s |
|
| 140 |
+ from {IMAGE}
|
|
| 141 | 141 |
volume /test |
| 142 | 142 |
cmd Hello world |
| 143 |
- `, nil}, t) |
|
| 143 |
+ `, nil, nil}, t) |
|
| 144 | 144 |
|
| 145 | 145 |
if len(img.Config.Volumes) == 0 {
|
| 146 | 146 |
t.Fail() |
| ... | ... |
@@ -154,9 +227,9 @@ func TestVolume(t *testing.T) {
|
| 154 | 154 |
|
| 155 | 155 |
func TestBuildMaintainer(t *testing.T) {
|
| 156 | 156 |
img := buildImage(testContextTemplate{`
|
| 157 |
- from %s |
|
| 157 |
+ from {IMAGE}
|
|
| 158 | 158 |
maintainer dockerio |
| 159 |
- `, nil}, t) |
|
| 159 |
+ `, nil, nil}, t) |
|
| 160 | 160 |
|
| 161 | 161 |
if img.Author != "dockerio" {
|
| 162 | 162 |
t.Fail() |
| ... | ... |
@@ -165,10 +238,10 @@ func TestBuildMaintainer(t *testing.T) {
|
| 165 | 165 |
|
| 166 | 166 |
func TestBuildEnv(t *testing.T) {
|
| 167 | 167 |
img := buildImage(testContextTemplate{`
|
| 168 |
- from %s |
|
| 168 |
+ from {IMAGE}
|
|
| 169 | 169 |
env port 4243 |
| 170 | 170 |
`, |
| 171 |
- nil}, t) |
|
| 171 |
+ nil, nil}, t) |
|
| 172 | 172 |
|
| 173 | 173 |
if img.Config.Env[0] != "port=4243" {
|
| 174 | 174 |
t.Fail() |
| ... | ... |
@@ -177,10 +250,10 @@ func TestBuildEnv(t *testing.T) {
|
| 177 | 177 |
|
| 178 | 178 |
func TestBuildCmd(t *testing.T) {
|
| 179 | 179 |
img := buildImage(testContextTemplate{`
|
| 180 |
- from %s |
|
| 180 |
+ from {IMAGE}
|
|
| 181 | 181 |
cmd ["/bin/echo", "Hello World"] |
| 182 | 182 |
`, |
| 183 |
- nil}, t) |
|
| 183 |
+ nil, nil}, t) |
|
| 184 | 184 |
|
| 185 | 185 |
if img.Config.Cmd[0] != "/bin/echo" {
|
| 186 | 186 |
t.Log(img.Config.Cmd[0]) |
| ... | ... |
@@ -194,10 +267,10 @@ func TestBuildCmd(t *testing.T) {
|
| 194 | 194 |
|
| 195 | 195 |
func TestBuildExpose(t *testing.T) {
|
| 196 | 196 |
img := buildImage(testContextTemplate{`
|
| 197 |
- from %s |
|
| 197 |
+ from {IMAGE}
|
|
| 198 | 198 |
expose 4243 |
| 199 | 199 |
`, |
| 200 |
- nil}, t) |
|
| 200 |
+ nil, nil}, t) |
|
| 201 | 201 |
|
| 202 | 202 |
if img.Config.PortSpecs[0] != "4243" {
|
| 203 | 203 |
t.Fail() |
| ... | ... |
@@ -206,10 +279,10 @@ func TestBuildExpose(t *testing.T) {
|
| 206 | 206 |
|
| 207 | 207 |
func TestBuildEntrypoint(t *testing.T) {
|
| 208 | 208 |
img := buildImage(testContextTemplate{`
|
| 209 |
- from %s |
|
| 209 |
+ from {IMAGE}
|
|
| 210 | 210 |
entrypoint ["/bin/echo"] |
| 211 | 211 |
`, |
| 212 |
- nil}, t) |
|
| 212 |
+ nil, nil}, t) |
|
| 213 | 213 |
|
| 214 | 214 |
if img.Config.Entrypoint[0] != "/bin/echo" {
|
| 215 | 215 |
} |
| ... | ... |
@@ -475,7 +475,7 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
|
| 475 | 475 |
|
| 476 | 476 |
func (cli *DockerCli) CmdStop(args ...string) error {
|
| 477 | 477 |
cmd := Subcmd("stop", "[OPTIONS] CONTAINER [CONTAINER...]", "Stop a running container")
|
| 478 |
- nSeconds := cmd.Int("t", 10, "Number of seconds to try to stop for before killing the container. Default=10")
|
|
| 478 |
+ nSeconds := cmd.Int("t", 10, "Number of seconds to wait for the container to stop before killing it.")
|
|
| 479 | 479 |
if err := cmd.Parse(args); err != nil {
|
| 480 | 480 |
return nil |
| 481 | 481 |
} |
| ... | ... |
@@ -1110,10 +1110,7 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
|
| 1110 | 1110 |
return nil |
| 1111 | 1111 |
} |
| 1112 | 1112 |
|
| 1113 |
- if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stdout=1", false, nil, cli.out); err != nil {
|
|
| 1114 |
- return err |
|
| 1115 |
- } |
|
| 1116 |
- if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stderr=1", false, nil, cli.err); err != nil {
|
|
| 1113 |
+ if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stdout=1&stderr=1", false, nil, cli.out); err != nil {
|
|
| 1117 | 1114 |
return err |
| 1118 | 1115 |
} |
| 1119 | 1116 |
return nil |
| ... | ... |
@@ -1316,6 +1313,18 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
| 1316 | 1316 |
return nil |
| 1317 | 1317 |
} |
| 1318 | 1318 |
|
| 1319 |
+ var containerIDFile *os.File |
|
| 1320 |
+ if len(hostConfig.ContainerIDFile) > 0 {
|
|
| 1321 |
+ if _, err := ioutil.ReadFile(hostConfig.ContainerIDFile); err == nil {
|
|
| 1322 |
+ return fmt.Errorf("cid file found, make sure the other container isn't running or delete %s", hostConfig.ContainerIDFile)
|
|
| 1323 |
+ } |
|
| 1324 |
+ containerIDFile, err = os.Create(hostConfig.ContainerIDFile) |
|
| 1325 |
+ if err != nil {
|
|
| 1326 |
+ return fmt.Errorf("failed to create the container ID file: %s", err)
|
|
| 1327 |
+ } |
|
| 1328 |
+ defer containerIDFile.Close() |
|
| 1329 |
+ } |
|
| 1330 |
+ |
|
| 1319 | 1331 |
//create the container |
| 1320 | 1332 |
body, statusCode, err := cli.call("POST", "/containers/create", config)
|
| 1321 | 1333 |
//if image not found try to pull it |
| ... | ... |
@@ -1346,6 +1355,11 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
| 1346 | 1346 |
for _, warning := range runResult.Warnings {
|
| 1347 | 1347 |
fmt.Fprintf(cli.err, "WARNING: %s\n", warning) |
| 1348 | 1348 |
} |
| 1349 |
+ if len(hostConfig.ContainerIDFile) > 0 {
|
|
| 1350 |
+ if _, err = containerIDFile.WriteString(runResult.ID); err != nil {
|
|
| 1351 |
+ return fmt.Errorf("failed to write the container ID to the file: %s", err)
|
|
| 1352 |
+ } |
|
| 1353 |
+ } |
|
| 1349 | 1354 |
|
| 1350 | 1355 |
//start the container |
| 1351 | 1356 |
if _, _, err = cli.call("POST", "/containers/"+runResult.ID+"/start", hostConfig); err != nil {
|
| ... | ... |
@@ -59,7 +59,6 @@ func assertPipe(input, output string, r io.Reader, w io.Writer, count int) error |
| 59 | 59 |
return nil |
| 60 | 60 |
} |
| 61 | 61 |
|
| 62 |
- |
|
| 63 | 62 |
// TestRunHostname checks that 'docker run -h' correctly sets a custom hostname |
| 64 | 63 |
func TestRunHostname(t *testing.T) {
|
| 65 | 64 |
stdout, stdoutPipe := io.Pipe() |
| ... | ... |
@@ -91,7 +90,6 @@ func TestRunHostname(t *testing.T) {
|
| 91 | 91 |
|
| 92 | 92 |
} |
| 93 | 93 |
|
| 94 |
- |
|
| 95 | 94 |
// TestAttachStdin checks attaching to stdin without stdout and stderr. |
| 96 | 95 |
// 'docker run -i -a stdin' should sends the client's stdin to the command, |
| 97 | 96 |
// then detach from it and print the container id. |
| ... | ... |
@@ -144,15 +142,17 @@ func TestRunAttachStdin(t *testing.T) {
|
| 144 | 144 |
}) |
| 145 | 145 |
|
| 146 | 146 |
// Check logs |
| 147 |
- if cmdLogs, err := container.ReadLog("stdout"); err != nil {
|
|
| 147 |
+ if cmdLogs, err := container.ReadLog("json"); err != nil {
|
|
| 148 | 148 |
t.Fatal(err) |
| 149 | 149 |
} else {
|
| 150 | 150 |
if output, err := ioutil.ReadAll(cmdLogs); err != nil {
|
| 151 | 151 |
t.Fatal(err) |
| 152 | 152 |
} else {
|
| 153 |
- expectedLog := "hello\nhi there\n" |
|
| 154 |
- if string(output) != expectedLog {
|
|
| 155 |
- t.Fatalf("Unexpected logs: should be '%s', not '%s'\n", expectedLog, output)
|
|
| 153 |
+ expectedLogs := []string{"{\"log\":\"hello\\n\",\"stream\":\"stdout\"", "{\"log\":\"hi there\\n\",\"stream\":\"stdout\""}
|
|
| 154 |
+ for _, expectedLog := range expectedLogs {
|
|
| 155 |
+ if !strings.Contains(string(output), expectedLog) {
|
|
| 156 |
+ t.Fatalf("Unexpected logs: should contains '%s', it is not '%s'\n", expectedLog, output)
|
|
| 157 |
+ } |
|
| 156 | 158 |
} |
| 157 | 159 |
} |
| 158 | 160 |
} |
| ... | ... |
@@ -80,7 +80,8 @@ type Config struct {
|
| 80 | 80 |
} |
| 81 | 81 |
|
| 82 | 82 |
type HostConfig struct {
|
| 83 |
- Binds []string |
|
| 83 |
+ Binds []string |
|
| 84 |
+ ContainerIDFile string |
|
| 84 | 85 |
} |
| 85 | 86 |
|
| 86 | 87 |
type BindMap struct {
|
| ... | ... |
@@ -93,6 +94,7 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, |
| 93 | 93 |
cmd := Subcmd("run", "[OPTIONS] IMAGE [COMMAND] [ARG...]", "Run a command in a new container")
|
| 94 | 94 |
if len(args) > 0 && args[0] != "--help" {
|
| 95 | 95 |
cmd.SetOutput(ioutil.Discard) |
| 96 |
+ cmd.Usage = nil |
|
| 96 | 97 |
} |
| 97 | 98 |
|
| 98 | 99 |
flHostname := cmd.String("h", "", "Container host name")
|
| ... | ... |
@@ -103,6 +105,7 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, |
| 103 | 103 |
flStdin := cmd.Bool("i", false, "Keep stdin open even if not attached")
|
| 104 | 104 |
flTty := cmd.Bool("t", false, "Allocate a pseudo-tty")
|
| 105 | 105 |
flMemory := cmd.Int64("m", 0, "Memory limit (in bytes)")
|
| 106 |
+ flContainerIDFile := cmd.String("cidfile", "", "Write the container ID to the file")
|
|
| 106 | 107 |
|
| 107 | 108 |
if capabilities != nil && *flMemory > 0 && !capabilities.MemoryLimit {
|
| 108 | 109 |
//fmt.Fprintf(stdout, "WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n") |
| ... | ... |
@@ -190,7 +193,8 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, |
| 190 | 190 |
Entrypoint: entrypoint, |
| 191 | 191 |
} |
| 192 | 192 |
hostConfig := &HostConfig{
|
| 193 |
- Binds: binds, |
|
| 193 |
+ Binds: binds, |
|
| 194 |
+ ContainerIDFile: *flContainerIDFile, |
|
| 194 | 195 |
} |
| 195 | 196 |
|
| 196 | 197 |
if capabilities != nil && *flMemory > 0 && !capabilities.SwapLimit {
|
| ... | ... |
@@ -637,6 +641,7 @@ func (container *Container) Start(hostConfig *HostConfig) error {
|
| 637 | 637 |
params = append(params, |
| 638 | 638 |
"-e", "HOME=/", |
| 639 | 639 |
"-e", "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", |
| 640 |
+ "-e", "container=lxc", |
|
| 640 | 641 |
) |
| 641 | 642 |
|
| 642 | 643 |
for _, elem := range container.Config.Env {
|
| ... | ... |
@@ -650,10 +655,10 @@ func (container *Container) Start(hostConfig *HostConfig) error {
|
| 650 | 650 |
container.cmd = exec.Command("lxc-start", params...)
|
| 651 | 651 |
|
| 652 | 652 |
// Setup logging of stdout and stderr to disk |
| 653 |
- if err := container.runtime.LogToDisk(container.stdout, container.logPath("stdout")); err != nil {
|
|
| 653 |
+ if err := container.runtime.LogToDisk(container.stdout, container.logPath("json"), "stdout"); err != nil {
|
|
| 654 | 654 |
return err |
| 655 | 655 |
} |
| 656 |
- if err := container.runtime.LogToDisk(container.stderr, container.logPath("stderr")); err != nil {
|
|
| 656 |
+ if err := container.runtime.LogToDisk(container.stderr, container.logPath("json"), "stderr"); err != nil {
|
|
| 657 | 657 |
return err |
| 658 | 658 |
} |
| 659 | 659 |
|
| ... | ... |
@@ -712,13 +717,13 @@ func (container *Container) StdinPipe() (io.WriteCloser, error) {
|
| 712 | 712 |
|
| 713 | 713 |
func (container *Container) StdoutPipe() (io.ReadCloser, error) {
|
| 714 | 714 |
reader, writer := io.Pipe() |
| 715 |
- container.stdout.AddWriter(writer) |
|
| 715 |
+ container.stdout.AddWriter(writer, "") |
|
| 716 | 716 |
return utils.NewBufReader(reader), nil |
| 717 | 717 |
} |
| 718 | 718 |
|
| 719 | 719 |
func (container *Container) StderrPipe() (io.ReadCloser, error) {
|
| 720 | 720 |
reader, writer := io.Pipe() |
| 721 |
- container.stderr.AddWriter(writer) |
|
| 721 |
+ container.stderr.AddWriter(writer, "") |
|
| 722 | 722 |
return utils.NewBufReader(reader), nil |
| 723 | 723 |
} |
| 724 | 724 |
|
| ... | ... |
@@ -39,16 +39,11 @@ func TestIDFormat(t *testing.T) {
|
| 39 | 39 |
func TestMultipleAttachRestart(t *testing.T) {
|
| 40 | 40 |
runtime := mkRuntime(t) |
| 41 | 41 |
defer nuke(runtime) |
| 42 |
- container, err := NewBuilder(runtime).Create( |
|
| 43 |
- &Config{
|
|
| 44 |
- Image: GetTestImage(runtime).ID, |
|
| 45 |
- Cmd: []string{"/bin/sh", "-c",
|
|
| 46 |
- "i=1; while [ $i -le 5 ]; do i=`expr $i + 1`; echo hello; done"}, |
|
| 47 |
- }, |
|
| 42 |
+ container, hostConfig, _ := mkContainer( |
|
| 43 |
+ runtime, |
|
| 44 |
+ []string{"_", "/bin/sh", "-c", "i=1; while [ $i -le 5 ]; do i=`expr $i + 1`; echo hello; done"},
|
|
| 45 |
+ t, |
|
| 48 | 46 |
) |
| 49 |
- if err != nil {
|
|
| 50 |
- t.Fatal(err) |
|
| 51 |
- } |
|
| 52 | 47 |
defer runtime.Destroy(container) |
| 53 | 48 |
|
| 54 | 49 |
// Simulate 3 client attaching to the container and stop/restart |
| ... | ... |
@@ -65,7 +60,6 @@ func TestMultipleAttachRestart(t *testing.T) {
|
| 65 | 65 |
if err != nil {
|
| 66 | 66 |
t.Fatal(err) |
| 67 | 67 |
} |
| 68 |
- hostConfig := &HostConfig{}
|
|
| 69 | 68 |
if err := container.Start(hostConfig); err != nil {
|
| 70 | 69 |
t.Fatal(err) |
| 71 | 70 |
} |
| ... | ... |
@@ -140,19 +134,8 @@ func TestMultipleAttachRestart(t *testing.T) {
|
| 140 | 140 |
func TestDiff(t *testing.T) {
|
| 141 | 141 |
runtime := mkRuntime(t) |
| 142 | 142 |
defer nuke(runtime) |
| 143 |
- |
|
| 144 |
- builder := NewBuilder(runtime) |
|
| 145 |
- |
|
| 146 | 143 |
// Create a container and remove a file |
| 147 |
- container1, err := builder.Create( |
|
| 148 |
- &Config{
|
|
| 149 |
- Image: GetTestImage(runtime).ID, |
|
| 150 |
- Cmd: []string{"/bin/rm", "/etc/passwd"},
|
|
| 151 |
- }, |
|
| 152 |
- ) |
|
| 153 |
- if err != nil {
|
|
| 154 |
- t.Fatal(err) |
|
| 155 |
- } |
|
| 144 |
+ container1, _, _ := mkContainer(runtime, []string{"_", "/bin/rm", "/etc/passwd"}, t)
|
|
| 156 | 145 |
defer runtime.Destroy(container1) |
| 157 | 146 |
|
| 158 | 147 |
if err := container1.Run(); err != nil {
|
| ... | ... |
@@ -185,15 +168,7 @@ func TestDiff(t *testing.T) {
|
| 185 | 185 |
} |
| 186 | 186 |
|
| 187 | 187 |
// Create a new container from the commited image |
| 188 |
- container2, err := builder.Create( |
|
| 189 |
- &Config{
|
|
| 190 |
- Image: img.ID, |
|
| 191 |
- Cmd: []string{"cat", "/etc/passwd"},
|
|
| 192 |
- }, |
|
| 193 |
- ) |
|
| 194 |
- if err != nil {
|
|
| 195 |
- t.Fatal(err) |
|
| 196 |
- } |
|
| 188 |
+ container2, _, _ := mkContainer(runtime, []string{img.ID, "cat", "/etc/passwd"}, t)
|
|
| 197 | 189 |
defer runtime.Destroy(container2) |
| 198 | 190 |
|
| 199 | 191 |
if err := container2.Run(); err != nil {
|
| ... | ... |
@@ -212,15 +187,7 @@ func TestDiff(t *testing.T) {
|
| 212 | 212 |
} |
| 213 | 213 |
|
| 214 | 214 |
// Create a new containere |
| 215 |
- container3, err := builder.Create( |
|
| 216 |
- &Config{
|
|
| 217 |
- Image: GetTestImage(runtime).ID, |
|
| 218 |
- Cmd: []string{"rm", "/bin/httpd"},
|
|
| 219 |
- }, |
|
| 220 |
- ) |
|
| 221 |
- if err != nil {
|
|
| 222 |
- t.Fatal(err) |
|
| 223 |
- } |
|
| 215 |
+ container3, _, _ := mkContainer(runtime, []string{"_", "rm", "/bin/httpd"}, t)
|
|
| 224 | 216 |
defer runtime.Destroy(container3) |
| 225 | 217 |
|
| 226 | 218 |
if err := container3.Run(); err != nil {
|
| ... | ... |
@@ -246,17 +213,7 @@ func TestDiff(t *testing.T) {
|
| 246 | 246 |
func TestCommitAutoRun(t *testing.T) {
|
| 247 | 247 |
runtime := mkRuntime(t) |
| 248 | 248 |
defer nuke(runtime) |
| 249 |
- |
|
| 250 |
- builder := NewBuilder(runtime) |
|
| 251 |
- container1, err := builder.Create( |
|
| 252 |
- &Config{
|
|
| 253 |
- Image: GetTestImage(runtime).ID, |
|
| 254 |
- Cmd: []string{"/bin/sh", "-c", "echo hello > /world"},
|
|
| 255 |
- }, |
|
| 256 |
- ) |
|
| 257 |
- if err != nil {
|
|
| 258 |
- t.Fatal(err) |
|
| 259 |
- } |
|
| 249 |
+ container1, _, _ := mkContainer(runtime, []string{"_", "/bin/sh", "-c", "echo hello > /world"}, t)
|
|
| 260 | 250 |
defer runtime.Destroy(container1) |
| 261 | 251 |
|
| 262 | 252 |
if container1.State.Running {
|
| ... | ... |
@@ -279,14 +236,7 @@ func TestCommitAutoRun(t *testing.T) {
|
| 279 | 279 |
} |
| 280 | 280 |
|
| 281 | 281 |
// FIXME: Make a TestCommit that stops here and check docker.root/layers/img.id/world |
| 282 |
- container2, err := builder.Create( |
|
| 283 |
- &Config{
|
|
| 284 |
- Image: img.ID, |
|
| 285 |
- }, |
|
| 286 |
- ) |
|
| 287 |
- if err != nil {
|
|
| 288 |
- t.Fatal(err) |
|
| 289 |
- } |
|
| 282 |
+ container2, hostConfig, _ := mkContainer(runtime, []string{img.ID}, t)
|
|
| 290 | 283 |
defer runtime.Destroy(container2) |
| 291 | 284 |
stdout, err := container2.StdoutPipe() |
| 292 | 285 |
if err != nil {
|
| ... | ... |
@@ -296,7 +246,6 @@ func TestCommitAutoRun(t *testing.T) {
|
| 296 | 296 |
if err != nil {
|
| 297 | 297 |
t.Fatal(err) |
| 298 | 298 |
} |
| 299 |
- hostConfig := &HostConfig{}
|
|
| 300 | 299 |
if err := container2.Start(hostConfig); err != nil {
|
| 301 | 300 |
t.Fatal(err) |
| 302 | 301 |
} |
| ... | ... |
@@ -324,17 +273,7 @@ func TestCommitRun(t *testing.T) {
|
| 324 | 324 |
runtime := mkRuntime(t) |
| 325 | 325 |
defer nuke(runtime) |
| 326 | 326 |
|
| 327 |
- builder := NewBuilder(runtime) |
|
| 328 |
- |
|
| 329 |
- container1, err := builder.Create( |
|
| 330 |
- &Config{
|
|
| 331 |
- Image: GetTestImage(runtime).ID, |
|
| 332 |
- Cmd: []string{"/bin/sh", "-c", "echo hello > /world"},
|
|
| 333 |
- }, |
|
| 334 |
- ) |
|
| 335 |
- if err != nil {
|
|
| 336 |
- t.Fatal(err) |
|
| 337 |
- } |
|
| 327 |
+ container1, hostConfig, _ := mkContainer(runtime, []string{"_", "/bin/sh", "-c", "echo hello > /world"}, t)
|
|
| 338 | 328 |
defer runtime.Destroy(container1) |
| 339 | 329 |
|
| 340 | 330 |
if container1.State.Running {
|
| ... | ... |
@@ -357,16 +296,7 @@ func TestCommitRun(t *testing.T) {
|
| 357 | 357 |
} |
| 358 | 358 |
|
| 359 | 359 |
// FIXME: Make a TestCommit that stops here and check docker.root/layers/img.id/world |
| 360 |
- |
|
| 361 |
- container2, err := builder.Create( |
|
| 362 |
- &Config{
|
|
| 363 |
- Image: img.ID, |
|
| 364 |
- Cmd: []string{"cat", "/world"},
|
|
| 365 |
- }, |
|
| 366 |
- ) |
|
| 367 |
- if err != nil {
|
|
| 368 |
- t.Fatal(err) |
|
| 369 |
- } |
|
| 360 |
+ container2, hostConfig, _ := mkContainer(runtime, []string{img.ID, "cat", "/world"}, t)
|
|
| 370 | 361 |
defer runtime.Destroy(container2) |
| 371 | 362 |
stdout, err := container2.StdoutPipe() |
| 372 | 363 |
if err != nil {
|
| ... | ... |
@@ -376,7 +306,6 @@ func TestCommitRun(t *testing.T) {
|
| 376 | 376 |
if err != nil {
|
| 377 | 377 |
t.Fatal(err) |
| 378 | 378 |
} |
| 379 |
- hostConfig := &HostConfig{}
|
|
| 380 | 379 |
if err := container2.Start(hostConfig); err != nil {
|
| 381 | 380 |
t.Fatal(err) |
| 382 | 381 |
} |
| ... | ... |
@@ -403,18 +332,7 @@ func TestCommitRun(t *testing.T) {
|
| 403 | 403 |
func TestStart(t *testing.T) {
|
| 404 | 404 |
runtime := mkRuntime(t) |
| 405 | 405 |
defer nuke(runtime) |
| 406 |
- container, err := NewBuilder(runtime).Create( |
|
| 407 |
- &Config{
|
|
| 408 |
- Image: GetTestImage(runtime).ID, |
|
| 409 |
- Memory: 33554432, |
|
| 410 |
- CpuShares: 1000, |
|
| 411 |
- Cmd: []string{"/bin/cat"},
|
|
| 412 |
- OpenStdin: true, |
|
| 413 |
- }, |
|
| 414 |
- ) |
|
| 415 |
- if err != nil {
|
|
| 416 |
- t.Fatal(err) |
|
| 417 |
- } |
|
| 406 |
+ container, hostConfig, _ := mkContainer(runtime, []string{"-m", "33554432", "-c", "1000", "-i", "_", "/bin/cat"}, t)
|
|
| 418 | 407 |
defer runtime.Destroy(container) |
| 419 | 408 |
|
| 420 | 409 |
cStdin, err := container.StdinPipe() |
| ... | ... |
@@ -422,7 +340,6 @@ func TestStart(t *testing.T) {
|
| 422 | 422 |
t.Fatal(err) |
| 423 | 423 |
} |
| 424 | 424 |
|
| 425 |
- hostConfig := &HostConfig{}
|
|
| 426 | 425 |
if err := container.Start(hostConfig); err != nil {
|
| 427 | 426 |
t.Fatal(err) |
| 428 | 427 |
} |
| ... | ... |
@@ -445,15 +362,7 @@ func TestStart(t *testing.T) {
|
| 445 | 445 |
func TestRun(t *testing.T) {
|
| 446 | 446 |
runtime := mkRuntime(t) |
| 447 | 447 |
defer nuke(runtime) |
| 448 |
- container, err := NewBuilder(runtime).Create( |
|
| 449 |
- &Config{
|
|
| 450 |
- Image: GetTestImage(runtime).ID, |
|
| 451 |
- Cmd: []string{"ls", "-al"},
|
|
| 452 |
- }, |
|
| 453 |
- ) |
|
| 454 |
- if err != nil {
|
|
| 455 |
- t.Fatal(err) |
|
| 456 |
- } |
|
| 448 |
+ container, _, _ := mkContainer(runtime, []string{"_", "ls", "-al"}, t)
|
|
| 457 | 449 |
defer runtime.Destroy(container) |
| 458 | 450 |
|
| 459 | 451 |
if container.State.Running {
|
| ... | ... |
@@ -2,6 +2,9 @@ |
| 2 | 2 |
:description: API Documentation for Docker |
| 3 | 3 |
:keywords: API, Docker, rcli, REST, documentation |
| 4 | 4 |
|
| 5 |
+.. COMMENT use http://pythonhosted.org/sphinxcontrib-httpdomain/ to |
|
| 6 |
+.. document the REST API. |
|
| 7 |
+ |
|
| 5 | 8 |
================= |
| 6 | 9 |
Docker Remote API |
| 7 | 10 |
================= |
| ... | ... |
@@ -13,15 +16,23 @@ Docker Remote API |
| 13 | 13 |
|
| 14 | 14 |
- The Remote API is replacing rcli |
| 15 | 15 |
- Default port in the docker deamon is 4243 |
| 16 |
-- The API tends to be REST, but for some complex commands, like attach or pull, the HTTP connection is hijacked to transport stdout stdin and stderr |
|
| 17 |
-- Since API version 1.2, the auth configuration is now handled client side, so the client has to send the authConfig as POST in /images/(name)/push |
|
| 16 |
+- The API tends to be REST, but for some complex commands, like attach |
|
| 17 |
+ or pull, the HTTP connection is hijacked to transport stdout stdin |
|
| 18 |
+ and stderr |
|
| 19 |
+- Since API version 1.2, the auth configuration is now handled client |
|
| 20 |
+ side, so the client has to send the authConfig as POST in |
|
| 21 |
+ /images/(name)/push |
|
| 18 | 22 |
|
| 19 | 23 |
2. Versions |
| 20 | 24 |
=========== |
| 21 | 25 |
|
| 22 |
-The current verson of the API is 1.4 |
|
| 23 |
-Calling /images/<name>/insert is the same as calling /v1.4/images/<name>/insert |
|
| 24 |
-You can still call an old version of the api using /v1.0/images/<name>/insert |
|
| 26 |
+The current verson of the API is 1.3 |
|
| 27 |
+ |
|
| 28 |
+Calling /images/<name>/insert is the same as calling |
|
| 29 |
+/v1.3/images/<name>/insert |
|
| 30 |
+ |
|
| 31 |
+You can still call an old version of the api using |
|
| 32 |
+/v1.0/images/<name>/insert |
|
| 25 | 33 |
|
| 26 | 34 |
:doc:`docker_remote_api_v1.3` |
| 27 | 35 |
***************************** |
| ... | ... |
@@ -29,9 +40,9 @@ You can still call an old version of the api using /v1.0/images/<name>/insert |
| 29 | 29 |
What's new |
| 30 | 30 |
---------- |
| 31 | 31 |
|
| 32 |
-Listing processes (/top): |
|
| 32 |
+.. http:get:: /containers/(id)/top |
|
| 33 | 33 |
|
| 34 |
-- You can now use ps args with docker top, like `docker top <container_id> aux` |
|
| 34 |
+ **New!** You can now use ps args with docker top, like `docker top <container_id> aux` |
|
| 35 | 35 |
|
| 36 | 36 |
:doc:`docker_remote_api_v1.3` |
| 37 | 37 |
***************************** |
| ... | ... |
@@ -41,19 +52,21 @@ docker v0.5.0 51f6c4a_ |
| 41 | 41 |
What's new |
| 42 | 42 |
---------- |
| 43 | 43 |
|
| 44 |
-Listing processes (/top): |
|
| 45 |
- |
|
| 46 |
-- List the processes inside a container |
|
| 44 |
+.. http:get:: /containers/(id)/top |
|
| 47 | 45 |
|
| 46 |
+ List the processes running inside a container. |
|
| 48 | 47 |
|
| 49 | 48 |
Builder (/build): |
| 50 | 49 |
|
| 51 | 50 |
- Simplify the upload of the build context |
| 52 |
-- Simply stream a tarball instead of multipart upload with 4 intermediary buffers |
|
| 51 |
+- Simply stream a tarball instead of multipart upload with 4 |
|
| 52 |
+ intermediary buffers |
|
| 53 | 53 |
- Simpler, less memory usage, less disk usage and faster |
| 54 | 54 |
|
| 55 |
-.. Note:: |
|
| 56 |
-The /build improvements are not reverse-compatible. Pre 1.3 clients will break on /build. |
|
| 55 |
+.. Warning:: |
|
| 56 |
+ |
|
| 57 |
+ The /build improvements are not reverse-compatible. Pre 1.3 clients |
|
| 58 |
+ will break on /build. |
|
| 57 | 59 |
|
| 58 | 60 |
List containers (/containers/json): |
| 59 | 61 |
|
| ... | ... |
@@ -61,7 +74,8 @@ List containers (/containers/json): |
| 61 | 61 |
|
| 62 | 62 |
Start containers (/containers/<id>/start): |
| 63 | 63 |
|
| 64 |
-- You can now pass host-specific configuration (e.g. bind mounts) in the POST body for start calls |
|
| 64 |
+- You can now pass host-specific configuration (e.g. bind mounts) in |
|
| 65 |
+ the POST body for start calls |
|
| 65 | 66 |
|
| 66 | 67 |
:doc:`docker_remote_api_v1.2` |
| 67 | 68 |
***************************** |
| ... | ... |
@@ -72,14 +86,25 @@ What's new |
| 72 | 72 |
---------- |
| 73 | 73 |
|
| 74 | 74 |
The auth configuration is now handled by the client. |
| 75 |
-The client should send it's authConfig as POST on each call of /images/(name)/push |
|
| 76 | 75 |
|
| 77 |
-.. http:get:: /auth is now deprecated |
|
| 78 |
-.. http:post:: /auth only checks the configuration but doesn't store it on the server |
|
| 76 |
+The client should send it's authConfig as POST on each call of |
|
| 77 |
+/images/(name)/push |
|
| 78 |
+ |
|
| 79 |
+.. http:get:: /auth |
|
| 80 |
+ |
|
| 81 |
+ **Deprecated.** |
|
| 82 |
+ |
|
| 83 |
+.. http:post:: /auth |
|
| 84 |
+ |
|
| 85 |
+ Only checks the configuration but doesn't store it on the server |
|
| 86 |
+ |
|
| 87 |
+ Deleting an image is now improved, will only untag the image if it |
|
| 88 |
+ has chidren and remove all the untagged parents if has any. |
|
| 79 | 89 |
|
| 80 |
-Deleting an image is now improved, will only untag the image if it has chidrens and remove all the untagged parents if has any. |
|
| 90 |
+.. http:post:: /images/<name>/delete |
|
| 81 | 91 |
|
| 82 |
-.. http:post:: /images/<name>/delete now returns a JSON with the list of images deleted/untagged |
|
| 92 |
+ Now returns a JSON structure with the list of images |
|
| 93 |
+ deleted/untagged. |
|
| 83 | 94 |
|
| 84 | 95 |
|
| 85 | 96 |
:doc:`docker_remote_api_v1.1` |
| ... | ... |
@@ -94,7 +119,7 @@ What's new |
| 94 | 94 |
.. http:post:: /images/(name)/insert |
| 95 | 95 |
.. http:post:: /images/(name)/push |
| 96 | 96 |
|
| 97 |
-Uses json stream instead of HTML hijack, it looks like this: |
|
| 97 |
+ Uses json stream instead of HTML hijack, it looks like this: |
|
| 98 | 98 |
|
| 99 | 99 |
.. sourcecode:: http |
| 100 | 100 |
|
| ... | ... |
@@ -1,3 +1,8 @@ |
| 1 |
+.. use orphan to suppress "WARNING: document isn't included in any toctree" |
|
| 2 |
+.. per http://sphinx-doc.org/markup/misc.html#file-wide-metadata |
|
| 3 |
+ |
|
| 4 |
+:orphan: |
|
| 5 |
+ |
|
| 1 | 6 |
:title: Remote API v1.0 |
| 2 | 7 |
:description: API Documentation for Docker |
| 3 | 8 |
:keywords: API, Docker, rcli, REST, documentation |
| ... | ... |
@@ -300,8 +305,8 @@ Start a container |
| 300 | 300 |
:statuscode 500: server error |
| 301 | 301 |
|
| 302 | 302 |
|
| 303 |
-Stop a contaier |
|
| 304 |
-*************** |
|
| 303 |
+Stop a container |
|
| 304 |
+**************** |
|
| 305 | 305 |
|
| 306 | 306 |
.. http:post:: /containers/(id)/stop |
| 307 | 307 |
|
| ... | ... |
@@ -1,3 +1,8 @@ |
| 1 |
+.. use orphan to suppress "WARNING: document isn't included in any toctree" |
|
| 2 |
+.. per http://sphinx-doc.org/markup/misc.html#file-wide-metadata |
|
| 3 |
+ |
|
| 4 |
+:orphan: |
|
| 5 |
+ |
|
| 1 | 6 |
:title: Remote API v1.2 |
| 2 | 7 |
:description: API Documentation for Docker |
| 3 | 8 |
:keywords: API, Docker, rcli, REST, documentation |
| ... | ... |
@@ -1,3 +1,8 @@ |
| 1 |
+.. use orphan to suppress "WARNING: document isn't included in any toctree" |
|
| 2 |
+.. per http://sphinx-doc.org/markup/misc.html#file-wide-metadata |
|
| 3 |
+ |
|
| 4 |
+:orphan: |
|
| 5 |
+ |
|
| 1 | 6 |
:title: Remote API v1.3 |
| 2 | 7 |
:description: API Documentation for Docker |
| 3 | 8 |
:keywords: API, Docker, rcli, REST, documentation |
| ... | ... |
@@ -452,7 +452,7 @@ User Register |
| 452 | 452 |
"username": "foobar"'} |
| 453 | 453 |
|
| 454 | 454 |
:jsonparameter email: valid email address, that needs to be confirmed |
| 455 |
- :jsonparameter username: min 4 character, max 30 characters, must match the regular expression [a-z0-9_]. |
|
| 455 |
+ :jsonparameter username: min 4 character, max 30 characters, must match the regular expression [a-z0-9\_]. |
|
| 456 | 456 |
:jsonparameter password: min 5 characters |
| 457 | 457 |
|
| 458 | 458 |
**Example Response**: |
| ... | ... |
@@ -367,7 +367,8 @@ POST /v1/users |
| 367 | 367 |
{"email": "sam@dotcloud.com", "password": "toto42", "username": "foobar"'}
|
| 368 | 368 |
|
| 369 | 369 |
**Validation**: |
| 370 |
- - **username** : min 4 character, max 30 characters, must match the regular expression [a-z0-9_]. |
|
| 370 |
+ - **username**: min 4 character, max 30 characters, must match the regular |
|
| 371 |
+ expression [a-z0-9\_]. |
|
| 371 | 372 |
- **password**: min 5 characters |
| 372 | 373 |
|
| 373 | 374 |
**Valid**: return HTTP 200 |
| ... | ... |
@@ -566,4 +567,4 @@ Next request:: |
| 566 | 566 |
--------------------- |
| 567 | 567 |
|
| 568 | 568 |
- 1.0 : May 6th 2013 : initial release |
| 569 |
-- 1.1 : June 1st 2013 : Added Delete Repository and way to handle new source namespace. |
|
| 570 | 569 |
\ No newline at end of file |
| 570 |
+- 1.1 : June 1st 2013 : Added Delete Repository and way to handle new source namespace. |
| ... | ... |
@@ -14,6 +14,7 @@ |
| 14 | 14 |
|
| 15 | 15 |
-a=map[]: Attach to stdin, stdout or stderr. |
| 16 | 16 |
-c=0: CPU shares (relative weight) |
| 17 |
+ -cidfile="": Write the container ID to the file |
|
| 17 | 18 |
-d=false: Detached mode: leave the container running in the background |
| 18 | 19 |
-e=[]: Set environment variables |
| 19 | 20 |
-h="": Container host name |
| ... | ... |
@@ -26,3 +27,13 @@ |
| 26 | 26 |
-v=[]: Create a bind mount with: [host-dir]:[container-dir]:[rw|ro]. If "host-dir" is missing, then docker creates a new volume. |
| 27 | 27 |
-volumes-from="": Mount all volumes from the given container. |
| 28 | 28 |
-entrypoint="": Overwrite the default entrypoint set by the image. |
| 29 |
+ |
|
| 30 |
+ |
|
| 31 |
+Examples |
|
| 32 |
+-------- |
|
| 33 |
+ |
|
| 34 |
+.. code-block:: bash |
|
| 35 |
+ |
|
| 36 |
+ docker run -cidfile /tmp/docker_test.cid ubuntu echo "test" |
|
| 37 |
+ |
|
| 38 |
+| This will create a container and print "test" to the console. The cidfile flag makes docker attempt to create a new file and write the container ID to it. If the file exists already, docker will return an error. Docker will close this file when docker run exits. |
| ... | ... |
@@ -46,11 +46,13 @@ in a standard build environment. |
| 46 | 46 |
You can run an interactive session in the newly built container: |
| 47 | 47 |
|
| 48 | 48 |
:: |
| 49 |
+ |
|
| 49 | 50 |
docker run -i -t docker bash |
| 50 | 51 |
|
| 51 | 52 |
|
| 52 | 53 |
To extract the binaries from the container: |
| 53 | 54 |
|
| 54 | 55 |
:: |
| 56 |
+ |
|
| 55 | 57 |
docker run docker sh -c 'cat $(which docker)' > docker-build && chmod +x docker-build |
| 56 | 58 |
|
| ... | ... |
@@ -1,6 +1,6 @@ |
| 1 |
-:title: Dockerfile Builder |
|
| 2 |
-:description: Docker Builder specifes a simple DSL which allows you to automate the steps you would normally manually take to create an image. |
|
| 3 |
-:keywords: builder, docker, Docker Builder, automation, image creation |
|
| 1 |
+:title: Dockerfiles for Images |
|
| 2 |
+:description: Dockerfiles use a simple DSL which allows you to automate the steps you would normally manually take to create an image. |
|
| 3 |
+:keywords: builder, docker, Dockerfile, automation, image creation |
|
| 4 | 4 |
|
| 5 | 5 |
================== |
| 6 | 6 |
Dockerfile Builder |
| ... | ... |
@@ -30,7 +30,7 @@ build succeeds: |
| 30 | 30 |
|
| 31 | 31 |
``docker build -t shykes/myapp .`` |
| 32 | 32 |
|
| 33 |
-Docker will run your steps one-by-one, committing the result if necessary, |
|
| 33 |
+Docker will run your steps one-by-one, committing the result if necessary, |
|
| 34 | 34 |
before finally outputting the ID of your new image. |
| 35 | 35 |
|
| 36 | 36 |
2. Format |
| ... | ... |
@@ -43,7 +43,7 @@ The Dockerfile format is quite simple: |
| 43 | 43 |
# Comment |
| 44 | 44 |
INSTRUCTION arguments |
| 45 | 45 |
|
| 46 |
-The Instruction is not case-sensitive, however convention is for them to be |
|
| 46 |
+The Instruction is not case-sensitive, however convention is for them to be |
|
| 47 | 47 |
UPPERCASE in order to distinguish them from arguments more easily. |
| 48 | 48 |
|
| 49 | 49 |
Docker evaluates the instructions in a Dockerfile in order. **The first |
| ... | ... |
@@ -106,7 +106,7 @@ The ``CMD`` instruction sets the command to be executed when running |
| 106 | 106 |
the image. This is functionally equivalent to running ``docker commit |
| 107 | 107 |
-run '{"Cmd": <command>}'`` outside the builder.
|
| 108 | 108 |
|
| 109 |
-.. note:: |
|
| 109 |
+.. note:: |
|
| 110 | 110 |
Don't confuse `RUN` with `CMD`. `RUN` actually runs a |
| 111 | 111 |
command and commits the result; `CMD` does not execute anything at |
| 112 | 112 |
build time, but specifies the intended command for the image. |
| ... | ... |
@@ -131,7 +131,7 @@ value ``<value>``. This value will be passed to all future ``RUN`` |
| 131 | 131 |
instructions. This is functionally equivalent to prefixing the command |
| 132 | 132 |
with ``<key>=<value>`` |
| 133 | 133 |
|
| 134 |
-.. note:: |
|
| 134 |
+.. note:: |
|
| 135 | 135 |
The environment variables will persist when a container is run |
| 136 | 136 |
from the resulting image. |
| 137 | 137 |
|
| ... | ... |
@@ -152,16 +152,24 @@ destination container. |
| 152 | 152 |
|
| 153 | 153 |
The copy obeys the following rules: |
| 154 | 154 |
|
| 155 |
+* If ``<src>`` is a URL and ``<dest>`` does not end with a trailing slash, |
|
| 156 |
+ then a file is downloaded from the URL and copied to ``<dest>``. |
|
| 157 |
+* If ``<src>`` is a URL and ``<dest>`` does end with a trailing slash, |
|
| 158 |
+ then the filename is inferred from the URL and the file is downloaded to |
|
| 159 |
+ ``<dest>/<filename>``. For instance, ``ADD http://example.com/foobar /`` |
|
| 160 |
+ would create the file ``/foobar``. The URL must have a nontrivial path |
|
| 161 |
+ so that an appropriate filename can be discovered in this case |
|
| 162 |
+ (``http://example.com`` will not work). |
|
| 155 | 163 |
* If ``<src>`` is a directory, the entire directory is copied, |
| 156 | 164 |
including filesystem metadata. |
| 157 | 165 |
* If ``<src>``` is a tar archive in a recognized compression format |
| 158 | 166 |
(identity, gzip, bzip2 or xz), it is unpacked as a directory. |
| 159 | 167 |
|
| 160 | 168 |
When a directory is copied or unpacked, it has the same behavior as |
| 161 |
- ``tar -x``: the result is the union of |
|
| 169 |
+ ``tar -x``: the result is the union of |
|
| 162 | 170 |
|
| 163 | 171 |
1. whatever existed at the destination path and |
| 164 |
- 2. the contents of the source tree, |
|
| 172 |
+ 2. the contents of the source tree, |
|
| 165 | 173 |
|
| 166 | 174 |
with conflicts resolved in favor of 2) on a file-by-file basis. |
| 167 | 175 |
|
| ... | ... |
@@ -177,7 +185,7 @@ The copy obeys the following rules: |
| 177 | 177 |
with mode 0700, uid and gid 0. |
| 178 | 178 |
|
| 179 | 179 |
3.8 ENTRYPOINT |
| 180 |
+-------------- |
|
| 180 | 181 |
|
| 181 | 182 |
``ENTRYPOINT /bin/echo`` |
| 182 | 183 |
|
| ... | ... |
@@ -203,14 +211,14 @@ container created from the image. |
| 203 | 203 |
# Nginx |
| 204 | 204 |
# |
| 205 | 205 |
# VERSION 0.0.1 |
| 206 |
- |
|
| 206 |
+ |
|
| 207 | 207 |
FROM ubuntu |
| 208 | 208 |
MAINTAINER Guillaume J. Charmes "guillaume@dotcloud.com" |
| 209 |
- |
|
| 209 |
+ |
|
| 210 | 210 |
# make sure the package repository is up to date |
| 211 | 211 |
RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list |
| 212 | 212 |
RUN apt-get update |
| 213 |
- |
|
| 213 |
+ |
|
| 214 | 214 |
RUN apt-get install -y inotify-tools nginx apache2 openssh-server |
| 215 | 215 |
|
| 216 | 216 |
.. code-block:: bash |
| ... | ... |
@@ -218,12 +226,12 @@ container created from the image. |
| 218 | 218 |
# Firefox over VNC |
| 219 | 219 |
# |
| 220 | 220 |
# VERSION 0.3 |
| 221 |
- |
|
| 221 |
+ |
|
| 222 | 222 |
FROM ubuntu |
| 223 | 223 |
# make sure the package repository is up to date |
| 224 | 224 |
RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list |
| 225 | 225 |
RUN apt-get update |
| 226 |
- |
|
| 226 |
+ |
|
| 227 | 227 |
# Install vnc, xvfb in order to create a 'fake' display and firefox |
| 228 | 228 |
RUN apt-get install -y x11vnc xvfb firefox |
| 229 | 229 |
RUN mkdir /.vnc |
| ... | ... |
@@ -231,7 +239,7 @@ container created from the image. |
| 231 | 231 |
RUN x11vnc -storepasswd 1234 ~/.vnc/passwd |
| 232 | 232 |
# Autostart firefox (might not be the best way, but it does the trick) |
| 233 | 233 |
RUN bash -c 'echo "firefox" >> /.bashrc' |
| 234 |
- |
|
| 234 |
+ |
|
| 235 | 235 |
EXPOSE 5900 |
| 236 | 236 |
CMD ["x11vnc", "-forever", "-usepw", "-create"] |
| 237 | 237 |
|
| ... | ... |
@@ -119,7 +119,7 @@ your container to an image within your username namespace. |
| 119 | 119 |
|
| 120 | 120 |
|
| 121 | 121 |
Pushing a container to its repository |
| 122 |
+------------------------------------- |
|
| 122 | 123 |
|
| 123 | 124 |
In order to push an image to its repository you need to have committed |
| 124 | 125 |
your container to a named image (see above) |
| ... | ... |
@@ -167,12 +167,12 @@ func (runtime *Runtime) Register(container *Container) error {
|
| 167 | 167 |
return nil |
| 168 | 168 |
} |
| 169 | 169 |
|
| 170 |
-func (runtime *Runtime) LogToDisk(src *utils.WriteBroadcaster, dst string) error {
|
|
| 170 |
+func (runtime *Runtime) LogToDisk(src *utils.WriteBroadcaster, dst, stream string) error {
|
|
| 171 | 171 |
log, err := os.OpenFile(dst, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0600) |
| 172 | 172 |
if err != nil {
|
| 173 | 173 |
return err |
| 174 | 174 |
} |
| 175 |
- src.AddWriter(log) |
|
| 175 |
+ src.AddWriter(log, stream) |
|
| 176 | 176 |
return nil |
| 177 | 177 |
} |
| 178 | 178 |
|
| ... | ... |
@@ -5,7 +5,6 @@ import ( |
| 5 | 5 |
"fmt" |
| 6 | 6 |
"github.com/dotcloud/docker/utils" |
| 7 | 7 |
"io" |
| 8 |
- "io/ioutil" |
|
| 9 | 8 |
"log" |
| 10 | 9 |
"net" |
| 11 | 10 |
"os" |
| ... | ... |
@@ -247,36 +246,13 @@ func TestGet(t *testing.T) {
|
| 247 | 247 |
runtime := mkRuntime(t) |
| 248 | 248 |
defer nuke(runtime) |
| 249 | 249 |
|
| 250 |
- builder := NewBuilder(runtime) |
|
| 251 |
- |
|
| 252 |
- container1, err := builder.Create(&Config{
|
|
| 253 |
- Image: GetTestImage(runtime).ID, |
|
| 254 |
- Cmd: []string{"ls", "-al"},
|
|
| 255 |
- }, |
|
| 256 |
- ) |
|
| 257 |
- if err != nil {
|
|
| 258 |
- t.Fatal(err) |
|
| 259 |
- } |
|
| 250 |
+ container1, _, _ := mkContainer(runtime, []string{"_", "ls", "-al"}, t)
|
|
| 260 | 251 |
defer runtime.Destroy(container1) |
| 261 | 252 |
|
| 262 |
- container2, err := builder.Create(&Config{
|
|
| 263 |
- Image: GetTestImage(runtime).ID, |
|
| 264 |
- Cmd: []string{"ls", "-al"},
|
|
| 265 |
- }, |
|
| 266 |
- ) |
|
| 267 |
- if err != nil {
|
|
| 268 |
- t.Fatal(err) |
|
| 269 |
- } |
|
| 253 |
+ container2, _, _ := mkContainer(runtime, []string{"_", "ls", "-al"}, t)
|
|
| 270 | 254 |
defer runtime.Destroy(container2) |
| 271 | 255 |
|
| 272 |
- container3, err := builder.Create(&Config{
|
|
| 273 |
- Image: GetTestImage(runtime).ID, |
|
| 274 |
- Cmd: []string{"ls", "-al"},
|
|
| 275 |
- }, |
|
| 276 |
- ) |
|
| 277 |
- if err != nil {
|
|
| 278 |
- t.Fatal(err) |
|
| 279 |
- } |
|
| 256 |
+ container3, _, _ := mkContainer(runtime, []string{"_", "ls", "-al"}, t)
|
|
| 280 | 257 |
defer runtime.Destroy(container3) |
| 281 | 258 |
|
| 282 | 259 |
if runtime.Get(container1.ID) != container1 {
|
| ... | ... |
@@ -431,46 +407,14 @@ func TestAllocateUDPPortLocalhost(t *testing.T) {
|
| 431 | 431 |
} |
| 432 | 432 |
|
| 433 | 433 |
func TestRestore(t *testing.T) {
|
| 434 |
- |
|
| 435 |
- root, err := ioutil.TempDir("", "docker-test")
|
|
| 436 |
- if err != nil {
|
|
| 437 |
- t.Fatal(err) |
|
| 438 |
- } |
|
| 439 |
- if err := os.Remove(root); err != nil {
|
|
| 440 |
- t.Fatal(err) |
|
| 441 |
- } |
|
| 442 |
- if err := utils.CopyDirectory(unitTestStoreBase, root); err != nil {
|
|
| 443 |
- t.Fatal(err) |
|
| 444 |
- } |
|
| 445 |
- |
|
| 446 |
- runtime1, err := NewRuntimeFromDirectory(root, false) |
|
| 447 |
- if err != nil {
|
|
| 448 |
- t.Fatal(err) |
|
| 449 |
- } |
|
| 450 |
- |
|
| 451 |
- builder := NewBuilder(runtime1) |
|
| 452 |
- |
|
| 434 |
+ runtime1 := mkRuntime(t) |
|
| 435 |
+ defer nuke(runtime1) |
|
| 453 | 436 |
// Create a container with one instance of docker |
| 454 |
- container1, err := builder.Create(&Config{
|
|
| 455 |
- Image: GetTestImage(runtime1).ID, |
|
| 456 |
- Cmd: []string{"ls", "-al"},
|
|
| 457 |
- }, |
|
| 458 |
- ) |
|
| 459 |
- if err != nil {
|
|
| 460 |
- t.Fatal(err) |
|
| 461 |
- } |
|
| 437 |
+ container1, _, _ := mkContainer(runtime1, []string{"_", "ls", "-al"}, t)
|
|
| 462 | 438 |
defer runtime1.Destroy(container1) |
| 463 | 439 |
|
| 464 | 440 |
// Create a second container meant to be killed |
| 465 |
- container2, err := builder.Create(&Config{
|
|
| 466 |
- Image: GetTestImage(runtime1).ID, |
|
| 467 |
- Cmd: []string{"/bin/cat"},
|
|
| 468 |
- OpenStdin: true, |
|
| 469 |
- }, |
|
| 470 |
- ) |
|
| 471 |
- if err != nil {
|
|
| 472 |
- t.Fatal(err) |
|
| 473 |
- } |
|
| 441 |
+ container2, _, _ := mkContainer(runtime1, []string{"-i", "_", "/bin/cat"}, t)
|
|
| 474 | 442 |
defer runtime1.Destroy(container2) |
| 475 | 443 |
|
| 476 | 444 |
// Start the container non blocking |
| ... | ... |
@@ -505,7 +449,7 @@ func TestRestore(t *testing.T) {
|
| 505 | 505 |
|
| 506 | 506 |
// Here are are simulating a docker restart - that is, reloading all containers |
| 507 | 507 |
// from scratch |
| 508 |
- runtime2, err := NewRuntimeFromDirectory(root, false) |
|
| 508 |
+ runtime2, err := NewRuntimeFromDirectory(runtime1.root, false) |
|
| 509 | 509 |
if err != nil {
|
| 510 | 510 |
t.Fatal(err) |
| 511 | 511 |
} |
| ... | ... |
@@ -2,6 +2,7 @@ package docker |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 | 4 |
"bufio" |
| 5 |
+ "encoding/json" |
|
| 5 | 6 |
"errors" |
| 6 | 7 |
"fmt" |
| 7 | 8 |
"github.com/dotcloud/docker/auth" |
| ... | ... |
@@ -1054,20 +1055,41 @@ func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, std |
| 1054 | 1054 |
} |
| 1055 | 1055 |
//logs |
| 1056 | 1056 |
if logs {
|
| 1057 |
- if stdout {
|
|
| 1058 |
- cLog, err := container.ReadLog("stdout")
|
|
| 1059 |
- if err != nil {
|
|
| 1060 |
- utils.Debugf("Error reading logs (stdout): %s", err)
|
|
| 1061 |
- } else if _, err := io.Copy(out, cLog); err != nil {
|
|
| 1062 |
- utils.Debugf("Error streaming logs (stdout): %s", err)
|
|
| 1057 |
+ cLog, err := container.ReadLog("json")
|
|
| 1058 |
+ if err != nil && os.IsNotExist(err) {
|
|
| 1059 |
+ // Legacy logs |
|
| 1060 |
+ utils.Debugf("Old logs format")
|
|
| 1061 |
+ if stdout {
|
|
| 1062 |
+ cLog, err := container.ReadLog("stdout")
|
|
| 1063 |
+ if err != nil {
|
|
| 1064 |
+ utils.Debugf("Error reading logs (stdout): %s", err)
|
|
| 1065 |
+ } else if _, err := io.Copy(out, cLog); err != nil {
|
|
| 1066 |
+ utils.Debugf("Error streaming logs (stdout): %s", err)
|
|
| 1067 |
+ } |
|
| 1063 | 1068 |
} |
| 1064 |
- } |
|
| 1065 |
- if stderr {
|
|
| 1066 |
- cLog, err := container.ReadLog("stderr")
|
|
| 1067 |
- if err != nil {
|
|
| 1068 |
- utils.Debugf("Error reading logs (stderr): %s", err)
|
|
| 1069 |
- } else if _, err := io.Copy(out, cLog); err != nil {
|
|
| 1070 |
- utils.Debugf("Error streaming logs (stderr): %s", err)
|
|
| 1069 |
+ if stderr {
|
|
| 1070 |
+ cLog, err := container.ReadLog("stderr")
|
|
| 1071 |
+ if err != nil {
|
|
| 1072 |
+ utils.Debugf("Error reading logs (stderr): %s", err)
|
|
| 1073 |
+ } else if _, err := io.Copy(out, cLog); err != nil {
|
|
| 1074 |
+ utils.Debugf("Error streaming logs (stderr): %s", err)
|
|
| 1075 |
+ } |
|
| 1076 |
+ } |
|
| 1077 |
+ } else if err != nil {
|
|
| 1078 |
+ utils.Debugf("Error reading logs (json): %s", err)
|
|
| 1079 |
+ } else {
|
|
| 1080 |
+ dec := json.NewDecoder(cLog) |
|
| 1081 |
+ for {
|
|
| 1082 |
+ var l utils.JSONLog |
|
| 1083 |
+ if err := dec.Decode(&l); err == io.EOF {
|
|
| 1084 |
+ break |
|
| 1085 |
+ } else if err != nil {
|
|
| 1086 |
+ utils.Debugf("Error streaming logs: %s", err)
|
|
| 1087 |
+ break |
|
| 1088 |
+ } |
|
| 1089 |
+ if (l.Stream == "stdout" && stdout) || (l.Stream == "stderr" && stderr) {
|
|
| 1090 |
+ fmt.Fprintf(out, "%s", l.Log) |
|
| 1091 |
+ } |
|
| 1071 | 1092 |
} |
| 1072 | 1093 |
} |
| 1073 | 1094 |
} |
| ... | ... |
@@ -248,30 +248,54 @@ func (r *bufReader) Close() error {
|
| 248 | 248 |
|
| 249 | 249 |
type WriteBroadcaster struct {
|
| 250 | 250 |
sync.Mutex |
| 251 |
- writers map[io.WriteCloser]struct{}
|
|
| 251 |
+ buf *bytes.Buffer |
|
| 252 |
+ writers map[StreamWriter]bool |
|
| 252 | 253 |
} |
| 253 | 254 |
|
| 254 |
-func (w *WriteBroadcaster) AddWriter(writer io.WriteCloser) {
|
|
| 255 |
- w.Lock() |
|
| 256 |
- w.writers[writer] = struct{}{}
|
|
| 257 |
- w.Unlock() |
|
| 255 |
+type StreamWriter struct {
|
|
| 256 |
+ wc io.WriteCloser |
|
| 257 |
+ stream string |
|
| 258 | 258 |
} |
| 259 | 259 |
|
| 260 |
-// FIXME: Is that function used? |
|
| 261 |
-// FIXME: This relies on the concrete writer type used having equality operator |
|
| 262 |
-func (w *WriteBroadcaster) RemoveWriter(writer io.WriteCloser) {
|
|
| 260 |
+func (w *WriteBroadcaster) AddWriter(writer io.WriteCloser, stream string) {
|
|
| 263 | 261 |
w.Lock() |
| 264 |
- delete(w.writers, writer) |
|
| 262 |
+ sw := StreamWriter{wc: writer, stream: stream}
|
|
| 263 |
+ w.writers[sw] = true |
|
| 265 | 264 |
w.Unlock() |
| 266 | 265 |
} |
| 267 | 266 |
|
| 267 |
+type JSONLog struct {
|
|
| 268 |
+ Log string `json:"log,omitempty"` |
|
| 269 |
+ Stream string `json:"stream,omitempty"` |
|
| 270 |
+ Created time.Time `json:"time"` |
|
| 271 |
+} |
|
| 272 |
+ |
|
| 268 | 273 |
func (w *WriteBroadcaster) Write(p []byte) (n int, err error) {
|
| 269 | 274 |
w.Lock() |
| 270 | 275 |
defer w.Unlock() |
| 271 |
- for writer := range w.writers {
|
|
| 272 |
- if n, err := writer.Write(p); err != nil || n != len(p) {
|
|
| 276 |
+ w.buf.Write(p) |
|
| 277 |
+ for sw := range w.writers {
|
|
| 278 |
+ lp := p |
|
| 279 |
+ if sw.stream != "" {
|
|
| 280 |
+ lp = nil |
|
| 281 |
+ for {
|
|
| 282 |
+ line, err := w.buf.ReadString('\n')
|
|
| 283 |
+ if err != nil {
|
|
| 284 |
+ w.buf.Write([]byte(line)) |
|
| 285 |
+ break |
|
| 286 |
+ } |
|
| 287 |
+ b, err := json.Marshal(&JSONLog{Log: line, Stream: sw.stream, Created: time.Now()})
|
|
| 288 |
+ if err != nil {
|
|
| 289 |
+ // On error, evict the writer |
|
| 290 |
+ delete(w.writers, sw) |
|
| 291 |
+ continue |
|
| 292 |
+ } |
|
| 293 |
+ lp = append(lp, b...) |
|
| 294 |
+ } |
|
| 295 |
+ } |
|
| 296 |
+ if n, err := sw.wc.Write(lp); err != nil || n != len(lp) {
|
|
| 273 | 297 |
// On error, evict the writer |
| 274 |
- delete(w.writers, writer) |
|
| 298 |
+ delete(w.writers, sw) |
|
| 275 | 299 |
} |
| 276 | 300 |
} |
| 277 | 301 |
return len(p), nil |
| ... | ... |
@@ -280,15 +304,15 @@ func (w *WriteBroadcaster) Write(p []byte) (n int, err error) {
|
| 280 | 280 |
func (w *WriteBroadcaster) CloseWriters() error {
|
| 281 | 281 |
w.Lock() |
| 282 | 282 |
defer w.Unlock() |
| 283 |
- for writer := range w.writers {
|
|
| 284 |
- writer.Close() |
|
| 283 |
+ for sw := range w.writers {
|
|
| 284 |
+ sw.wc.Close() |
|
| 285 | 285 |
} |
| 286 |
- w.writers = make(map[io.WriteCloser]struct{})
|
|
| 286 |
+ w.writers = make(map[StreamWriter]bool) |
|
| 287 | 287 |
return nil |
| 288 | 288 |
} |
| 289 | 289 |
|
| 290 | 290 |
func NewWriteBroadcaster() *WriteBroadcaster {
|
| 291 |
- return &WriteBroadcaster{writers: make(map[io.WriteCloser]struct{})}
|
|
| 291 |
+ return &WriteBroadcaster{writers: make(map[StreamWriter]bool), buf: bytes.NewBuffer(nil)}
|
|
| 292 | 292 |
} |
| 293 | 293 |
|
| 294 | 294 |
func GetTotalUsedFds() int {
|
| ... | ... |
@@ -60,9 +60,9 @@ func TestWriteBroadcaster(t *testing.T) {
|
| 60 | 60 |
|
| 61 | 61 |
// Test 1: Both bufferA and bufferB should contain "foo" |
| 62 | 62 |
bufferA := &dummyWriter{}
|
| 63 |
- writer.AddWriter(bufferA) |
|
| 63 |
+ writer.AddWriter(bufferA, "") |
|
| 64 | 64 |
bufferB := &dummyWriter{}
|
| 65 |
- writer.AddWriter(bufferB) |
|
| 65 |
+ writer.AddWriter(bufferB, "") |
|
| 66 | 66 |
writer.Write([]byte("foo"))
|
| 67 | 67 |
|
| 68 | 68 |
if bufferA.String() != "foo" {
|
| ... | ... |
@@ -76,7 +76,7 @@ func TestWriteBroadcaster(t *testing.T) {
|
| 76 | 76 |
// Test2: bufferA and bufferB should contain "foobar", |
| 77 | 77 |
// while bufferC should only contain "bar" |
| 78 | 78 |
bufferC := &dummyWriter{}
|
| 79 |
- writer.AddWriter(bufferC) |
|
| 79 |
+ writer.AddWriter(bufferC, "") |
|
| 80 | 80 |
writer.Write([]byte("bar"))
|
| 81 | 81 |
|
| 82 | 82 |
if bufferA.String() != "foobar" {
|
| ... | ... |
@@ -91,35 +91,22 @@ func TestWriteBroadcaster(t *testing.T) {
|
| 91 | 91 |
t.Errorf("Buffer contains %v", bufferC.String())
|
| 92 | 92 |
} |
| 93 | 93 |
|
| 94 |
- // Test3: Test removal |
|
| 95 |
- writer.RemoveWriter(bufferB) |
|
| 96 |
- writer.Write([]byte("42"))
|
|
| 97 |
- if bufferA.String() != "foobar42" {
|
|
| 98 |
- t.Errorf("Buffer contains %v", bufferA.String())
|
|
| 99 |
- } |
|
| 100 |
- if bufferB.String() != "foobar" {
|
|
| 101 |
- t.Errorf("Buffer contains %v", bufferB.String())
|
|
| 102 |
- } |
|
| 103 |
- if bufferC.String() != "bar42" {
|
|
| 104 |
- t.Errorf("Buffer contains %v", bufferC.String())
|
|
| 105 |
- } |
|
| 106 |
- |
|
| 107 |
- // Test4: Test eviction on failure |
|
| 94 |
+ // Test3: Test eviction on failure |
|
| 108 | 95 |
bufferA.failOnWrite = true |
| 109 | 96 |
writer.Write([]byte("fail"))
|
| 110 |
- if bufferA.String() != "foobar42" {
|
|
| 97 |
+ if bufferA.String() != "foobar" {
|
|
| 111 | 98 |
t.Errorf("Buffer contains %v", bufferA.String())
|
| 112 | 99 |
} |
| 113 |
- if bufferC.String() != "bar42fail" {
|
|
| 100 |
+ if bufferC.String() != "barfail" {
|
|
| 114 | 101 |
t.Errorf("Buffer contains %v", bufferC.String())
|
| 115 | 102 |
} |
| 116 | 103 |
// Even though we reset the flag, no more writes should go in there |
| 117 | 104 |
bufferA.failOnWrite = false |
| 118 | 105 |
writer.Write([]byte("test"))
|
| 119 |
- if bufferA.String() != "foobar42" {
|
|
| 106 |
+ if bufferA.String() != "foobar" {
|
|
| 120 | 107 |
t.Errorf("Buffer contains %v", bufferA.String())
|
| 121 | 108 |
} |
| 122 |
- if bufferC.String() != "bar42failtest" {
|
|
| 109 |
+ if bufferC.String() != "barfailtest" {
|
|
| 123 | 110 |
t.Errorf("Buffer contains %v", bufferC.String())
|
| 124 | 111 |
} |
| 125 | 112 |
|
| ... | ... |
@@ -141,7 +128,7 @@ func TestRaceWriteBroadcaster(t *testing.T) {
|
| 141 | 141 |
writer := NewWriteBroadcaster() |
| 142 | 142 |
c := make(chan bool) |
| 143 | 143 |
go func() {
|
| 144 |
- writer.AddWriter(devNullCloser(0)) |
|
| 144 |
+ writer.AddWriter(devNullCloser(0), "") |
|
| 145 | 145 |
c <- true |
| 146 | 146 |
}() |
| 147 | 147 |
writer.Write([]byte("hello"))
|
| ... | ... |
@@ -84,18 +84,25 @@ func readFile(src string, t *testing.T) (content string) {
|
| 84 | 84 |
} |
| 85 | 85 |
|
| 86 | 86 |
// Create a test container from the given runtime `r` and run arguments `args`. |
| 87 |
-// The image name (eg. the XXX in []string{"-i", "-t", "XXX", "bash"}, is dynamically replaced by the current test image.
|
|
| 87 |
+// If the image name is "_", (eg. []string{"-i", "-t", "_", "bash"}, it is
|
|
| 88 |
+// dynamically replaced by the current test image. |
|
| 88 | 89 |
// The caller is responsible for destroying the container. |
| 89 | 90 |
// Call t.Fatal() at the first error. |
| 90 | 91 |
func mkContainer(r *Runtime, args []string, t *testing.T) (*Container, *HostConfig, error) {
|
| 91 | 92 |
config, hostConfig, _, err := ParseRun(args, nil) |
| 93 |
+ defer func() {
|
|
| 94 |
+ if err != nil && t != nil {
|
|
| 95 |
+ t.Fatal(err) |
|
| 96 |
+ } |
|
| 97 |
+ }() |
|
| 92 | 98 |
if err != nil {
|
| 93 | 99 |
return nil, nil, err |
| 94 | 100 |
} |
| 95 |
- config.Image = GetTestImage(r).ID |
|
| 101 |
+ if config.Image == "_" {
|
|
| 102 |
+ config.Image = GetTestImage(r).ID |
|
| 103 |
+ } |
|
| 96 | 104 |
c, err := NewBuilder(r).Create(config) |
| 97 | 105 |
if err != nil {
|
| 98 |
- t.Fatal(err) |
|
| 99 | 106 |
return nil, nil, err |
| 100 | 107 |
} |
| 101 | 108 |
return c, hostConfig, nil |