... | ... |
@@ -38,7 +38,7 @@ $(DOCKER_BIN): $(DOCKER_DIR) |
38 | 38 |
|
39 | 39 |
$(DOCKER_DIR): |
40 | 40 |
@mkdir -p $(dir $@) |
41 |
- @if [ -h $@ ]; then rm -f $@; ln -sf $(CURDIR)/ $@; fi |
|
41 |
+ @if [ -h $@ ]; then rm -f $@; fi; ln -sf $(CURDIR)/ $@ |
|
42 | 42 |
@(cd $(DOCKER_MAIN); go get $(GO_OPTIONS)) |
43 | 43 |
|
44 | 44 |
whichrelease: |
... | ... |
@@ -5,24 +5,46 @@ import ( |
5 | 5 |
"encoding/json" |
6 | 6 |
"fmt" |
7 | 7 |
"github.com/gorilla/mux" |
8 |
- "io" |
|
9 | 8 |
"log" |
10 | 9 |
"net" |
11 | 10 |
"net/http" |
12 |
- "net/url" |
|
13 | 11 |
"os" |
14 |
- "runtime" |
|
15 | 12 |
"strconv" |
16 | 13 |
"strings" |
17 | 14 |
) |
18 | 15 |
|
19 |
-func ListenAndServe(addr string, rtime *Runtime) error { |
|
16 |
+func hijackServer(w http.ResponseWriter) (*os.File, net.Conn, error) { |
|
17 |
+ rwc, _, err := w.(http.Hijacker).Hijack() |
|
18 |
+ if err != nil { |
|
19 |
+ return nil, nil, err |
|
20 |
+ } |
|
21 |
+ |
|
22 |
+ file, err := rwc.(*net.TCPConn).File() |
|
23 |
+ if err != nil { |
|
24 |
+ return nil, rwc, err |
|
25 |
+ } |
|
26 |
+ |
|
27 |
+ // Flush the options to make sure the client sets the raw mode |
|
28 |
+ rwc.Write([]byte{}) |
|
29 |
+ |
|
30 |
+ return file, rwc, nil |
|
31 |
+} |
|
32 |
+ |
|
33 |
+func httpError(w http.ResponseWriter, err error) { |
|
34 |
+ if strings.HasPrefix(err.Error(), "No such") { |
|
35 |
+ http.Error(w, err.Error(), http.StatusNotFound) |
|
36 |
+ } else { |
|
37 |
+ http.Error(w, err.Error(), http.StatusInternalServerError) |
|
38 |
+ } |
|
39 |
+} |
|
40 |
+ |
|
41 |
+func ListenAndServe(addr string, srv *Server) error { |
|
20 | 42 |
r := mux.NewRouter() |
21 | 43 |
log.Printf("Listening for HTTP on %s\n", addr) |
22 | 44 |
|
23 | 45 |
r.Path("/version").Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
24 | 46 |
log.Println(r.Method, r.RequestURI) |
25 |
- m := ApiVersion{VERSION, GIT_COMMIT, rtime.capabilities.MemoryLimit, rtime.capabilities.SwapLimit} |
|
47 |
+ m := srv.DockerVersion() |
|
26 | 48 |
b, err := json.Marshal(m) |
27 | 49 |
if err != nil { |
28 | 50 |
http.Error(w, err.Error(), http.StatusInternalServerError) |
... | ... |
@@ -35,16 +57,11 @@ func ListenAndServe(addr string, rtime *Runtime) error { |
35 | 35 |
log.Println(r.Method, r.RequestURI) |
36 | 36 |
vars := mux.Vars(r) |
37 | 37 |
name := vars["name"] |
38 |
- if container := rtime.Get(name); container != nil { |
|
39 |
- if err := container.Kill(); err != nil { |
|
40 |
- http.Error(w, "Error restarting container "+name+": "+err.Error(), http.StatusInternalServerError) |
|
41 |
- return |
|
42 |
- } |
|
38 |
+ if err := srv.ContainerKill(name); err != nil { |
|
39 |
+ httpError(w, err) |
|
43 | 40 |
} else { |
44 |
- http.Error(w, "No such container: "+name, http.StatusNotFound) |
|
45 |
- return |
|
41 |
+ w.WriteHeader(http.StatusOK) |
|
46 | 42 |
} |
47 |
- w.WriteHeader(http.StatusOK) |
|
48 | 43 |
}) |
49 | 44 |
|
50 | 45 |
r.Path("/containers/{name:.*}/export").Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
... | ... |
@@ -52,36 +69,21 @@ func ListenAndServe(addr string, rtime *Runtime) error { |
52 | 52 |
vars := mux.Vars(r) |
53 | 53 |
name := vars["name"] |
54 | 54 |
|
55 |
- if container := rtime.Get(name); container != nil { |
|
56 |
- |
|
57 |
- data, err := container.Export() |
|
58 |
- if err != nil { |
|
59 |
- http.Error(w, err.Error(), http.StatusInternalServerError) |
|
60 |
- return |
|
61 |
- } |
|
62 |
- conn, _, err := w.(http.Hijacker).Hijack() |
|
63 |
- if err != nil { |
|
64 |
- http.Error(w, err.Error(), http.StatusInternalServerError) |
|
65 |
- return |
|
66 |
- } |
|
67 |
- defer conn.Close() |
|
68 |
- file, err := conn.(*net.TCPConn).File() |
|
69 |
- if err != nil { |
|
70 |
- http.Error(w, err.Error(), http.StatusInternalServerError) |
|
71 |
- return |
|
72 |
- } |
|
55 |
+ file, rwc, err := hijackServer(w) |
|
56 |
+ if file != nil { |
|
73 | 57 |
defer file.Close() |
74 |
- |
|
75 |
- fmt.Fprintln(file, "HTTP/1.1 200 OK\r\nContent-Type: raw-stream-hijack\r\n\r\n") |
|
76 |
- // Stream the entire contents of the container (basically a volatile snapshot) |
|
77 |
- if _, err := io.Copy(file, data); err != nil { |
|
78 |
- fmt.Fprintln(file, "Error: "+err.Error()) |
|
79 |
- return |
|
80 |
- } |
|
81 |
- } else { |
|
82 |
- http.Error(w, "No such container: "+name, http.StatusNotFound) |
|
83 | 58 |
} |
84 |
- |
|
59 |
+ if rwc != nil { |
|
60 |
+ defer rwc.Close() |
|
61 |
+ } |
|
62 |
+ if err != nil { |
|
63 |
+ httpError(w, err) |
|
64 |
+ return |
|
65 |
+ } |
|
66 |
+ fmt.Fprintf(file, "HTTP/1.1 200 OK\r\nContent-Type: raw-stream-hijack\r\n\r\n") |
|
67 |
+ if err := srv.ContainerExport(name, file); err != nil { |
|
68 |
+ fmt.Fprintln(file, "Error: "+err.Error()) |
|
69 |
+ } |
|
85 | 70 |
}) |
86 | 71 |
|
87 | 72 |
r.Path("/images").Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
... | ... |
@@ -89,61 +91,14 @@ func ListenAndServe(addr string, rtime *Runtime) error { |
89 | 89 |
if err := r.ParseForm(); err != nil { |
90 | 90 |
http.Error(w, err.Error(), http.StatusInternalServerError) |
91 | 91 |
} |
92 |
- All := r.Form.Get("all") |
|
93 |
- NameFilter := r.Form.Get("filter") |
|
94 |
- Quiet := r.Form.Get("quiet") |
|
92 |
+ all := r.Form.Get("all") |
|
93 |
+ filter := r.Form.Get("filter") |
|
94 |
+ quiet := r.Form.Get("quiet") |
|
95 | 95 |
|
96 |
- var allImages map[string]*Image |
|
97 |
- var err error |
|
98 |
- if All == "1" { |
|
99 |
- allImages, err = rtime.graph.Map() |
|
100 |
- } else { |
|
101 |
- allImages, err = rtime.graph.Heads() |
|
102 |
- } |
|
96 |
+ outs, err := srv.Images(all, filter, quiet) |
|
103 | 97 |
if err != nil { |
104 |
- w.WriteHeader(500) |
|
105 |
- return |
|
106 |
- } |
|
107 |
- var outs []ApiImages = []ApiImages{} //produce [] when empty instead of 'null' |
|
108 |
- for name, repository := range rtime.repositories.Repositories { |
|
109 |
- if NameFilter != "" && name != NameFilter { |
|
110 |
- continue |
|
111 |
- } |
|
112 |
- for tag, id := range repository { |
|
113 |
- var out ApiImages |
|
114 |
- image, err := rtime.graph.Get(id) |
|
115 |
- if err != nil { |
|
116 |
- log.Printf("Warning: couldn't load %s from %s/%s: %s", id, name, tag, err) |
|
117 |
- continue |
|
118 |
- } |
|
119 |
- delete(allImages, id) |
|
120 |
- if Quiet != "1" { |
|
121 |
- out.Repository = name |
|
122 |
- out.Tag = tag |
|
123 |
- out.Id = TruncateId(id) |
|
124 |
- out.Created = image.Created.Unix() |
|
125 |
- } else { |
|
126 |
- out.Id = image.ShortId() |
|
127 |
- } |
|
128 |
- outs = append(outs, out) |
|
129 |
- } |
|
130 |
- } |
|
131 |
- // Display images which aren't part of a |
|
132 |
- if NameFilter == "" { |
|
133 |
- for id, image := range allImages { |
|
134 |
- var out ApiImages |
|
135 |
- if Quiet != "1" { |
|
136 |
- out.Repository = "<none>" |
|
137 |
- out.Tag = "<none>" |
|
138 |
- out.Id = TruncateId(id) |
|
139 |
- out.Created = image.Created.Unix() |
|
140 |
- } else { |
|
141 |
- out.Id = image.ShortId() |
|
142 |
- } |
|
143 |
- outs = append(outs, out) |
|
144 |
- } |
|
98 |
+ httpError(w, err) |
|
145 | 99 |
} |
146 |
- |
|
147 | 100 |
b, err := json.Marshal(outs) |
148 | 101 |
if err != nil { |
149 | 102 |
http.Error(w, err.Error(), http.StatusInternalServerError) |
... | ... |
@@ -154,22 +109,7 @@ func ListenAndServe(addr string, rtime *Runtime) error { |
154 | 154 |
|
155 | 155 |
r.Path("/info").Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
156 | 156 |
log.Println(r.Method, r.RequestURI) |
157 |
- images, _ := rtime.graph.All() |
|
158 |
- var imgcount int |
|
159 |
- if images == nil { |
|
160 |
- imgcount = 0 |
|
161 |
- } else { |
|
162 |
- imgcount = len(images) |
|
163 |
- } |
|
164 |
- var out ApiInfo |
|
165 |
- out.Containers = len(rtime.List()) |
|
166 |
- out.Version = VERSION |
|
167 |
- out.Images = imgcount |
|
168 |
- if os.Getenv("DEBUG") == "1" { |
|
169 |
- out.Debug = true |
|
170 |
- out.NFd = getTotalUsedFds() |
|
171 |
- out.NGoroutines = runtime.NumGoroutine() |
|
172 |
- } |
|
157 |
+ out := srv.DockerInfo() |
|
173 | 158 |
b, err := json.Marshal(out) |
174 | 159 |
if err != nil { |
175 | 160 |
http.Error(w, err.Error(), http.StatusInternalServerError) |
... | ... |
@@ -182,22 +122,10 @@ func ListenAndServe(addr string, rtime *Runtime) error { |
182 | 182 |
log.Println(r.Method, r.RequestURI) |
183 | 183 |
vars := mux.Vars(r) |
184 | 184 |
name := vars["name"] |
185 |
- |
|
186 |
- image, err := rtime.repositories.LookupImage(name) |
|
185 |
+ outs, err := srv.ImageHistory(name) |
|
187 | 186 |
if err != nil { |
188 |
- http.Error(w, err.Error(), http.StatusInternalServerError) |
|
189 |
- return |
|
187 |
+ httpError(w, err) |
|
190 | 188 |
} |
191 |
- |
|
192 |
- var outs []ApiHistory = []ApiHistory{} //produce [] when empty instead of 'null' |
|
193 |
- err = image.WalkHistory(func(img *Image) error { |
|
194 |
- var out ApiHistory |
|
195 |
- out.Id = rtime.repositories.ImageName(img.ShortId()) |
|
196 |
- out.Created = img.Created.Unix() |
|
197 |
- out.CreatedBy = strings.Join(img.ContainerConfig.Cmd, " ") |
|
198 |
- return nil |
|
199 |
- }) |
|
200 |
- |
|
201 | 189 |
b, err := json.Marshal(outs) |
202 | 190 |
if err != nil { |
203 | 191 |
http.Error(w, err.Error(), http.StatusInternalServerError) |
... | ... |
@@ -210,25 +138,15 @@ func ListenAndServe(addr string, rtime *Runtime) error { |
210 | 210 |
log.Println(r.Method, r.RequestURI) |
211 | 211 |
vars := mux.Vars(r) |
212 | 212 |
name := vars["name"] |
213 |
- |
|
214 |
- if container := rtime.Get(name); container != nil { |
|
215 |
- changes, err := container.Changes() |
|
216 |
- if err != nil { |
|
217 |
- http.Error(w, err.Error(), http.StatusInternalServerError) |
|
218 |
- return |
|
219 |
- } |
|
220 |
- var changesStr []string |
|
221 |
- for _, name := range changes { |
|
222 |
- changesStr = append(changesStr, name.String()) |
|
223 |
- } |
|
224 |
- b, err := json.Marshal(changesStr) |
|
225 |
- if err != nil { |
|
226 |
- http.Error(w, err.Error(), http.StatusInternalServerError) |
|
227 |
- } else { |
|
228 |
- w.Write(b) |
|
229 |
- } |
|
213 |
+ changesStr, err := srv.ContainerChanges(name) |
|
214 |
+ if err != nil { |
|
215 |
+ httpError(w, err) |
|
216 |
+ } |
|
217 |
+ b, err := json.Marshal(changesStr) |
|
218 |
+ if err != nil { |
|
219 |
+ http.Error(w, err.Error(), http.StatusInternalServerError) |
|
230 | 220 |
} else { |
231 |
- http.Error(w, "No such container: "+name, http.StatusNotFound) |
|
221 |
+ w.Write(b) |
|
232 | 222 |
} |
233 | 223 |
}) |
234 | 224 |
|
... | ... |
@@ -237,99 +155,36 @@ func ListenAndServe(addr string, rtime *Runtime) error { |
237 | 237 |
if err := r.ParseForm(); err != nil { |
238 | 238 |
http.Error(w, err.Error(), http.StatusInternalServerError) |
239 | 239 |
} |
240 |
- privatePort := r.Form.Get("port") |
|
241 | 240 |
vars := mux.Vars(r) |
242 | 241 |
name := vars["name"] |
243 |
- |
|
244 |
- if container := rtime.Get(name); container == nil { |
|
245 |
- http.Error(w, "No such container: "+name, http.StatusNotFound) |
|
246 |
- return |
|
247 |
- } else { |
|
248 |
- if frontend, exists := container.NetworkSettings.PortMapping[privatePort]; !exists { |
|
249 |
- http.Error(w, "No private port '"+privatePort+"' allocated on "+name, http.StatusInternalServerError) |
|
250 |
- return |
|
251 |
- } else { |
|
252 |
- b, err := json.Marshal(ApiPort{frontend}) |
|
253 |
- if err != nil { |
|
254 |
- http.Error(w, err.Error(), http.StatusInternalServerError) |
|
255 |
- } else { |
|
256 |
- w.Write(b) |
|
257 |
- } |
|
258 |
- } |
|
259 |
- } |
|
260 |
- }) |
|
261 |
- |
|
262 |
- r.Path("/containers").Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
|
263 |
- log.Println(r.Method, r.RequestURI) |
|
264 |
- if err := r.ParseForm(); err != nil { |
|
265 |
- http.Error(w, err.Error(), http.StatusInternalServerError) |
|
266 |
- } |
|
267 |
- All := r.Form.Get("all") |
|
268 |
- NoTrunc := r.Form.Get("notrunc") |
|
269 |
- Quiet := r.Form.Get("quiet") |
|
270 |
- Last := r.Form.Get("n") |
|
271 |
- n, err := strconv.Atoi(Last) |
|
242 |
+ out, err := srv.ContainerPort(name, r.Form.Get("port")) |
|
272 | 243 |
if err != nil { |
273 |
- n = -1 |
|
274 |
- } |
|
275 |
- var outs []ApiContainers = []ApiContainers{} //produce [] when empty instead of 'null' |
|
276 |
- for i, container := range rtime.List() { |
|
277 |
- if !container.State.Running && All != "1" && n == -1 { |
|
278 |
- continue |
|
279 |
- } |
|
280 |
- if i == n { |
|
281 |
- break |
|
282 |
- } |
|
283 |
- var out ApiContainers |
|
284 |
- out.Id = container.ShortId() |
|
285 |
- if Quiet != "1" { |
|
286 |
- command := fmt.Sprintf("%s %s", container.Path, strings.Join(container.Args, " ")) |
|
287 |
- if NoTrunc != "1" { |
|
288 |
- command = Trunc(command, 20) |
|
289 |
- } |
|
290 |
- out.Image = rtime.repositories.ImageName(container.Image) |
|
291 |
- out.Command = command |
|
292 |
- out.Created = container.Created.Unix() |
|
293 |
- out.Status = container.State.String() |
|
294 |
- out.Ports = container.NetworkSettings.PortMappingHuman() |
|
295 |
- } |
|
296 |
- outs = append(outs, out) |
|
244 |
+ httpError(w, err) |
|
297 | 245 |
} |
298 |
- |
|
299 |
- b, err := json.Marshal(outs) |
|
246 |
+ b, err := json.Marshal(ApiPort{out}) |
|
300 | 247 |
if err != nil { |
301 | 248 |
http.Error(w, err.Error(), http.StatusInternalServerError) |
302 | 249 |
} else { |
303 | 250 |
w.Write(b) |
304 | 251 |
} |
252 |
+ |
|
305 | 253 |
}) |
306 | 254 |
|
307 |
- r.Path("/containers/{name:.*}/commit").Methods("POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
|
255 |
+ r.Path("/containers").Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
|
308 | 256 |
log.Println(r.Method, r.RequestURI) |
309 |
- var config Config |
|
310 |
- if err := json.NewDecoder(r.Body).Decode(&config); err != nil { |
|
311 |
- http.Error(w, err.Error(), http.StatusInternalServerError) |
|
312 |
- return |
|
313 |
- } |
|
314 |
- |
|
315 | 257 |
if err := r.ParseForm(); err != nil { |
316 | 258 |
http.Error(w, err.Error(), http.StatusInternalServerError) |
317 |
- return |
|
318 | 259 |
} |
319 |
- vars := mux.Vars(r) |
|
320 |
- name := vars["name"] |
|
321 |
- repo := r.Form.Get("repo") |
|
322 |
- tag := r.Form.Get("tag") |
|
323 |
- author := r.Form.Get("author") |
|
324 |
- comment := r.Form.Get("comment") |
|
325 |
- |
|
326 |
- img, err := rtime.Commit(name, repo, tag, comment, author, &config) |
|
260 |
+ all := r.Form.Get("all") |
|
261 |
+ notrunc := r.Form.Get("notrunc") |
|
262 |
+ quiet := r.Form.Get("quiet") |
|
263 |
+ n, err := strconv.Atoi(r.Form.Get("n")) |
|
327 | 264 |
if err != nil { |
328 |
- http.Error(w, err.Error(), http.StatusInternalServerError) |
|
329 |
- return |
|
265 |
+ n = -1 |
|
330 | 266 |
} |
331 | 267 |
|
332 |
- b, err := json.Marshal(ApiId{img.ShortId()}) |
|
268 |
+ outs := srv.Containers(all, notrunc, quiet, n) |
|
269 |
+ b, err := json.Marshal(outs) |
|
333 | 270 |
if err != nil { |
334 | 271 |
http.Error(w, err.Error(), http.StatusInternalServerError) |
335 | 272 |
} else { |
... | ... |
@@ -342,121 +197,79 @@ func ListenAndServe(addr string, rtime *Runtime) error { |
342 | 342 |
if err := r.ParseForm(); err != nil { |
343 | 343 |
http.Error(w, err.Error(), http.StatusInternalServerError) |
344 | 344 |
} |
345 |
- vars := mux.Vars(r) |
|
346 |
- name := vars["name"] |
|
347 | 345 |
repo := r.Form.Get("repo") |
348 | 346 |
tag := r.Form.Get("tag") |
347 |
+ vars := mux.Vars(r) |
|
348 |
+ name := vars["name"] |
|
349 | 349 |
var force bool |
350 | 350 |
if r.Form.Get("force") == "1" { |
351 | 351 |
force = true |
352 | 352 |
} |
353 | 353 |
|
354 |
- if err := rtime.repositories.Set(repo, tag, name, force); err != nil { |
|
354 |
+ if err := srv.ContainerTag(name, repo, tag, force); err != nil { |
|
355 | 355 |
http.Error(w, err.Error(), http.StatusInternalServerError) |
356 | 356 |
return |
357 | 357 |
} |
358 | 358 |
w.WriteHeader(http.StatusCreated) |
359 | 359 |
}) |
360 | 360 |
|
361 |
- r.Path("/images/{name:.*}/pull").Methods("POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
|
362 |
- log.Println(r.Method, r.RequestURI) |
|
363 |
- vars := mux.Vars(r) |
|
364 |
- name := vars["name"] |
|
365 |
- |
|
366 |
- conn, _, err := w.(http.Hijacker).Hijack() |
|
367 |
- if err != nil { |
|
368 |
- http.Error(w, err.Error(), http.StatusInternalServerError) |
|
369 |
- return |
|
370 |
- } |
|
371 |
- defer conn.Close() |
|
372 |
- file, err := conn.(*net.TCPConn).File() |
|
373 |
- if err != nil { |
|
374 |
- http.Error(w, err.Error(), http.StatusInternalServerError) |
|
375 |
- return |
|
376 |
- } |
|
377 |
- defer file.Close() |
|
378 |
- |
|
379 |
- fmt.Fprintln(file, "HTTP/1.1 200 OK\r\nContent-Type: raw-stream-hijack\r\n\r\n") |
|
380 |
- if rtime.graph.LookupRemoteImage(name, rtime.authConfig) { |
|
381 |
- if err := rtime.graph.PullImage(file, name, rtime.authConfig); err != nil { |
|
382 |
- fmt.Fprintln(file, "Error: "+err.Error()) |
|
383 |
- } |
|
384 |
- return |
|
385 |
- } |
|
386 |
- if err := rtime.graph.PullRepository(file, name, "", rtime.repositories, rtime.authConfig); err != nil { |
|
387 |
- fmt.Fprintln(file, "Error: "+err.Error()) |
|
388 |
- } |
|
389 |
- }) |
|
390 |
- |
|
391 |
- /* /!\ W.I.P /!\ */ |
|
392 | 361 |
r.Path("/images").Methods("POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
393 | 362 |
log.Println(r.Method, r.RequestURI) |
394 | 363 |
if err := r.ParseForm(); err != nil { |
395 | 364 |
http.Error(w, err.Error(), http.StatusInternalServerError) |
396 | 365 |
} |
397 |
- src := r.Form.Get("src") |
|
366 |
+ |
|
367 |
+ src := r.Form.Get("fromSrc") |
|
368 |
+ image := r.Form.Get("fromImage") |
|
369 |
+ container := r.Form.Get("fromContainer") |
|
398 | 370 |
repo := r.Form.Get("repo") |
399 | 371 |
tag := r.Form.Get("tag") |
400 | 372 |
|
401 |
- var archive io.Reader |
|
402 |
- var resp *http.Response |
|
373 |
+ if container != "" { //commit |
|
374 |
+ var config Config |
|
375 |
+ if err := json.NewDecoder(r.Body).Decode(&config); err != nil { |
|
376 |
+ http.Error(w, err.Error(), http.StatusInternalServerError) |
|
377 |
+ return |
|
378 |
+ } |
|
379 |
+ author := r.Form.Get("author") |
|
380 |
+ comment := r.Form.Get("comment") |
|
403 | 381 |
|
404 |
- conn, _, err := w.(http.Hijacker).Hijack() |
|
405 |
- if err != nil { |
|
406 |
- http.Error(w, err.Error(), http.StatusInternalServerError) |
|
407 |
- return |
|
408 |
- } |
|
409 |
- defer conn.Close() |
|
410 |
- file, err := conn.(*net.TCPConn).File() |
|
411 |
- if err != nil { |
|
412 |
- http.Error(w, err.Error(), http.StatusInternalServerError) |
|
413 |
- return |
|
414 |
- } |
|
415 |
- defer file.Close() |
|
416 |
- |
|
417 |
- fmt.Fprintln(file, "HTTP/1.1 201 Created\r\nContent-Type: application/json\r\n\r\n") |
|
418 |
- if src == "-" { |
|
419 |
- r, w := io.Pipe() |
|
420 |
- go func() { |
|
421 |
- defer w.Close() |
|
422 |
- defer Debugf("Closing buffered stdin pipe") |
|
423 |
- io.Copy(w, file) |
|
424 |
- }() |
|
425 |
- archive = r |
|
426 |
- } else { |
|
427 |
- u, err := url.Parse(src) |
|
382 |
+ id, err := srv.ContainerCommit(container, repo, tag, author, comment, &config) |
|
428 | 383 |
if err != nil { |
429 |
- fmt.Fprintln(file, "Error: "+err.Error()) |
|
384 |
+ httpError(w, err) |
|
430 | 385 |
} |
431 |
- if u.Scheme == "" { |
|
432 |
- u.Scheme = "http" |
|
433 |
- u.Host = src |
|
434 |
- u.Path = "" |
|
435 |
- } |
|
436 |
- fmt.Fprintln(file, "Downloading from", u) |
|
437 |
- // Download with curl (pretty progress bar) |
|
438 |
- // If curl is not available, fallback to http.Get() |
|
439 |
- resp, err = Download(u.String(), file) |
|
386 |
+ b, err := json.Marshal(ApiId{id}) |
|
440 | 387 |
if err != nil { |
441 | 388 |
http.Error(w, err.Error(), http.StatusInternalServerError) |
442 |
- return |
|
389 |
+ } else { |
|
390 |
+ w.Write(b) |
|
443 | 391 |
} |
444 |
- archive = ProgressReader(resp.Body, int(resp.ContentLength), file, "Importing %v/%v (%v)") |
|
445 |
- } |
|
446 |
- img, err := rtime.graph.Create(archive, nil, "Imported from "+src, "", nil) |
|
447 |
- if err != nil { |
|
448 |
- http.Error(w, err.Error(), http.StatusInternalServerError) |
|
449 |
- return |
|
450 |
- } |
|
451 |
- // Optionally register the image at REPO/TAG |
|
452 |
- if repo != "" { |
|
453 |
- if err := rtime.repositories.Set(repo, tag, img.Id, true); err != nil { |
|
454 |
- http.Error(w, err.Error(), http.StatusInternalServerError) |
|
392 |
+ } else if image != "" || src != "" { |
|
393 |
+ file, rwc, err := hijackServer(w) |
|
394 |
+ if file != nil { |
|
395 |
+ defer file.Close() |
|
396 |
+ } |
|
397 |
+ if rwc != nil { |
|
398 |
+ defer rwc.Close() |
|
399 |
+ } |
|
400 |
+ if err != nil { |
|
401 |
+ httpError(w, err) |
|
455 | 402 |
return |
456 | 403 |
} |
457 |
- } |
|
404 |
+ fmt.Fprintf(file, "HTTP/1.1 200 OK\r\nContent-Type: raw-stream-hijack\r\n\r\n") |
|
458 | 405 |
|
459 |
- fmt.Fprintln(file, img.ShortId()) |
|
406 |
+ if image != "" { //pull |
|
407 |
+ if err := srv.ImagePull(image, file); err != nil { |
|
408 |
+ fmt.Fprintln(file, "Error: "+err.Error()) |
|
409 |
+ } |
|
410 |
+ } else { //import |
|
411 |
+ if err := srv.ImageImport(src, repo, tag, file); err != nil { |
|
412 |
+ fmt.Fprintln(file, "Error: "+err.Error()) |
|
413 |
+ } |
|
414 |
+ } |
|
415 |
+ } else { |
|
416 |
+ w.WriteHeader(http.StatusNotFound) |
|
417 |
+ } |
|
460 | 418 |
}) |
461 | 419 |
|
462 | 420 |
r.Path("/containers").Methods("POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
... | ... |
@@ -466,37 +279,19 @@ func ListenAndServe(addr string, rtime *Runtime) error { |
466 | 466 |
http.Error(w, err.Error(), http.StatusInternalServerError) |
467 | 467 |
return |
468 | 468 |
} |
469 |
- var memoryW, swapW bool |
|
470 |
- |
|
471 |
- if config.Memory > 0 && !rtime.capabilities.MemoryLimit { |
|
472 |
- memoryW = true |
|
473 |
- log.Println("WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.") |
|
474 |
- config.Memory = 0 |
|
475 |
- } |
|
476 |
- |
|
477 |
- if config.Memory > 0 && !rtime.capabilities.SwapLimit { |
|
478 |
- swapW = true |
|
479 |
- log.Println("WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.") |
|
480 |
- config.MemorySwap = -1 |
|
481 |
- } |
|
482 |
- container, err := rtime.Create(&config) |
|
469 |
+ id, memoryW, swapW, err := srv.ContainerCreate(config) |
|
483 | 470 |
if err != nil { |
484 |
- if rtime.graph.IsNotExist(err) { |
|
485 |
- http.Error(w, "No such image: "+config.Image, http.StatusNotFound) |
|
486 |
- } else { |
|
487 |
- http.Error(w, err.Error(), http.StatusInternalServerError) |
|
488 |
- } |
|
471 |
+ httpError(w, err) |
|
489 | 472 |
return |
490 | 473 |
} |
491 | 474 |
var out ApiRun |
492 |
- out.Id = container.ShortId() |
|
475 |
+ out.Id = id |
|
493 | 476 |
if memoryW { |
494 | 477 |
out.Warnings = append(out.Warnings, "Your kernel does not support memory limit capabilities. Limitation discarded.") |
495 | 478 |
} |
496 | 479 |
if swapW { |
497 | 480 |
out.Warnings = append(out.Warnings, "Your kernel does not support memory swap capabilities. Limitation discarded.") |
498 | 481 |
} |
499 |
- |
|
500 | 482 |
b, err := json.Marshal(out) |
501 | 483 |
if err != nil { |
502 | 484 |
http.Error(w, err.Error(), http.StatusInternalServerError) |
... | ... |
@@ -516,66 +311,44 @@ func ListenAndServe(addr string, rtime *Runtime) error { |
516 | 516 |
} |
517 | 517 |
vars := mux.Vars(r) |
518 | 518 |
name := vars["name"] |
519 |
- if container := rtime.Get(name); container != nil { |
|
520 |
- if err := container.Restart(t); err != nil { |
|
521 |
- http.Error(w, "Error restarting container "+name+": "+err.Error(), http.StatusInternalServerError) |
|
522 |
- return |
|
523 |
- } |
|
519 |
+ if err := srv.ContainerRestart(name, t); err != nil { |
|
520 |
+ httpError(w, err) |
|
524 | 521 |
} else { |
525 |
- http.Error(w, "No such container: "+name, http.StatusNotFound) |
|
526 |
- return |
|
522 |
+ w.WriteHeader(http.StatusOK) |
|
527 | 523 |
} |
528 |
- w.WriteHeader(http.StatusOK) |
|
529 | 524 |
}) |
530 | 525 |
|
531 | 526 |
r.Path("/containers/{name:.*}").Methods("DELETE").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
532 | 527 |
log.Println(r.Method, r.RequestURI) |
533 | 528 |
vars := mux.Vars(r) |
534 | 529 |
name := vars["name"] |
535 |
- if container := rtime.Get(name); container != nil { |
|
536 |
- if err := rtime.Destroy(container); err != nil { |
|
537 |
- http.Error(w, "Error destroying container "+name+": "+err.Error(), http.StatusInternalServerError) |
|
538 |
- return |
|
539 |
- } |
|
530 |
+ if err := srv.ContainerDestroy(name); err != nil { |
|
531 |
+ httpError(w, err) |
|
540 | 532 |
} else { |
541 |
- http.Error(w, "No such container: "+name, http.StatusNotFound) |
|
542 |
- return |
|
533 |
+ w.WriteHeader(http.StatusOK) |
|
543 | 534 |
} |
544 |
- w.WriteHeader(http.StatusOK) |
|
545 | 535 |
}) |
546 | 536 |
|
547 | 537 |
r.Path("/images/{name:.*}").Methods("DELETE").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
548 | 538 |
log.Println(r.Method, r.RequestURI) |
549 | 539 |
vars := mux.Vars(r) |
550 | 540 |
name := vars["name"] |
551 |
- |
|
552 |
- img, err := rtime.repositories.LookupImage(name) |
|
553 |
- if err != nil { |
|
554 |
- http.Error(w, "No such image: "+name, http.StatusNotFound) |
|
555 |
- return |
|
541 |
+ if err := srv.ImageDelete(name); err != nil { |
|
542 |
+ httpError(w, err) |
|
556 | 543 |
} else { |
557 |
- if err := rtime.graph.Delete(img.Id); err != nil { |
|
558 |
- http.Error(w, "Error deleting image "+name+": "+err.Error(), http.StatusInternalServerError) |
|
559 |
- return |
|
560 |
- } |
|
544 |
+ w.WriteHeader(http.StatusOK) |
|
561 | 545 |
} |
562 |
- w.WriteHeader(http.StatusOK) |
|
563 | 546 |
}) |
564 | 547 |
|
565 | 548 |
r.Path("/containers/{name:.*}/start").Methods("POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
566 | 549 |
log.Println(r.Method, r.RequestURI) |
567 | 550 |
vars := mux.Vars(r) |
568 | 551 |
name := vars["name"] |
569 |
- if container := rtime.Get(name); container != nil { |
|
570 |
- if err := container.Start(); err != nil { |
|
571 |
- http.Error(w, "Error starting container "+name+": "+err.Error(), http.StatusInternalServerError) |
|
572 |
- return |
|
573 |
- } |
|
552 |
+ if err := srv.ContainerStart(name); err != nil { |
|
553 |
+ httpError(w, err) |
|
574 | 554 |
} else { |
575 |
- http.Error(w, "No such container: "+name, http.StatusNotFound) |
|
576 |
- return |
|
555 |
+ w.WriteHeader(http.StatusOK) |
|
577 | 556 |
} |
578 |
- w.WriteHeader(http.StatusOK) |
|
579 | 557 |
}) |
580 | 558 |
|
581 | 559 |
r.Path("/containers/{name:.*}/stop").Methods("POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
... | ... |
@@ -589,33 +362,27 @@ func ListenAndServe(addr string, rtime *Runtime) error { |
589 | 589 |
} |
590 | 590 |
vars := mux.Vars(r) |
591 | 591 |
name := vars["name"] |
592 |
- if container := rtime.Get(name); container != nil { |
|
593 |
- if err := container.Stop(t); err != nil { |
|
594 |
- http.Error(w, "Error stopping container "+name+": "+err.Error(), http.StatusInternalServerError) |
|
595 |
- return |
|
596 |
- } |
|
592 |
+ |
|
593 |
+ if err := srv.ContainerStop(name, t); err != nil { |
|
594 |
+ httpError(w, err) |
|
597 | 595 |
} else { |
598 |
- http.Error(w, "No such container: "+name, http.StatusNotFound) |
|
599 |
- return |
|
596 |
+ w.WriteHeader(http.StatusOK) |
|
600 | 597 |
} |
601 |
- w.WriteHeader(http.StatusOK) |
|
602 | 598 |
}) |
603 | 599 |
|
604 | 600 |
r.Path("/containers/{name:.*}/wait").Methods("POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
605 | 601 |
log.Println(r.Method, r.RequestURI) |
606 | 602 |
vars := mux.Vars(r) |
607 | 603 |
name := vars["name"] |
608 |
- if container := rtime.Get(name); container != nil { |
|
609 |
- b, err := json.Marshal(ApiWait{container.Wait()}) |
|
610 |
- if err != nil { |
|
611 |
- http.Error(w, err.Error(), http.StatusInternalServerError) |
|
612 |
- } else { |
|
613 |
- w.Write(b) |
|
614 |
- } |
|
615 |
- return |
|
604 |
+ status, err := srv.ContainerWait(name) |
|
605 |
+ if err != nil { |
|
606 |
+ httpError(w, err) |
|
607 |
+ } |
|
608 |
+ b, err := json.Marshal(ApiWait{status}) |
|
609 |
+ if err != nil { |
|
610 |
+ http.Error(w, err.Error(), http.StatusInternalServerError) |
|
616 | 611 |
} else { |
617 |
- http.Error(w, "No such container: "+name, http.StatusNotFound) |
|
618 |
- return |
|
612 |
+ w.Write(b) |
|
619 | 613 |
} |
620 | 614 |
}) |
621 | 615 |
|
... | ... |
@@ -632,90 +399,21 @@ func ListenAndServe(addr string, rtime *Runtime) error { |
632 | 632 |
vars := mux.Vars(r) |
633 | 633 |
name := vars["name"] |
634 | 634 |
|
635 |
- if container := rtime.Get(name); container != nil { |
|
636 |
- conn, _, err := w.(http.Hijacker).Hijack() |
|
637 |
- if err != nil { |
|
638 |
- http.Error(w, err.Error(), http.StatusInternalServerError) |
|
639 |
- return |
|
640 |
- } |
|
641 |
- defer conn.Close() |
|
642 |
- file, err := conn.(*net.TCPConn).File() |
|
643 |
- if err != nil { |
|
644 |
- http.Error(w, err.Error(), http.StatusInternalServerError) |
|
645 |
- return |
|
646 |
- } |
|
635 |
+ file, rwc, err := hijackServer(w) |
|
636 |
+ if file != nil { |
|
647 | 637 |
defer file.Close() |
638 |
+ } |
|
639 |
+ if rwc != nil { |
|
640 |
+ defer rwc.Close() |
|
641 |
+ } |
|
642 |
+ if err != nil { |
|
643 |
+ httpError(w, err) |
|
644 |
+ return |
|
645 |
+ } |
|
648 | 646 |
|
649 |
- // Flush the options to make sure the client sets the raw mode |
|
650 |
- conn.Write([]byte{}) |
|
651 |
- |
|
652 |
- fmt.Fprintln(file, "HTTP/1.1 200 OK\r\nContent-Type: raw-stream-hijack\r\n\r\n") |
|
653 |
- //logs |
|
654 |
- if logs == "1" { |
|
655 |
- if stdout == "1" { |
|
656 |
- cLog, err := container.ReadLog("stdout") |
|
657 |
- if err != nil { |
|
658 |
- Debugf(err.Error()) |
|
659 |
- } else if _, err := io.Copy(file, cLog); err != nil { |
|
660 |
- Debugf(err.Error()) |
|
661 |
- } |
|
662 |
- } |
|
663 |
- if stderr == "1" { |
|
664 |
- cLog, err := container.ReadLog("stderr") |
|
665 |
- if err != nil { |
|
666 |
- Debugf(err.Error()) |
|
667 |
- } else if _, err := io.Copy(file, cLog); err != nil { |
|
668 |
- Debugf(err.Error()) |
|
669 |
- } |
|
670 |
- } |
|
671 |
- } |
|
672 |
- |
|
673 |
- //stream |
|
674 |
- if stream == "1" { |
|
675 |
- |
|
676 |
- if container.State.Ghost { |
|
677 |
- fmt.Fprintf(file, "error: Impossible to attach to a ghost container") |
|
678 |
- return |
|
679 |
- } |
|
680 |
- |
|
681 |
- if container.Config.Tty { |
|
682 |
- oldState, err := SetRawTerminal() |
|
683 |
- if err != nil { |
|
684 |
- if os.Getenv("DEBUG") != "" { |
|
685 |
- log.Printf("Can't set the terminal in raw mode: %s", err) |
|
686 |
- } |
|
687 |
- } else { |
|
688 |
- defer RestoreTerminal(oldState) |
|
689 |
- } |
|
690 |
- |
|
691 |
- } |
|
692 |
- var ( |
|
693 |
- cStdin io.ReadCloser |
|
694 |
- cStdout, cStderr io.Writer |
|
695 |
- cStdinCloser io.Closer |
|
696 |
- ) |
|
697 |
- |
|
698 |
- if stdin == "1" { |
|
699 |
- r, w := io.Pipe() |
|
700 |
- go func() { |
|
701 |
- defer w.Close() |
|
702 |
- defer Debugf("Closing buffered stdin pipe") |
|
703 |
- io.Copy(w, file) |
|
704 |
- }() |
|
705 |
- cStdin = r |
|
706 |
- cStdinCloser = file |
|
707 |
- } |
|
708 |
- if stdout == "1" { |
|
709 |
- cStdout = file |
|
710 |
- } |
|
711 |
- if stderr == "1" { |
|
712 |
- cStderr = file |
|
713 |
- } |
|
714 |
- |
|
715 |
- <-container.Attach(cStdin, cStdinCloser, cStdout, cStderr) |
|
716 |
- } |
|
717 |
- } else { |
|
718 |
- http.Error(w, "No such container: "+name, http.StatusNotFound) |
|
647 |
+ fmt.Fprintf(file, "HTTP/1.1 200 OK\r\nContent-Type: raw-stream-hijack\r\n\r\n") |
|
648 |
+ if err := srv.ContainerAttach(name, logs, stream, stdin, stdout, stderr, file); err != nil { |
|
649 |
+ fmt.Fprintln(file, "Error: "+err.Error()) |
|
719 | 650 |
} |
720 | 651 |
}) |
721 | 652 |
|
... | ... |
@@ -724,16 +422,16 @@ func ListenAndServe(addr string, rtime *Runtime) error { |
724 | 724 |
vars := mux.Vars(r) |
725 | 725 |
name := vars["name"] |
726 | 726 |
|
727 |
- if container := rtime.Get(name); container != nil { |
|
728 |
- b, err := json.Marshal(container) |
|
729 |
- if err != nil { |
|
730 |
- http.Error(w, err.Error(), http.StatusInternalServerError) |
|
731 |
- } else { |
|
732 |
- w.Write(b) |
|
733 |
- } |
|
734 |
- return |
|
727 |
+ container, err := srv.ContainerInspect(name) |
|
728 |
+ if err != nil { |
|
729 |
+ httpError(w, err) |
|
730 |
+ } |
|
731 |
+ b, err := json.Marshal(container) |
|
732 |
+ if err != nil { |
|
733 |
+ http.Error(w, err.Error(), http.StatusInternalServerError) |
|
734 |
+ } else { |
|
735 |
+ w.Write(b) |
|
735 | 736 |
} |
736 |
- http.Error(w, "No such container: "+name, http.StatusNotFound) |
|
737 | 737 |
}) |
738 | 738 |
|
739 | 739 |
r.Path("/images/{name:.*}").Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
... | ... |
@@ -741,16 +439,16 @@ func ListenAndServe(addr string, rtime *Runtime) error { |
741 | 741 |
vars := mux.Vars(r) |
742 | 742 |
name := vars["name"] |
743 | 743 |
|
744 |
- if image, err := rtime.repositories.LookupImage(name); err == nil && image != nil { |
|
745 |
- b, err := json.Marshal(image) |
|
746 |
- if err != nil { |
|
747 |
- http.Error(w, err.Error(), http.StatusInternalServerError) |
|
748 |
- } else { |
|
749 |
- w.Write(b) |
|
750 |
- } |
|
751 |
- return |
|
744 |
+ image, err := srv.ImageInspect(name) |
|
745 |
+ if err != nil { |
|
746 |
+ httpError(w, err) |
|
747 |
+ } |
|
748 |
+ b, err := json.Marshal(image) |
|
749 |
+ if err != nil { |
|
750 |
+ http.Error(w, err.Error(), http.StatusInternalServerError) |
|
751 |
+ } else { |
|
752 |
+ w.Write(b) |
|
752 | 753 |
} |
753 |
- http.Error(w, "No such image: "+name, http.StatusNotFound) |
|
754 | 754 |
}) |
755 | 755 |
|
756 | 756 |
return http.ListenAndServe(addr, r) |
... | ... |
@@ -24,9 +24,30 @@ var ( |
24 | 24 |
GIT_COMMIT string |
25 | 25 |
) |
26 | 26 |
|
27 |
-func ParseCommands(args []string) error { |
|
27 |
+func checkRemoteVersion() error { |
|
28 |
+ body, _, err := call("GET", "/version", nil) |
|
29 |
+ if err != nil { |
|
30 |
+ return fmt.Errorf("Can't connect to docker daemon. Is 'docker -d' running on this host?") |
|
31 |
+ } |
|
32 |
+ |
|
33 |
+ var out ApiVersion |
|
34 |
+ err = json.Unmarshal(body, &out) |
|
35 |
+ if err != nil { |
|
36 |
+ return err |
|
37 |
+ } |
|
38 |
+ if out.Version != VERSION { |
|
39 |
+ fmt.Fprintf(os.Stderr, "Warning: client and server don't have the same version (client: %s, server: %)", VERSION, out.Version) |
|
40 |
+ } |
|
41 |
+ return nil |
|
42 |
+} |
|
43 |
+ |
|
44 |
+func ParseCommands(args ...string) error { |
|
45 |
+ |
|
46 |
+ if err := checkRemoteVersion(); err != nil { |
|
47 |
+ return err |
|
48 |
+ } |
|
28 | 49 |
|
29 |
- cmds := map[string]func(args []string) error{ |
|
50 |
+ cmds := map[string]func(args ...string) error{ |
|
30 | 51 |
"attach": CmdAttach, |
31 | 52 |
"commit": CmdCommit, |
32 | 53 |
"diff": CmdDiff, |
... | ... |
@@ -34,7 +55,7 @@ func ParseCommands(args []string) error { |
34 | 34 |
"images": CmdImages, |
35 | 35 |
"info": CmdInfo, |
36 | 36 |
"inspect": CmdInspect, |
37 |
- //"import": CmdImport, |
|
37 |
+ "import": CmdImport, |
|
38 | 38 |
"history": CmdHistory, |
39 | 39 |
"kill": CmdKill, |
40 | 40 |
"logs": CmdLogs, |
... | ... |
@@ -56,14 +77,14 @@ func ParseCommands(args []string) error { |
56 | 56 |
cmd, exists := cmds[args[0]] |
57 | 57 |
if !exists { |
58 | 58 |
fmt.Println("Error: Command not found:", args[0]) |
59 |
- return cmdHelp(args) |
|
59 |
+ return cmdHelp(args...) |
|
60 | 60 |
} |
61 |
- return cmd(args[1:]) |
|
61 |
+ return cmd(args[1:]...) |
|
62 | 62 |
} |
63 |
- return cmdHelp(args) |
|
63 |
+ return cmdHelp(args...) |
|
64 | 64 |
} |
65 | 65 |
|
66 |
-func cmdHelp(args []string) error { |
|
66 |
+func cmdHelp(args ...string) error { |
|
67 | 67 |
help := "Usage: docker COMMAND [arg...]\n\nA self-sufficient runtime for linux containers.\n\nCommands:\n" |
68 | 68 |
for _, cmd := range [][]string{ |
69 | 69 |
{"attach", "Attach to a running container"}, |
... | ... |
@@ -72,7 +93,7 @@ func cmdHelp(args []string) error { |
72 | 72 |
{"export", "Stream the contents of a container as a tar archive"}, |
73 | 73 |
{"history", "Show the history of an image"}, |
74 | 74 |
{"images", "List images"}, |
75 |
- //{"import", "Create a new filesystem image from the contents of a tarball"}, |
|
75 |
+ {"import", "Create a new filesystem image from the contents of a tarball"}, |
|
76 | 76 |
{"info", "Display system-wide information"}, |
77 | 77 |
{"inspect", "Return low-level information on a container/image"}, |
78 | 78 |
{"kill", "Kill a running container"}, |
... | ... |
@@ -199,7 +220,7 @@ func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout rcli.DockerConn, args .. |
199 | 199 |
*/ |
200 | 200 |
|
201 | 201 |
// 'docker wait': block until a container stops |
202 |
-func CmdWait(args []string) error { |
|
202 |
+func CmdWait(args ...string) error { |
|
203 | 203 |
cmd := Subcmd("wait", "CONTAINER [CONTAINER...]", "Block until a container stops, then print its exit code.") |
204 | 204 |
if err := cmd.Parse(args); err != nil { |
205 | 205 |
return nil |
... | ... |
@@ -225,7 +246,7 @@ func CmdWait(args []string) error { |
225 | 225 |
} |
226 | 226 |
|
227 | 227 |
// 'docker version': show version information |
228 |
-func CmdVersion(args []string) error { |
|
228 |
+func CmdVersion(args ...string) error { |
|
229 | 229 |
cmd := Subcmd("version", "", "Show the docker version information.") |
230 | 230 |
if err := cmd.Parse(args); err != nil { |
231 | 231 |
return nil |
... | ... |
@@ -258,7 +279,7 @@ func CmdVersion(args []string) error { |
258 | 258 |
} |
259 | 259 |
|
260 | 260 |
// 'docker info': display system-wide information. |
261 |
-func CmdInfo(args []string) error { |
|
261 |
+func CmdInfo(args ...string) error { |
|
262 | 262 |
cmd := Subcmd("info", "", "Display system-wide information") |
263 | 263 |
if err := cmd.Parse(args); err != nil { |
264 | 264 |
return nil |
... | ... |
@@ -286,7 +307,7 @@ func CmdInfo(args []string) error { |
286 | 286 |
return nil |
287 | 287 |
} |
288 | 288 |
|
289 |
-func CmdStop(args []string) error { |
|
289 |
+func CmdStop(args ...string) error { |
|
290 | 290 |
cmd := Subcmd("stop", "[OPTIONS] CONTAINER [CONTAINER...]", "Stop a running container") |
291 | 291 |
nSeconds := cmd.Int("t", 10, "wait t seconds before killing the container") |
292 | 292 |
if err := cmd.Parse(args); err != nil { |
... | ... |
@@ -311,7 +332,7 @@ func CmdStop(args []string) error { |
311 | 311 |
return nil |
312 | 312 |
} |
313 | 313 |
|
314 |
-func CmdRestart(args []string) error { |
|
314 |
+func CmdRestart(args ...string) error { |
|
315 | 315 |
cmd := Subcmd("restart", "[OPTIONS] CONTAINER [CONTAINER...]", "Restart a running container") |
316 | 316 |
nSeconds := cmd.Int("t", 10, "wait t seconds before killing the container") |
317 | 317 |
if err := cmd.Parse(args); err != nil { |
... | ... |
@@ -336,7 +357,7 @@ func CmdRestart(args []string) error { |
336 | 336 |
return nil |
337 | 337 |
} |
338 | 338 |
|
339 |
-func CmdStart(args []string) error { |
|
339 |
+func CmdStart(args ...string) error { |
|
340 | 340 |
cmd := Subcmd("start", "CONTAINER [CONTAINER...]", "Restart a stopped container") |
341 | 341 |
if err := cmd.Parse(args); err != nil { |
342 | 342 |
return nil |
... | ... |
@@ -357,7 +378,7 @@ func CmdStart(args []string) error { |
357 | 357 |
return nil |
358 | 358 |
} |
359 | 359 |
|
360 |
-func CmdInspect(args []string) error { |
|
360 |
+func CmdInspect(args ...string) error { |
|
361 | 361 |
cmd := Subcmd("inspect", "CONTAINER|IMAGE", "Return low-level information on a container/image") |
362 | 362 |
if err := cmd.Parse(args); err != nil { |
363 | 363 |
return nil |
... | ... |
@@ -377,7 +398,7 @@ func CmdInspect(args []string) error { |
377 | 377 |
return nil |
378 | 378 |
} |
379 | 379 |
|
380 |
-func CmdPort(args []string) error { |
|
380 |
+func CmdPort(args ...string) error { |
|
381 | 381 |
cmd := Subcmd("port", "CONTAINER PRIVATE_PORT", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT") |
382 | 382 |
if err := cmd.Parse(args); err != nil { |
383 | 383 |
return nil |
... | ... |
@@ -403,7 +424,7 @@ func CmdPort(args []string) error { |
403 | 403 |
} |
404 | 404 |
|
405 | 405 |
// 'docker rmi IMAGE' removes all images with the name IMAGE |
406 |
-func CmdRmi(args []string) error { |
|
406 |
+func CmdRmi(args ...string) error { |
|
407 | 407 |
cmd := Subcmd("rmi", "IMAGE [IMAGE...]", "Remove an image") |
408 | 408 |
if err := cmd.Parse(args); err != nil { |
409 | 409 |
return nil |
... | ... |
@@ -424,7 +445,7 @@ func CmdRmi(args []string) error { |
424 | 424 |
return nil |
425 | 425 |
} |
426 | 426 |
|
427 |
-func CmdHistory(args []string) error { |
|
427 |
+func CmdHistory(args ...string) error { |
|
428 | 428 |
cmd := Subcmd("history", "IMAGE", "Show the history of an image") |
429 | 429 |
if err := cmd.Parse(args); err != nil { |
430 | 430 |
return nil |
... | ... |
@@ -454,7 +475,7 @@ func CmdHistory(args []string) error { |
454 | 454 |
return nil |
455 | 455 |
} |
456 | 456 |
|
457 |
-func CmdRm(args []string) error { |
|
457 |
+func CmdRm(args ...string) error { |
|
458 | 458 |
cmd := Subcmd("rm", "CONTAINER [CONTAINER...]", "Remove a container") |
459 | 459 |
if err := cmd.Parse(args); err != nil { |
460 | 460 |
return nil |
... | ... |
@@ -476,7 +497,7 @@ func CmdRm(args []string) error { |
476 | 476 |
} |
477 | 477 |
|
478 | 478 |
// 'docker kill NAME' kills a running container |
479 |
-func CmdKill(args []string) error { |
|
479 |
+func CmdKill(args ...string) error { |
|
480 | 480 |
cmd := Subcmd("kill", "CONTAINER [CONTAINER...]", "Kill a running container") |
481 | 481 |
if err := cmd.Parse(args); err != nil { |
482 | 482 |
return nil |
... | ... |
@@ -498,7 +519,7 @@ func CmdKill(args []string) error { |
498 | 498 |
} |
499 | 499 |
|
500 | 500 |
/* /!\ W.I.P /!\ */ |
501 |
-func CmdImport(args []string) error { |
|
501 |
+func CmdImport(args ...string) error { |
|
502 | 502 |
cmd := Subcmd("import", "URL|- [REPOSITORY [TAG]]", "Create a new filesystem image from the contents of a tarball") |
503 | 503 |
|
504 | 504 |
if err := cmd.Parse(args); err != nil { |
... | ... |
@@ -512,7 +533,7 @@ func CmdImport(args []string) error { |
512 | 512 |
v := url.Values{} |
513 | 513 |
v.Set("repo", repository) |
514 | 514 |
v.Set("tag", tag) |
515 |
- v.Set("src", src) |
|
515 |
+ v.Set("fromSrc", src) |
|
516 | 516 |
|
517 | 517 |
err := hijack("POST", "/images?"+v.Encode(), false) |
518 | 518 |
if err != nil { |
... | ... |
@@ -582,7 +603,7 @@ func (srv *Server) CmdPush(stdin io.ReadCloser, stdout rcli.DockerConn, args ... |
582 | 582 |
} |
583 | 583 |
*/ |
584 | 584 |
|
585 |
-func CmdPull(args []string) error { |
|
585 |
+func CmdPull(args ...string) error { |
|
586 | 586 |
cmd := Subcmd("pull", "NAME", "Pull an image or a repository from the registry") |
587 | 587 |
if err := cmd.Parse(args); err != nil { |
588 | 588 |
return nil |
... | ... |
@@ -592,15 +613,17 @@ func CmdPull(args []string) error { |
592 | 592 |
cmd.Usage() |
593 | 593 |
return nil |
594 | 594 |
} |
595 |
+ v := url.Values{} |
|
596 |
+ v.Set("fromImage", cmd.Arg(0)) |
|
595 | 597 |
|
596 |
- if err := hijack("POST", "/images/"+cmd.Arg(0)+"/pull", false); err != nil { |
|
598 |
+ if err := hijack("POST", "/images?"+v.Encode(), false); err != nil { |
|
597 | 599 |
return err |
598 | 600 |
} |
599 | 601 |
|
600 | 602 |
return nil |
601 | 603 |
} |
602 | 604 |
|
603 |
-func CmdImages(args []string) error { |
|
605 |
+func CmdImages(args ...string) error { |
|
604 | 606 |
cmd := Subcmd("images", "[OPTIONS] [NAME]", "List images") |
605 | 607 |
quiet := cmd.Bool("q", false, "only show numeric IDs") |
606 | 608 |
all := cmd.Bool("a", false, "show all images") |
... | ... |
@@ -652,7 +675,7 @@ func CmdImages(args []string) error { |
652 | 652 |
return nil |
653 | 653 |
} |
654 | 654 |
|
655 |
-func CmdPs(args []string) error { |
|
655 |
+func CmdPs(args ...string) error { |
|
656 | 656 |
cmd := Subcmd("ps", "[OPTIONS]", "List containers") |
657 | 657 |
quiet := cmd.Bool("q", false, "Only display numeric IDs") |
658 | 658 |
all := cmd.Bool("a", false, "Show all containers. Only running containers are shown by default.") |
... | ... |
@@ -709,7 +732,7 @@ func CmdPs(args []string) error { |
709 | 709 |
return nil |
710 | 710 |
} |
711 | 711 |
|
712 |
-func CmdCommit(args []string) error { |
|
712 |
+func CmdCommit(args ...string) error { |
|
713 | 713 |
cmd := Subcmd("commit", "[OPTIONS] CONTAINER [REPOSITORY [TAG]]", "Create a new image from a container's changes") |
714 | 714 |
flComment := cmd.String("m", "", "Commit message") |
715 | 715 |
flAuthor := cmd.String("author", "", "Author (eg. \"John Hannibal Smith <hannibal@a-team.com>\"") |
... | ... |
@@ -724,6 +747,7 @@ func CmdCommit(args []string) error { |
724 | 724 |
} |
725 | 725 |
|
726 | 726 |
v := url.Values{} |
727 |
+ v.Set("fromContainer", name) |
|
727 | 728 |
v.Set("repo", repository) |
728 | 729 |
v.Set("tag", tag) |
729 | 730 |
v.Set("comment", *flComment) |
... | ... |
@@ -736,7 +760,7 @@ func CmdCommit(args []string) error { |
736 | 736 |
} |
737 | 737 |
} |
738 | 738 |
|
739 |
- body, _, err := call("POST", "/containers/"+name+"/commit?"+v.Encode(), config) |
|
739 |
+ body, _, err := call("POST", "/images?"+v.Encode(), config) |
|
740 | 740 |
if err != nil { |
741 | 741 |
return err |
742 | 742 |
} |
... | ... |
@@ -751,7 +775,7 @@ func CmdCommit(args []string) error { |
751 | 751 |
return nil |
752 | 752 |
} |
753 | 753 |
|
754 |
-func CmdExport(args []string) error { |
|
754 |
+func CmdExport(args ...string) error { |
|
755 | 755 |
cmd := Subcmd("export", "CONTAINER", "Export the contents of a filesystem as a tar archive") |
756 | 756 |
if err := cmd.Parse(args); err != nil { |
757 | 757 |
return nil |
... | ... |
@@ -768,7 +792,7 @@ func CmdExport(args []string) error { |
768 | 768 |
return nil |
769 | 769 |
} |
770 | 770 |
|
771 |
-func CmdDiff(args []string) error { |
|
771 |
+func CmdDiff(args ...string) error { |
|
772 | 772 |
cmd := Subcmd("diff", "CONTAINER", "Inspect changes on a container's filesystem") |
773 | 773 |
if err := cmd.Parse(args); err != nil { |
774 | 774 |
return nil |
... | ... |
@@ -794,7 +818,7 @@ func CmdDiff(args []string) error { |
794 | 794 |
return nil |
795 | 795 |
} |
796 | 796 |
|
797 |
-func CmdLogs(args []string) error { |
|
797 |
+func CmdLogs(args ...string) error { |
|
798 | 798 |
cmd := Subcmd("logs", "CONTAINER", "Fetch the logs of a container") |
799 | 799 |
if err := cmd.Parse(args); err != nil { |
800 | 800 |
return nil |
... | ... |
@@ -815,7 +839,7 @@ func CmdLogs(args []string) error { |
815 | 815 |
return nil |
816 | 816 |
} |
817 | 817 |
|
818 |
-func CmdAttach(args []string) error { |
|
818 |
+func CmdAttach(args ...string) error { |
|
819 | 819 |
cmd := Subcmd("attach", "CONTAINER", "Attach to a running container") |
820 | 820 |
if err := cmd.Parse(args); err != nil { |
821 | 821 |
return nil |
... | ... |
@@ -906,7 +930,7 @@ func (opts AttachOpts) Get(val string) bool { |
906 | 906 |
return false |
907 | 907 |
} |
908 | 908 |
|
909 |
-func CmdTag(args []string) error { |
|
909 |
+func CmdTag(args ...string) error { |
|
910 | 910 |
cmd := Subcmd("tag", "[OPTIONS] IMAGE REPOSITORY [TAG]", "Tag an image into a repository") |
911 | 911 |
force := cmd.Bool("f", false, "Force") |
912 | 912 |
if err := cmd.Parse(args); err != nil { |
... | ... |
@@ -933,7 +957,7 @@ func CmdTag(args []string) error { |
933 | 933 |
return nil |
934 | 934 |
} |
935 | 935 |
|
936 |
-func CmdRun(args []string) error { |
|
936 |
+func CmdRun(args ...string) error { |
|
937 | 937 |
config, cmd, err := ParseRun(args) |
938 | 938 |
if err != nil { |
939 | 939 |
return err |
... | ... |
@@ -952,11 +976,18 @@ func CmdRun(args []string) error { |
952 | 952 |
|
953 | 953 |
//if image not found try to pull it |
954 | 954 |
if statusCode == 404 { |
955 |
- err = hijack("POST", "/images/"+config.Image+"/pull", false) |
|
955 |
+ v := url.Values{} |
|
956 |
+ v.Set("fromImage", config.Image) |
|
957 |
+ err = hijack("POST", "/images?"+v.Encode(), false) |
|
956 | 958 |
if err != nil { |
957 | 959 |
return err |
958 | 960 |
} |
959 | 961 |
body, _, err = call("POST", "/containers", *config) |
962 |
+ if err != nil { |
|
963 |
+ return err |
|
964 |
+ } |
|
965 |
+ return nil |
|
966 |
+ |
|
960 | 967 |
} |
961 | 968 |
if err != nil { |
962 | 969 |
return err |
... | ... |
@@ -1001,6 +1032,18 @@ func CmdRun(args []string) error { |
1001 | 1001 |
if err := hijack("POST", "/containers/"+out.Id+"/attach?"+v.Encode(), config.Tty); err != nil { |
1002 | 1002 |
return err |
1003 | 1003 |
} |
1004 |
+ body, _, err := call("POST", "/containers/"+out.Id+"/wait", nil) |
|
1005 |
+ if err != nil { |
|
1006 |
+ fmt.Printf("%s", err) |
|
1007 |
+ } else { |
|
1008 |
+ var out ApiWait |
|
1009 |
+ err = json.Unmarshal(body, &out) |
|
1010 |
+ if err != nil { |
|
1011 |
+ return err |
|
1012 |
+ } |
|
1013 |
+ os.Exit(out.StatusCode) |
|
1014 |
+ } |
|
1015 |
+ |
|
1004 | 1016 |
} else { |
1005 | 1017 |
fmt.Println(out.Id) |
1006 | 1018 |
} |
... | ... |
@@ -1054,9 +1097,14 @@ func hijack(method, path string, setRawTerminal bool) error { |
1054 | 1054 |
clientconn.Do(req) |
1055 | 1055 |
defer clientconn.Close() |
1056 | 1056 |
|
1057 |
- rwc, _ := clientconn.Hijack() |
|
1057 |
+ rwc, br := clientconn.Hijack() |
|
1058 | 1058 |
defer rwc.Close() |
1059 | 1059 |
|
1060 |
+ receiveStdout := Go(func() error { |
|
1061 |
+ _, err := io.Copy(os.Stdout, br) |
|
1062 |
+ return err |
|
1063 |
+ }) |
|
1064 |
+ |
|
1060 | 1065 |
if setRawTerminal && term.IsTerminal(int(os.Stdin.Fd())) && os.Getenv("NORAW") == "" { |
1061 | 1066 |
if oldState, err := SetRawTerminal(); err != nil { |
1062 | 1067 |
return err |
... | ... |
@@ -1065,10 +1113,6 @@ func hijack(method, path string, setRawTerminal bool) error { |
1065 | 1065 |
} |
1066 | 1066 |
} |
1067 | 1067 |
|
1068 |
- receiveStdout := Go(func() error { |
|
1069 |
- _, err := io.Copy(os.Stdout, rwc) |
|
1070 |
- return err |
|
1071 |
- }) |
|
1072 | 1068 |
sendStdin := Go(func() error { |
1073 | 1069 |
_, err := io.Copy(rwc, os.Stdin) |
1074 | 1070 |
rwc.Close() |
... | ... |
@@ -1,16 +1,17 @@ |
1 | 1 |
package docker |
2 | 2 |
|
3 | 3 |
import ( |
4 |
- "bufio" |
|
4 |
+ /*"bufio" |
|
5 | 5 |
"fmt" |
6 | 6 |
"github.com/dotcloud/docker/rcli" |
7 | 7 |
"io" |
8 | 8 |
"io/ioutil" |
9 |
- "strings" |
|
9 |
+ "strings"*/ |
|
10 | 10 |
"testing" |
11 | 11 |
"time" |
12 | 12 |
) |
13 | 13 |
|
14 |
+/*TODO |
|
14 | 15 |
func closeWrap(args ...io.Closer) error { |
15 | 16 |
e := false |
16 | 17 |
ret := fmt.Errorf("Error closing elements") |
... | ... |
@@ -25,7 +26,7 @@ func closeWrap(args ...io.Closer) error { |
25 | 25 |
} |
26 | 26 |
return nil |
27 | 27 |
} |
28 |
- |
|
28 |
+*/ |
|
29 | 29 |
func setTimeout(t *testing.T, msg string, d time.Duration, f func()) { |
30 | 30 |
c := make(chan bool) |
31 | 31 |
|
... | ... |
@@ -43,6 +44,7 @@ func setTimeout(t *testing.T, msg string, d time.Duration, f func()) { |
43 | 43 |
} |
44 | 44 |
} |
45 | 45 |
|
46 |
+/*TODO |
|
46 | 47 |
func assertPipe(input, output string, r io.Reader, w io.Writer, count int) error { |
47 | 48 |
for i := 0; i < count; i++ { |
48 | 49 |
if _, err := w.Write([]byte(input)); err != nil { |
... | ... |
@@ -396,3 +398,4 @@ func TestAttachDisconnect(t *testing.T) { |
396 | 396 |
cStdin.Close() |
397 | 397 |
container.Wait() |
398 | 398 |
} |
399 |
+*/ |
... | ... |
@@ -4,14 +4,10 @@ import ( |
4 | 4 |
"flag" |
5 | 5 |
"fmt" |
6 | 6 |
"github.com/dotcloud/docker" |
7 |
- "github.com/dotcloud/docker/rcli" |
|
8 |
- "github.com/dotcloud/docker/term" |
|
9 |
- "io" |
|
10 | 7 |
"io/ioutil" |
11 | 8 |
"log" |
12 | 9 |
"os" |
13 | 10 |
"os/signal" |
14 |
- "runtime" |
|
15 | 11 |
"strconv" |
16 | 12 |
"syscall" |
17 | 13 |
) |
... | ... |
@@ -49,10 +45,12 @@ func main() { |
49 | 49 |
} |
50 | 50 |
if err := daemon(*pidfile, *flAutoRestart); err != nil { |
51 | 51 |
log.Fatal(err) |
52 |
+ os.Exit(-1) |
|
52 | 53 |
} |
53 | 54 |
} else { |
54 |
- if err := docker.ParseCommands(flag.Args()); err != nil { |
|
55 |
+ if err := docker.ParseCommands(flag.Args()...); err != nil { |
|
55 | 56 |
log.Fatal(err) |
57 |
+ os.Exit(-1) |
|
56 | 58 |
} |
57 | 59 |
} |
58 | 60 |
} |
... | ... |
@@ -99,54 +97,10 @@ func daemon(pidfile string, autoRestart bool) error { |
99 | 99 |
os.Exit(0) |
100 | 100 |
}() |
101 | 101 |
|
102 |
- if runtime.GOARCH != "amd64" { |
|
103 |
- log.Fatalf("The docker runtime currently only supports amd64 (not %s). This will change in the future. Aborting.", runtime.GOARCH) |
|
104 |
- } |
|
105 |
- runtime, err := docker.NewRuntime(autoRestart) |
|
102 |
+ server, err := docker.NewServer(autoRestart) |
|
106 | 103 |
if err != nil { |
107 | 104 |
return err |
108 | 105 |
} |
109 | 106 |
|
110 |
- return docker.ListenAndServe("0.0.0.0:4243", runtime) |
|
111 |
-} |
|
112 |
- |
|
113 |
-func runCommand(args []string) error { |
|
114 |
- // FIXME: we want to use unix sockets here, but net.UnixConn doesn't expose |
|
115 |
- // CloseWrite(), which we need to cleanly signal that stdin is closed without |
|
116 |
- // closing the connection. |
|
117 |
- // See http://code.google.com/p/go/issues/detail?id=3345 |
|
118 |
- if conn, err := rcli.Call("tcp", "127.0.0.1:4242", args...); err == nil { |
|
119 |
- options := conn.GetOptions() |
|
120 |
- if options.RawTerminal && |
|
121 |
- term.IsTerminal(int(os.Stdin.Fd())) && |
|
122 |
- os.Getenv("NORAW") == "" { |
|
123 |
- if oldState, err := rcli.SetRawTerminal(); err != nil { |
|
124 |
- return err |
|
125 |
- } else { |
|
126 |
- defer rcli.RestoreTerminal(oldState) |
|
127 |
- } |
|
128 |
- } |
|
129 |
- receiveStdout := docker.Go(func() error { |
|
130 |
- _, err := io.Copy(os.Stdout, conn) |
|
131 |
- return err |
|
132 |
- }) |
|
133 |
- sendStdin := docker.Go(func() error { |
|
134 |
- _, err := io.Copy(conn, os.Stdin) |
|
135 |
- if err := conn.CloseWrite(); err != nil { |
|
136 |
- log.Printf("Couldn't send EOF: " + err.Error()) |
|
137 |
- } |
|
138 |
- return err |
|
139 |
- }) |
|
140 |
- if err := <-receiveStdout; err != nil { |
|
141 |
- return err |
|
142 |
- } |
|
143 |
- if !term.IsTerminal(int(os.Stdin.Fd())) { |
|
144 |
- if err := <-sendStdin; err != nil { |
|
145 |
- return err |
|
146 |
- } |
|
147 |
- } |
|
148 |
- } else { |
|
149 |
- return fmt.Errorf("Can't connect to docker daemon. Is 'docker -d' running on this host?") |
|
150 |
- } |
|
151 |
- return nil |
|
107 |
+ return docker.ListenAndServe("0.0.0.0:4243", server) |
|
152 | 108 |
} |
... | ... |
@@ -2,7 +2,6 @@ package docker |
2 | 2 |
|
3 | 3 |
import ( |
4 | 4 |
"fmt" |
5 |
- "github.com/dotcloud/docker/rcli" |
|
6 | 5 |
"io" |
7 | 6 |
"io/ioutil" |
8 | 7 |
"net" |
... | ... |
@@ -67,12 +66,13 @@ func init() { |
67 | 67 |
if err != nil { |
68 | 68 |
panic(err) |
69 | 69 |
} |
70 |
+ |
|
70 | 71 |
// Create the "Server" |
71 | 72 |
srv := &Server{ |
72 | 73 |
runtime: runtime, |
73 | 74 |
} |
74 | 75 |
// Retrieve the Image |
75 |
- if err := srv.CmdPull(os.Stdin, rcli.NewDockerLocalConn(os.Stdout), unitTestImageName); err != nil { |
|
76 |
+ if err := srv.ImagePull(unitTestImageName, os.Stdout); err != nil { |
|
76 | 77 |
panic(err) |
77 | 78 |
} |
78 | 79 |
} |
79 | 80 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,448 @@ |
0 |
+package docker |
|
1 |
+ |
|
2 |
+import ( |
|
3 |
+ "fmt" |
|
4 |
+ "io" |
|
5 |
+ "log" |
|
6 |
+ "net/http" |
|
7 |
+ "net/url" |
|
8 |
+ "os" |
|
9 |
+ "runtime" |
|
10 |
+ "strings" |
|
11 |
+) |
|
12 |
+ |
|
13 |
+func (srv *Server) DockerVersion() ApiVersion { |
|
14 |
+ return ApiVersion{VERSION, GIT_COMMIT, srv.runtime.capabilities.MemoryLimit, srv.runtime.capabilities.SwapLimit} |
|
15 |
+} |
|
16 |
+ |
|
17 |
+func (srv *Server) ContainerKill(name string) error { |
|
18 |
+ if container := srv.runtime.Get(name); container != nil { |
|
19 |
+ if err := container.Kill(); err != nil { |
|
20 |
+ return fmt.Errorf("Error restarting container %s: %s", name, err.Error()) |
|
21 |
+ } |
|
22 |
+ } else { |
|
23 |
+ return fmt.Errorf("No such container: %s", name) |
|
24 |
+ } |
|
25 |
+ return nil |
|
26 |
+} |
|
27 |
+ |
|
28 |
+func (srv *Server) ContainerExport(name string, file *os.File) error { |
|
29 |
+ if container := srv.runtime.Get(name); container != nil { |
|
30 |
+ |
|
31 |
+ data, err := container.Export() |
|
32 |
+ if err != nil { |
|
33 |
+ return err |
|
34 |
+ } |
|
35 |
+ |
|
36 |
+ // Stream the entire contents of the container (basically a volatile snapshot) |
|
37 |
+ if _, err := io.Copy(file, data); err != nil { |
|
38 |
+ return err |
|
39 |
+ } |
|
40 |
+ return nil |
|
41 |
+ } |
|
42 |
+ return fmt.Errorf("No such container: %s", name) |
|
43 |
+} |
|
44 |
+ |
|
45 |
+func (srv *Server) Images(all, filter, quiet string) ([]ApiImages, error) { |
|
46 |
+ var allImages map[string]*Image |
|
47 |
+ var err error |
|
48 |
+ if all == "1" { |
|
49 |
+ allImages, err = srv.runtime.graph.Map() |
|
50 |
+ } else { |
|
51 |
+ allImages, err = srv.runtime.graph.Heads() |
|
52 |
+ } |
|
53 |
+ if err != nil { |
|
54 |
+ return nil, err |
|
55 |
+ } |
|
56 |
+ var outs []ApiImages = []ApiImages{} //produce [] when empty instead of 'null' |
|
57 |
+ for name, repository := range srv.runtime.repositories.Repositories { |
|
58 |
+ if filter != "" && name != filter { |
|
59 |
+ continue |
|
60 |
+ } |
|
61 |
+ for tag, id := range repository { |
|
62 |
+ var out ApiImages |
|
63 |
+ image, err := srv.runtime.graph.Get(id) |
|
64 |
+ if err != nil { |
|
65 |
+ log.Printf("Warning: couldn't load %s from %s/%s: %s", id, name, tag, err) |
|
66 |
+ continue |
|
67 |
+ } |
|
68 |
+ delete(allImages, id) |
|
69 |
+ if quiet != "1" { |
|
70 |
+ out.Repository = name |
|
71 |
+ out.Tag = tag |
|
72 |
+ out.Id = TruncateId(id) |
|
73 |
+ out.Created = image.Created.Unix() |
|
74 |
+ } else { |
|
75 |
+ out.Id = image.ShortId() |
|
76 |
+ } |
|
77 |
+ outs = append(outs, out) |
|
78 |
+ } |
|
79 |
+ } |
|
80 |
+ // Display images which aren't part of a |
|
81 |
+ if filter == "" { |
|
82 |
+ for id, image := range allImages { |
|
83 |
+ var out ApiImages |
|
84 |
+ if quiet != "1" { |
|
85 |
+ out.Repository = "<none>" |
|
86 |
+ out.Tag = "<none>" |
|
87 |
+ out.Id = TruncateId(id) |
|
88 |
+ out.Created = image.Created.Unix() |
|
89 |
+ } else { |
|
90 |
+ out.Id = image.ShortId() |
|
91 |
+ } |
|
92 |
+ outs = append(outs, out) |
|
93 |
+ } |
|
94 |
+ } |
|
95 |
+ return outs, nil |
|
96 |
+} |
|
97 |
+ |
|
98 |
+func (srv *Server) DockerInfo() ApiInfo { |
|
99 |
+ images, _ := srv.runtime.graph.All() |
|
100 |
+ var imgcount int |
|
101 |
+ if images == nil { |
|
102 |
+ imgcount = 0 |
|
103 |
+ } else { |
|
104 |
+ imgcount = len(images) |
|
105 |
+ } |
|
106 |
+ var out ApiInfo |
|
107 |
+ out.Containers = len(srv.runtime.List()) |
|
108 |
+ out.Version = VERSION |
|
109 |
+ out.Images = imgcount |
|
110 |
+ if os.Getenv("DEBUG") == "1" { |
|
111 |
+ out.Debug = true |
|
112 |
+ out.NFd = getTotalUsedFds() |
|
113 |
+ out.NGoroutines = runtime.NumGoroutine() |
|
114 |
+ } |
|
115 |
+ return out |
|
116 |
+} |
|
117 |
+ |
|
118 |
+func (srv *Server) ImageHistory(name string) ([]ApiHistory, error) { |
|
119 |
+ image, err := srv.runtime.repositories.LookupImage(name) |
|
120 |
+ if err != nil { |
|
121 |
+ return nil, err |
|
122 |
+ } |
|
123 |
+ |
|
124 |
+ var outs []ApiHistory = []ApiHistory{} //produce [] when empty instead of 'null' |
|
125 |
+ err = image.WalkHistory(func(img *Image) error { |
|
126 |
+ var out ApiHistory |
|
127 |
+ out.Id = srv.runtime.repositories.ImageName(img.ShortId()) |
|
128 |
+ out.Created = img.Created.Unix() |
|
129 |
+ out.CreatedBy = strings.Join(img.ContainerConfig.Cmd, " ") |
|
130 |
+ outs = append(outs, out) |
|
131 |
+ return nil |
|
132 |
+ }) |
|
133 |
+ return outs, nil |
|
134 |
+ |
|
135 |
+} |
|
136 |
+ |
|
137 |
+func (srv *Server) ContainerChanges(name string) ([]string, error) { |
|
138 |
+ if container := srv.runtime.Get(name); container != nil { |
|
139 |
+ changes, err := container.Changes() |
|
140 |
+ if err != nil { |
|
141 |
+ return nil, err |
|
142 |
+ } |
|
143 |
+ var changesStr []string |
|
144 |
+ for _, name := range changes { |
|
145 |
+ changesStr = append(changesStr, name.String()) |
|
146 |
+ } |
|
147 |
+ return changesStr, nil |
|
148 |
+ } |
|
149 |
+ return nil, fmt.Errorf("No such container: %s", name) |
|
150 |
+} |
|
151 |
+ |
|
152 |
+func (srv *Server) ContainerPort(name, privatePort string) (string, error) { |
|
153 |
+ if container := srv.runtime.Get(name); container != nil { |
|
154 |
+ if frontend, exists := container.NetworkSettings.PortMapping[privatePort]; exists { |
|
155 |
+ return frontend, nil |
|
156 |
+ } |
|
157 |
+ return "", fmt.Errorf("No private port '%s' allocated on %s", privatePort, name) |
|
158 |
+ } |
|
159 |
+ return "", fmt.Errorf("No such container: %s", name) |
|
160 |
+} |
|
161 |
+ |
|
162 |
+func (srv *Server) Containers(all, notrunc, quiet string, n int) []ApiContainers { |
|
163 |
+ var outs []ApiContainers = []ApiContainers{} //produce [] when empty instead of 'null' |
|
164 |
+ for i, container := range srv.runtime.List() { |
|
165 |
+ if !container.State.Running && all != "1" && n == -1 { |
|
166 |
+ continue |
|
167 |
+ } |
|
168 |
+ if i == n { |
|
169 |
+ break |
|
170 |
+ } |
|
171 |
+ var out ApiContainers |
|
172 |
+ out.Id = container.ShortId() |
|
173 |
+ if quiet != "1" { |
|
174 |
+ command := fmt.Sprintf("%s %s", container.Path, strings.Join(container.Args, " ")) |
|
175 |
+ if notrunc != "1" { |
|
176 |
+ command = Trunc(command, 20) |
|
177 |
+ } |
|
178 |
+ out.Image = srv.runtime.repositories.ImageName(container.Image) |
|
179 |
+ out.Command = command |
|
180 |
+ out.Created = container.Created.Unix() |
|
181 |
+ out.Status = container.State.String() |
|
182 |
+ out.Ports = container.NetworkSettings.PortMappingHuman() |
|
183 |
+ } |
|
184 |
+ outs = append(outs, out) |
|
185 |
+ } |
|
186 |
+ return outs |
|
187 |
+} |
|
188 |
+ |
|
189 |
+func (srv *Server) ContainerCommit(name, repo, tag, author, comment string, config *Config) (string, error) { |
|
190 |
+ img, err := srv.runtime.Commit(name, repo, tag, comment, author, config) |
|
191 |
+ if err != nil { |
|
192 |
+ return "", err |
|
193 |
+ } |
|
194 |
+ return img.ShortId(), err |
|
195 |
+} |
|
196 |
+ |
|
197 |
+func (srv *Server) ContainerTag(name, repo, tag string, force bool) error { |
|
198 |
+ if err := srv.runtime.repositories.Set(repo, tag, name, force); err != nil { |
|
199 |
+ return err |
|
200 |
+ } |
|
201 |
+ return nil |
|
202 |
+} |
|
203 |
+ |
|
204 |
+func (srv *Server) ImagePull(name string, file *os.File) error { |
|
205 |
+ if srv.runtime.graph.LookupRemoteImage(name, srv.runtime.authConfig) { |
|
206 |
+ if err := srv.runtime.graph.PullImage(file, name, srv.runtime.authConfig); err != nil { |
|
207 |
+ return err |
|
208 |
+ } |
|
209 |
+ } |
|
210 |
+ if err := srv.runtime.graph.PullRepository(file, name, "", srv.runtime.repositories, srv.runtime.authConfig); err != nil { |
|
211 |
+ return err |
|
212 |
+ } |
|
213 |
+ return nil |
|
214 |
+} |
|
215 |
+ |
|
216 |
+func (srv *Server) ImageImport(src, repo, tag string, file *os.File) error { |
|
217 |
+ var archive io.Reader |
|
218 |
+ var resp *http.Response |
|
219 |
+ |
|
220 |
+ if src == "-" { |
|
221 |
+ r, w := io.Pipe() |
|
222 |
+ go func() { |
|
223 |
+ defer w.Close() |
|
224 |
+ defer Debugf("Closing buffered stdin pipe") |
|
225 |
+ io.Copy(w, file) |
|
226 |
+ }() |
|
227 |
+ archive = r |
|
228 |
+ } else { |
|
229 |
+ u, err := url.Parse(src) |
|
230 |
+ if err != nil { |
|
231 |
+ fmt.Fprintln(file, "Error: "+err.Error()) |
|
232 |
+ } |
|
233 |
+ if u.Scheme == "" { |
|
234 |
+ u.Scheme = "http" |
|
235 |
+ u.Host = src |
|
236 |
+ u.Path = "" |
|
237 |
+ } |
|
238 |
+ fmt.Fprintln(file, "Downloading from", u) |
|
239 |
+ // Download with curl (pretty progress bar) |
|
240 |
+ // If curl is not available, fallback to http.Get() |
|
241 |
+ resp, err = Download(u.String(), file) |
|
242 |
+ if err != nil { |
|
243 |
+ return err |
|
244 |
+ } |
|
245 |
+ archive = ProgressReader(resp.Body, int(resp.ContentLength), file, "Importing %v/%v (%v)") |
|
246 |
+ } |
|
247 |
+ img, err := srv.runtime.graph.Create(archive, nil, "Imported from "+src, "", nil) |
|
248 |
+ if err != nil { |
|
249 |
+ return err |
|
250 |
+ } |
|
251 |
+ // Optionally register the image at REPO/TAG |
|
252 |
+ if repo != "" { |
|
253 |
+ if err := srv.runtime.repositories.Set(repo, tag, img.Id, true); err != nil { |
|
254 |
+ return err |
|
255 |
+ } |
|
256 |
+ } |
|
257 |
+ fmt.Fprintln(file, img.ShortId()) |
|
258 |
+ return nil |
|
259 |
+} |
|
260 |
+ |
|
261 |
+func (srv *Server) ContainerCreate(config Config) (string, bool, bool, error) { |
|
262 |
+ var memoryW, swapW bool |
|
263 |
+ |
|
264 |
+ if config.Memory > 0 && !srv.runtime.capabilities.MemoryLimit { |
|
265 |
+ memoryW = true |
|
266 |
+ log.Println("WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.") |
|
267 |
+ config.Memory = 0 |
|
268 |
+ } |
|
269 |
+ |
|
270 |
+ if config.Memory > 0 && !srv.runtime.capabilities.SwapLimit { |
|
271 |
+ swapW = true |
|
272 |
+ log.Println("WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.") |
|
273 |
+ config.MemorySwap = -1 |
|
274 |
+ } |
|
275 |
+ container, err := srv.runtime.Create(&config) |
|
276 |
+ if err != nil { |
|
277 |
+ if srv.runtime.graph.IsNotExist(err) { |
|
278 |
+ return "", false, false, fmt.Errorf("No such image: %s", config.Image) |
|
279 |
+ } |
|
280 |
+ return "", false, false, err |
|
281 |
+ } |
|
282 |
+ return container.ShortId(), memoryW, swapW, nil |
|
283 |
+} |
|
284 |
+ |
|
285 |
+func (srv *Server) ContainerRestart(name string, t int) error { |
|
286 |
+ if container := srv.runtime.Get(name); container != nil { |
|
287 |
+ if err := container.Restart(t); err != nil { |
|
288 |
+ return fmt.Errorf("Error restarting container %s: %s", name, err.Error()) |
|
289 |
+ } |
|
290 |
+ } else { |
|
291 |
+ return fmt.Errorf("No such container: %s", name) |
|
292 |
+ } |
|
293 |
+ return nil |
|
294 |
+} |
|
295 |
+ |
|
296 |
+func (srv *Server) ContainerDestroy(name string) error { |
|
297 |
+ if container := srv.runtime.Get(name); container != nil { |
|
298 |
+ if err := srv.runtime.Destroy(container); err != nil { |
|
299 |
+ return fmt.Errorf("Error destroying container %s: %s", name, err.Error()) |
|
300 |
+ } |
|
301 |
+ } else { |
|
302 |
+ return fmt.Errorf("No such container: %s", name) |
|
303 |
+ } |
|
304 |
+ return nil |
|
305 |
+} |
|
306 |
+ |
|
307 |
+func (srv *Server) ImageDelete(name string) error { |
|
308 |
+ img, err := srv.runtime.repositories.LookupImage(name) |
|
309 |
+ if err != nil { |
|
310 |
+ return fmt.Errorf("No such image: %s", name) |
|
311 |
+ } else { |
|
312 |
+ if err := srv.runtime.graph.Delete(img.Id); err != nil { |
|
313 |
+ return fmt.Errorf("Error deleteing image %s: %s", name, err.Error()) |
|
314 |
+ } |
|
315 |
+ } |
|
316 |
+ return nil |
|
317 |
+} |
|
318 |
+ |
|
319 |
+func (srv *Server) ContainerStart(name string) error { |
|
320 |
+ if container := srv.runtime.Get(name); container != nil { |
|
321 |
+ if err := container.Start(); err != nil { |
|
322 |
+ return fmt.Errorf("Error starting container %s: %s", name, err.Error()) |
|
323 |
+ } |
|
324 |
+ } else { |
|
325 |
+ return fmt.Errorf("No such container: %s", name) |
|
326 |
+ } |
|
327 |
+ return nil |
|
328 |
+} |
|
329 |
+ |
|
330 |
+func (srv *Server) ContainerStop(name string, t int) error { |
|
331 |
+ if container := srv.runtime.Get(name); container != nil { |
|
332 |
+ if err := container.Stop(t); err != nil { |
|
333 |
+ return fmt.Errorf("Error stopping container %s: %s", name, err.Error()) |
|
334 |
+ } |
|
335 |
+ } else { |
|
336 |
+ return fmt.Errorf("No such container: %s", name) |
|
337 |
+ } |
|
338 |
+ return nil |
|
339 |
+} |
|
340 |
+ |
|
341 |
+func (srv *Server) ContainerWait(name string) (int, error) { |
|
342 |
+ if container := srv.runtime.Get(name); container != nil { |
|
343 |
+ return container.Wait(), nil |
|
344 |
+ } |
|
345 |
+ return 0, fmt.Errorf("No such container: %s", name) |
|
346 |
+} |
|
347 |
+ |
|
348 |
+func (srv *Server) ContainerAttach(name, logs, stream, stdin, stdout, stderr string, file *os.File) error { |
|
349 |
+ if container := srv.runtime.Get(name); container != nil { |
|
350 |
+ //logs |
|
351 |
+ if logs == "1" { |
|
352 |
+ if stdout == "1" { |
|
353 |
+ cLog, err := container.ReadLog("stdout") |
|
354 |
+ if err != nil { |
|
355 |
+ Debugf(err.Error()) |
|
356 |
+ } else if _, err := io.Copy(file, cLog); err != nil { |
|
357 |
+ Debugf(err.Error()) |
|
358 |
+ } |
|
359 |
+ } |
|
360 |
+ if stderr == "1" { |
|
361 |
+ cLog, err := container.ReadLog("stderr") |
|
362 |
+ if err != nil { |
|
363 |
+ Debugf(err.Error()) |
|
364 |
+ } else if _, err := io.Copy(file, cLog); err != nil { |
|
365 |
+ Debugf(err.Error()) |
|
366 |
+ } |
|
367 |
+ } |
|
368 |
+ } |
|
369 |
+ |
|
370 |
+ //stream |
|
371 |
+ if stream == "1" { |
|
372 |
+ if container.State.Ghost { |
|
373 |
+ return fmt.Errorf("Impossible to attach to a ghost container") |
|
374 |
+ } |
|
375 |
+ |
|
376 |
+ if container.Config.Tty { |
|
377 |
+ oldState, err := SetRawTerminal() |
|
378 |
+ if err != nil { |
|
379 |
+ if os.Getenv("DEBUG") != "" { |
|
380 |
+ log.Printf("Can't set the terminal in raw mode: %s", err) |
|
381 |
+ } |
|
382 |
+ } else { |
|
383 |
+ defer RestoreTerminal(oldState) |
|
384 |
+ } |
|
385 |
+ } |
|
386 |
+ var ( |
|
387 |
+ cStdin io.ReadCloser |
|
388 |
+ cStdout, cStderr io.Writer |
|
389 |
+ cStdinCloser io.Closer |
|
390 |
+ ) |
|
391 |
+ |
|
392 |
+ if stdin == "1" { |
|
393 |
+ r, w := io.Pipe() |
|
394 |
+ go func() { |
|
395 |
+ defer w.Close() |
|
396 |
+ defer Debugf("Closing buffered stdin pipe") |
|
397 |
+ io.Copy(w, file) |
|
398 |
+ }() |
|
399 |
+ cStdin = r |
|
400 |
+ cStdinCloser = file |
|
401 |
+ } |
|
402 |
+ if stdout == "1" { |
|
403 |
+ cStdout = file |
|
404 |
+ } |
|
405 |
+ if stderr == "1" { |
|
406 |
+ cStderr = file |
|
407 |
+ } |
|
408 |
+ |
|
409 |
+ <-container.Attach(cStdin, cStdinCloser, cStdout, cStderr) |
|
410 |
+ } |
|
411 |
+ } else { |
|
412 |
+ return fmt.Errorf("No such container: %s", name) |
|
413 |
+ } |
|
414 |
+ return nil |
|
415 |
+} |
|
416 |
+ |
|
417 |
+func (srv *Server) ContainerInspect(name string) (*Container, error) { |
|
418 |
+ if container := srv.runtime.Get(name); container != nil { |
|
419 |
+ return container, nil |
|
420 |
+ } |
|
421 |
+ return nil, fmt.Errorf("No such container: %s", name) |
|
422 |
+} |
|
423 |
+ |
|
424 |
+func (srv *Server) ImageInspect(name string) (*Image, error) { |
|
425 |
+ if image, err := srv.runtime.repositories.LookupImage(name); err == nil && image != nil { |
|
426 |
+ return image, nil |
|
427 |
+ } |
|
428 |
+ return nil, fmt.Errorf("No such image: %s", name) |
|
429 |
+} |
|
430 |
+ |
|
431 |
+func NewServer(autoRestart bool) (*Server, error) { |
|
432 |
+ if runtime.GOARCH != "amd64" { |
|
433 |
+ log.Fatalf("The docker runtime currently only supports amd64 (not %s). This will change in the future. Aborting.", runtime.GOARCH) |
|
434 |
+ } |
|
435 |
+ runtime, err := NewRuntime(autoRestart) |
|
436 |
+ if err != nil { |
|
437 |
+ return nil, err |
|
438 |
+ } |
|
439 |
+ srv := &Server{ |
|
440 |
+ runtime: runtime, |
|
441 |
+ } |
|
442 |
+ return srv, nil |
|
443 |
+} |
|
444 |
+ |
|
445 |
+type Server struct { |
|
446 |
+ runtime *Runtime |
|
447 |
+} |