Initial release
Dmitry Smirnov
8 years ago
0 | Apache License | |
1 | Version 2.0, January 2004 | |
2 | http://www.apache.org/licenses/ | |
3 | ||
4 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION | |
5 | ||
6 | 1. Definitions. | |
7 | ||
8 | "License" shall mean the terms and conditions for use, reproduction, | |
9 | and distribution as defined by Sections 1 through 9 of this document. | |
10 | ||
11 | "Licensor" shall mean the copyright owner or entity authorized by | |
12 | the copyright owner that is granting the License. | |
13 | ||
14 | "Legal Entity" shall mean the union of the acting entity and all | |
15 | other entities that control, are controlled by, or are under common | |
16 | control with that entity. For the purposes of this definition, | |
17 | "control" means (i) the power, direct or indirect, to cause the | |
18 | direction or management of such entity, whether by contract or | |
19 | otherwise, or (ii) ownership of fifty percent (50%) or more of the | |
20 | outstanding shares, or (iii) beneficial ownership of such entity. | |
21 | ||
22 | "You" (or "Your") shall mean an individual or Legal Entity | |
23 | exercising permissions granted by this License. | |
24 | ||
25 | "Source" form shall mean the preferred form for making modifications, | |
26 | including but not limited to software source code, documentation | |
27 | source, and configuration files. | |
28 | ||
29 | "Object" form shall mean any form resulting from mechanical | |
30 | transformation or translation of a Source form, including but | |
31 | not limited to compiled object code, generated documentation, | |
32 | and conversions to other media types. | |
33 | ||
34 | "Work" shall mean the work of authorship, whether in Source or | |
35 | Object form, made available under the License, as indicated by a | |
36 | copyright notice that is included in or attached to the work | |
37 | (an example is provided in the Appendix below). | |
38 | ||
39 | "Derivative Works" shall mean any work, whether in Source or Object | |
40 | form, that is based on (or derived from) the Work and for which the | |
41 | editorial revisions, annotations, elaborations, or other modifications | |
42 | represent, as a whole, an original work of authorship. For the purposes | |
43 | of this License, Derivative Works shall not include works that remain | |
44 | separable from, or merely link (or bind by name) to the interfaces of, | |
45 | the Work and Derivative Works thereof. | |
46 | ||
47 | "Contribution" shall mean any work of authorship, including | |
48 | the original version of the Work and any modifications or additions | |
49 | to that Work or Derivative Works thereof, that is intentionally | |
50 | submitted to Licensor for inclusion in the Work by the copyright owner | |
51 | or by an individual or Legal Entity authorized to submit on behalf of | |
52 | the copyright owner. For the purposes of this definition, "submitted" | |
53 | means any form of electronic, verbal, or written communication sent | |
54 | to the Licensor or its representatives, including but not limited to | |
55 | communication on electronic mailing lists, source code control systems, | |
56 | and issue tracking systems that are managed by, or on behalf of, the | |
57 | Licensor for the purpose of discussing and improving the Work, but | |
58 | excluding communication that is conspicuously marked or otherwise | |
59 | designated in writing by the copyright owner as "Not a Contribution." | |
60 | ||
61 | "Contributor" shall mean Licensor and any individual or Legal Entity | |
62 | on behalf of whom a Contribution has been received by Licensor and | |
63 | subsequently incorporated within the Work. | |
64 | ||
65 | 2. Grant of Copyright License. Subject to the terms and conditions of | |
66 | this License, each Contributor hereby grants to You a perpetual, | |
67 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable | |
68 | copyright license to reproduce, prepare Derivative Works of, | |
69 | publicly display, publicly perform, sublicense, and distribute the | |
70 | Work and such Derivative Works in Source or Object form. | |
71 | ||
72 | 3. Grant of Patent License. Subject to the terms and conditions of | |
73 | this License, each Contributor hereby grants to You a perpetual, | |
74 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable | |
75 | (except as stated in this section) patent license to make, have made, | |
76 | use, offer to sell, sell, import, and otherwise transfer the Work, | |
77 | where such license applies only to those patent claims licensable | |
78 | by such Contributor that are necessarily infringed by their | |
79 | Contribution(s) alone or by combination of their Contribution(s) | |
80 | with the Work to which such Contribution(s) was submitted. If You | |
81 | institute patent litigation against any entity (including a | |
82 | cross-claim or counterclaim in a lawsuit) alleging that the Work | |
83 | or a Contribution incorporated within the Work constitutes direct | |
84 | or contributory patent infringement, then any patent licenses | |
85 | granted to You under this License for that Work shall terminate | |
86 | as of the date such litigation is filed. | |
87 | ||
88 | 4. Redistribution. You may reproduce and distribute copies of the | |
89 | Work or Derivative Works thereof in any medium, with or without | |
90 | modifications, and in Source or Object form, provided that You | |
91 | meet the following conditions: | |
92 | ||
93 | (a) You must give any other recipients of the Work or | |
94 | Derivative Works a copy of this License; and | |
95 | ||
96 | (b) You must cause any modified files to carry prominent notices | |
97 | stating that You changed the files; and | |
98 | ||
99 | (c) You must retain, in the Source form of any Derivative Works | |
100 | that You distribute, all copyright, patent, trademark, and | |
101 | attribution notices from the Source form of the Work, | |
102 | excluding those notices that do not pertain to any part of | |
103 | the Derivative Works; and | |
104 | ||
105 | (d) If the Work includes a "NOTICE" text file as part of its | |
106 | distribution, then any Derivative Works that You distribute must | |
107 | include a readable copy of the attribution notices contained | |
108 | within such NOTICE file, excluding those notices that do not | |
109 | pertain to any part of the Derivative Works, in at least one | |
110 | of the following places: within a NOTICE text file distributed | |
111 | as part of the Derivative Works; within the Source form or | |
112 | documentation, if provided along with the Derivative Works; or, | |
113 | within a display generated by the Derivative Works, if and | |
114 | wherever such third-party notices normally appear. The contents | |
115 | of the NOTICE file are for informational purposes only and | |
116 | do not modify the License. You may add Your own attribution | |
117 | notices within Derivative Works that You distribute, alongside | |
118 | or as an addendum to the NOTICE text from the Work, provided | |
119 | that such additional attribution notices cannot be construed | |
120 | as modifying the License. | |
121 | ||
122 | You may add Your own copyright statement to Your modifications and | |
123 | may provide additional or different license terms and conditions | |
124 | for use, reproduction, or distribution of Your modifications, or | |
125 | for any such Derivative Works as a whole, provided Your use, | |
126 | reproduction, and distribution of the Work otherwise complies with | |
127 | the conditions stated in this License. | |
128 | ||
129 | 5. Submission of Contributions. Unless You explicitly state otherwise, | |
130 | any Contribution intentionally submitted for inclusion in the Work | |
131 | by You to the Licensor shall be under the terms and conditions of | |
132 | this License, without any additional terms or conditions. | |
133 | Notwithstanding the above, nothing herein shall supersede or modify | |
134 | the terms of any separate license agreement you may have executed | |
135 | with Licensor regarding such Contributions. | |
136 | ||
137 | 6. Trademarks. This License does not grant permission to use the trade | |
138 | names, trademarks, service marks, or product names of the Licensor, | |
139 | except as required for reasonable and customary use in describing the | |
140 | origin of the Work and reproducing the content of the NOTICE file. | |
141 | ||
142 | 7. Disclaimer of Warranty. Unless required by applicable law or | |
143 | agreed to in writing, Licensor provides the Work (and each | |
144 | Contributor provides its Contributions) on an "AS IS" BASIS, | |
145 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | |
146 | implied, including, without limitation, any warranties or conditions | |
147 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A | |
148 | PARTICULAR PURPOSE. You are solely responsible for determining the | |
149 | appropriateness of using or redistributing the Work and assume any | |
150 | risks associated with Your exercise of permissions under this License. | |
151 | ||
152 | 8. Limitation of Liability. In no event and under no legal theory, | |
153 | whether in tort (including negligence), contract, or otherwise, | |
154 | unless required by applicable law (such as deliberate and grossly | |
155 | negligent acts) or agreed to in writing, shall any Contributor be | |
156 | liable to You for damages, including any direct, indirect, special, | |
157 | incidental, or consequential damages of any character arising as a | |
158 | result of this License or out of the use or inability to use the | |
159 | Work (including but not limited to damages for loss of goodwill, | |
160 | work stoppage, computer failure or malfunction, or any and all | |
161 | other commercial damages or losses), even if such Contributor | |
162 | has been advised of the possibility of such damages. | |
163 | ||
164 | 9. Accepting Warranty or Additional Liability. While redistributing | |
165 | the Work or Derivative Works thereof, You may choose to offer, | |
166 | and charge a fee for, acceptance of support, warranty, indemnity, | |
167 | or other liability obligations and/or rights consistent with this | |
168 | License. However, in accepting such obligations, You may act only | |
169 | on Your own behalf and on Your sole responsibility, not on behalf | |
170 | of any other Contributor, and only if You agree to indemnify, | |
171 | defend, and hold each Contributor harmless for any liability | |
172 | incurred by, or claims asserted against, such Contributor by reason | |
173 | of your accepting any such warranty or additional liability. | |
174 | ||
175 | END OF TERMS AND CONDITIONS | |
176 | ||
177 | APPENDIX: How to apply the Apache License to your work. | |
178 | ||
179 | To apply the Apache License to your work, attach the following | |
180 | boilerplate notice, with the fields enclosed by brackets "{}" | |
181 | replaced with your own identifying information. (Don't include | |
182 | the brackets!) The text should be enclosed in the appropriate | |
183 | comment syntax for the file format. We also recommend that a | |
184 | file or class name and description of purpose be included on the | |
185 | same "printed page" as the copyright notice for easier | |
186 | identification within third-party archives. | |
187 | ||
188 | Copyright 2014 Sam Alba <sam.alba@gmail.com> | |
189 | ||
190 | Licensed under the Apache License, Version 2.0 (the "License"); | |
191 | you may not use this file except in compliance with the License. | |
192 | You may obtain a copy of the License at | |
193 | ||
194 | http://www.apache.org/licenses/LICENSE-2.0 | |
195 | ||
196 | Unless required by applicable law or agreed to in writing, software | |
197 | distributed under the License is distributed on an "AS IS" BASIS, | |
198 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
199 | See the License for the specific language governing permissions and | |
200 | limitations under the License. | |
201 |
0 | Docker client library in Go | |
1 | =========================== | |
2 | [![GoDoc](http://godoc.org/github.com/samalba/dockerclient?status.png)](http://godoc.org/github.com/samalba/dockerclient) | |
3 | ||
4 | Well maintained docker client library. | |
5 | ||
6 | # How to use it? | |
7 | ||
8 | Here is an example showing how to use it: | |
9 | ||
10 | ```go | |
11 | package main | |
12 | ||
13 | import ( | |
14 | "github.com/samalba/dockerclient" | |
15 | "log" | |
16 | "time" | |
17 | "os" | |
18 | ) | |
19 | ||
20 | // Callback used to listen to Docker's events | |
21 | func eventCallback(event *dockerclient.Event, ec chan error, args ...interface{}) { | |
22 | log.Printf("Received event: %#v\n", *event) | |
23 | } | |
24 | ||
25 | func main() { | |
26 | // Init the client | |
27 | docker, _ := dockerclient.NewDockerClient("unix:///var/run/docker.sock", nil) | |
28 | ||
29 | // Get only running containers | |
30 | containers, err := docker.ListContainers(false, false, "") | |
31 | if err != nil { | |
32 | log.Fatal(err) | |
33 | } | |
34 | for _, c := range containers { | |
35 | log.Println(c.Id, c.Names) | |
36 | } | |
37 | ||
38 | // Inspect the first container returned | |
39 | if len(containers) > 0 { | |
40 | id := containers[0].Id | |
41 | info, _ := docker.InspectContainer(id) | |
42 | log.Println(info) | |
43 | } | |
44 | ||
45 | // Build a docker image | |
46 | // some.tar contains the build context (Dockerfile any any files it needs to add/copy) | |
47 | dockerBuildContext, err := os.Open("some.tar") | |
48 | defer dockerBuildContext.Close() | |
49 | buildImageConfig := &dockerclient.BuildImage{ | |
50 | Context: dockerBuildContext, | |
51 | RepoName: "your_image_name", | |
52 | SuppressOutput: false, | |
53 | } | |
54 | reader, err := docker.BuildImage(buildImageConfig) | |
55 | if err != nil { | |
56 | log.Fatal(err) | |
57 | } | |
58 | ||
59 | // Create a container | |
60 | containerConfig := &dockerclient.ContainerConfig{ | |
61 | Image: "ubuntu:14.04", | |
62 | Cmd: []string{"bash"}, | |
63 | AttachStdin: true, | |
64 | Tty: true} | |
65 | containerId, err := docker.CreateContainer(containerConfig, "foobar") | |
66 | if err != nil { | |
67 | log.Fatal(err) | |
68 | } | |
69 | ||
70 | // Start the container | |
71 | hostConfig := &dockerclient.HostConfig{} | |
72 | err = docker.StartContainer(containerId, hostConfig) | |
73 | if err != nil { | |
74 | log.Fatal(err) | |
75 | } | |
76 | ||
77 | // Stop the container (with 5 seconds timeout) | |
78 | docker.StopContainer(containerId, 5) | |
79 | ||
80 | // Listen to events | |
81 | docker.StartMonitorEvents(eventCallback, nil) | |
82 | ||
83 | // Hold the execution to look at the events coming | |
84 | time.Sleep(3600 * time.Second) | |
85 | } | |
86 | ``` | |
87 | ||
88 | # Maintainers | |
89 | ||
90 | List of people you can ping for feedback on Pull Requests or any questions. | |
91 | ||
92 | - [Sam Alba](https://github.com/samalba) | |
93 | - [Michael Crosby](https://github.com/crosbymichael) | |
94 | - [Andrea Luzzardi](https://github.com/aluzzardi) | |
95 | - [Victor Vieux](https://github.com/vieux) | |
96 | - [Evan Hazlett](https://github.com/ehazlett) | |
97 | - [Donald Huang](https://github.com/donhcd) |
0 | package dockerclient | |
1 | ||
2 | import ( | |
3 | "bytes" | |
4 | "encoding/base64" | |
5 | "encoding/json" | |
6 | ) | |
7 | ||
8 | // AuthConfig hold parameters for authenticating with the docker registry | |
9 | type AuthConfig struct { | |
10 | Username string `json:"username,omitempty"` | |
11 | Password string `json:"password,omitempty"` | |
12 | Email string `json:"email,omitempty"` | |
13 | } | |
14 | ||
15 | // encode the auth configuration struct into base64 for the X-Registry-Auth header | |
16 | func (c *AuthConfig) encode() (string, error) { | |
17 | var buf bytes.Buffer | |
18 | if err := json.NewEncoder(&buf).Encode(c); err != nil { | |
19 | return "", err | |
20 | } | |
21 | return base64.URLEncoding.EncodeToString(buf.Bytes()), nil | |
22 | } | |
23 | ||
24 | // ConfigFile holds parameters for authenticating during a BuildImage request | |
25 | type ConfigFile struct { | |
26 | Configs map[string]AuthConfig `json:"configs,omitempty"` | |
27 | rootPath string | |
28 | } | |
29 | ||
30 | // encode the configuration struct into base64 for the X-Registry-Config header | |
31 | func (c *ConfigFile) encode() (string, error) { | |
32 | var buf bytes.Buffer | |
33 | if err := json.NewEncoder(&buf).Encode(c); err != nil { | |
34 | return "", err | |
35 | } | |
36 | return base64.URLEncoding.EncodeToString(buf.Bytes()), nil | |
37 | } |
0 | package dockerclient | |
1 | ||
2 | import ( | |
3 | "testing" | |
4 | ) | |
5 | ||
6 | func TestAuthEncode(t *testing.T) { | |
7 | a := AuthConfig{Username: "foo", Password: "password", Email: "bar@baz.com"} | |
8 | expected := "eyJ1c2VybmFtZSI6ImZvbyIsInBhc3N3b3JkIjoicGFzc3dvcmQiLCJlbWFpbCI6ImJhckBiYXouY29tIn0K" | |
9 | got, _ := a.encode() | |
10 | ||
11 | if expected != got { | |
12 | t.Errorf("testAuthEncode failed. Expected [%s] got [%s]", expected, got) | |
13 | } | |
14 | } |
0 | golang-github-samalba-dockerclient (0.0~git20150905.0.77b723e-1) unstable; urgency=medium | |
1 | ||
2 | * Initial release (Closes: TODO) | |
3 | ||
4 | -- Dmitry Smirnov <onlyjob@debian.org> Tue, 08 Sep 2015 07:20:24 +1000 |
0 | 9 |
0 | Source: golang-github-samalba-dockerclient | |
1 | Section: devel | |
2 | Priority: extra | |
3 | Maintainer: pkg-go <pkg-go-maintainers@lists.alioth.debian.org> | |
4 | Uploaders: Dmitry Smirnov <onlyjob@debian.org> | |
5 | Build-Depends: debhelper (>= 9), | |
6 | dh-golang, | |
7 | golang-go, | |
8 | golang-github-docker-docker-dev | golang-docker-dev, | |
9 | golang-github-gorilla-mux-dev, | |
10 | golang-github-stretchr-testify-dev | golang-testify-dev | |
11 | Standards-Version: 3.9.6 | |
12 | Homepage: https://github.com/samalba/dockerclient | |
13 | Vcs-Browser: https://anonscm.debian.org/cgit/pkg-go/packages/golang-github-samalba-dockerclient.git | |
14 | Vcs-Git: git://anonscm.debian.org/pkg-go/packages/golang-github-samalba-dockerclient.git | |
15 | ||
16 | Package: golang-github-samalba-dockerclient-dev | |
17 | Architecture: all | |
18 | Depends: ${shlibs:Depends}, | |
19 | ${misc:Depends}, | |
20 | golang-go, | |
21 | golang-github-docker-docker-dev | golang-docker-dev, | |
22 | golang-github-gorilla-mux-dev, | |
23 | golang-github-stretchr-testify-dev | golang-testify-dev | |
24 | Built-Using: ${misc:Built-Using} | |
25 | Description: Docker client library in Go | |
26 | Well maintained docker client library. |
0 | Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ | |
1 | Upstream-Name: dockerclient | |
2 | Source: https://github.com/samalba/dockerclient | |
3 | ||
4 | Files: * | |
5 | Copyright: 2014 Sam Alba <sam.alba@gmail.com> | |
6 | License: Apache-2.0 | |
7 | ||
8 | Files: debian/* | |
9 | Copyright: 2015 Dmitry Smirnov <onlyjob@debian.org> | |
10 | License: GPL-3+ | |
11 | ||
12 | Files: debian/patches/* | |
13 | Copyright: 2015 Dmitry Smirnov <onlyjob@debian.org> | |
14 | License: GPL-3+ or Apache-2.0 | |
15 | Comment: patches can be licensed under the same terms as upstream. | |
16 | ||
17 | License: Apache-2.0 | |
18 | Licensed under the Apache License, Version 2.0 (the "License"); | |
19 | you may not use this file except in compliance with the License. | |
20 | You may obtain a copy of the License at | |
21 | . | |
22 | http://www.apache.org/licenses/LICENSE-2.0 | |
23 | . | |
24 | Unless required by applicable law or agreed to in writing, software | |
25 | distributed under the License is distributed on an "AS IS" BASIS, | |
26 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
27 | See the License for the specific language governing permissions and | |
28 | limitations under the License. | |
29 | . | |
30 | On Debian systems, the complete text of the Apache version 2.0 license | |
31 | can be found in "/usr/share/common-licenses/Apache-2.0". | |
32 | ||
33 | License: GPL-3+ | |
34 | This program is free software: you can redistribute it and/or modify | |
35 | it under the terms of the GNU General Public License as published by | |
36 | the Free Software Foundation, either version 3 of the License, or | |
37 | (at your option) any later version. | |
38 | ․ | |
39 | This program is distributed in the hope that it will be useful, | |
40 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
41 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
42 | GNU General Public License for more details. | |
43 | ․ | |
44 | The complete text of the GNU General Public License version 3 | |
45 | can be found in "/usr/share/common-licenses/GPL-3". |
0 | README* |
0 | examples/* |
0 | #!/usr/bin/make -f | |
1 | ||
2 | # Uncomment this to turn on verbose mode. | |
3 | #export DH_VERBOSE=1 | |
4 | ||
5 | export DH_GOPKG := github.com/samalba/dockerclient | |
6 | export DH_GOLANG_EXCLUDES := examples | |
7 | ||
8 | %: | |
9 | dh $@ --buildsystem=golang --with=golang | |
10 | ||
11 | override_dh_auto_test: | |
12 | -dh_auto_test |
0 | 3.0 (quilt) |
0 | # uscan(1) configuration file. | |
1 | version=3 | |
2 | ||
3 | https://github.com/samalba/dockerclient/releases \ | |
4 | .*/archive/v?(\d[\d\.]+)\.tar\.gz |
0 | package dockerclient | |
1 | ||
2 | import ( | |
3 | "bytes" | |
4 | "crypto/tls" | |
5 | "encoding/json" | |
6 | "errors" | |
7 | "fmt" | |
8 | "io" | |
9 | "io/ioutil" | |
10 | "net/http" | |
11 | "net/url" | |
12 | "strconv" | |
13 | "strings" | |
14 | "sync/atomic" | |
15 | "time" | |
16 | ) | |
17 | ||
18 | const ( | |
19 | APIVersion = "v1.15" | |
20 | ) | |
21 | ||
22 | var ( | |
23 | ErrNotFound = errors.New("Not found") | |
24 | ||
25 | defaultTimeout = 30 * time.Second | |
26 | ) | |
27 | ||
28 | type DockerClient struct { | |
29 | URL *url.URL | |
30 | HTTPClient *http.Client | |
31 | TLSConfig *tls.Config | |
32 | monitorStats int32 | |
33 | eventStopChan chan (struct{}) | |
34 | } | |
35 | ||
36 | type Error struct { | |
37 | StatusCode int | |
38 | Status string | |
39 | msg string | |
40 | } | |
41 | ||
42 | func (e Error) Error() string { | |
43 | return fmt.Sprintf("%s: %s", e.Status, e.msg) | |
44 | } | |
45 | ||
46 | func NewDockerClient(daemonUrl string, tlsConfig *tls.Config) (*DockerClient, error) { | |
47 | return NewDockerClientTimeout(daemonUrl, tlsConfig, time.Duration(defaultTimeout)) | |
48 | } | |
49 | ||
50 | func NewDockerClientTimeout(daemonUrl string, tlsConfig *tls.Config, timeout time.Duration) (*DockerClient, error) { | |
51 | u, err := url.Parse(daemonUrl) | |
52 | if err != nil { | |
53 | return nil, err | |
54 | } | |
55 | if u.Scheme == "" || u.Scheme == "tcp" { | |
56 | if tlsConfig == nil { | |
57 | u.Scheme = "http" | |
58 | } else { | |
59 | u.Scheme = "https" | |
60 | } | |
61 | } | |
62 | httpClient := newHTTPClient(u, tlsConfig, timeout) | |
63 | return &DockerClient{u, httpClient, tlsConfig, 0, nil}, nil | |
64 | } | |
65 | ||
66 | func (client *DockerClient) doRequest(method string, path string, body []byte, headers map[string]string) ([]byte, error) { | |
67 | b := bytes.NewBuffer(body) | |
68 | ||
69 | reader, err := client.doStreamRequest(method, path, b, headers) | |
70 | if err != nil { | |
71 | return nil, err | |
72 | } | |
73 | ||
74 | defer reader.Close() | |
75 | data, err := ioutil.ReadAll(reader) | |
76 | if err != nil { | |
77 | return nil, err | |
78 | } | |
79 | return data, nil | |
80 | } | |
81 | ||
82 | func (client *DockerClient) doStreamRequest(method string, path string, in io.Reader, headers map[string]string) (io.ReadCloser, error) { | |
83 | if (method == "POST" || method == "PUT") && in == nil { | |
84 | in = bytes.NewReader(nil) | |
85 | } | |
86 | req, err := http.NewRequest(method, client.URL.String()+path, in) | |
87 | if err != nil { | |
88 | return nil, err | |
89 | } | |
90 | req.Header.Add("Content-Type", "application/json") | |
91 | if headers != nil { | |
92 | for header, value := range headers { | |
93 | req.Header.Add(header, value) | |
94 | } | |
95 | } | |
96 | resp, err := client.HTTPClient.Do(req) | |
97 | if err != nil { | |
98 | if !strings.Contains(err.Error(), "connection refused") && client.TLSConfig == nil { | |
99 | return nil, fmt.Errorf("%v. Are you trying to connect to a TLS-enabled daemon without TLS?", err) | |
100 | } | |
101 | return nil, err | |
102 | } | |
103 | if resp.StatusCode == 404 { | |
104 | return nil, ErrNotFound | |
105 | } | |
106 | if resp.StatusCode >= 400 { | |
107 | defer resp.Body.Close() | |
108 | data, err := ioutil.ReadAll(resp.Body) | |
109 | if err != nil { | |
110 | return nil, err | |
111 | } | |
112 | return nil, Error{StatusCode: resp.StatusCode, Status: resp.Status, msg: string(data)} | |
113 | } | |
114 | ||
115 | return resp.Body, nil | |
116 | } | |
117 | ||
118 | func (client *DockerClient) Info() (*Info, error) { | |
119 | uri := fmt.Sprintf("/%s/info", APIVersion) | |
120 | data, err := client.doRequest("GET", uri, nil, nil) | |
121 | if err != nil { | |
122 | return nil, err | |
123 | } | |
124 | ret := &Info{} | |
125 | err = json.Unmarshal(data, &ret) | |
126 | if err != nil { | |
127 | return nil, err | |
128 | } | |
129 | return ret, nil | |
130 | } | |
131 | ||
132 | func (client *DockerClient) ListContainers(all bool, size bool, filters string) ([]Container, error) { | |
133 | argAll := 0 | |
134 | if all == true { | |
135 | argAll = 1 | |
136 | } | |
137 | showSize := 0 | |
138 | if size == true { | |
139 | showSize = 1 | |
140 | } | |
141 | uri := fmt.Sprintf("/%s/containers/json?all=%d&size=%d", APIVersion, argAll, showSize) | |
142 | ||
143 | if filters != "" { | |
144 | uri += "&filters=" + filters | |
145 | } | |
146 | ||
147 | data, err := client.doRequest("GET", uri, nil, nil) | |
148 | if err != nil { | |
149 | return nil, err | |
150 | } | |
151 | ret := []Container{} | |
152 | err = json.Unmarshal(data, &ret) | |
153 | if err != nil { | |
154 | return nil, err | |
155 | } | |
156 | return ret, nil | |
157 | } | |
158 | ||
159 | func (client *DockerClient) InspectContainer(id string) (*ContainerInfo, error) { | |
160 | uri := fmt.Sprintf("/%s/containers/%s/json", APIVersion, id) | |
161 | data, err := client.doRequest("GET", uri, nil, nil) | |
162 | if err != nil { | |
163 | return nil, err | |
164 | } | |
165 | info := &ContainerInfo{} | |
166 | err = json.Unmarshal(data, info) | |
167 | if err != nil { | |
168 | return nil, err | |
169 | } | |
170 | return info, nil | |
171 | } | |
172 | ||
173 | func (client *DockerClient) CreateContainer(config *ContainerConfig, name string) (string, error) { | |
174 | data, err := json.Marshal(config) | |
175 | if err != nil { | |
176 | return "", err | |
177 | } | |
178 | uri := fmt.Sprintf("/%s/containers/create", APIVersion) | |
179 | if name != "" { | |
180 | v := url.Values{} | |
181 | v.Set("name", name) | |
182 | uri = fmt.Sprintf("%s?%s", uri, v.Encode()) | |
183 | } | |
184 | data, err = client.doRequest("POST", uri, data, nil) | |
185 | if err != nil { | |
186 | return "", err | |
187 | } | |
188 | result := &RespContainersCreate{} | |
189 | err = json.Unmarshal(data, result) | |
190 | if err != nil { | |
191 | return "", err | |
192 | } | |
193 | return result.Id, nil | |
194 | } | |
195 | ||
196 | func (client *DockerClient) ContainerLogs(id string, options *LogOptions) (io.ReadCloser, error) { | |
197 | v := url.Values{} | |
198 | v.Add("follow", strconv.FormatBool(options.Follow)) | |
199 | v.Add("stdout", strconv.FormatBool(options.Stdout)) | |
200 | v.Add("stderr", strconv.FormatBool(options.Stderr)) | |
201 | v.Add("timestamps", strconv.FormatBool(options.Timestamps)) | |
202 | if options.Tail > 0 { | |
203 | v.Add("tail", strconv.FormatInt(options.Tail, 10)) | |
204 | } | |
205 | ||
206 | uri := fmt.Sprintf("/%s/containers/%s/logs?%s", APIVersion, id, v.Encode()) | |
207 | req, err := http.NewRequest("GET", client.URL.String()+uri, nil) | |
208 | if err != nil { | |
209 | return nil, err | |
210 | } | |
211 | req.Header.Add("Content-Type", "application/json") | |
212 | resp, err := client.HTTPClient.Do(req) | |
213 | if err != nil { | |
214 | return nil, err | |
215 | } | |
216 | return resp.Body, nil | |
217 | } | |
218 | ||
219 | func (client *DockerClient) ContainerChanges(id string) ([]*ContainerChanges, error) { | |
220 | uri := fmt.Sprintf("/%s/containers/%s/changes", APIVersion, id) | |
221 | data, err := client.doRequest("GET", uri, nil, nil) | |
222 | if err != nil { | |
223 | return nil, err | |
224 | } | |
225 | changes := []*ContainerChanges{} | |
226 | err = json.Unmarshal(data, &changes) | |
227 | if err != nil { | |
228 | return nil, err | |
229 | } | |
230 | return changes, nil | |
231 | } | |
232 | ||
233 | func (client *DockerClient) readJSONStream(stream io.ReadCloser, decode func(*json.Decoder) decodingResult, stopChan <-chan struct{}) <-chan decodingResult { | |
234 | resultChan := make(chan decodingResult) | |
235 | ||
236 | go func() { | |
237 | decodeChan := make(chan decodingResult) | |
238 | ||
239 | go func() { | |
240 | decoder := json.NewDecoder(stream) | |
241 | for { | |
242 | decodeResult := decode(decoder) | |
243 | decodeChan <- decodeResult | |
244 | if decodeResult.err != nil { | |
245 | close(decodeChan) | |
246 | return | |
247 | } | |
248 | } | |
249 | }() | |
250 | ||
251 | defer close(resultChan) | |
252 | ||
253 | for { | |
254 | select { | |
255 | case <-stopChan: | |
256 | stream.Close() | |
257 | for range decodeChan { | |
258 | } | |
259 | return | |
260 | case decodeResult := <-decodeChan: | |
261 | resultChan <- decodeResult | |
262 | if decodeResult.err != nil { | |
263 | stream.Close() | |
264 | return | |
265 | } | |
266 | } | |
267 | } | |
268 | ||
269 | }() | |
270 | ||
271 | return resultChan | |
272 | } | |
273 | ||
274 | func (client *DockerClient) ExecCreate(config *ExecConfig) (string, error) { | |
275 | data, err := json.Marshal(config) | |
276 | if err != nil { | |
277 | return "", err | |
278 | } | |
279 | uri := fmt.Sprintf("/%s/containers/%s/exec", APIVersion, config.Container) | |
280 | resp, err := client.doRequest("POST", uri, data, nil) | |
281 | if err != nil { | |
282 | return "", err | |
283 | } | |
284 | var createExecResp struct { | |
285 | Id string | |
286 | } | |
287 | if err = json.Unmarshal(resp, &createExecResp); err != nil { | |
288 | return "", err | |
289 | } | |
290 | return createExecResp.Id, nil | |
291 | } | |
292 | ||
293 | func (client *DockerClient) ExecStart(id string, config *ExecConfig) error { | |
294 | data, err := json.Marshal(config) | |
295 | if err != nil { | |
296 | return err | |
297 | } | |
298 | ||
299 | uri := fmt.Sprintf("/%s/exec/%s/start", APIVersion, id) | |
300 | if _, err := client.doRequest("POST", uri, data, nil); err != nil { | |
301 | return err | |
302 | } | |
303 | ||
304 | return nil | |
305 | } | |
306 | ||
307 | func (client *DockerClient) ExecResize(id string, width, height int) error { | |
308 | v := url.Values{} | |
309 | ||
310 | w := strconv.Itoa(width) | |
311 | h := strconv.Itoa(height) | |
312 | ||
313 | v.Set("w", w) | |
314 | v.Set("h", h) | |
315 | ||
316 | uri := fmt.Sprintf("/%s/exec/%s/resize?%s", APIVersion, id, v.Encode()) | |
317 | if _, err := client.doRequest("POST", client.URL.String()+uri, nil, nil); err != nil { | |
318 | return err | |
319 | } | |
320 | ||
321 | return nil | |
322 | } | |
323 | ||
324 | func (client *DockerClient) StartContainer(id string, config *HostConfig) error { | |
325 | data, err := json.Marshal(config) | |
326 | if err != nil { | |
327 | return err | |
328 | } | |
329 | uri := fmt.Sprintf("/%s/containers/%s/start", APIVersion, id) | |
330 | _, err = client.doRequest("POST", uri, data, nil) | |
331 | if err != nil { | |
332 | return err | |
333 | } | |
334 | return nil | |
335 | } | |
336 | ||
337 | func (client *DockerClient) StopContainer(id string, timeout int) error { | |
338 | uri := fmt.Sprintf("/%s/containers/%s/stop?t=%d", APIVersion, id, timeout) | |
339 | _, err := client.doRequest("POST", uri, nil, nil) | |
340 | if err != nil { | |
341 | return err | |
342 | } | |
343 | return nil | |
344 | } | |
345 | ||
346 | func (client *DockerClient) RestartContainer(id string, timeout int) error { | |
347 | uri := fmt.Sprintf("/%s/containers/%s/restart?t=%d", APIVersion, id, timeout) | |
348 | _, err := client.doRequest("POST", uri, nil, nil) | |
349 | if err != nil { | |
350 | return err | |
351 | } | |
352 | return nil | |
353 | } | |
354 | ||
355 | func (client *DockerClient) KillContainer(id, signal string) error { | |
356 | uri := fmt.Sprintf("/%s/containers/%s/kill?signal=%s", APIVersion, id, signal) | |
357 | _, err := client.doRequest("POST", uri, nil, nil) | |
358 | if err != nil { | |
359 | return err | |
360 | } | |
361 | return nil | |
362 | } | |
363 | ||
364 | func (client *DockerClient) Wait(id string) <-chan WaitResult { | |
365 | ch := make(chan WaitResult) | |
366 | uri := fmt.Sprintf("/%s/containers/%s/wait", APIVersion, id) | |
367 | ||
368 | go func() { | |
369 | data, err := client.doRequest("POST", uri, nil, nil) | |
370 | if err != nil { | |
371 | ch <- WaitResult{ExitCode: -1, Error: err} | |
372 | return | |
373 | } | |
374 | ||
375 | var result struct { | |
376 | StatusCode int `json:"StatusCode"` | |
377 | } | |
378 | err = json.Unmarshal(data, &result) | |
379 | ch <- WaitResult{ExitCode: result.StatusCode, Error: err} | |
380 | }() | |
381 | return ch | |
382 | } | |
383 | ||
384 | func (client *DockerClient) MonitorEvents(options *MonitorEventsOptions, stopChan <-chan struct{}) (<-chan EventOrError, error) { | |
385 | v := url.Values{} | |
386 | if options != nil { | |
387 | if options.Since != 0 { | |
388 | v.Add("since", strconv.Itoa(options.Since)) | |
389 | } | |
390 | if options.Until != 0 { | |
391 | v.Add("until", strconv.Itoa(options.Until)) | |
392 | } | |
393 | if options.Filters != nil { | |
394 | filterMap := make(map[string][]string) | |
395 | if len(options.Filters.Event) > 0 { | |
396 | filterMap["event"] = []string{options.Filters.Event} | |
397 | } | |
398 | if len(options.Filters.Image) > 0 { | |
399 | filterMap["image"] = []string{options.Filters.Image} | |
400 | } | |
401 | if len(options.Filters.Container) > 0 { | |
402 | filterMap["container"] = []string{options.Filters.Container} | |
403 | } | |
404 | if len(filterMap) > 0 { | |
405 | filterJSONBytes, err := json.Marshal(filterMap) | |
406 | if err != nil { | |
407 | return nil, err | |
408 | } | |
409 | v.Add("filters", string(filterJSONBytes)) | |
410 | } | |
411 | } | |
412 | } | |
413 | uri := fmt.Sprintf("%s/%s/events?%s", client.URL.String(), APIVersion, v.Encode()) | |
414 | resp, err := client.HTTPClient.Get(uri) | |
415 | if err != nil { | |
416 | return nil, err | |
417 | } | |
418 | ||
419 | decode := func(decoder *json.Decoder) decodingResult { | |
420 | var event Event | |
421 | if err := decoder.Decode(&event); err != nil { | |
422 | return decodingResult{err: err} | |
423 | } else { | |
424 | return decodingResult{result: event} | |
425 | } | |
426 | } | |
427 | decodingResultChan := client.readJSONStream(resp.Body, decode, stopChan) | |
428 | eventOrErrorChan := make(chan EventOrError) | |
429 | go func() { | |
430 | for decodingResult := range decodingResultChan { | |
431 | event, _ := decodingResult.result.(Event) | |
432 | eventOrErrorChan <- EventOrError{ | |
433 | Event: event, | |
434 | Error: decodingResult.err, | |
435 | } | |
436 | } | |
437 | close(eventOrErrorChan) | |
438 | }() | |
439 | return eventOrErrorChan, nil | |
440 | } | |
441 | ||
442 | func (client *DockerClient) StartMonitorEvents(cb Callback, ec chan error, args ...interface{}) { | |
443 | client.eventStopChan = make(chan struct{}) | |
444 | ||
445 | go func() { | |
446 | eventErrChan, err := client.MonitorEvents(nil, client.eventStopChan) | |
447 | if err != nil { | |
448 | if ec != nil { | |
449 | ec <- err | |
450 | } | |
451 | return | |
452 | } | |
453 | ||
454 | for e := range eventErrChan { | |
455 | if e.Error != nil { | |
456 | if ec != nil { | |
457 | ec <- err | |
458 | } | |
459 | return | |
460 | } | |
461 | cb(&e.Event, ec, args...) | |
462 | } | |
463 | }() | |
464 | } | |
465 | ||
466 | func (client *DockerClient) StopAllMonitorEvents() { | |
467 | close(client.eventStopChan) | |
468 | } | |
469 | ||
470 | func (client *DockerClient) StartMonitorStats(id string, cb StatCallback, ec chan error, args ...interface{}) { | |
471 | atomic.StoreInt32(&client.monitorStats, 1) | |
472 | go client.getStats(id, cb, ec, args...) | |
473 | } | |
474 | ||
475 | func (client *DockerClient) getStats(id string, cb StatCallback, ec chan error, args ...interface{}) { | |
476 | uri := fmt.Sprintf("%s/%s/containers/%s/stats", client.URL.String(), APIVersion, id) | |
477 | resp, err := client.HTTPClient.Get(uri) | |
478 | if err != nil { | |
479 | ec <- err | |
480 | return | |
481 | } | |
482 | defer resp.Body.Close() | |
483 | ||
484 | dec := json.NewDecoder(resp.Body) | |
485 | for atomic.LoadInt32(&client.monitorStats) > 0 { | |
486 | var stats *Stats | |
487 | if err := dec.Decode(&stats); err != nil { | |
488 | ec <- err | |
489 | return | |
490 | } | |
491 | cb(id, stats, ec, args...) | |
492 | } | |
493 | } | |
494 | ||
495 | func (client *DockerClient) StopAllMonitorStats() { | |
496 | atomic.StoreInt32(&client.monitorStats, 0) | |
497 | } | |
498 | ||
499 | func (client *DockerClient) TagImage(nameOrID string, repo string, tag string, force bool) error { | |
500 | v := url.Values{} | |
501 | v.Set("repo", repo) | |
502 | v.Set("tag", tag) | |
503 | if force { | |
504 | v.Set("force", "1") | |
505 | } | |
506 | uri := fmt.Sprintf("/%s/images/%s/tag?%s", APIVersion, nameOrID, v.Encode()) | |
507 | if _, err := client.doRequest("POST", uri, nil, nil); err != nil { | |
508 | return err | |
509 | } | |
510 | return nil | |
511 | } | |
512 | ||
513 | func (client *DockerClient) Version() (*Version, error) { | |
514 | uri := fmt.Sprintf("/%s/version", APIVersion) | |
515 | data, err := client.doRequest("GET", uri, nil, nil) | |
516 | if err != nil { | |
517 | return nil, err | |
518 | } | |
519 | version := &Version{} | |
520 | err = json.Unmarshal(data, version) | |
521 | if err != nil { | |
522 | return nil, err | |
523 | } | |
524 | return version, nil | |
525 | } | |
526 | ||
527 | func (client *DockerClient) PullImage(name string, auth *AuthConfig) error { | |
528 | v := url.Values{} | |
529 | v.Set("fromImage", name) | |
530 | uri := fmt.Sprintf("/%s/images/create?%s", APIVersion, v.Encode()) | |
531 | req, err := http.NewRequest("POST", client.URL.String()+uri, nil) | |
532 | if auth != nil { | |
533 | encoded_auth, err := auth.encode() | |
534 | if err != nil { | |
535 | return err | |
536 | } | |
537 | req.Header.Add("X-Registry-Auth", encoded_auth) | |
538 | } | |
539 | resp, err := client.HTTPClient.Do(req) | |
540 | if err != nil { | |
541 | return err | |
542 | } | |
543 | ||
544 | defer resp.Body.Close() | |
545 | if resp.StatusCode == 404 { | |
546 | return ErrNotFound | |
547 | } | |
548 | if resp.StatusCode >= 400 { | |
549 | data, err := ioutil.ReadAll(resp.Body) | |
550 | if err != nil { | |
551 | return err | |
552 | } | |
553 | return fmt.Errorf("%s", string(data)) | |
554 | } | |
555 | ||
556 | var finalObj map[string]interface{} | |
557 | for decoder := json.NewDecoder(resp.Body); err == nil; err = decoder.Decode(&finalObj) { | |
558 | } | |
559 | if err != io.EOF { | |
560 | return err | |
561 | } | |
562 | if err, ok := finalObj["error"]; ok { | |
563 | return fmt.Errorf("%v", err) | |
564 | } | |
565 | return nil | |
566 | } | |
567 | ||
568 | func (client *DockerClient) InspectImage(id string) (*ImageInfo, error) { | |
569 | uri := fmt.Sprintf("/%s/images/%s/json", APIVersion, id) | |
570 | data, err := client.doRequest("GET", uri, nil, nil) | |
571 | if err != nil { | |
572 | return nil, err | |
573 | } | |
574 | info := &ImageInfo{} | |
575 | err = json.Unmarshal(data, info) | |
576 | if err != nil { | |
577 | return nil, err | |
578 | } | |
579 | return info, nil | |
580 | } | |
581 | ||
582 | func (client *DockerClient) LoadImage(reader io.Reader) error { | |
583 | data, err := ioutil.ReadAll(reader) | |
584 | if err != nil { | |
585 | return err | |
586 | } | |
587 | ||
588 | uri := fmt.Sprintf("/%s/images/load", APIVersion) | |
589 | _, err = client.doRequest("POST", uri, data, nil) | |
590 | if err != nil { | |
591 | return err | |
592 | } | |
593 | return nil | |
594 | } | |
595 | ||
596 | func (client *DockerClient) RemoveContainer(id string, force, volumes bool) error { | |
597 | argForce := 0 | |
598 | argVolumes := 0 | |
599 | if force == true { | |
600 | argForce = 1 | |
601 | } | |
602 | if volumes == true { | |
603 | argVolumes = 1 | |
604 | } | |
605 | args := fmt.Sprintf("force=%d&v=%d", argForce, argVolumes) | |
606 | uri := fmt.Sprintf("/%s/containers/%s?%s", APIVersion, id, args) | |
607 | _, err := client.doRequest("DELETE", uri, nil, nil) | |
608 | return err | |
609 | } | |
610 | ||
611 | func (client *DockerClient) ListImages(all bool) ([]*Image, error) { | |
612 | argAll := 0 | |
613 | if all { | |
614 | argAll = 1 | |
615 | } | |
616 | uri := fmt.Sprintf("/%s/images/json?all=%d", APIVersion, argAll) | |
617 | data, err := client.doRequest("GET", uri, nil, nil) | |
618 | if err != nil { | |
619 | return nil, err | |
620 | } | |
621 | var images []*Image | |
622 | if err := json.Unmarshal(data, &images); err != nil { | |
623 | return nil, err | |
624 | } | |
625 | return images, nil | |
626 | } | |
627 | ||
628 | func (client *DockerClient) RemoveImage(name string, force bool) ([]*ImageDelete, error) { | |
629 | argForce := 0 | |
630 | if force { | |
631 | argForce = 1 | |
632 | } | |
633 | ||
634 | args := fmt.Sprintf("force=%d", argForce) | |
635 | uri := fmt.Sprintf("/%s/images/%s?%s", APIVersion, name, args) | |
636 | data, err := client.doRequest("DELETE", uri, nil, nil) | |
637 | if err != nil { | |
638 | return nil, err | |
639 | } | |
640 | var imageDelete []*ImageDelete | |
641 | if err := json.Unmarshal(data, &imageDelete); err != nil { | |
642 | return nil, err | |
643 | } | |
644 | return imageDelete, nil | |
645 | } | |
646 | ||
647 | func (client *DockerClient) PauseContainer(id string) error { | |
648 | uri := fmt.Sprintf("/%s/containers/%s/pause", APIVersion, id) | |
649 | _, err := client.doRequest("POST", uri, nil, nil) | |
650 | if err != nil { | |
651 | return err | |
652 | } | |
653 | return nil | |
654 | } | |
655 | func (client *DockerClient) UnpauseContainer(id string) error { | |
656 | uri := fmt.Sprintf("/%s/containers/%s/unpause", APIVersion, id) | |
657 | _, err := client.doRequest("POST", uri, nil, nil) | |
658 | if err != nil { | |
659 | return err | |
660 | } | |
661 | return nil | |
662 | } | |
663 | ||
664 | func (client *DockerClient) RenameContainer(oldName string, newName string) error { | |
665 | uri := fmt.Sprintf("/containers/%s/rename?name=%s", oldName, newName) | |
666 | _, err := client.doRequest("POST", uri, nil, nil) | |
667 | return err | |
668 | } | |
669 | ||
670 | func (client *DockerClient) ImportImage(source string, repository string, tag string, tar io.Reader) (io.ReadCloser, error) { | |
671 | var fromSrc string | |
672 | v := &url.Values{} | |
673 | if source == "" { | |
674 | fromSrc = "-" | |
675 | } else { | |
676 | fromSrc = source | |
677 | } | |
678 | ||
679 | v.Set("fromSrc", fromSrc) | |
680 | v.Set("repo", repository) | |
681 | if tag != "" { | |
682 | v.Set("tag", tag) | |
683 | } | |
684 | ||
685 | var in io.Reader | |
686 | if fromSrc == "-" { | |
687 | in = tar | |
688 | } | |
689 | return client.doStreamRequest("POST", "/images/create?"+v.Encode(), in, nil) | |
690 | } | |
691 | ||
692 | func (client *DockerClient) BuildImage(image *BuildImage) (io.ReadCloser, error) { | |
693 | v := url.Values{} | |
694 | ||
695 | if image.DockerfileName != "" { | |
696 | v.Set("dockerfile", image.DockerfileName) | |
697 | } | |
698 | if image.RepoName != "" { | |
699 | v.Set("t", image.RepoName) | |
700 | } | |
701 | if image.RemoteURL != "" { | |
702 | v.Set("remote", image.RemoteURL) | |
703 | } | |
704 | if image.NoCache { | |
705 | v.Set("nocache", "1") | |
706 | } | |
707 | if image.Pull { | |
708 | v.Set("pull", "1") | |
709 | } | |
710 | if image.Remove { | |
711 | v.Set("rm", "1") | |
712 | } else { | |
713 | v.Set("rm", "0") | |
714 | } | |
715 | if image.ForceRemove { | |
716 | v.Set("forcerm", "1") | |
717 | } | |
718 | if image.SuppressOutput { | |
719 | v.Set("q", "1") | |
720 | } | |
721 | ||
722 | v.Set("memory", strconv.FormatInt(image.Memory, 10)) | |
723 | v.Set("memswap", strconv.FormatInt(image.MemorySwap, 10)) | |
724 | v.Set("cpushares", strconv.FormatInt(image.CpuShares, 10)) | |
725 | v.Set("cpuperiod", strconv.FormatInt(image.CpuPeriod, 10)) | |
726 | v.Set("cpuquota", strconv.FormatInt(image.CpuQuota, 10)) | |
727 | v.Set("cpusetcpus", image.CpuSetCpus) | |
728 | v.Set("cpusetmems", image.CpuSetMems) | |
729 | v.Set("cgroupparent", image.CgroupParent) | |
730 | ||
731 | headers := make(map[string]string) | |
732 | if image.Config != nil { | |
733 | encoded_config, err := image.Config.encode() | |
734 | if err != nil { | |
735 | return nil, err | |
736 | } | |
737 | headers["X-Registry-Config"] = encoded_config | |
738 | } | |
739 | if image.Context != nil { | |
740 | headers["Content-Type"] = "application/tar" | |
741 | } | |
742 | ||
743 | uri := fmt.Sprintf("/%s/build?%s", APIVersion, v.Encode()) | |
744 | return client.doStreamRequest("POST", uri, image.Context, headers) | |
745 | } | |
746 | ||
747 | func (client *DockerClient) ListVolumes() ([]*Volume, error) { | |
748 | uri := fmt.Sprintf("/%s/volumes", APIVersion) | |
749 | data, err := client.doRequest("GET", uri, nil, nil) | |
750 | if err != nil { | |
751 | return nil, err | |
752 | } | |
753 | var volumesList VolumesListResponse | |
754 | if err := json.Unmarshal(data, &volumesList); err != nil { | |
755 | return nil, err | |
756 | } | |
757 | return volumesList.Volumes, nil | |
758 | } |
0 | package dockerclient | |
1 | ||
2 | import ( | |
3 | "bytes" | |
4 | "encoding/json" | |
5 | "fmt" | |
6 | "io" | |
7 | "reflect" | |
8 | "strings" | |
9 | "testing" | |
10 | "time" | |
11 | ||
12 | "github.com/docker/docker/pkg/stdcopy" | |
13 | ) | |
14 | ||
15 | func assertEqual(t *testing.T, a interface{}, b interface{}, message string) { | |
16 | if a == b { | |
17 | return | |
18 | } | |
19 | if len(message) == 0 { | |
20 | message = fmt.Sprintf("%v != %v", a, b) | |
21 | } | |
22 | t.Fatal(message) | |
23 | } | |
24 | ||
25 | func testDockerClient(t *testing.T) *DockerClient { | |
26 | client, err := NewDockerClient(testHTTPServer.URL, nil) | |
27 | if err != nil { | |
28 | t.Fatal("Cannot init the docker client") | |
29 | } | |
30 | return client | |
31 | } | |
32 | ||
33 | func TestInfo(t *testing.T) { | |
34 | client := testDockerClient(t) | |
35 | info, err := client.Info() | |
36 | if err != nil { | |
37 | t.Fatal("Cannot get server info") | |
38 | } | |
39 | assertEqual(t, info.Images, int64(1), "") | |
40 | assertEqual(t, info.Containers, int64(2), "") | |
41 | } | |
42 | ||
43 | func TestKillContainer(t *testing.T) { | |
44 | client := testDockerClient(t) | |
45 | if err := client.KillContainer("23132acf2ac", "5"); err != nil { | |
46 | t.Fatal("cannot kill container: %s", err) | |
47 | } | |
48 | } | |
49 | ||
50 | func TestWait(t *testing.T) { | |
51 | client := testDockerClient(t) | |
52 | ||
53 | // This provokes an error on the server. | |
54 | select { | |
55 | case wr := <-client.Wait("1234"): | |
56 | assertEqual(t, wr.ExitCode, int(-1), "") | |
57 | case <-time.After(2 * time.Second): | |
58 | t.Fatal("Timed out!") | |
59 | } | |
60 | ||
61 | // Valid case. | |
62 | select { | |
63 | case wr := <-client.Wait("valid-id"): | |
64 | assertEqual(t, wr.ExitCode, int(0), "") | |
65 | case <-time.After(2 * time.Second): | |
66 | t.Fatal("Timed out!") | |
67 | } | |
68 | } | |
69 | ||
70 | func TestPullImage(t *testing.T) { | |
71 | client := testDockerClient(t) | |
72 | err := client.PullImage("busybox", nil) | |
73 | if err != nil { | |
74 | t.Fatal("unable to pull busybox") | |
75 | } | |
76 | ||
77 | err = client.PullImage("haproxy", nil) | |
78 | if err != nil { | |
79 | t.Fatal("unable to pull haproxy") | |
80 | } | |
81 | ||
82 | err = client.PullImage("wrongimg", nil) | |
83 | if err == nil { | |
84 | t.Fatal("should return error when it fails to pull wrongimg") | |
85 | } | |
86 | } | |
87 | ||
88 | func TestListContainers(t *testing.T) { | |
89 | client := testDockerClient(t) | |
90 | containers, err := client.ListContainers(true, false, "") | |
91 | if err != nil { | |
92 | t.Fatal("cannot get containers: %s", err) | |
93 | } | |
94 | assertEqual(t, len(containers), 1, "") | |
95 | cnt := containers[0] | |
96 | assertEqual(t, cnt.SizeRw, int64(0), "") | |
97 | } | |
98 | ||
99 | func TestContainerChanges(t *testing.T) { | |
100 | client := testDockerClient(t) | |
101 | changes, err := client.ContainerChanges("foobar") | |
102 | if err != nil { | |
103 | t.Fatal("cannot get container changes: %s", err) | |
104 | } | |
105 | assertEqual(t, len(changes), 3, "unexpected number of changes") | |
106 | c := changes[0] | |
107 | assertEqual(t, c.Path, "/dev", "unexpected") | |
108 | assertEqual(t, c.Kind, 0, "unexpected") | |
109 | } | |
110 | ||
111 | func TestListContainersWithSize(t *testing.T) { | |
112 | client := testDockerClient(t) | |
113 | containers, err := client.ListContainers(true, true, "") | |
114 | if err != nil { | |
115 | t.Fatal("cannot get containers: %s", err) | |
116 | } | |
117 | assertEqual(t, len(containers), 1, "") | |
118 | cnt := containers[0] | |
119 | assertEqual(t, cnt.SizeRw, int64(123), "") | |
120 | } | |
121 | func TestListContainersWithFilters(t *testing.T) { | |
122 | client := testDockerClient(t) | |
123 | containers, err := client.ListContainers(true, true, "{'id':['332375cfbc23edb921a21026314c3497674ba8bdcb2c85e0e65ebf2017f688ce']}") | |
124 | if err != nil { | |
125 | t.Fatal("cannot get containers: %s", err) | |
126 | } | |
127 | assertEqual(t, len(containers), 1, "") | |
128 | ||
129 | containers, err = client.ListContainers(true, true, "{'id':['332375cfbc23edb921a21026314c3497674ba8bdcb2c85e0e65ebf2017f688cf']}") | |
130 | if err != nil { | |
131 | t.Fatal("cannot get containers: %s", err) | |
132 | } | |
133 | assertEqual(t, len(containers), 0, "") | |
134 | } | |
135 | ||
136 | func TestContainerLogs(t *testing.T) { | |
137 | client := testDockerClient(t) | |
138 | containerId := "foobar" | |
139 | logOptions := &LogOptions{ | |
140 | Follow: true, | |
141 | Stdout: true, | |
142 | Stderr: true, | |
143 | Timestamps: true, | |
144 | Tail: 10, | |
145 | } | |
146 | logsReader, err := client.ContainerLogs(containerId, logOptions) | |
147 | if err != nil { | |
148 | t.Fatal("cannot read logs from server") | |
149 | } | |
150 | ||
151 | stdoutBuffer := new(bytes.Buffer) | |
152 | stderrBuffer := new(bytes.Buffer) | |
153 | if _, err = stdcopy.StdCopy(stdoutBuffer, stderrBuffer, logsReader); err != nil { | |
154 | t.Fatal("cannot read logs from logs reader") | |
155 | } | |
156 | stdoutLogs := strings.TrimSpace(stdoutBuffer.String()) | |
157 | stderrLogs := strings.TrimSpace(stderrBuffer.String()) | |
158 | stdoutLogLines := strings.Split(stdoutLogs, "\n") | |
159 | stderrLogLines := strings.Split(stderrLogs, "\n") | |
160 | if len(stdoutLogLines) != 5 { | |
161 | t.Fatalf("wrong number of stdout logs: len=%d", len(stdoutLogLines)) | |
162 | } | |
163 | if len(stderrLogLines) != 5 { | |
164 | t.Fatalf("wrong number of stderr logs: len=%d", len(stdoutLogLines)) | |
165 | } | |
166 | for i, line := range stdoutLogLines { | |
167 | expectedSuffix := fmt.Sprintf("Z line %d", 41+2*i) | |
168 | if !strings.HasSuffix(line, expectedSuffix) { | |
169 | t.Fatalf("expected stdout log line \"%s\" to end with \"%s\"", line, expectedSuffix) | |
170 | } | |
171 | } | |
172 | for i, line := range stderrLogLines { | |
173 | expectedSuffix := fmt.Sprintf("Z line %d", 40+2*i) | |
174 | if !strings.HasSuffix(line, expectedSuffix) { | |
175 | t.Fatalf("expected stderr log line \"%s\" to end with \"%s\"", line, expectedSuffix) | |
176 | } | |
177 | } | |
178 | } | |
179 | ||
180 | func TestMonitorEvents(t *testing.T) { | |
181 | client := testDockerClient(t) | |
182 | decoder := json.NewDecoder(bytes.NewBufferString(eventsResp)) | |
183 | var expectedEvents []Event | |
184 | for { | |
185 | var event Event | |
186 | if err := decoder.Decode(&event); err != nil { | |
187 | if err == io.EOF { | |
188 | break | |
189 | } else { | |
190 | t.Fatalf("cannot parse expected resp: %s", err.Error()) | |
191 | } | |
192 | } else { | |
193 | expectedEvents = append(expectedEvents, event) | |
194 | } | |
195 | } | |
196 | ||
197 | // test passing stop chan | |
198 | stopChan := make(chan struct{}) | |
199 | eventInfoChan, err := client.MonitorEvents(nil, stopChan) | |
200 | if err != nil { | |
201 | t.Fatalf("cannot get events from server: %s", err.Error()) | |
202 | } | |
203 | ||
204 | eventInfo := <-eventInfoChan | |
205 | if eventInfo.Error != nil || eventInfo.Event != expectedEvents[0] { | |
206 | t.Fatalf("got:\n%#v\nexpected:\n%#v", eventInfo, expectedEvents[0]) | |
207 | } | |
208 | close(stopChan) | |
209 | for i := 0; i < 3; i++ { | |
210 | _, ok := <-eventInfoChan | |
211 | if i == 2 && ok { | |
212 | t.Fatalf("read more than 2 events successfully after closing stopChan") | |
213 | } | |
214 | } | |
215 | ||
216 | // test when you don't pass stop chan | |
217 | eventInfoChan, err = client.MonitorEvents(nil, nil) | |
218 | if err != nil { | |
219 | t.Fatalf("cannot get events from server: %s", err.Error()) | |
220 | } | |
221 | ||
222 | for i, expectedEvent := range expectedEvents { | |
223 | t.Logf("on iter %d\n", i) | |
224 | eventInfo := <-eventInfoChan | |
225 | if eventInfo.Error != nil || eventInfo.Event != expectedEvent { | |
226 | t.Fatalf("index %d, got:\n%#v\nexpected:\n%#v", i, eventInfo, expectedEvent) | |
227 | } | |
228 | t.Logf("done with iter %d\n", i) | |
229 | } | |
230 | } | |
231 | ||
232 | func TestDockerClientInterface(t *testing.T) { | |
233 | iface := reflect.TypeOf((*Client)(nil)).Elem() | |
234 | test := testDockerClient(t) | |
235 | ||
236 | if !reflect.TypeOf(test).Implements(iface) { | |
237 | t.Fatalf("DockerClient does not implement the Client interface") | |
238 | } | |
239 | } |
0 | package dockerclient | |
1 | ||
2 | import ( | |
3 | "encoding/json" | |
4 | "fmt" | |
5 | "io" | |
6 | "log" | |
7 | "net/http" | |
8 | "net/http/httptest" | |
9 | "strconv" | |
10 | "time" | |
11 | ||
12 | "github.com/docker/docker/pkg/ioutils" | |
13 | "github.com/docker/docker/pkg/jsonlog" | |
14 | "github.com/docker/docker/pkg/stdcopy" | |
15 | "github.com/docker/docker/pkg/timeutils" | |
16 | "github.com/gorilla/mux" | |
17 | ) | |
18 | ||
19 | var ( | |
20 | testHTTPServer *httptest.Server | |
21 | ) | |
22 | ||
23 | func init() { | |
24 | r := mux.NewRouter() | |
25 | baseURL := "/" + APIVersion | |
26 | r.HandleFunc(baseURL+"/info", handlerGetInfo).Methods("GET") | |
27 | r.HandleFunc(baseURL+"/containers/json", handlerGetContainers).Methods("GET") | |
28 | r.HandleFunc(baseURL+"/containers/{id}/logs", handleContainerLogs).Methods("GET") | |
29 | r.HandleFunc(baseURL+"/containers/{id}/changes", handleContainerChanges).Methods("GET") | |
30 | r.HandleFunc(baseURL+"/containers/{id}/kill", handleContainerKill).Methods("POST") | |
31 | r.HandleFunc(baseURL+"/containers/{id}/wait", handleWait).Methods("POST") | |
32 | r.HandleFunc(baseURL+"/images/create", handleImagePull).Methods("POST") | |
33 | r.HandleFunc(baseURL+"/events", handleEvents).Methods("GET") | |
34 | testHTTPServer = httptest.NewServer(handlerAccessLog(r)) | |
35 | } | |
36 | ||
37 | func handlerAccessLog(handler http.Handler) http.Handler { | |
38 | logHandler := func(w http.ResponseWriter, r *http.Request) { | |
39 | log.Printf("%s \"%s %s\"", r.RemoteAddr, r.Method, r.URL) | |
40 | handler.ServeHTTP(w, r) | |
41 | } | |
42 | return http.HandlerFunc(logHandler) | |
43 | } | |
44 | ||
45 | func handleContainerKill(w http.ResponseWriter, r *http.Request) { | |
46 | fmt.Fprintf(w, "{%q:%q", "Id", "421373210afd132") | |
47 | } | |
48 | ||
49 | func handleWait(w http.ResponseWriter, r *http.Request) { | |
50 | vars := mux.Vars(r) | |
51 | if vars["id"] == "valid-id" { | |
52 | fmt.Fprintf(w, `{"StatusCode":0}`) | |
53 | } else { | |
54 | http.Error(w, "failed", 500) | |
55 | } | |
56 | } | |
57 | ||
58 | func handleImagePull(w http.ResponseWriter, r *http.Request) { | |
59 | imageName := r.URL.Query()["fromImage"][0] | |
60 | responses := []map[string]interface{}{{ | |
61 | "status": fmt.Sprintf("Pulling repository mydockerregistry/%s", imageName), | |
62 | }} | |
63 | switch imageName { | |
64 | case "busybox": | |
65 | responses = append(responses, map[string]interface{}{ | |
66 | "status": "Status: Image is up to date for mydockerregistry/busybox", | |
67 | }) | |
68 | case "haproxy": | |
69 | fmt.Fprintf(w, haproxyPullOutput) | |
70 | return | |
71 | default: | |
72 | errorMsg := fmt.Sprintf("Error: image %s not found", imageName) | |
73 | responses = append(responses, map[string]interface{}{ | |
74 | "errorDetail": map[string]interface{}{ | |
75 | "message": errorMsg, | |
76 | }, | |
77 | "error": errorMsg, | |
78 | }) | |
79 | } | |
80 | for _, response := range responses { | |
81 | json.NewEncoder(w).Encode(response) | |
82 | } | |
83 | } | |
84 | ||
85 | func handleContainerLogs(w http.ResponseWriter, r *http.Request) { | |
86 | var outStream, errStream io.Writer | |
87 | outStream = ioutils.NewWriteFlusher(w) | |
88 | ||
89 | // not sure how to test follow | |
90 | if err := r.ParseForm(); err != nil { | |
91 | http.Error(w, err.Error(), 500) | |
92 | } | |
93 | stdout, stderr := getBoolValue(r.Form.Get("stdout")), getBoolValue(r.Form.Get("stderr")) | |
94 | if stderr { | |
95 | errStream = stdcopy.NewStdWriter(outStream, stdcopy.Stderr) | |
96 | } | |
97 | if stdout { | |
98 | outStream = stdcopy.NewStdWriter(outStream, stdcopy.Stdout) | |
99 | } | |
100 | var i int | |
101 | if tail, err := strconv.Atoi(r.Form.Get("tail")); err == nil && tail > 0 { | |
102 | i = 50 - tail | |
103 | if i < 0 { | |
104 | i = 0 | |
105 | } | |
106 | } | |
107 | for ; i < 50; i++ { | |
108 | line := fmt.Sprintf("line %d", i) | |
109 | if getBoolValue(r.Form.Get("timestamps")) { | |
110 | l := &jsonlog.JSONLog{Log: line, Created: time.Now().UTC()} | |
111 | line = fmt.Sprintf("%s %s", l.Created.Format(timeutils.RFC3339NanoFixed), line) | |
112 | } | |
113 | if i%2 == 0 && stderr { | |
114 | fmt.Fprintln(errStream, line) | |
115 | } else if i%2 == 1 && stdout { | |
116 | fmt.Fprintln(outStream, line) | |
117 | } | |
118 | } | |
119 | } | |
120 | ||
121 | func handleContainerChanges(w http.ResponseWriter, r *http.Request) { | |
122 | writeHeaders(w, 200, "changes") | |
123 | body := `[ | |
124 | { | |
125 | "Path": "/dev", | |
126 | "Kind": 0 | |
127 | }, | |
128 | { | |
129 | "Path": "/dev/kmsg", | |
130 | "Kind": 1 | |
131 | }, | |
132 | { | |
133 | "Path": "/test", | |
134 | "Kind": 1 | |
135 | } | |
136 | ]` | |
137 | w.Write([]byte(body)) | |
138 | } | |
139 | ||
140 | func getBoolValue(boolString string) bool { | |
141 | switch boolString { | |
142 | case "1": | |
143 | return true | |
144 | case "True": | |
145 | return true | |
146 | case "true": | |
147 | return true | |
148 | default: | |
149 | return false | |
150 | } | |
151 | } | |
152 | ||
153 | func writeHeaders(w http.ResponseWriter, code int, jobName string) { | |
154 | h := w.Header() | |
155 | h.Add("Content-Type", "application/json") | |
156 | if jobName != "" { | |
157 | h.Add("Job-Name", jobName) | |
158 | } | |
159 | w.WriteHeader(code) | |
160 | } | |
161 | ||
162 | func handlerGetInfo(w http.ResponseWriter, r *http.Request) { | |
163 | writeHeaders(w, 200, "info") | |
164 | body := `{ | |
165 | "Containers": 2, | |
166 | "Debug": 1, | |
167 | "Driver": "aufs", | |
168 | "DriverStatus": [["Root Dir", "/mnt/sda1/var/lib/docker/aufs"], | |
169 | ["Dirs", "0"]], | |
170 | "ExecutionDriver": "native-0.2", | |
171 | "IPv4Forwarding": 1, | |
172 | "Images": 1, | |
173 | "IndexServerAddress": "https://index.docker.io/v1/", | |
174 | "InitPath": "/usr/local/bin/docker", | |
175 | "InitSha1": "", | |
176 | "KernelVersion": "3.16.4-tinycore64", | |
177 | "MemoryLimit": 1, | |
178 | "NEventsListener": 0, | |
179 | "NFd": 10, | |
180 | "NGoroutines": 11, | |
181 | "OperatingSystem": "Boot2Docker 1.3.1 (TCL 5.4); master : a083df4 - Thu Jan 01 00:00:00 UTC 1970", | |
182 | "SwapLimit": 1}` | |
183 | w.Write([]byte(body)) | |
184 | } | |
185 | ||
186 | func handlerGetContainers(w http.ResponseWriter, r *http.Request) { | |
187 | writeHeaders(w, 200, "containers") | |
188 | body := `[ | |
189 | { | |
190 | "Status": "Up 39 seconds", | |
191 | "Ports": [ | |
192 | { | |
193 | "Type": "tcp", | |
194 | "PublicPort": 49163, | |
195 | "PrivatePort": 8080, | |
196 | "IP": "0.0.0.0" | |
197 | } | |
198 | ], | |
199 | "Names": [ | |
200 | "/trusting_heisenberg" | |
201 | ], | |
202 | "Image": "foo:latest", | |
203 | "Id": "332375cfbc23edb921a21026314c3497674ba8bdcb2c85e0e65ebf2017f688ce", | |
204 | "Created": 1415720105, | |
205 | "Command": "/bin/go-run" | |
206 | } | |
207 | ]` | |
208 | if v, ok := r.URL.Query()["size"]; ok { | |
209 | if v[0] == "1" { | |
210 | body = `[ | |
211 | { | |
212 | "Status": "Up 39 seconds", | |
213 | "Ports": [ | |
214 | { | |
215 | "Type": "tcp", | |
216 | "PublicPort": 49163, | |
217 | "PrivatePort": 8080, | |
218 | "IP": "0.0.0.0" | |
219 | } | |
220 | ], | |
221 | "Names": [ | |
222 | "/trusting_heisenberg" | |
223 | ], | |
224 | "Image": "foo:latest", | |
225 | "Id": "332375cfbc23edb921a21026314c3497674ba8bdcb2c85e0e65ebf2017f688ce", | |
226 | "Created": 1415720105, | |
227 | "SizeRootFs": 12345, | |
228 | "SizeRW": 123, | |
229 | "Command": "/bin/go-run" | |
230 | } | |
231 | ]` | |
232 | } | |
233 | } | |
234 | if v, ok := r.URL.Query()["filters"]; ok { | |
235 | if v[0] != "{'id':['332375cfbc23edb921a21026314c3497674ba8bdcb2c85e0e65ebf2017f688ce']}" { | |
236 | body = "[]" | |
237 | } | |
238 | } | |
239 | w.Write([]byte(body)) | |
240 | } | |
241 | ||
242 | func handleEvents(w http.ResponseWriter, r *http.Request) { | |
243 | w.Write([]byte(eventsResp)) | |
244 | } |
0 | package dockerclient | |
1 | ||
2 | var haproxyPullOutput = `{"status":"The image you are pulling has been verified","id":"haproxy:1"} | |
3 | {"status":"Already exists","progressDetail":{},"id":"511136ea3c5a"}{"status":"Already exists","progressDetail":{},"id":"1aeada447715"}{"status":"Already exists","progressDetail":{},"id":"479215127fa7"}{"status":"Already exists","progressDetail":{},"id":"66301eb54a7d"}{"status":"Already exists","progressDetail":{},"id":"e3990b07573f"}{"status":"Already exists","progressDetail":{},"id":"ecb4b23ca7ce"}{"status":"Already exists","progressDetail":{},"id":"f453e940c177"}{"status":"Already exists","progressDetail":{},"id":"fc5ea1bc05ab"}{"status":"Already exists","progressDetail":{},"id":"380557f8f7b3"}{"status":"The image you are pulling has been verified","id":"haproxy:1.4"} | |
4 | {"status":"Already exists","progressDetail":{},"id":"511136ea3c5a"}{"status":"Already exists","progressDetail":{},"id":"1aeada447715"}{"status":"Already exists","progressDetail":{},"id":"479215127fa7"}{"status":"Already exists","progressDetail":{},"id":"63a1b9929e14"}{"status":"Already exists","progressDetail":{},"id":"af43bf7d176e"}{"status":"Already exists","progressDetail":{},"id":"851aac2d69aa"}{"status":"Already exists","progressDetail":{},"id":"345053a92c95"}{"status":"Already exists","progressDetail":{},"id":"b41231d429c9"}{"status":"The image you are pulling has been verified","id":"haproxy:1.4.25"} | |
5 | {"status":"Already exists","progressDetail":{},"id":"511136ea3c5a"}{"status":"Already exists","progressDetail":{},"id":"1aeada447715"}{"status":"Already exists","progressDetail":{},"id":"479215127fa7"}{"status":"Already exists","progressDetail":{},"id":"63a1b9929e14"}{"status":"Already exists","progressDetail":{},"id":"af43bf7d176e"}{"status":"Already exists","progressDetail":{},"id":"851aac2d69aa"}{"status":"Already exists","progressDetail":{},"id":"345053a92c95"}{"status":"Already exists","progressDetail":{},"id":"b41231d429c9"}{"status":"The image you are pulling has been verified","id":"haproxy:1.5"} | |
6 | {"status":"Already exists","progressDetail":{},"id":"511136ea3c5a"}{"status":"Already exists","progressDetail":{},"id":"1aeada447715"}{"status":"Already exists","progressDetail":{},"id":"479215127fa7"}{"status":"Already exists","progressDetail":{},"id":"66301eb54a7d"}{"status":"Already exists","progressDetail":{},"id":"e3990b07573f"}{"status":"Already exists","progressDetail":{},"id":"ecb4b23ca7ce"}{"status":"Already exists","progressDetail":{},"id":"f453e940c177"}{"status":"Already exists","progressDetail":{},"id":"fc5ea1bc05ab"}{"status":"Already exists","progressDetail":{},"id":"380557f8f7b3"}{"status":"The image you are pulling has been verified","id":"haproxy:1.5.10"} | |
7 | {"status":"Already exists","progressDetail":{},"id":"511136ea3c5a"}{"status":"Already exists","progressDetail":{},"id":"1aeada447715"}{"status":"Already exists","progressDetail":{},"id":"479215127fa7"}{"status":"Already exists","progressDetail":{},"id":"66301eb54a7d"}{"status":"Already exists","progressDetail":{},"id":"e3990b07573f"}{"status":"Already exists","progressDetail":{},"id":"ecb4b23ca7ce"}{"status":"Already exists","progressDetail":{},"id":"f453e940c177"}{"status":"Already exists","progressDetail":{},"id":"fc5ea1bc05ab"}{"status":"Already exists","progressDetail":{},"id":"380557f8f7b3"}{"status":"The image you are pulling has been verified","id":"haproxy:1.5.9"} | |
8 | {"status":"Already exists","progressDetail":{},"id":"511136ea3c5a"}{"status":"Already exists","progressDetail":{},"id":"1aeada447715"}{"status":"Already exists","progressDetail":{},"id":"479215127fa7"}{"status":"Already exists","progressDetail":{},"id":"66301eb54a7d"}{"status":"Already exists","progressDetail":{},"id":"e3990b07573f"}{"status":"Already exists","progressDetail":{},"id":"3d894e6f7e63"}{"status":"Already exists","progressDetail":{},"id":"4d949c40bc77"}{"status":"Already exists","progressDetail":{},"id":"55e031889365"}{"status":"Already exists","progressDetail":{},"id":"c7aa675e1876"}{"status":"The image you are pulling has been verified","id":"haproxy:latest"} | |
9 | {"status":"Already exists","progressDetail":{},"id":"511136ea3c5a"}{"status":"Already exists","progressDetail":{},"id":"1aeada447715"}{"status":"Already exists","progressDetail":{},"id":"479215127fa7"}{"status":"Already exists","progressDetail":{},"id":"66301eb54a7d"}{"status":"Already exists","progressDetail":{},"id":"e3990b07573f"}{"status":"Already exists","progressDetail":{},"id":"ecb4b23ca7ce"}{"status":"Already exists","progressDetail":{},"id":"f453e940c177"}{"status":"Already exists","progressDetail":{},"id":"fc5ea1bc05ab"}{"status":"Already exists","progressDetail":{},"id":"380557f8f7b3"}{"status":"Status: Image is up to date for haproxy"} | |
10 | ` | |
11 | ||
12 | var eventsResp = `{"status":"pull","id":"nginx:latest","time":1428620433}{"status":"create","id":"9b818c3b8291708fdcecd7c4086b75c222cb503be10a93d9c11040886032a48b","from":"nginx:latest","time":1428620433}{"status":"start","id":"9b818c3b8291708fdcecd7c4086b75c222cb503be10a93d9c11040886032a48b","from":"nginx:latest","time":1428620433}{"status":"die","id":"9b818c3b8291708fdcecd7c4086b75c222cb503be10a93d9c11040886032a48b","from":"nginx:latest","time":1428620442}{"status":"create","id":"352d0b412aae5a5d2b14ae9d88be59dc276602d9edb9dcc33e138e475b3e4720","from":"52.11.96.81/foobar/ubuntu:latest","time":1428620444}{"status":"start","id":"352d0b412aae5a5d2b14ae9d88be59dc276602d9edb9dcc33e138e475b3e4720","from":"52.11.96.81/foobar/ubuntu:latest","time":1428620444}{"status":"die","id":"352d0b412aae5a5d2b14ae9d88be59dc276602d9edb9dcc33e138e475b3e4720","from":"52.11.96.81/foobar/ubuntu:latest","time":1428620444}{"status":"pull","id":"debian:latest","time":1428620453}{"status":"create","id":"668887b5729946546b3072655dc6da08f0e3210111b68b704eb842adfce53f6c","from":"debian:latest","time":1428620453}{"status":"start","id":"668887b5729946546b3072655dc6da08f0e3210111b68b704eb842adfce53f6c","from":"debian:latest","time":1428620453}{"status":"die","id":"668887b5729946546b3072655dc6da08f0e3210111b68b704eb842adfce53f6c","from":"debian:latest","time":1428620453}{"status":"create","id":"eb4a19ec21ab29bbbffbf3ee2e2df9d99cb749780e1eff06a591cee5ba505180","from":"nginx:latest","time":1428620458}{"status":"start","id":"eb4a19ec21ab29bbbffbf3ee2e2df9d99cb749780e1eff06a591cee5ba505180","from":"nginx:latest","time":1428620458}{"status":"pause","id":"eb4a19ec21ab29bbbffbf3ee2e2df9d99cb749780e1eff06a591cee5ba505180","from":"nginx:latest","time":1428620462}{"status":"unpause","id":"eb4a19ec21ab29bbbffbf3ee2e2df9d99cb749780e1eff06a591cee5ba505180","from":"nginx:latest","time":1428620466}{"status":"die","id":"eb4a19ec21ab29bbbffbf3ee2e2df9d99cb749780e1eff06a591cee5ba505180","from":"nginx:latest","time":1428620469}` |
0 | package main | |
1 | ||
2 | import ( | |
3 | "github.com/samalba/dockerclient" | |
4 | "log" | |
5 | "os" | |
6 | "os/signal" | |
7 | "syscall" | |
8 | ) | |
9 | ||
10 | func eventCallback(e *dockerclient.Event, ec chan error, args ...interface{}) { | |
11 | log.Println(e) | |
12 | } | |
13 | ||
14 | var ( | |
15 | client *dockerclient.DockerClient | |
16 | ) | |
17 | ||
18 | func waitForInterrupt() { | |
19 | sigChan := make(chan os.Signal, 1) | |
20 | signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT) | |
21 | for _ = range sigChan { | |
22 | client.StopAllMonitorEvents() | |
23 | os.Exit(0) | |
24 | } | |
25 | } | |
26 | ||
27 | func main() { | |
28 | docker, err := dockerclient.NewDockerClient(os.Getenv("DOCKER_HOST"), nil) | |
29 | if err != nil { | |
30 | log.Fatal(err) | |
31 | } | |
32 | ||
33 | client = docker | |
34 | ||
35 | client.StartMonitorEvents(eventCallback, nil) | |
36 | ||
37 | waitForInterrupt() | |
38 | } |
0 | package main | |
1 | ||
2 | import ( | |
3 | "github.com/samalba/dockerclient" | |
4 | "log" | |
5 | "os" | |
6 | "os/signal" | |
7 | "syscall" | |
8 | ) | |
9 | ||
10 | func statCallback(id string, stat *dockerclient.Stats, ec chan error, args ...interface{}) { | |
11 | log.Println(stat) | |
12 | } | |
13 | ||
14 | func waitForInterrupt() { | |
15 | sigChan := make(chan os.Signal, 1) | |
16 | signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT) | |
17 | for _ = range sigChan { | |
18 | os.Exit(0) | |
19 | } | |
20 | } | |
21 | ||
22 | func main() { | |
23 | docker, err := dockerclient.NewDockerClient(os.Getenv("DOCKER_HOST"), nil) | |
24 | if err != nil { | |
25 | log.Fatal(err) | |
26 | } | |
27 | ||
28 | containerConfig := &dockerclient.ContainerConfig{Image: "busybox", Cmd: []string{"sh"}} | |
29 | containerId, err := docker.CreateContainer(containerConfig, "") | |
30 | if err != nil { | |
31 | log.Fatal(err) | |
32 | } | |
33 | ||
34 | // Start the container | |
35 | err = docker.StartContainer(containerId, nil) | |
36 | if err != nil { | |
37 | log.Fatal(err) | |
38 | } | |
39 | docker.StartMonitorStats(containerId, statCallback, nil) | |
40 | ||
41 | waitForInterrupt() | |
42 | } |
0 | package dockerclient | |
1 | ||
2 | import ( | |
3 | "io" | |
4 | ) | |
5 | ||
6 | type Callback func(*Event, chan error, ...interface{}) | |
7 | ||
8 | type StatCallback func(string, *Stats, chan error, ...interface{}) | |
9 | ||
10 | type Client interface { | |
11 | Info() (*Info, error) | |
12 | ListContainers(all, size bool, filters string) ([]Container, error) | |
13 | InspectContainer(id string) (*ContainerInfo, error) | |
14 | InspectImage(id string) (*ImageInfo, error) | |
15 | CreateContainer(config *ContainerConfig, name string) (string, error) | |
16 | ContainerLogs(id string, options *LogOptions) (io.ReadCloser, error) | |
17 | ContainerChanges(id string) ([]*ContainerChanges, error) | |
18 | ExecCreate(config *ExecConfig) (string, error) | |
19 | ExecStart(id string, config *ExecConfig) error | |
20 | ExecResize(id string, width, height int) error | |
21 | StartContainer(id string, config *HostConfig) error | |
22 | StopContainer(id string, timeout int) error | |
23 | RestartContainer(id string, timeout int) error | |
24 | KillContainer(id, signal string) error | |
25 | Wait(id string) <-chan WaitResult | |
26 | // MonitorEvents takes options and an optional stop channel, and returns | |
27 | // an EventOrError channel. If an error is ever sent, then no more | |
28 | // events will be sent. If a stop channel is provided, events will stop | |
29 | // being monitored after the stop channel is closed. | |
30 | MonitorEvents(options *MonitorEventsOptions, stopChan <-chan struct{}) (<-chan EventOrError, error) | |
31 | StartMonitorEvents(cb Callback, ec chan error, args ...interface{}) | |
32 | StopAllMonitorEvents() | |
33 | StartMonitorStats(id string, cb StatCallback, ec chan error, args ...interface{}) | |
34 | StopAllMonitorStats() | |
35 | TagImage(nameOrID string, repo string, tag string, force bool) error | |
36 | Version() (*Version, error) | |
37 | PullImage(name string, auth *AuthConfig) error | |
38 | LoadImage(reader io.Reader) error | |
39 | RemoveContainer(id string, force, volumes bool) error | |
40 | ListImages(all bool) ([]*Image, error) | |
41 | RemoveImage(name string, force bool) ([]*ImageDelete, error) | |
42 | PauseContainer(name string) error | |
43 | UnpauseContainer(name string) error | |
44 | RenameContainer(oldName string, newName string) error | |
45 | ImportImage(source string, repository string, tag string, tar io.Reader) (io.ReadCloser, error) | |
46 | BuildImage(image *BuildImage) (io.ReadCloser, error) | |
47 | ListVolumes() ([]*Volume, error) | |
48 | } |
0 | package mockclient | |
1 | ||
2 | import ( | |
3 | "io" | |
4 | ||
5 | "github.com/samalba/dockerclient" | |
6 | "github.com/stretchr/testify/mock" | |
7 | ) | |
8 | ||
9 | type MockClient struct { | |
10 | mock.Mock | |
11 | } | |
12 | ||
13 | func NewMockClient() *MockClient { | |
14 | return &MockClient{} | |
15 | } | |
16 | ||
17 | func (client *MockClient) Info() (*dockerclient.Info, error) { | |
18 | args := client.Mock.Called() | |
19 | return args.Get(0).(*dockerclient.Info), args.Error(1) | |
20 | } | |
21 | ||
22 | func (client *MockClient) ListContainers(all bool, size bool, filters string) ([]dockerclient.Container, error) { | |
23 | args := client.Mock.Called(all, size, filters) | |
24 | return args.Get(0).([]dockerclient.Container), args.Error(1) | |
25 | } | |
26 | ||
27 | func (client *MockClient) InspectContainer(id string) (*dockerclient.ContainerInfo, error) { | |
28 | args := client.Mock.Called(id) | |
29 | return args.Get(0).(*dockerclient.ContainerInfo), args.Error(1) | |
30 | } | |
31 | ||
32 | func (client *MockClient) InspectImage(id string) (*dockerclient.ImageInfo, error) { | |
33 | args := client.Mock.Called(id) | |
34 | return args.Get(0).(*dockerclient.ImageInfo), args.Error(1) | |
35 | } | |
36 | ||
37 | func (client *MockClient) CreateContainer(config *dockerclient.ContainerConfig, name string) (string, error) { | |
38 | args := client.Mock.Called(config, name) | |
39 | return args.String(0), args.Error(1) | |
40 | } | |
41 | ||
42 | func (client *MockClient) ContainerLogs(id string, options *dockerclient.LogOptions) (io.ReadCloser, error) { | |
43 | args := client.Mock.Called(id, options) | |
44 | return args.Get(0).(io.ReadCloser), args.Error(1) | |
45 | } | |
46 | ||
47 | func (client *MockClient) ContainerChanges(id string) ([]*dockerclient.ContainerChanges, error) { | |
48 | args := client.Mock.Called(id) | |
49 | return args.Get(0).([]*dockerclient.ContainerChanges), args.Error(1) | |
50 | } | |
51 | ||
52 | func (client *MockClient) StartContainer(id string, config *dockerclient.HostConfig) error { | |
53 | args := client.Mock.Called(id, config) | |
54 | return args.Error(0) | |
55 | } | |
56 | ||
57 | func (client *MockClient) StopContainer(id string, timeout int) error { | |
58 | args := client.Mock.Called(id, timeout) | |
59 | return args.Error(0) | |
60 | } | |
61 | ||
62 | func (client *MockClient) RestartContainer(id string, timeout int) error { | |
63 | args := client.Mock.Called(id, timeout) | |
64 | return args.Error(0) | |
65 | } | |
66 | ||
67 | func (client *MockClient) KillContainer(id, signal string) error { | |
68 | args := client.Mock.Called(id, signal) | |
69 | return args.Error(0) | |
70 | } | |
71 | ||
72 | func (client *MockClient) Wait(id string) <-chan dockerclient.WaitResult { | |
73 | args := client.Mock.Called(id) | |
74 | return args.Get(0).(<-chan dockerclient.WaitResult) | |
75 | } | |
76 | ||
77 | func (client *MockClient) MonitorEvents(options *dockerclient.MonitorEventsOptions, stopChan <-chan struct{}) (<-chan dockerclient.EventOrError, error) { | |
78 | args := client.Mock.Called(options, stopChan) | |
79 | return args.Get(0).(<-chan dockerclient.EventOrError), args.Error(1) | |
80 | } | |
81 | ||
82 | func (client *MockClient) StartMonitorEvents(cb dockerclient.Callback, ec chan error, args ...interface{}) { | |
83 | client.Mock.Called(cb, ec, args) | |
84 | } | |
85 | ||
86 | func (client *MockClient) StopAllMonitorEvents() { | |
87 | client.Mock.Called() | |
88 | } | |
89 | ||
90 | func (client *MockClient) TagImage(nameOrID string, repo string, tag string, force bool) error { | |
91 | args := client.Mock.Called(nameOrID, repo, tag, force) | |
92 | return args.Error(0) | |
93 | } | |
94 | ||
95 | func (client *MockClient) StartMonitorStats(id string, cb dockerclient.StatCallback, ec chan error, args ...interface{}) { | |
96 | client.Mock.Called(id, cb, ec, args) | |
97 | } | |
98 | ||
99 | func (client *MockClient) StopAllMonitorStats() { | |
100 | client.Mock.Called() | |
101 | } | |
102 | ||
103 | func (client *MockClient) Version() (*dockerclient.Version, error) { | |
104 | args := client.Mock.Called() | |
105 | return args.Get(0).(*dockerclient.Version), args.Error(1) | |
106 | } | |
107 | ||
108 | func (client *MockClient) PullImage(name string, auth *dockerclient.AuthConfig) error { | |
109 | args := client.Mock.Called(name, auth) | |
110 | return args.Error(0) | |
111 | } | |
112 | ||
113 | func (client *MockClient) LoadImage(reader io.Reader) error { | |
114 | args := client.Mock.Called(reader) | |
115 | return args.Error(0) | |
116 | } | |
117 | ||
118 | func (client *MockClient) RemoveContainer(id string, force, volumes bool) error { | |
119 | args := client.Mock.Called(id, force, volumes) | |
120 | return args.Error(0) | |
121 | } | |
122 | ||
123 | func (client *MockClient) ListImages(all bool) ([]*dockerclient.Image, error) { | |
124 | args := client.Mock.Called(all) | |
125 | return args.Get(0).([]*dockerclient.Image), args.Error(1) | |
126 | } | |
127 | ||
128 | func (client *MockClient) RemoveImage(name string, force bool) ([]*dockerclient.ImageDelete, error) { | |
129 | args := client.Mock.Called(name, force) | |
130 | return args.Get(0).([]*dockerclient.ImageDelete), args.Error(1) | |
131 | } | |
132 | ||
133 | func (client *MockClient) PauseContainer(name string) error { | |
134 | args := client.Mock.Called(name) | |
135 | return args.Error(0) | |
136 | } | |
137 | ||
138 | func (client *MockClient) UnpauseContainer(name string) error { | |
139 | args := client.Mock.Called(name) | |
140 | return args.Error(0) | |
141 | } | |
142 | ||
143 | func (client *MockClient) ExecCreate(config *dockerclient.ExecConfig) (string, error) { | |
144 | args := client.Mock.Called(config) | |
145 | return args.String(0), args.Error(1) | |
146 | } | |
147 | ||
148 | func (client *MockClient) ExecStart(id string, config *dockerclient.ExecConfig) error { | |
149 | args := client.Mock.Called(id, config) | |
150 | return args.Error(0) | |
151 | } | |
152 | ||
153 | func (client *MockClient) ExecResize(id string, width, height int) error { | |
154 | args := client.Mock.Called(id, width, height) | |
155 | return args.Error(0) | |
156 | } | |
157 | ||
158 | func (client *MockClient) RenameContainer(oldName string, newName string) error { | |
159 | args := client.Mock.Called(oldName, newName) | |
160 | return args.Error(0) | |
161 | } | |
162 | ||
163 | func (client *MockClient) ImportImage(source string, repository string, tag string, tar io.Reader) (io.ReadCloser, error) { | |
164 | args := client.Mock.Called(source, repository, tag, tar) | |
165 | return args.Get(0).(io.ReadCloser), args.Error(1) | |
166 | } | |
167 | ||
168 | func (client *MockClient) BuildImage(image *dockerclient.BuildImage) (io.ReadCloser, error) { | |
169 | args := client.Mock.Called(image) | |
170 | return args.Get(0).(io.ReadCloser), args.Error(1) | |
171 | } | |
172 | ||
173 | func (client *MockClient) ListVolumes() ([]*dockerclient.Volume, error) { | |
174 | args := client.Mock.Called() | |
175 | return args.Get(0).([]*dockerclient.Volume), args.Error(1) | |
176 | } |
0 | package mockclient | |
1 | ||
2 | import ( | |
3 | "reflect" | |
4 | "testing" | |
5 | ||
6 | "github.com/samalba/dockerclient" | |
7 | ) | |
8 | ||
9 | func TestMock(t *testing.T) { | |
10 | mock := NewMockClient() | |
11 | mock.On("Version").Return(&dockerclient.Version{Version: "foo"}, nil).Once() | |
12 | ||
13 | v, err := mock.Version() | |
14 | if err != nil { | |
15 | t.Fatal(err) | |
16 | } | |
17 | if v.Version != "foo" { | |
18 | t.Fatal(v) | |
19 | } | |
20 | ||
21 | mock.Mock.AssertExpectations(t) | |
22 | } | |
23 | ||
24 | func TestMockInterface(t *testing.T) { | |
25 | iface := reflect.TypeOf((*dockerclient.Client)(nil)).Elem() | |
26 | mock := NewMockClient() | |
27 | ||
28 | if !reflect.TypeOf(mock).Implements(iface) { | |
29 | t.Fatalf("Mock does not implement the Client interface") | |
30 | } | |
31 | } |
0 | package nopclient | |
1 | ||
2 | import ( | |
3 | "errors" | |
4 | "io" | |
5 | ||
6 | "github.com/samalba/dockerclient" | |
7 | ) | |
8 | ||
9 | var ( | |
10 | ErrNoEngine = errors.New("Engine no longer exists") | |
11 | ) | |
12 | ||
13 | type NopClient struct { | |
14 | } | |
15 | ||
16 | func NewNopClient() *NopClient { | |
17 | return &NopClient{} | |
18 | } | |
19 | ||
20 | func (client *NopClient) Info() (*dockerclient.Info, error) { | |
21 | return nil, ErrNoEngine | |
22 | } | |
23 | ||
24 | func (client *NopClient) ListContainers(all bool, size bool, filters string) ([]dockerclient.Container, error) { | |
25 | return nil, ErrNoEngine | |
26 | } | |
27 | ||
28 | func (client *NopClient) InspectContainer(id string) (*dockerclient.ContainerInfo, error) { | |
29 | return nil, ErrNoEngine | |
30 | } | |
31 | ||
32 | func (client *NopClient) InspectImage(id string) (*dockerclient.ImageInfo, error) { | |
33 | return nil, ErrNoEngine | |
34 | } | |
35 | ||
36 | func (client *NopClient) CreateContainer(config *dockerclient.ContainerConfig, name string) (string, error) { | |
37 | return "", ErrNoEngine | |
38 | } | |
39 | ||
40 | func (client *NopClient) ContainerLogs(id string, options *dockerclient.LogOptions) (io.ReadCloser, error) { | |
41 | return nil, ErrNoEngine | |
42 | } | |
43 | ||
44 | func (client *NopClient) ContainerChanges(id string) ([]*dockerclient.ContainerChanges, error) { | |
45 | return nil, ErrNoEngine | |
46 | } | |
47 | ||
48 | func (client *NopClient) StartContainer(id string, config *dockerclient.HostConfig) error { | |
49 | return ErrNoEngine | |
50 | } | |
51 | ||
52 | func (client *NopClient) StopContainer(id string, timeout int) error { | |
53 | return ErrNoEngine | |
54 | } | |
55 | ||
56 | func (client *NopClient) RestartContainer(id string, timeout int) error { | |
57 | return ErrNoEngine | |
58 | } | |
59 | ||
60 | func (client *NopClient) KillContainer(id, signal string) error { | |
61 | return ErrNoEngine | |
62 | } | |
63 | ||
64 | func (client *NopClient) Wait(id string) <-chan dockerclient.WaitResult { | |
65 | return nil | |
66 | } | |
67 | ||
68 | func (client *NopClient) MonitorEvents(options *dockerclient.MonitorEventsOptions, stopChan <-chan struct{}) (<-chan dockerclient.EventOrError, error) { | |
69 | return nil, ErrNoEngine | |
70 | } | |
71 | ||
72 | func (client *NopClient) StartMonitorEvents(cb dockerclient.Callback, ec chan error, args ...interface{}) { | |
73 | return | |
74 | } | |
75 | ||
76 | func (client *NopClient) StopAllMonitorEvents() { | |
77 | return | |
78 | } | |
79 | ||
80 | func (client *NopClient) TagImage(nameOrID string, repo string, tag string, force bool) error { | |
81 | return ErrNoEngine | |
82 | } | |
83 | ||
84 | func (client *NopClient) StartMonitorStats(id string, cb dockerclient.StatCallback, ec chan error, args ...interface{}) { | |
85 | return | |
86 | } | |
87 | ||
88 | func (client *NopClient) StopAllMonitorStats() { | |
89 | return | |
90 | } | |
91 | ||
92 | func (client *NopClient) Version() (*dockerclient.Version, error) { | |
93 | return nil, ErrNoEngine | |
94 | } | |
95 | ||
96 | func (client *NopClient) PullImage(name string, auth *dockerclient.AuthConfig) error { | |
97 | return ErrNoEngine | |
98 | } | |
99 | ||
100 | func (client *NopClient) LoadImage(reader io.Reader) error { | |
101 | return ErrNoEngine | |
102 | } | |
103 | ||
104 | func (client *NopClient) RemoveContainer(id string, force, volumes bool) error { | |
105 | return ErrNoEngine | |
106 | } | |
107 | ||
108 | func (client *NopClient) ListImages(all bool) ([]*dockerclient.Image, error) { | |
109 | return nil, ErrNoEngine | |
110 | } | |
111 | ||
112 | func (client *NopClient) RemoveImage(name string, force bool) ([]*dockerclient.ImageDelete, error) { | |
113 | return nil, ErrNoEngine | |
114 | } | |
115 | ||
116 | func (client *NopClient) PauseContainer(name string) error { | |
117 | return ErrNoEngine | |
118 | } | |
119 | ||
120 | func (client *NopClient) UnpauseContainer(name string) error { | |
121 | return ErrNoEngine | |
122 | } | |
123 | ||
124 | func (client *NopClient) ExecCreate(config *dockerclient.ExecConfig) (string, error) { | |
125 | return "", ErrNoEngine | |
126 | } | |
127 | ||
128 | func (client *NopClient) ExecStart(id string, config *dockerclient.ExecConfig) error { | |
129 | return ErrNoEngine | |
130 | } | |
131 | ||
132 | func (client *NopClient) ExecResize(id string, width, height int) error { | |
133 | return ErrNoEngine | |
134 | } | |
135 | ||
136 | func (client *NopClient) RenameContainer(oldName string, newName string) error { | |
137 | return ErrNoEngine | |
138 | } | |
139 | ||
140 | func (client *NopClient) ImportImage(source string, repository string, tag string, tar io.Reader) (io.ReadCloser, error) { | |
141 | return nil, ErrNoEngine | |
142 | } | |
143 | ||
144 | func (client *NopClient) BuildImage(image *dockerclient.BuildImage) (io.ReadCloser, error) { | |
145 | return nil, ErrNoEngine | |
146 | } | |
147 | ||
148 | func (client *NopClient) ListVolumes() ([]*dockerclient.Volume, error) { | |
149 | return nil, ErrNoEngine | |
150 | } |
0 | package dockerclient | |
1 | ||
2 | import ( | |
3 | "fmt" | |
4 | "io" | |
5 | "time" | |
6 | ||
7 | "github.com/docker/docker/pkg/units" | |
8 | ) | |
9 | ||
10 | type ContainerConfig struct { | |
11 | Hostname string | |
12 | Domainname string | |
13 | User string | |
14 | AttachStdin bool | |
15 | AttachStdout bool | |
16 | AttachStderr bool | |
17 | ExposedPorts map[string]struct{} | |
18 | Tty bool | |
19 | OpenStdin bool | |
20 | StdinOnce bool | |
21 | Env []string | |
22 | Cmd []string | |
23 | Image string | |
24 | Volumes map[string]struct{} | |
25 | VolumeDriver string | |
26 | WorkingDir string | |
27 | Entrypoint []string | |
28 | NetworkDisabled bool | |
29 | MacAddress string | |
30 | OnBuild []string | |
31 | Labels map[string]string | |
32 | ||
33 | // FIXME: The following fields have been removed since API v1.18 | |
34 | Memory int64 | |
35 | MemorySwap int64 | |
36 | CpuShares int64 | |
37 | Cpuset string | |
38 | PortSpecs []string | |
39 | ||
40 | // This is used only by the create command | |
41 | HostConfig HostConfig | |
42 | } | |
43 | ||
44 | type HostConfig struct { | |
45 | Binds []string | |
46 | ContainerIDFile string | |
47 | LxcConf []map[string]string | |
48 | Memory int64 | |
49 | MemorySwap int64 | |
50 | CpuShares int64 | |
51 | CpuPeriod int64 | |
52 | CpusetCpus string | |
53 | CpusetMems string | |
54 | CpuQuota int64 | |
55 | BlkioWeight int64 | |
56 | OomKillDisable bool | |
57 | Privileged bool | |
58 | PortBindings map[string][]PortBinding | |
59 | Links []string | |
60 | PublishAllPorts bool | |
61 | Dns []string | |
62 | DnsSearch []string | |
63 | ExtraHosts []string | |
64 | VolumesFrom []string | |
65 | Devices []DeviceMapping | |
66 | NetworkMode string | |
67 | IpcMode string | |
68 | PidMode string | |
69 | UTSMode string | |
70 | CapAdd []string | |
71 | CapDrop []string | |
72 | RestartPolicy RestartPolicy | |
73 | SecurityOpt []string | |
74 | ReadonlyRootfs bool | |
75 | Ulimits []Ulimit | |
76 | LogConfig LogConfig | |
77 | CgroupParent string | |
78 | } | |
79 | ||
80 | type DeviceMapping struct { | |
81 | PathOnHost string `json:"PathOnHost"` | |
82 | PathInContainer string `json:"PathInContainer"` | |
83 | CgroupPermissions string `json:"CgroupPermissions"` | |
84 | } | |
85 | ||
86 | type ExecConfig struct { | |
87 | AttachStdin bool | |
88 | AttachStdout bool | |
89 | AttachStderr bool | |
90 | Tty bool | |
91 | Cmd []string | |
92 | Container string | |
93 | Detach bool | |
94 | } | |
95 | ||
96 | type LogOptions struct { | |
97 | Follow bool | |
98 | Stdout bool | |
99 | Stderr bool | |
100 | Timestamps bool | |
101 | Tail int64 | |
102 | } | |
103 | ||
104 | type MonitorEventsFilters struct { | |
105 | Event string `json:",omitempty"` | |
106 | Image string `json:",omitempty"` | |
107 | Container string `json:",omitempty"` | |
108 | } | |
109 | ||
110 | type MonitorEventsOptions struct { | |
111 | Since int | |
112 | Until int | |
113 | Filters *MonitorEventsFilters `json:",omitempty"` | |
114 | } | |
115 | ||
116 | type RestartPolicy struct { | |
117 | Name string | |
118 | MaximumRetryCount int64 | |
119 | } | |
120 | ||
121 | type PortBinding struct { | |
122 | HostIp string | |
123 | HostPort string | |
124 | } | |
125 | ||
126 | type State struct { | |
127 | Running bool | |
128 | Paused bool | |
129 | Restarting bool | |
130 | OOMKilled bool | |
131 | Dead bool | |
132 | Pid int | |
133 | ExitCode int | |
134 | Error string // contains last known error when starting the container | |
135 | StartedAt time.Time | |
136 | FinishedAt time.Time | |
137 | Ghost bool | |
138 | } | |
139 | ||
140 | // String returns a human-readable description of the state | |
141 | // Stoken from docker/docker/daemon/state.go | |
142 | func (s *State) String() string { | |
143 | if s.Running { | |
144 | if s.Paused { | |
145 | return fmt.Sprintf("Up %s (Paused)", units.HumanDuration(time.Now().UTC().Sub(s.StartedAt))) | |
146 | } | |
147 | if s.Restarting { | |
148 | return fmt.Sprintf("Restarting (%d) %s ago", s.ExitCode, units.HumanDuration(time.Now().UTC().Sub(s.FinishedAt))) | |
149 | } | |
150 | ||
151 | return fmt.Sprintf("Up %s", units.HumanDuration(time.Now().UTC().Sub(s.StartedAt))) | |
152 | } | |
153 | ||
154 | if s.Dead { | |
155 | return "Dead" | |
156 | } | |
157 | ||
158 | if s.FinishedAt.IsZero() { | |
159 | return "" | |
160 | } | |
161 | ||
162 | return fmt.Sprintf("Exited (%d) %s ago", s.ExitCode, units.HumanDuration(time.Now().UTC().Sub(s.FinishedAt))) | |
163 | } | |
164 | ||
165 | // StateString returns a single string to describe state | |
166 | // Stoken from docker/docker/daemon/state.go | |
167 | func (s *State) StateString() string { | |
168 | if s.Running { | |
169 | if s.Paused { | |
170 | return "paused" | |
171 | } | |
172 | if s.Restarting { | |
173 | return "restarting" | |
174 | } | |
175 | return "running" | |
176 | } | |
177 | ||
178 | if s.Dead { | |
179 | return "dead" | |
180 | } | |
181 | ||
182 | return "exited" | |
183 | } | |
184 | ||
185 | type ImageInfo struct { | |
186 | Architecture string | |
187 | Author string | |
188 | Comment string | |
189 | Config *ContainerConfig | |
190 | Container string | |
191 | ContainerConfig *ContainerConfig | |
192 | Created time.Time | |
193 | DockerVersion string | |
194 | Id string | |
195 | Os string | |
196 | Parent string | |
197 | Size int64 | |
198 | VirtualSize int64 | |
199 | } | |
200 | ||
201 | type ContainerInfo struct { | |
202 | Id string | |
203 | Created string | |
204 | Path string | |
205 | Name string | |
206 | Args []string | |
207 | ExecIDs []string | |
208 | Config *ContainerConfig | |
209 | State *State | |
210 | Image string | |
211 | NetworkSettings struct { | |
212 | IPAddress string `json:"IpAddress"` | |
213 | IPPrefixLen int `json:"IpPrefixLen"` | |
214 | Gateway string | |
215 | Bridge string | |
216 | Ports map[string][]PortBinding | |
217 | } | |
218 | SysInitPath string | |
219 | ResolvConfPath string | |
220 | Volumes map[string]string | |
221 | HostConfig *HostConfig | |
222 | } | |
223 | ||
224 | type ContainerChanges struct { | |
225 | Path string | |
226 | Kind int | |
227 | } | |
228 | ||
229 | type Port struct { | |
230 | IP string | |
231 | PrivatePort int | |
232 | PublicPort int | |
233 | Type string | |
234 | } | |
235 | ||
236 | type Container struct { | |
237 | Id string | |
238 | Names []string | |
239 | Image string | |
240 | Command string | |
241 | Created int64 | |
242 | Status string | |
243 | Ports []Port | |
244 | SizeRw int64 | |
245 | SizeRootFs int64 | |
246 | Labels map[string]string | |
247 | } | |
248 | ||
249 | type Event struct { | |
250 | Id string | |
251 | Status string | |
252 | From string | |
253 | Time int64 | |
254 | } | |
255 | ||
256 | type Version struct { | |
257 | ApiVersion string | |
258 | Arch string | |
259 | GitCommit string | |
260 | GoVersion string | |
261 | KernelVersion string | |
262 | Os string | |
263 | Version string | |
264 | } | |
265 | ||
266 | type RespContainersCreate struct { | |
267 | Id string | |
268 | Warnings []string | |
269 | } | |
270 | ||
271 | type Image struct { | |
272 | Created int64 | |
273 | Id string | |
274 | ParentId string | |
275 | RepoTags []string | |
276 | Size int64 | |
277 | VirtualSize int64 | |
278 | } | |
279 | ||
280 | // Info is the struct returned by /info | |
281 | // The API is currently in flux, so Debug, MemoryLimit, SwapLimit, and | |
282 | // IPv4Forwarding are interfaces because in docker 1.6.1 they are 0 or 1 but in | |
283 | // master they are bools. | |
284 | type Info struct { | |
285 | ID string | |
286 | Containers int64 | |
287 | Driver string | |
288 | DriverStatus [][]string | |
289 | ExecutionDriver string | |
290 | Images int64 | |
291 | KernelVersion string | |
292 | OperatingSystem string | |
293 | NCPU int64 | |
294 | MemTotal int64 | |
295 | Name string | |
296 | Labels []string | |
297 | Debug interface{} | |
298 | NFd int64 | |
299 | NGoroutines int64 | |
300 | SystemTime string | |
301 | NEventsListener int64 | |
302 | InitPath string | |
303 | InitSha1 string | |
304 | IndexServerAddress string | |
305 | MemoryLimit interface{} | |
306 | SwapLimit interface{} | |
307 | IPv4Forwarding interface{} | |
308 | BridgeNfIptables bool | |
309 | BridgeNfIp6tables bool | |
310 | DockerRootDir string | |
311 | HttpProxy string | |
312 | HttpsProxy string | |
313 | NoProxy string | |
314 | } | |
315 | ||
316 | type ImageDelete struct { | |
317 | Deleted string | |
318 | Untagged string | |
319 | } | |
320 | ||
321 | type EventOrError struct { | |
322 | Event | |
323 | Error error | |
324 | } | |
325 | ||
326 | type WaitResult struct { | |
327 | ExitCode int | |
328 | Error error | |
329 | } | |
330 | ||
331 | type decodingResult struct { | |
332 | result interface{} | |
333 | err error | |
334 | } | |
335 | ||
336 | // The following are types for the API stats endpoint | |
337 | type ThrottlingData struct { | |
338 | // Number of periods with throttling active | |
339 | Periods uint64 `json:"periods"` | |
340 | // Number of periods when the container hit its throttling limit. | |
341 | ThrottledPeriods uint64 `json:"throttled_periods"` | |
342 | // Aggregate time the container was throttled for in nanoseconds. | |
343 | ThrottledTime uint64 `json:"throttled_time"` | |
344 | } | |
345 | ||
346 | type CpuUsage struct { | |
347 | // Total CPU time consumed. | |
348 | // Units: nanoseconds. | |
349 | TotalUsage uint64 `json:"total_usage"` | |
350 | // Total CPU time consumed per core. | |
351 | // Units: nanoseconds. | |
352 | PercpuUsage []uint64 `json:"percpu_usage"` | |
353 | // Time spent by tasks of the cgroup in kernel mode. | |
354 | // Units: nanoseconds. | |
355 | UsageInKernelmode uint64 `json:"usage_in_kernelmode"` | |
356 | // Time spent by tasks of the cgroup in user mode. | |
357 | // Units: nanoseconds. | |
358 | UsageInUsermode uint64 `json:"usage_in_usermode"` | |
359 | } | |
360 | ||
361 | type CpuStats struct { | |
362 | CpuUsage CpuUsage `json:"cpu_usage"` | |
363 | SystemUsage uint64 `json:"system_cpu_usage"` | |
364 | ThrottlingData ThrottlingData `json:"throttling_data,omitempty"` | |
365 | } | |
366 | ||
367 | type NetworkStats struct { | |
368 | RxBytes uint64 `json:"rx_bytes"` | |
369 | RxPackets uint64 `json:"rx_packets"` | |
370 | RxErrors uint64 `json:"rx_errors"` | |
371 | RxDropped uint64 `json:"rx_dropped"` | |
372 | TxBytes uint64 `json:"tx_bytes"` | |
373 | TxPackets uint64 `json:"tx_packets"` | |
374 | TxErrors uint64 `json:"tx_errors"` | |
375 | TxDropped uint64 `json:"tx_dropped"` | |
376 | } | |
377 | ||
378 | type MemoryStats struct { | |
379 | Usage uint64 `json:"usage"` | |
380 | MaxUsage uint64 `json:"max_usage"` | |
381 | Stats map[string]uint64 `json:"stats"` | |
382 | Failcnt uint64 `json:"failcnt"` | |
383 | Limit uint64 `json:"limit"` | |
384 | } | |
385 | ||
386 | type BlkioStatEntry struct { | |
387 | Major uint64 `json:"major"` | |
388 | Minor uint64 `json:"minor"` | |
389 | Op string `json:"op"` | |
390 | Value uint64 `json:"value"` | |
391 | } | |
392 | ||
393 | type BlkioStats struct { | |
394 | // number of bytes tranferred to and from the block device | |
395 | IoServiceBytesRecursive []BlkioStatEntry `json:"io_service_bytes_recursive"` | |
396 | IoServicedRecursive []BlkioStatEntry `json:"io_serviced_recursive"` | |
397 | IoQueuedRecursive []BlkioStatEntry `json:"io_queue_recursive"` | |
398 | IoServiceTimeRecursive []BlkioStatEntry `json:"io_service_time_recursive"` | |
399 | IoWaitTimeRecursive []BlkioStatEntry `json:"io_wait_time_recursive"` | |
400 | IoMergedRecursive []BlkioStatEntry `json:"io_merged_recursive"` | |
401 | IoTimeRecursive []BlkioStatEntry `json:"io_time_recursive"` | |
402 | SectorsRecursive []BlkioStatEntry `json:"sectors_recursive"` | |
403 | } | |
404 | ||
405 | type Stats struct { | |
406 | Read time.Time `json:"read"` | |
407 | NetworkStats NetworkStats `json:"network,omitempty"` | |
408 | CpuStats CpuStats `json:"cpu_stats,omitempty"` | |
409 | MemoryStats MemoryStats `json:"memory_stats,omitempty"` | |
410 | BlkioStats BlkioStats `json:"blkio_stats,omitempty"` | |
411 | } | |
412 | ||
413 | type Ulimit struct { | |
414 | Name string `json:"name"` | |
415 | Soft uint64 `json:"soft"` | |
416 | Hard uint64 `json:"hard"` | |
417 | } | |
418 | ||
419 | type LogConfig struct { | |
420 | Type string `json:"type"` | |
421 | Config map[string]string `json:"config"` | |
422 | } | |
423 | ||
424 | type BuildImage struct { | |
425 | Config *ConfigFile | |
426 | DockerfileName string | |
427 | Context io.Reader | |
428 | RemoteURL string | |
429 | RepoName string | |
430 | SuppressOutput bool | |
431 | NoCache bool | |
432 | Remove bool | |
433 | ForceRemove bool | |
434 | Pull bool | |
435 | Memory int64 | |
436 | MemorySwap int64 | |
437 | CpuShares int64 | |
438 | CpuPeriod int64 | |
439 | CpuQuota int64 | |
440 | CpuSetCpus string | |
441 | CpuSetMems string | |
442 | CgroupParent string | |
443 | } | |
444 | ||
445 | type Volume struct { | |
446 | Name string // Name is the name of the volume | |
447 | Driver string // Driver is the Driver name used to create the volume | |
448 | Mountpoint string // Mountpoint is the location on disk of the volume | |
449 | } | |
450 | ||
451 | type VolumesListResponse struct { | |
452 | Volumes []*Volume // Volumes is the list of volumes being returned | |
453 | } |
0 | package dockerclient | |
1 | ||
2 | import ( | |
3 | "crypto/tls" | |
4 | "net" | |
5 | "net/http" | |
6 | "net/url" | |
7 | "time" | |
8 | ) | |
9 | ||
10 | func newHTTPClient(u *url.URL, tlsConfig *tls.Config, timeout time.Duration) *http.Client { | |
11 | httpTransport := &http.Transport{ | |
12 | TLSClientConfig: tlsConfig, | |
13 | } | |
14 | ||
15 | switch u.Scheme { | |
16 | default: | |
17 | httpTransport.Dial = func(proto, addr string) (net.Conn, error) { | |
18 | return net.DialTimeout(proto, addr, timeout) | |
19 | } | |
20 | case "unix": | |
21 | socketPath := u.Path | |
22 | unixDial := func(proto, addr string) (net.Conn, error) { | |
23 | return net.DialTimeout("unix", socketPath, timeout) | |
24 | } | |
25 | httpTransport.Dial = unixDial | |
26 | // Override the main URL object so the HTTP lib won't complain | |
27 | u.Scheme = "http" | |
28 | u.Host = "unix.sock" | |
29 | u.Path = "" | |
30 | } | |
31 | return &http.Client{Transport: httpTransport} | |
32 | } |