]>
Commit | Line | Data |
---|---|---|
9b12e4fe JC |
1 | // Copyright 2015 go-dockerclient authors. All rights reserved. |
2 | // Use of this source code is governed by a BSD-style | |
3 | // license that can be found in the LICENSE file. | |
4 | ||
5 | // Package testing provides a fake implementation of the Docker API, useful for | |
6 | // testing purpose. | |
7 | package testing | |
8 | ||
9 | import ( | |
10 | "archive/tar" | |
11 | "crypto/rand" | |
12 | "encoding/json" | |
13 | "errors" | |
14 | "fmt" | |
15 | "io/ioutil" | |
16 | mathrand "math/rand" | |
17 | "net" | |
18 | "net/http" | |
19 | "regexp" | |
20 | "strconv" | |
21 | "strings" | |
22 | "sync" | |
23 | "time" | |
24 | ||
25 | "github.com/fsouza/go-dockerclient" | |
26 | "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/stdcopy" | |
27 | "github.com/fsouza/go-dockerclient/external/github.com/gorilla/mux" | |
28 | ) | |
29 | ||
30 | var nameRegexp = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9_.-]+$`) | |
31 | ||
32 | // DockerServer represents a programmable, concurrent (not much), HTTP server | |
33 | // implementing a fake version of the Docker remote API. | |
34 | // | |
35 | // It can used in standalone mode, listening for connections or as an arbitrary | |
36 | // HTTP handler. | |
37 | // | |
38 | // For more details on the remote API, check http://goo.gl/G3plxW. | |
39 | type DockerServer struct { | |
40 | containers []*docker.Container | |
41 | uploadedFiles map[string]string | |
42 | execs []*docker.ExecInspect | |
43 | execMut sync.RWMutex | |
44 | cMut sync.RWMutex | |
45 | images []docker.Image | |
46 | iMut sync.RWMutex | |
47 | imgIDs map[string]string | |
48 | networks []*docker.Network | |
49 | netMut sync.RWMutex | |
50 | listener net.Listener | |
51 | mux *mux.Router | |
52 | hook func(*http.Request) | |
53 | failures map[string]string | |
54 | multiFailures []map[string]string | |
55 | execCallbacks map[string]func() | |
56 | statsCallbacks map[string]func(string) docker.Stats | |
57 | customHandlers map[string]http.Handler | |
58 | handlerMutex sync.RWMutex | |
59 | cChan chan<- *docker.Container | |
60 | volStore map[string]*volumeCounter | |
61 | volMut sync.RWMutex | |
62 | } | |
63 | ||
64 | type volumeCounter struct { | |
65 | volume docker.Volume | |
66 | count int | |
67 | } | |
68 | ||
69 | // NewServer returns a new instance of the fake server, in standalone mode. Use | |
70 | // the method URL to get the URL of the server. | |
71 | // | |
72 | // It receives the bind address (use 127.0.0.1:0 for getting an available port | |
73 | // on the host), a channel of containers and a hook function, that will be | |
74 | // called on every request. | |
75 | // | |
76 | // The fake server will send containers in the channel whenever the container | |
77 | // changes its state, via the HTTP API (i.e.: create, start and stop). This | |
78 | // channel may be nil, which means that the server won't notify on state | |
79 | // changes. | |
80 | func NewServer(bind string, containerChan chan<- *docker.Container, hook func(*http.Request)) (*DockerServer, error) { | |
81 | listener, err := net.Listen("tcp", bind) | |
82 | if err != nil { | |
83 | return nil, err | |
84 | } | |
85 | server := DockerServer{ | |
86 | listener: listener, | |
87 | imgIDs: make(map[string]string), | |
88 | hook: hook, | |
89 | failures: make(map[string]string), | |
90 | execCallbacks: make(map[string]func()), | |
91 | statsCallbacks: make(map[string]func(string) docker.Stats), | |
92 | customHandlers: make(map[string]http.Handler), | |
93 | uploadedFiles: make(map[string]string), | |
94 | cChan: containerChan, | |
95 | } | |
96 | server.buildMuxer() | |
97 | go http.Serve(listener, &server) | |
98 | return &server, nil | |
99 | } | |
100 | ||
101 | func (s *DockerServer) notify(container *docker.Container) { | |
102 | if s.cChan != nil { | |
103 | s.cChan <- container | |
104 | } | |
105 | } | |
106 | ||
107 | func (s *DockerServer) buildMuxer() { | |
108 | s.mux = mux.NewRouter() | |
109 | s.mux.Path("/commit").Methods("POST").HandlerFunc(s.handlerWrapper(s.commitContainer)) | |
110 | s.mux.Path("/containers/json").Methods("GET").HandlerFunc(s.handlerWrapper(s.listContainers)) | |
111 | s.mux.Path("/containers/create").Methods("POST").HandlerFunc(s.handlerWrapper(s.createContainer)) | |
112 | s.mux.Path("/containers/{id:.*}/json").Methods("GET").HandlerFunc(s.handlerWrapper(s.inspectContainer)) | |
113 | s.mux.Path("/containers/{id:.*}/rename").Methods("POST").HandlerFunc(s.handlerWrapper(s.renameContainer)) | |
114 | s.mux.Path("/containers/{id:.*}/top").Methods("GET").HandlerFunc(s.handlerWrapper(s.topContainer)) | |
115 | s.mux.Path("/containers/{id:.*}/start").Methods("POST").HandlerFunc(s.handlerWrapper(s.startContainer)) | |
116 | s.mux.Path("/containers/{id:.*}/kill").Methods("POST").HandlerFunc(s.handlerWrapper(s.stopContainer)) | |
117 | s.mux.Path("/containers/{id:.*}/stop").Methods("POST").HandlerFunc(s.handlerWrapper(s.stopContainer)) | |
118 | s.mux.Path("/containers/{id:.*}/pause").Methods("POST").HandlerFunc(s.handlerWrapper(s.pauseContainer)) | |
119 | s.mux.Path("/containers/{id:.*}/unpause").Methods("POST").HandlerFunc(s.handlerWrapper(s.unpauseContainer)) | |
120 | s.mux.Path("/containers/{id:.*}/wait").Methods("POST").HandlerFunc(s.handlerWrapper(s.waitContainer)) | |
121 | s.mux.Path("/containers/{id:.*}/attach").Methods("POST").HandlerFunc(s.handlerWrapper(s.attachContainer)) | |
122 | s.mux.Path("/containers/{id:.*}").Methods("DELETE").HandlerFunc(s.handlerWrapper(s.removeContainer)) | |
123 | s.mux.Path("/containers/{id:.*}/exec").Methods("POST").HandlerFunc(s.handlerWrapper(s.createExecContainer)) | |
124 | s.mux.Path("/containers/{id:.*}/stats").Methods("GET").HandlerFunc(s.handlerWrapper(s.statsContainer)) | |
125 | s.mux.Path("/containers/{id:.*}/archive").Methods("PUT").HandlerFunc(s.handlerWrapper(s.uploadToContainer)) | |
126 | s.mux.Path("/exec/{id:.*}/resize").Methods("POST").HandlerFunc(s.handlerWrapper(s.resizeExecContainer)) | |
127 | s.mux.Path("/exec/{id:.*}/start").Methods("POST").HandlerFunc(s.handlerWrapper(s.startExecContainer)) | |
128 | s.mux.Path("/exec/{id:.*}/json").Methods("GET").HandlerFunc(s.handlerWrapper(s.inspectExecContainer)) | |
129 | s.mux.Path("/images/create").Methods("POST").HandlerFunc(s.handlerWrapper(s.pullImage)) | |
130 | s.mux.Path("/build").Methods("POST").HandlerFunc(s.handlerWrapper(s.buildImage)) | |
131 | s.mux.Path("/images/json").Methods("GET").HandlerFunc(s.handlerWrapper(s.listImages)) | |
132 | s.mux.Path("/images/{id:.*}").Methods("DELETE").HandlerFunc(s.handlerWrapper(s.removeImage)) | |
133 | s.mux.Path("/images/{name:.*}/json").Methods("GET").HandlerFunc(s.handlerWrapper(s.inspectImage)) | |
134 | s.mux.Path("/images/{name:.*}/push").Methods("POST").HandlerFunc(s.handlerWrapper(s.pushImage)) | |
135 | s.mux.Path("/images/{name:.*}/tag").Methods("POST").HandlerFunc(s.handlerWrapper(s.tagImage)) | |
136 | s.mux.Path("/events").Methods("GET").HandlerFunc(s.listEvents) | |
137 | s.mux.Path("/_ping").Methods("GET").HandlerFunc(s.handlerWrapper(s.pingDocker)) | |
138 | s.mux.Path("/images/load").Methods("POST").HandlerFunc(s.handlerWrapper(s.loadImage)) | |
139 | s.mux.Path("/images/{id:.*}/get").Methods("GET").HandlerFunc(s.handlerWrapper(s.getImage)) | |
140 | s.mux.Path("/networks").Methods("GET").HandlerFunc(s.handlerWrapper(s.listNetworks)) | |
141 | s.mux.Path("/networks/{id:.*}").Methods("GET").HandlerFunc(s.handlerWrapper(s.networkInfo)) | |
142 | s.mux.Path("/networks").Methods("POST").HandlerFunc(s.handlerWrapper(s.createNetwork)) | |
143 | s.mux.Path("/volumes").Methods("GET").HandlerFunc(s.handlerWrapper(s.listVolumes)) | |
144 | s.mux.Path("/volumes/create").Methods("POST").HandlerFunc(s.handlerWrapper(s.createVolume)) | |
145 | s.mux.Path("/volumes/{name:.*}").Methods("GET").HandlerFunc(s.handlerWrapper(s.inspectVolume)) | |
146 | s.mux.Path("/volumes/{name:.*}").Methods("DELETE").HandlerFunc(s.handlerWrapper(s.removeVolume)) | |
147 | s.mux.Path("/info").Methods("GET").HandlerFunc(s.handlerWrapper(s.infoDocker)) | |
148 | } | |
149 | ||
150 | // SetHook changes the hook function used by the server. | |
151 | // | |
152 | // The hook function is a function called on every request. | |
153 | func (s *DockerServer) SetHook(hook func(*http.Request)) { | |
154 | s.hook = hook | |
155 | } | |
156 | ||
157 | // PrepareExec adds a callback to a container exec in the fake server. | |
158 | // | |
159 | // This function will be called whenever the given exec id is started, and the | |
160 | // given exec id will remain in the "Running" start while the function is | |
161 | // running, so it's useful for emulating an exec that runs for two seconds, for | |
162 | // example: | |
163 | // | |
164 | // opts := docker.CreateExecOptions{ | |
165 | // AttachStdin: true, | |
166 | // AttachStdout: true, | |
167 | // AttachStderr: true, | |
168 | // Tty: true, | |
169 | // Cmd: []string{"/bin/bash", "-l"}, | |
170 | // } | |
171 | // // Client points to a fake server. | |
172 | // exec, err := client.CreateExec(opts) | |
173 | // // handle error | |
174 | // server.PrepareExec(exec.ID, func() {time.Sleep(2 * time.Second)}) | |
175 | // err = client.StartExec(exec.ID, docker.StartExecOptions{Tty: true}) // will block for 2 seconds | |
176 | // // handle error | |
177 | func (s *DockerServer) PrepareExec(id string, callback func()) { | |
178 | s.execCallbacks[id] = callback | |
179 | } | |
180 | ||
181 | // PrepareStats adds a callback that will be called for each container stats | |
182 | // call. | |
183 | // | |
184 | // This callback function will be called multiple times if stream is set to | |
185 | // true when stats is called. | |
186 | func (s *DockerServer) PrepareStats(id string, callback func(string) docker.Stats) { | |
187 | s.statsCallbacks[id] = callback | |
188 | } | |
189 | ||
190 | // PrepareFailure adds a new expected failure based on a URL regexp it receives | |
191 | // an id for the failure. | |
192 | func (s *DockerServer) PrepareFailure(id string, urlRegexp string) { | |
193 | s.failures[id] = urlRegexp | |
194 | } | |
195 | ||
196 | // PrepareMultiFailures enqueues a new expected failure based on a URL regexp | |
197 | // it receives an id for the failure. | |
198 | func (s *DockerServer) PrepareMultiFailures(id string, urlRegexp string) { | |
199 | s.multiFailures = append(s.multiFailures, map[string]string{"error": id, "url": urlRegexp}) | |
200 | } | |
201 | ||
202 | // ResetFailure removes an expected failure identified by the given id. | |
203 | func (s *DockerServer) ResetFailure(id string) { | |
204 | delete(s.failures, id) | |
205 | } | |
206 | ||
207 | // ResetMultiFailures removes all enqueued failures. | |
208 | func (s *DockerServer) ResetMultiFailures() { | |
209 | s.multiFailures = []map[string]string{} | |
210 | } | |
211 | ||
212 | // CustomHandler registers a custom handler for a specific path. | |
213 | // | |
214 | // For example: | |
215 | // | |
216 | // server.CustomHandler("/containers/json", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
217 | // http.Error(w, "Something wrong is not right", http.StatusInternalServerError) | |
218 | // })) | |
219 | func (s *DockerServer) CustomHandler(path string, handler http.Handler) { | |
220 | s.handlerMutex.Lock() | |
221 | s.customHandlers[path] = handler | |
222 | s.handlerMutex.Unlock() | |
223 | } | |
224 | ||
225 | // MutateContainer changes the state of a container, returning an error if the | |
226 | // given id does not match to any container "running" in the server. | |
227 | func (s *DockerServer) MutateContainer(id string, state docker.State) error { | |
228 | for _, container := range s.containers { | |
229 | if container.ID == id { | |
230 | container.State = state | |
231 | return nil | |
232 | } | |
233 | } | |
234 | return errors.New("container not found") | |
235 | } | |
236 | ||
237 | // Stop stops the server. | |
238 | func (s *DockerServer) Stop() { | |
239 | if s.listener != nil { | |
240 | s.listener.Close() | |
241 | } | |
242 | } | |
243 | ||
244 | // URL returns the HTTP URL of the server. | |
245 | func (s *DockerServer) URL() string { | |
246 | if s.listener == nil { | |
247 | return "" | |
248 | } | |
249 | return "http://" + s.listener.Addr().String() + "/" | |
250 | } | |
251 | ||
252 | // ServeHTTP handles HTTP requests sent to the server. | |
253 | func (s *DockerServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { | |
254 | s.handlerMutex.RLock() | |
255 | defer s.handlerMutex.RUnlock() | |
256 | for re, handler := range s.customHandlers { | |
257 | if m, _ := regexp.MatchString(re, r.URL.Path); m { | |
258 | handler.ServeHTTP(w, r) | |
259 | return | |
260 | } | |
261 | } | |
262 | s.mux.ServeHTTP(w, r) | |
263 | if s.hook != nil { | |
264 | s.hook(r) | |
265 | } | |
266 | } | |
267 | ||
268 | // DefaultHandler returns default http.Handler mux, it allows customHandlers to | |
269 | // call the default behavior if wanted. | |
270 | func (s *DockerServer) DefaultHandler() http.Handler { | |
271 | return s.mux | |
272 | } | |
273 | ||
274 | func (s *DockerServer) handlerWrapper(f func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) { | |
275 | return func(w http.ResponseWriter, r *http.Request) { | |
276 | for errorID, urlRegexp := range s.failures { | |
277 | matched, err := regexp.MatchString(urlRegexp, r.URL.Path) | |
278 | if err != nil { | |
279 | http.Error(w, err.Error(), http.StatusBadRequest) | |
280 | return | |
281 | } | |
282 | if !matched { | |
283 | continue | |
284 | } | |
285 | http.Error(w, errorID, http.StatusBadRequest) | |
286 | return | |
287 | } | |
288 | for i, failure := range s.multiFailures { | |
289 | matched, err := regexp.MatchString(failure["url"], r.URL.Path) | |
290 | if err != nil { | |
291 | http.Error(w, err.Error(), http.StatusBadRequest) | |
292 | return | |
293 | } | |
294 | if !matched { | |
295 | continue | |
296 | } | |
297 | http.Error(w, failure["error"], http.StatusBadRequest) | |
298 | s.multiFailures = append(s.multiFailures[:i], s.multiFailures[i+1:]...) | |
299 | return | |
300 | } | |
301 | f(w, r) | |
302 | } | |
303 | } | |
304 | ||
305 | func (s *DockerServer) listContainers(w http.ResponseWriter, r *http.Request) { | |
306 | all := r.URL.Query().Get("all") | |
307 | s.cMut.RLock() | |
308 | result := make([]docker.APIContainers, 0, len(s.containers)) | |
309 | for _, container := range s.containers { | |
310 | if all == "1" || container.State.Running { | |
311 | result = append(result, docker.APIContainers{ | |
312 | ID: container.ID, | |
313 | Image: container.Image, | |
314 | Command: fmt.Sprintf("%s %s", container.Path, strings.Join(container.Args, " ")), | |
315 | Created: container.Created.Unix(), | |
316 | Status: container.State.String(), | |
317 | Ports: container.NetworkSettings.PortMappingAPI(), | |
318 | Names: []string{fmt.Sprintf("/%s", container.Name)}, | |
319 | }) | |
320 | } | |
321 | } | |
322 | s.cMut.RUnlock() | |
323 | w.Header().Set("Content-Type", "application/json") | |
324 | w.WriteHeader(http.StatusOK) | |
325 | json.NewEncoder(w).Encode(result) | |
326 | } | |
327 | ||
328 | func (s *DockerServer) listImages(w http.ResponseWriter, r *http.Request) { | |
329 | s.cMut.RLock() | |
330 | result := make([]docker.APIImages, len(s.images)) | |
331 | for i, image := range s.images { | |
332 | result[i] = docker.APIImages{ | |
333 | ID: image.ID, | |
334 | Created: image.Created.Unix(), | |
335 | } | |
336 | for tag, id := range s.imgIDs { | |
337 | if id == image.ID { | |
338 | result[i].RepoTags = append(result[i].RepoTags, tag) | |
339 | } | |
340 | } | |
341 | } | |
342 | s.cMut.RUnlock() | |
343 | w.Header().Set("Content-Type", "application/json") | |
344 | w.WriteHeader(http.StatusOK) | |
345 | json.NewEncoder(w).Encode(result) | |
346 | } | |
347 | ||
348 | func (s *DockerServer) findImage(id string) (string, error) { | |
349 | s.iMut.RLock() | |
350 | defer s.iMut.RUnlock() | |
351 | image, ok := s.imgIDs[id] | |
352 | if ok { | |
353 | return image, nil | |
354 | } | |
355 | image, _, err := s.findImageByID(id) | |
356 | return image, err | |
357 | } | |
358 | ||
359 | func (s *DockerServer) findImageByID(id string) (string, int, error) { | |
360 | s.iMut.RLock() | |
361 | defer s.iMut.RUnlock() | |
362 | for i, image := range s.images { | |
363 | if image.ID == id { | |
364 | return image.ID, i, nil | |
365 | } | |
366 | } | |
367 | return "", -1, errors.New("No such image") | |
368 | } | |
369 | ||
370 | func (s *DockerServer) createContainer(w http.ResponseWriter, r *http.Request) { | |
371 | var config struct { | |
372 | *docker.Config | |
373 | HostConfig *docker.HostConfig | |
374 | } | |
375 | defer r.Body.Close() | |
376 | err := json.NewDecoder(r.Body).Decode(&config) | |
377 | if err != nil { | |
378 | http.Error(w, err.Error(), http.StatusBadRequest) | |
379 | return | |
380 | } | |
381 | name := r.URL.Query().Get("name") | |
382 | if name != "" && !nameRegexp.MatchString(name) { | |
383 | http.Error(w, "Invalid container name", http.StatusInternalServerError) | |
384 | return | |
385 | } | |
386 | if _, err := s.findImage(config.Image); err != nil { | |
387 | http.Error(w, err.Error(), http.StatusNotFound) | |
388 | return | |
389 | } | |
390 | ports := map[docker.Port][]docker.PortBinding{} | |
391 | for port := range config.ExposedPorts { | |
392 | ports[port] = []docker.PortBinding{{ | |
393 | HostIP: "0.0.0.0", | |
394 | HostPort: strconv.Itoa(mathrand.Int() % 0xffff), | |
395 | }} | |
396 | } | |
397 | ||
398 | //the container may not have cmd when using a Dockerfile | |
399 | var path string | |
400 | var args []string | |
401 | if len(config.Cmd) == 1 { | |
402 | path = config.Cmd[0] | |
403 | } else if len(config.Cmd) > 1 { | |
404 | path = config.Cmd[0] | |
405 | args = config.Cmd[1:] | |
406 | } | |
407 | ||
408 | generatedID := s.generateID() | |
409 | config.Config.Hostname = generatedID[:12] | |
410 | container := docker.Container{ | |
411 | Name: name, | |
412 | ID: generatedID, | |
413 | Created: time.Now(), | |
414 | Path: path, | |
415 | Args: args, | |
416 | Config: config.Config, | |
417 | HostConfig: config.HostConfig, | |
418 | State: docker.State{ | |
419 | Running: false, | |
420 | Pid: mathrand.Int() % 50000, | |
421 | ExitCode: 0, | |
422 | StartedAt: time.Now(), | |
423 | }, | |
424 | Image: config.Image, | |
425 | NetworkSettings: &docker.NetworkSettings{ | |
426 | IPAddress: fmt.Sprintf("172.16.42.%d", mathrand.Int()%250+2), | |
427 | IPPrefixLen: 24, | |
428 | Gateway: "172.16.42.1", | |
429 | Bridge: "docker0", | |
430 | Ports: ports, | |
431 | }, | |
432 | } | |
433 | s.cMut.Lock() | |
434 | if container.Name != "" { | |
435 | for _, c := range s.containers { | |
436 | if c.Name == container.Name { | |
437 | defer s.cMut.Unlock() | |
438 | http.Error(w, "there's already a container with this name", http.StatusConflict) | |
439 | return | |
440 | } | |
441 | } | |
442 | } | |
443 | s.containers = append(s.containers, &container) | |
444 | s.cMut.Unlock() | |
445 | w.WriteHeader(http.StatusCreated) | |
446 | s.notify(&container) | |
447 | ||
448 | json.NewEncoder(w).Encode(container) | |
449 | } | |
450 | ||
451 | func (s *DockerServer) generateID() string { | |
452 | var buf [16]byte | |
453 | rand.Read(buf[:]) | |
454 | return fmt.Sprintf("%x", buf) | |
455 | } | |
456 | ||
457 | func (s *DockerServer) renameContainer(w http.ResponseWriter, r *http.Request) { | |
458 | id := mux.Vars(r)["id"] | |
459 | container, index, err := s.findContainer(id) | |
460 | if err != nil { | |
461 | http.Error(w, err.Error(), http.StatusNotFound) | |
462 | return | |
463 | } | |
464 | copy := *container | |
465 | copy.Name = r.URL.Query().Get("name") | |
466 | s.cMut.Lock() | |
467 | defer s.cMut.Unlock() | |
468 | if s.containers[index].ID == copy.ID { | |
469 | s.containers[index] = © | |
470 | } | |
471 | w.WriteHeader(http.StatusNoContent) | |
472 | } | |
473 | ||
474 | func (s *DockerServer) inspectContainer(w http.ResponseWriter, r *http.Request) { | |
475 | id := mux.Vars(r)["id"] | |
476 | container, _, err := s.findContainer(id) | |
477 | if err != nil { | |
478 | http.Error(w, err.Error(), http.StatusNotFound) | |
479 | return | |
480 | } | |
481 | w.Header().Set("Content-Type", "application/json") | |
482 | w.WriteHeader(http.StatusOK) | |
483 | json.NewEncoder(w).Encode(container) | |
484 | } | |
485 | ||
486 | func (s *DockerServer) statsContainer(w http.ResponseWriter, r *http.Request) { | |
487 | id := mux.Vars(r)["id"] | |
488 | _, _, err := s.findContainer(id) | |
489 | if err != nil { | |
490 | http.Error(w, err.Error(), http.StatusNotFound) | |
491 | return | |
492 | } | |
493 | stream, _ := strconv.ParseBool(r.URL.Query().Get("stream")) | |
494 | callback := s.statsCallbacks[id] | |
495 | w.Header().Set("Content-Type", "application/json") | |
496 | w.WriteHeader(http.StatusOK) | |
497 | encoder := json.NewEncoder(w) | |
498 | for { | |
499 | var stats docker.Stats | |
500 | if callback != nil { | |
501 | stats = callback(id) | |
502 | } | |
503 | encoder.Encode(stats) | |
504 | if !stream { | |
505 | break | |
506 | } | |
507 | } | |
508 | } | |
509 | ||
510 | func (s *DockerServer) uploadToContainer(w http.ResponseWriter, r *http.Request) { | |
511 | id := mux.Vars(r)["id"] | |
512 | container, _, err := s.findContainer(id) | |
513 | if err != nil { | |
514 | http.Error(w, err.Error(), http.StatusNotFound) | |
515 | return | |
516 | } | |
517 | if !container.State.Running { | |
518 | w.WriteHeader(http.StatusInternalServerError) | |
519 | fmt.Fprintf(w, "Container %s is not running", id) | |
520 | return | |
521 | } | |
522 | path := r.URL.Query().Get("path") | |
523 | s.uploadedFiles[id] = path | |
524 | w.WriteHeader(http.StatusOK) | |
525 | } | |
526 | ||
527 | func (s *DockerServer) topContainer(w http.ResponseWriter, r *http.Request) { | |
528 | id := mux.Vars(r)["id"] | |
529 | container, _, err := s.findContainer(id) | |
530 | if err != nil { | |
531 | http.Error(w, err.Error(), http.StatusNotFound) | |
532 | return | |
533 | } | |
534 | if !container.State.Running { | |
535 | w.WriteHeader(http.StatusInternalServerError) | |
536 | fmt.Fprintf(w, "Container %s is not running", id) | |
537 | return | |
538 | } | |
539 | w.Header().Set("Content-Type", "application/json") | |
540 | w.WriteHeader(http.StatusOK) | |
541 | result := docker.TopResult{ | |
542 | Titles: []string{"UID", "PID", "PPID", "C", "STIME", "TTY", "TIME", "CMD"}, | |
543 | Processes: [][]string{ | |
544 | {"root", "7535", "7516", "0", "03:20", "?", "00:00:00", container.Path + " " + strings.Join(container.Args, " ")}, | |
545 | }, | |
546 | } | |
547 | json.NewEncoder(w).Encode(result) | |
548 | } | |
549 | ||
550 | func (s *DockerServer) startContainer(w http.ResponseWriter, r *http.Request) { | |
551 | id := mux.Vars(r)["id"] | |
552 | container, _, err := s.findContainer(id) | |
553 | if err != nil { | |
554 | http.Error(w, err.Error(), http.StatusNotFound) | |
555 | return | |
556 | } | |
557 | s.cMut.Lock() | |
558 | defer s.cMut.Unlock() | |
559 | defer r.Body.Close() | |
560 | var hostConfig docker.HostConfig | |
561 | err = json.NewDecoder(r.Body).Decode(&hostConfig) | |
562 | if err != nil { | |
563 | http.Error(w, err.Error(), http.StatusInternalServerError) | |
564 | return | |
565 | } | |
566 | container.HostConfig = &hostConfig | |
567 | if len(hostConfig.PortBindings) > 0 { | |
568 | ports := map[docker.Port][]docker.PortBinding{} | |
569 | for key, items := range hostConfig.PortBindings { | |
570 | bindings := make([]docker.PortBinding, len(items)) | |
571 | for i := range items { | |
572 | binding := docker.PortBinding{ | |
573 | HostIP: items[i].HostIP, | |
574 | HostPort: items[i].HostPort, | |
575 | } | |
576 | if binding.HostIP == "" { | |
577 | binding.HostIP = "0.0.0.0" | |
578 | } | |
579 | if binding.HostPort == "" { | |
580 | binding.HostPort = strconv.Itoa(mathrand.Int() % 0xffff) | |
581 | } | |
582 | bindings[i] = binding | |
583 | } | |
584 | ports[key] = bindings | |
585 | } | |
586 | container.NetworkSettings.Ports = ports | |
587 | } | |
588 | if container.State.Running { | |
589 | http.Error(w, "", http.StatusNotModified) | |
590 | return | |
591 | } | |
592 | container.State.Running = true | |
593 | s.notify(container) | |
594 | } | |
595 | ||
596 | func (s *DockerServer) stopContainer(w http.ResponseWriter, r *http.Request) { | |
597 | id := mux.Vars(r)["id"] | |
598 | container, _, err := s.findContainer(id) | |
599 | if err != nil { | |
600 | http.Error(w, err.Error(), http.StatusNotFound) | |
601 | return | |
602 | } | |
603 | s.cMut.Lock() | |
604 | defer s.cMut.Unlock() | |
605 | if !container.State.Running { | |
606 | http.Error(w, "Container not running", http.StatusBadRequest) | |
607 | return | |
608 | } | |
609 | w.WriteHeader(http.StatusNoContent) | |
610 | container.State.Running = false | |
611 | s.notify(container) | |
612 | } | |
613 | ||
614 | func (s *DockerServer) pauseContainer(w http.ResponseWriter, r *http.Request) { | |
615 | id := mux.Vars(r)["id"] | |
616 | container, _, err := s.findContainer(id) | |
617 | if err != nil { | |
618 | http.Error(w, err.Error(), http.StatusNotFound) | |
619 | return | |
620 | } | |
621 | s.cMut.Lock() | |
622 | defer s.cMut.Unlock() | |
623 | if container.State.Paused { | |
624 | http.Error(w, "Container already paused", http.StatusBadRequest) | |
625 | return | |
626 | } | |
627 | w.WriteHeader(http.StatusNoContent) | |
628 | container.State.Paused = true | |
629 | } | |
630 | ||
631 | func (s *DockerServer) unpauseContainer(w http.ResponseWriter, r *http.Request) { | |
632 | id := mux.Vars(r)["id"] | |
633 | container, _, err := s.findContainer(id) | |
634 | if err != nil { | |
635 | http.Error(w, err.Error(), http.StatusNotFound) | |
636 | return | |
637 | } | |
638 | s.cMut.Lock() | |
639 | defer s.cMut.Unlock() | |
640 | if !container.State.Paused { | |
641 | http.Error(w, "Container not paused", http.StatusBadRequest) | |
642 | return | |
643 | } | |
644 | w.WriteHeader(http.StatusNoContent) | |
645 | container.State.Paused = false | |
646 | } | |
647 | ||
648 | func (s *DockerServer) attachContainer(w http.ResponseWriter, r *http.Request) { | |
649 | id := mux.Vars(r)["id"] | |
650 | container, _, err := s.findContainer(id) | |
651 | if err != nil { | |
652 | http.Error(w, err.Error(), http.StatusNotFound) | |
653 | return | |
654 | } | |
655 | hijacker, ok := w.(http.Hijacker) | |
656 | if !ok { | |
657 | http.Error(w, "cannot hijack connection", http.StatusInternalServerError) | |
658 | return | |
659 | } | |
660 | w.Header().Set("Content-Type", "application/vnd.docker.raw-stream") | |
661 | w.WriteHeader(http.StatusOK) | |
662 | conn, _, err := hijacker.Hijack() | |
663 | if err != nil { | |
664 | http.Error(w, err.Error(), http.StatusInternalServerError) | |
665 | return | |
666 | } | |
667 | wg := sync.WaitGroup{} | |
668 | if r.URL.Query().Get("stdin") == "1" { | |
669 | wg.Add(1) | |
670 | go func() { | |
671 | ioutil.ReadAll(conn) | |
672 | wg.Done() | |
673 | }() | |
674 | } | |
675 | outStream := stdcopy.NewStdWriter(conn, stdcopy.Stdout) | |
676 | if container.State.Running { | |
677 | fmt.Fprintf(outStream, "Container is running\n") | |
678 | } else { | |
679 | fmt.Fprintf(outStream, "Container is not running\n") | |
680 | } | |
681 | fmt.Fprintln(outStream, "What happened?") | |
682 | fmt.Fprintln(outStream, "Something happened") | |
683 | wg.Wait() | |
684 | if r.URL.Query().Get("stream") == "1" { | |
685 | for { | |
686 | time.Sleep(1e6) | |
687 | s.cMut.RLock() | |
688 | if !container.State.Running { | |
689 | s.cMut.RUnlock() | |
690 | break | |
691 | } | |
692 | s.cMut.RUnlock() | |
693 | } | |
694 | } | |
695 | conn.Close() | |
696 | } | |
697 | ||
698 | func (s *DockerServer) waitContainer(w http.ResponseWriter, r *http.Request) { | |
699 | id := mux.Vars(r)["id"] | |
700 | container, _, err := s.findContainer(id) | |
701 | if err != nil { | |
702 | http.Error(w, err.Error(), http.StatusNotFound) | |
703 | return | |
704 | } | |
705 | for { | |
706 | time.Sleep(1e6) | |
707 | s.cMut.RLock() | |
708 | if !container.State.Running { | |
709 | s.cMut.RUnlock() | |
710 | break | |
711 | } | |
712 | s.cMut.RUnlock() | |
713 | } | |
714 | result := map[string]int{"StatusCode": container.State.ExitCode} | |
715 | json.NewEncoder(w).Encode(result) | |
716 | } | |
717 | ||
718 | func (s *DockerServer) removeContainer(w http.ResponseWriter, r *http.Request) { | |
719 | id := mux.Vars(r)["id"] | |
720 | force := r.URL.Query().Get("force") | |
721 | s.cMut.Lock() | |
722 | defer s.cMut.Unlock() | |
723 | container, index, err := s.findContainerWithLock(id, false) | |
724 | if err != nil { | |
725 | http.Error(w, err.Error(), http.StatusNotFound) | |
726 | return | |
727 | } | |
728 | if container.State.Running && force != "1" { | |
729 | msg := "Error: API error (406): Impossible to remove a running container, please stop it first" | |
730 | http.Error(w, msg, http.StatusInternalServerError) | |
731 | return | |
732 | } | |
733 | w.WriteHeader(http.StatusNoContent) | |
734 | s.containers[index] = s.containers[len(s.containers)-1] | |
735 | s.containers = s.containers[:len(s.containers)-1] | |
736 | } | |
737 | ||
738 | func (s *DockerServer) commitContainer(w http.ResponseWriter, r *http.Request) { | |
739 | id := r.URL.Query().Get("container") | |
740 | container, _, err := s.findContainer(id) | |
741 | if err != nil { | |
742 | http.Error(w, err.Error(), http.StatusNotFound) | |
743 | return | |
744 | } | |
745 | config := new(docker.Config) | |
746 | runConfig := r.URL.Query().Get("run") | |
747 | if runConfig != "" { | |
748 | err = json.Unmarshal([]byte(runConfig), config) | |
749 | if err != nil { | |
750 | http.Error(w, err.Error(), http.StatusBadRequest) | |
751 | return | |
752 | } | |
753 | } | |
754 | w.WriteHeader(http.StatusOK) | |
755 | image := docker.Image{ | |
756 | ID: "img-" + container.ID, | |
757 | Parent: container.Image, | |
758 | Container: container.ID, | |
759 | Comment: r.URL.Query().Get("m"), | |
760 | Author: r.URL.Query().Get("author"), | |
761 | Config: config, | |
762 | } | |
763 | repository := r.URL.Query().Get("repo") | |
764 | tag := r.URL.Query().Get("tag") | |
765 | s.iMut.Lock() | |
766 | s.images = append(s.images, image) | |
767 | if repository != "" { | |
768 | if tag != "" { | |
769 | repository += ":" + tag | |
770 | } | |
771 | s.imgIDs[repository] = image.ID | |
772 | } | |
773 | s.iMut.Unlock() | |
774 | fmt.Fprintf(w, `{"ID":%q}`, image.ID) | |
775 | } | |
776 | ||
777 | func (s *DockerServer) findContainer(idOrName string) (*docker.Container, int, error) { | |
778 | return s.findContainerWithLock(idOrName, true) | |
779 | } | |
780 | ||
781 | func (s *DockerServer) findContainerWithLock(idOrName string, shouldLock bool) (*docker.Container, int, error) { | |
782 | if shouldLock { | |
783 | s.cMut.RLock() | |
784 | defer s.cMut.RUnlock() | |
785 | } | |
786 | for i, container := range s.containers { | |
787 | if container.ID == idOrName || container.Name == idOrName { | |
788 | return container, i, nil | |
789 | } | |
790 | } | |
791 | return nil, -1, errors.New("No such container") | |
792 | } | |
793 | ||
794 | func (s *DockerServer) buildImage(w http.ResponseWriter, r *http.Request) { | |
795 | if ct := r.Header.Get("Content-Type"); ct == "application/tar" { | |
796 | gotDockerFile := false | |
797 | tr := tar.NewReader(r.Body) | |
798 | for { | |
799 | header, err := tr.Next() | |
800 | if err != nil { | |
801 | break | |
802 | } | |
803 | if header.Name == "Dockerfile" { | |
804 | gotDockerFile = true | |
805 | } | |
806 | } | |
807 | if !gotDockerFile { | |
808 | w.WriteHeader(http.StatusBadRequest) | |
809 | w.Write([]byte("miss Dockerfile")) | |
810 | return | |
811 | } | |
812 | } | |
813 | //we did not use that Dockerfile to build image cause we are a fake Docker daemon | |
814 | image := docker.Image{ | |
815 | ID: s.generateID(), | |
816 | Created: time.Now(), | |
817 | } | |
818 | ||
819 | query := r.URL.Query() | |
820 | repository := image.ID | |
821 | if t := query.Get("t"); t != "" { | |
822 | repository = t | |
823 | } | |
824 | s.iMut.Lock() | |
825 | s.images = append(s.images, image) | |
826 | s.imgIDs[repository] = image.ID | |
827 | s.iMut.Unlock() | |
828 | w.Write([]byte(fmt.Sprintf("Successfully built %s", image.ID))) | |
829 | } | |
830 | ||
831 | func (s *DockerServer) pullImage(w http.ResponseWriter, r *http.Request) { | |
832 | fromImageName := r.URL.Query().Get("fromImage") | |
833 | tag := r.URL.Query().Get("tag") | |
834 | image := docker.Image{ | |
835 | ID: s.generateID(), | |
836 | Config: &docker.Config{}, | |
837 | } | |
838 | s.iMut.Lock() | |
839 | s.images = append(s.images, image) | |
840 | if fromImageName != "" { | |
841 | if tag != "" { | |
842 | fromImageName = fmt.Sprintf("%s:%s", fromImageName, tag) | |
843 | } | |
844 | s.imgIDs[fromImageName] = image.ID | |
845 | } | |
846 | s.iMut.Unlock() | |
847 | } | |
848 | ||
849 | func (s *DockerServer) pushImage(w http.ResponseWriter, r *http.Request) { | |
850 | name := mux.Vars(r)["name"] | |
851 | tag := r.URL.Query().Get("tag") | |
852 | if tag != "" { | |
853 | name += ":" + tag | |
854 | } | |
855 | s.iMut.RLock() | |
856 | if _, ok := s.imgIDs[name]; !ok { | |
857 | s.iMut.RUnlock() | |
858 | http.Error(w, "No such image", http.StatusNotFound) | |
859 | return | |
860 | } | |
861 | s.iMut.RUnlock() | |
862 | fmt.Fprintln(w, "Pushing...") | |
863 | fmt.Fprintln(w, "Pushed") | |
864 | } | |
865 | ||
866 | func (s *DockerServer) tagImage(w http.ResponseWriter, r *http.Request) { | |
867 | name := mux.Vars(r)["name"] | |
868 | s.iMut.RLock() | |
869 | if _, ok := s.imgIDs[name]; !ok { | |
870 | s.iMut.RUnlock() | |
871 | http.Error(w, "No such image", http.StatusNotFound) | |
872 | return | |
873 | } | |
874 | s.iMut.RUnlock() | |
875 | s.iMut.Lock() | |
876 | defer s.iMut.Unlock() | |
877 | newRepo := r.URL.Query().Get("repo") | |
878 | newTag := r.URL.Query().Get("tag") | |
879 | if newTag != "" { | |
880 | newRepo += ":" + newTag | |
881 | } | |
882 | s.imgIDs[newRepo] = s.imgIDs[name] | |
883 | w.WriteHeader(http.StatusCreated) | |
884 | } | |
885 | ||
886 | func (s *DockerServer) removeImage(w http.ResponseWriter, r *http.Request) { | |
887 | id := mux.Vars(r)["id"] | |
888 | s.iMut.RLock() | |
889 | var tag string | |
890 | if img, ok := s.imgIDs[id]; ok { | |
891 | id, tag = img, id | |
892 | } | |
893 | var tags []string | |
894 | for tag, taggedID := range s.imgIDs { | |
895 | if taggedID == id { | |
896 | tags = append(tags, tag) | |
897 | } | |
898 | } | |
899 | s.iMut.RUnlock() | |
900 | _, index, err := s.findImageByID(id) | |
901 | if err != nil { | |
902 | http.Error(w, err.Error(), http.StatusNotFound) | |
903 | return | |
904 | } | |
905 | w.WriteHeader(http.StatusNoContent) | |
906 | s.iMut.Lock() | |
907 | defer s.iMut.Unlock() | |
908 | if len(tags) < 2 { | |
909 | s.images[index] = s.images[len(s.images)-1] | |
910 | s.images = s.images[:len(s.images)-1] | |
911 | } | |
912 | if tag != "" { | |
913 | delete(s.imgIDs, tag) | |
914 | } | |
915 | } | |
916 | ||
917 | func (s *DockerServer) inspectImage(w http.ResponseWriter, r *http.Request) { | |
918 | name := mux.Vars(r)["name"] | |
919 | s.iMut.RLock() | |
920 | defer s.iMut.RUnlock() | |
921 | if id, ok := s.imgIDs[name]; ok { | |
922 | for _, img := range s.images { | |
923 | if img.ID == id { | |
924 | w.Header().Set("Content-Type", "application/json") | |
925 | w.WriteHeader(http.StatusOK) | |
926 | json.NewEncoder(w).Encode(img) | |
927 | return | |
928 | } | |
929 | } | |
930 | } | |
931 | http.Error(w, "not found", http.StatusNotFound) | |
932 | } | |
933 | ||
934 | func (s *DockerServer) listEvents(w http.ResponseWriter, r *http.Request) { | |
935 | w.Header().Set("Content-Type", "application/json") | |
936 | var events [][]byte | |
937 | count := mathrand.Intn(20) | |
938 | for i := 0; i < count; i++ { | |
939 | data, err := json.Marshal(s.generateEvent()) | |
940 | if err != nil { | |
941 | w.WriteHeader(http.StatusInternalServerError) | |
942 | return | |
943 | } | |
944 | events = append(events, data) | |
945 | } | |
946 | w.WriteHeader(http.StatusOK) | |
947 | for _, d := range events { | |
948 | fmt.Fprintln(w, d) | |
949 | time.Sleep(time.Duration(mathrand.Intn(200)) * time.Millisecond) | |
950 | } | |
951 | } | |
952 | ||
953 | func (s *DockerServer) pingDocker(w http.ResponseWriter, r *http.Request) { | |
954 | w.WriteHeader(http.StatusOK) | |
955 | } | |
956 | ||
957 | func (s *DockerServer) generateEvent() *docker.APIEvents { | |
958 | var eventType string | |
959 | switch mathrand.Intn(4) { | |
960 | case 0: | |
961 | eventType = "create" | |
962 | case 1: | |
963 | eventType = "start" | |
964 | case 2: | |
965 | eventType = "stop" | |
966 | case 3: | |
967 | eventType = "destroy" | |
968 | } | |
969 | return &docker.APIEvents{ | |
970 | ID: s.generateID(), | |
971 | Status: eventType, | |
972 | From: "mybase:latest", | |
973 | Time: time.Now().Unix(), | |
974 | } | |
975 | } | |
976 | ||
977 | func (s *DockerServer) loadImage(w http.ResponseWriter, r *http.Request) { | |
978 | w.WriteHeader(http.StatusOK) | |
979 | } | |
980 | ||
981 | func (s *DockerServer) getImage(w http.ResponseWriter, r *http.Request) { | |
982 | w.WriteHeader(http.StatusOK) | |
983 | w.Header().Set("Content-Type", "application/tar") | |
984 | } | |
985 | ||
986 | func (s *DockerServer) createExecContainer(w http.ResponseWriter, r *http.Request) { | |
987 | id := mux.Vars(r)["id"] | |
988 | container, _, err := s.findContainer(id) | |
989 | if err != nil { | |
990 | http.Error(w, err.Error(), http.StatusNotFound) | |
991 | return | |
992 | } | |
993 | ||
994 | execID := s.generateID() | |
995 | container.ExecIDs = append(container.ExecIDs, execID) | |
996 | ||
997 | exec := docker.ExecInspect{ | |
998 | ID: execID, | |
999 | Container: *container, | |
1000 | } | |
1001 | ||
1002 | var params docker.CreateExecOptions | |
1003 | err = json.NewDecoder(r.Body).Decode(¶ms) | |
1004 | if err != nil { | |
1005 | http.Error(w, err.Error(), http.StatusInternalServerError) | |
1006 | return | |
1007 | } | |
1008 | if len(params.Cmd) > 0 { | |
1009 | exec.ProcessConfig.EntryPoint = params.Cmd[0] | |
1010 | if len(params.Cmd) > 1 { | |
1011 | exec.ProcessConfig.Arguments = params.Cmd[1:] | |
1012 | } | |
1013 | } | |
1014 | ||
1015 | exec.ProcessConfig.User = params.User | |
1016 | exec.ProcessConfig.Tty = params.Tty | |
1017 | ||
1018 | s.execMut.Lock() | |
1019 | s.execs = append(s.execs, &exec) | |
1020 | s.execMut.Unlock() | |
1021 | w.WriteHeader(http.StatusOK) | |
1022 | w.Header().Set("Content-Type", "application/json") | |
1023 | json.NewEncoder(w).Encode(map[string]string{"Id": exec.ID}) | |
1024 | } | |
1025 | ||
1026 | func (s *DockerServer) startExecContainer(w http.ResponseWriter, r *http.Request) { | |
1027 | id := mux.Vars(r)["id"] | |
1028 | if exec, err := s.getExec(id, false); err == nil { | |
1029 | s.execMut.Lock() | |
1030 | exec.Running = true | |
1031 | s.execMut.Unlock() | |
1032 | if callback, ok := s.execCallbacks[id]; ok { | |
1033 | callback() | |
1034 | delete(s.execCallbacks, id) | |
1035 | } else if callback, ok := s.execCallbacks["*"]; ok { | |
1036 | callback() | |
1037 | delete(s.execCallbacks, "*") | |
1038 | } | |
1039 | s.execMut.Lock() | |
1040 | exec.Running = false | |
1041 | s.execMut.Unlock() | |
1042 | w.WriteHeader(http.StatusOK) | |
1043 | return | |
1044 | } | |
1045 | w.WriteHeader(http.StatusNotFound) | |
1046 | } | |
1047 | ||
1048 | func (s *DockerServer) resizeExecContainer(w http.ResponseWriter, r *http.Request) { | |
1049 | id := mux.Vars(r)["id"] | |
1050 | if _, err := s.getExec(id, false); err == nil { | |
1051 | w.WriteHeader(http.StatusOK) | |
1052 | return | |
1053 | } | |
1054 | w.WriteHeader(http.StatusNotFound) | |
1055 | } | |
1056 | ||
1057 | func (s *DockerServer) inspectExecContainer(w http.ResponseWriter, r *http.Request) { | |
1058 | id := mux.Vars(r)["id"] | |
1059 | if exec, err := s.getExec(id, true); err == nil { | |
1060 | w.WriteHeader(http.StatusOK) | |
1061 | w.Header().Set("Content-Type", "application/json") | |
1062 | json.NewEncoder(w).Encode(exec) | |
1063 | return | |
1064 | } | |
1065 | w.WriteHeader(http.StatusNotFound) | |
1066 | } | |
1067 | ||
1068 | func (s *DockerServer) getExec(id string, copy bool) (*docker.ExecInspect, error) { | |
1069 | s.execMut.RLock() | |
1070 | defer s.execMut.RUnlock() | |
1071 | for _, exec := range s.execs { | |
1072 | if exec.ID == id { | |
1073 | if copy { | |
1074 | cp := *exec | |
1075 | exec = &cp | |
1076 | } | |
1077 | return exec, nil | |
1078 | } | |
1079 | } | |
1080 | return nil, errors.New("exec not found") | |
1081 | } | |
1082 | ||
1083 | func (s *DockerServer) findNetwork(idOrName string) (*docker.Network, int, error) { | |
1084 | s.netMut.RLock() | |
1085 | defer s.netMut.RUnlock() | |
1086 | for i, network := range s.networks { | |
1087 | if network.ID == idOrName || network.Name == idOrName { | |
1088 | return network, i, nil | |
1089 | } | |
1090 | } | |
1091 | return nil, -1, errors.New("No such network") | |
1092 | } | |
1093 | ||
1094 | func (s *DockerServer) listNetworks(w http.ResponseWriter, r *http.Request) { | |
1095 | s.netMut.RLock() | |
1096 | result := make([]docker.Network, 0, len(s.networks)) | |
1097 | for _, network := range s.networks { | |
1098 | result = append(result, *network) | |
1099 | } | |
1100 | s.netMut.RUnlock() | |
1101 | w.Header().Set("Content-Type", "application/json") | |
1102 | w.WriteHeader(http.StatusOK) | |
1103 | json.NewEncoder(w).Encode(result) | |
1104 | } | |
1105 | ||
1106 | func (s *DockerServer) networkInfo(w http.ResponseWriter, r *http.Request) { | |
1107 | id := mux.Vars(r)["id"] | |
1108 | network, _, err := s.findNetwork(id) | |
1109 | if err != nil { | |
1110 | http.Error(w, err.Error(), http.StatusNotFound) | |
1111 | return | |
1112 | } | |
1113 | w.Header().Set("Content-Type", "application/json") | |
1114 | w.WriteHeader(http.StatusOK) | |
1115 | json.NewEncoder(w).Encode(network) | |
1116 | } | |
1117 | ||
1118 | // isValidName validates configuration objects supported by libnetwork | |
1119 | func isValidName(name string) bool { | |
1120 | if name == "" || strings.Contains(name, ".") { | |
1121 | return false | |
1122 | } | |
1123 | return true | |
1124 | } | |
1125 | ||
1126 | func (s *DockerServer) createNetwork(w http.ResponseWriter, r *http.Request) { | |
1127 | var config *docker.CreateNetworkOptions | |
1128 | defer r.Body.Close() | |
1129 | err := json.NewDecoder(r.Body).Decode(&config) | |
1130 | if err != nil { | |
1131 | http.Error(w, err.Error(), http.StatusBadRequest) | |
1132 | return | |
1133 | } | |
1134 | if !isValidName(config.Name) { | |
1135 | http.Error(w, "Invalid network name", http.StatusBadRequest) | |
1136 | return | |
1137 | } | |
1138 | if n, _, _ := s.findNetwork(config.Name); n != nil { | |
1139 | http.Error(w, "network already exists", http.StatusForbidden) | |
1140 | return | |
1141 | } | |
1142 | ||
1143 | generatedID := s.generateID() | |
1144 | network := docker.Network{ | |
1145 | Name: config.Name, | |
1146 | ID: generatedID, | |
1147 | Driver: config.Driver, | |
1148 | } | |
1149 | s.netMut.Lock() | |
1150 | s.networks = append(s.networks, &network) | |
1151 | s.netMut.Unlock() | |
1152 | w.WriteHeader(http.StatusCreated) | |
1153 | var c = struct{ ID string }{ID: network.ID} | |
1154 | json.NewEncoder(w).Encode(c) | |
1155 | } | |
1156 | ||
1157 | func (s *DockerServer) listVolumes(w http.ResponseWriter, r *http.Request) { | |
1158 | s.volMut.RLock() | |
1159 | result := make([]docker.Volume, 0, len(s.volStore)) | |
1160 | for _, volumeCounter := range s.volStore { | |
1161 | result = append(result, volumeCounter.volume) | |
1162 | } | |
1163 | s.volMut.RUnlock() | |
1164 | w.Header().Set("Content-Type", "application/json") | |
1165 | w.WriteHeader(http.StatusOK) | |
1166 | json.NewEncoder(w).Encode(result) | |
1167 | } | |
1168 | ||
1169 | func (s *DockerServer) createVolume(w http.ResponseWriter, r *http.Request) { | |
1170 | var data struct { | |
1171 | *docker.CreateVolumeOptions | |
1172 | } | |
1173 | defer r.Body.Close() | |
1174 | err := json.NewDecoder(r.Body).Decode(&data) | |
1175 | if err != nil { | |
1176 | http.Error(w, err.Error(), http.StatusBadRequest) | |
1177 | return | |
1178 | } | |
1179 | volume := &docker.Volume{ | |
1180 | Name: data.CreateVolumeOptions.Name, | |
1181 | Driver: data.CreateVolumeOptions.Driver, | |
1182 | } | |
1183 | // If the name is not specified, generate one. Just using generateID for now | |
1184 | if len(volume.Name) == 0 { | |
1185 | volume.Name = s.generateID() | |
1186 | } | |
1187 | // If driver is not specified, use local | |
1188 | if len(volume.Driver) == 0 { | |
1189 | volume.Driver = "local" | |
1190 | } | |
1191 | // Mount point is a default one with name | |
1192 | volume.Mountpoint = "/var/lib/docker/volumes/" + volume.Name | |
1193 | ||
1194 | // If the volume already exists, don't re-add it. | |
1195 | exists := false | |
1196 | s.volMut.Lock() | |
1197 | if s.volStore != nil { | |
1198 | _, exists = s.volStore[volume.Name] | |
1199 | } else { | |
1200 | // No volumes, create volStore | |
1201 | s.volStore = make(map[string]*volumeCounter) | |
1202 | } | |
1203 | if !exists { | |
1204 | s.volStore[volume.Name] = &volumeCounter{ | |
1205 | volume: *volume, | |
1206 | count: 0, | |
1207 | } | |
1208 | } | |
1209 | s.volMut.Unlock() | |
1210 | w.WriteHeader(http.StatusCreated) | |
1211 | json.NewEncoder(w).Encode(volume) | |
1212 | } | |
1213 | ||
1214 | func (s *DockerServer) inspectVolume(w http.ResponseWriter, r *http.Request) { | |
1215 | s.volMut.RLock() | |
1216 | defer s.volMut.RUnlock() | |
1217 | name := mux.Vars(r)["name"] | |
1218 | vol, err := s.findVolume(name) | |
1219 | if err != nil { | |
1220 | http.Error(w, err.Error(), http.StatusNotFound) | |
1221 | return | |
1222 | } | |
1223 | w.Header().Set("Content-Type", "application/json") | |
1224 | w.WriteHeader(http.StatusOK) | |
1225 | json.NewEncoder(w).Encode(vol.volume) | |
1226 | } | |
1227 | ||
1228 | func (s *DockerServer) findVolume(name string) (*volumeCounter, error) { | |
1229 | vol, ok := s.volStore[name] | |
1230 | if !ok { | |
1231 | return nil, errors.New("no such volume") | |
1232 | } | |
1233 | return vol, nil | |
1234 | } | |
1235 | ||
1236 | func (s *DockerServer) removeVolume(w http.ResponseWriter, r *http.Request) { | |
1237 | s.volMut.Lock() | |
1238 | defer s.volMut.Unlock() | |
1239 | name := mux.Vars(r)["name"] | |
1240 | vol, err := s.findVolume(name) | |
1241 | if err != nil { | |
1242 | http.Error(w, err.Error(), http.StatusNotFound) | |
1243 | return | |
1244 | } | |
1245 | if vol.count != 0 { | |
1246 | http.Error(w, "volume in use and cannot be removed", http.StatusConflict) | |
1247 | return | |
1248 | } | |
1249 | s.volStore[vol.volume.Name] = nil | |
1250 | w.WriteHeader(http.StatusNoContent) | |
1251 | } | |
1252 | ||
1253 | func (s *DockerServer) infoDocker(w http.ResponseWriter, r *http.Request) { | |
1254 | s.cMut.RLock() | |
1255 | defer s.cMut.RUnlock() | |
1256 | s.iMut.RLock() | |
1257 | defer s.iMut.RUnlock() | |
1258 | var running, stopped, paused int | |
1259 | for _, c := range s.containers { | |
1260 | if c.State.Running { | |
1261 | running++ | |
1262 | } else { | |
1263 | stopped++ | |
1264 | } | |
1265 | if c.State.Paused { | |
1266 | paused++ | |
1267 | } | |
1268 | } | |
1269 | envs := map[string]interface{}{ | |
1270 | "ID": "AAAA:XXXX:0000:BBBB:AAAA:XXXX:0000:BBBB:AAAA:XXXX:0000:BBBB", | |
1271 | "Containers": len(s.containers), | |
1272 | "ContainersRunning": running, | |
1273 | "ContainersPaused": paused, | |
1274 | "ContainersStopped": stopped, | |
1275 | "Images": len(s.images), | |
1276 | "Driver": "aufs", | |
1277 | "DriverStatus": [][]string{}, | |
1278 | "SystemStatus": nil, | |
1279 | "Plugins": map[string]interface{}{ | |
1280 | "Volume": []string{ | |
1281 | "local", | |
1282 | }, | |
1283 | "Network": []string{ | |
1284 | "bridge", | |
1285 | "null", | |
1286 | "host", | |
1287 | }, | |
1288 | "Authorization": nil, | |
1289 | }, | |
1290 | "MemoryLimit": true, | |
1291 | "SwapLimit": false, | |
1292 | "CpuCfsPeriod": true, | |
1293 | "CpuCfsQuota": true, | |
1294 | "CPUShares": true, | |
1295 | "CPUSet": true, | |
1296 | "IPv4Forwarding": true, | |
1297 | "BridgeNfIptables": true, | |
1298 | "BridgeNfIp6tables": true, | |
1299 | "Debug": false, | |
1300 | "NFd": 79, | |
1301 | "OomKillDisable": true, | |
1302 | "NGoroutines": 101, | |
1303 | "SystemTime": "2016-02-25T18:13:10.25870078Z", | |
1304 | "ExecutionDriver": "native-0.2", | |
1305 | "LoggingDriver": "json-file", | |
1306 | "NEventsListener": 0, | |
1307 | "KernelVersion": "3.13.0-77-generic", | |
1308 | "OperatingSystem": "Ubuntu 14.04.3 LTS", | |
1309 | "OSType": "linux", | |
1310 | "Architecture": "x86_64", | |
1311 | "IndexServerAddress": "https://index.docker.io/v1/", | |
1312 | "RegistryConfig": map[string]interface{}{ | |
1313 | "InsecureRegistryCIDRs": []string{}, | |
1314 | "IndexConfigs": map[string]interface{}{}, | |
1315 | "Mirrors": nil, | |
1316 | }, | |
1317 | "InitSha1": "e2042dbb0fcf49bb9da199186d9a5063cda92a01", | |
1318 | "InitPath": "/usr/lib/docker/dockerinit", | |
1319 | "NCPU": 1, | |
1320 | "MemTotal": 2099204096, | |
1321 | "DockerRootDir": "/var/lib/docker", | |
1322 | "HttpProxy": "", | |
1323 | "HttpsProxy": "", | |
1324 | "NoProxy": "", | |
1325 | "Name": "vagrant-ubuntu-trusty-64", | |
1326 | "Labels": nil, | |
1327 | "ExperimentalBuild": false, | |
1328 | "ServerVersion": "1.10.1", | |
1329 | "ClusterStore": "", | |
1330 | "ClusterAdvertise": "", | |
1331 | } | |
1332 | w.WriteHeader(http.StatusOK) | |
1333 | json.NewEncoder(w).Encode(envs) | |
1334 | } |