Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
Aaron Lehmann authored on 2017/03/16 07:04:321 | 1 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,22 @@ |
0 |
+package client |
|
1 |
+ |
|
2 |
+import ( |
|
3 |
+ "encoding/json" |
|
4 |
+ |
|
5 |
+ "github.com/docker/docker/api/types" |
|
6 |
+ "github.com/docker/docker/api/types/swarm" |
|
7 |
+ "golang.org/x/net/context" |
|
8 |
+) |
|
9 |
+ |
|
10 |
+// ConfigCreate creates a new Config. |
|
11 |
+func (cli *Client) ConfigCreate(ctx context.Context, config swarm.ConfigSpec) (types.ConfigCreateResponse, error) { |
|
12 |
+ var response types.ConfigCreateResponse |
|
13 |
+ resp, err := cli.post(ctx, "/configs/create", nil, config, nil) |
|
14 |
+ if err != nil { |
|
15 |
+ return response, err |
|
16 |
+ } |
|
17 |
+ |
|
18 |
+ err = json.NewDecoder(resp.body).Decode(&response) |
|
19 |
+ ensureReaderClosed(resp) |
|
20 |
+ return response, err |
|
21 |
+} |
0 | 22 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,57 @@ |
0 |
+package client |
|
1 |
+ |
|
2 |
+import ( |
|
3 |
+ "bytes" |
|
4 |
+ "encoding/json" |
|
5 |
+ "fmt" |
|
6 |
+ "io/ioutil" |
|
7 |
+ "net/http" |
|
8 |
+ "strings" |
|
9 |
+ "testing" |
|
10 |
+ |
|
11 |
+ "github.com/docker/docker/api/types" |
|
12 |
+ "github.com/docker/docker/api/types/swarm" |
|
13 |
+ "golang.org/x/net/context" |
|
14 |
+) |
|
15 |
+ |
|
16 |
+func TestConfigCreateError(t *testing.T) { |
|
17 |
+ client := &Client{ |
|
18 |
+ client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")), |
|
19 |
+ } |
|
20 |
+ _, err := client.ConfigCreate(context.Background(), swarm.ConfigSpec{}) |
|
21 |
+ if err == nil || err.Error() != "Error response from daemon: Server error" { |
|
22 |
+ t.Fatalf("expected a Server Error, got %v", err) |
|
23 |
+ } |
|
24 |
+} |
|
25 |
+ |
|
26 |
+func TestConfigCreate(t *testing.T) { |
|
27 |
+ expectedURL := "/configs/create" |
|
28 |
+ client := &Client{ |
|
29 |
+ client: newMockClient(func(req *http.Request) (*http.Response, error) { |
|
30 |
+ if !strings.HasPrefix(req.URL.Path, expectedURL) { |
|
31 |
+ return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) |
|
32 |
+ } |
|
33 |
+ if req.Method != "POST" { |
|
34 |
+ return nil, fmt.Errorf("expected POST method, got %s", req.Method) |
|
35 |
+ } |
|
36 |
+ b, err := json.Marshal(types.ConfigCreateResponse{ |
|
37 |
+ ID: "test_config", |
|
38 |
+ }) |
|
39 |
+ if err != nil { |
|
40 |
+ return nil, err |
|
41 |
+ } |
|
42 |
+ return &http.Response{ |
|
43 |
+ StatusCode: http.StatusCreated, |
|
44 |
+ Body: ioutil.NopCloser(bytes.NewReader(b)), |
|
45 |
+ }, nil |
|
46 |
+ }), |
|
47 |
+ } |
|
48 |
+ |
|
49 |
+ r, err := client.ConfigCreate(context.Background(), swarm.ConfigSpec{}) |
|
50 |
+ if err != nil { |
|
51 |
+ t.Fatal(err) |
|
52 |
+ } |
|
53 |
+ if r.ID != "test_config" { |
|
54 |
+ t.Fatalf("expected `test_config`, got %s", r.ID) |
|
55 |
+ } |
|
56 |
+} |
0 | 57 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,34 @@ |
0 |
+package client |
|
1 |
+ |
|
2 |
+import ( |
|
3 |
+ "bytes" |
|
4 |
+ "encoding/json" |
|
5 |
+ "io/ioutil" |
|
6 |
+ "net/http" |
|
7 |
+ |
|
8 |
+ "github.com/docker/docker/api/types/swarm" |
|
9 |
+ "golang.org/x/net/context" |
|
10 |
+) |
|
11 |
+ |
|
12 |
+// ConfigInspectWithRaw returns the config information with raw data |
|
13 |
+func (cli *Client) ConfigInspectWithRaw(ctx context.Context, id string) (swarm.Config, []byte, error) { |
|
14 |
+ resp, err := cli.get(ctx, "/configs/"+id, nil, nil) |
|
15 |
+ if err != nil { |
|
16 |
+ if resp.statusCode == http.StatusNotFound { |
|
17 |
+ return swarm.Config{}, nil, configNotFoundError{id} |
|
18 |
+ } |
|
19 |
+ return swarm.Config{}, nil, err |
|
20 |
+ } |
|
21 |
+ defer ensureReaderClosed(resp) |
|
22 |
+ |
|
23 |
+ body, err := ioutil.ReadAll(resp.body) |
|
24 |
+ if err != nil { |
|
25 |
+ return swarm.Config{}, nil, err |
|
26 |
+ } |
|
27 |
+ |
|
28 |
+ var config swarm.Config |
|
29 |
+ rdr := bytes.NewReader(body) |
|
30 |
+ err = json.NewDecoder(rdr).Decode(&config) |
|
31 |
+ |
|
32 |
+ return config, body, err |
|
33 |
+} |
0 | 34 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,65 @@ |
0 |
+package client |
|
1 |
+ |
|
2 |
+import ( |
|
3 |
+ "bytes" |
|
4 |
+ "encoding/json" |
|
5 |
+ "fmt" |
|
6 |
+ "io/ioutil" |
|
7 |
+ "net/http" |
|
8 |
+ "strings" |
|
9 |
+ "testing" |
|
10 |
+ |
|
11 |
+ "github.com/docker/docker/api/types/swarm" |
|
12 |
+ "golang.org/x/net/context" |
|
13 |
+) |
|
14 |
+ |
|
15 |
+func TestConfigInspectError(t *testing.T) { |
|
16 |
+ client := &Client{ |
|
17 |
+ client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")), |
|
18 |
+ } |
|
19 |
+ |
|
20 |
+ _, _, err := client.ConfigInspectWithRaw(context.Background(), "nothing") |
|
21 |
+ if err == nil || err.Error() != "Error response from daemon: Server error" { |
|
22 |
+ t.Fatalf("expected a Server Error, got %v", err) |
|
23 |
+ } |
|
24 |
+} |
|
25 |
+ |
|
26 |
+func TestConfigInspectConfigNotFound(t *testing.T) { |
|
27 |
+ client := &Client{ |
|
28 |
+ client: newMockClient(errorMock(http.StatusNotFound, "Server error")), |
|
29 |
+ } |
|
30 |
+ |
|
31 |
+ _, _, err := client.ConfigInspectWithRaw(context.Background(), "unknown") |
|
32 |
+ if err == nil || !IsErrConfigNotFound(err) { |
|
33 |
+ t.Fatalf("expected a configNotFoundError error, got %v", err) |
|
34 |
+ } |
|
35 |
+} |
|
36 |
+ |
|
37 |
+func TestConfigInspect(t *testing.T) { |
|
38 |
+ expectedURL := "/configs/config_id" |
|
39 |
+ client := &Client{ |
|
40 |
+ client: newMockClient(func(req *http.Request) (*http.Response, error) { |
|
41 |
+ if !strings.HasPrefix(req.URL.Path, expectedURL) { |
|
42 |
+ return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) |
|
43 |
+ } |
|
44 |
+ content, err := json.Marshal(swarm.Config{ |
|
45 |
+ ID: "config_id", |
|
46 |
+ }) |
|
47 |
+ if err != nil { |
|
48 |
+ return nil, err |
|
49 |
+ } |
|
50 |
+ return &http.Response{ |
|
51 |
+ StatusCode: http.StatusOK, |
|
52 |
+ Body: ioutil.NopCloser(bytes.NewReader(content)), |
|
53 |
+ }, nil |
|
54 |
+ }), |
|
55 |
+ } |
|
56 |
+ |
|
57 |
+ configInspect, _, err := client.ConfigInspectWithRaw(context.Background(), "config_id") |
|
58 |
+ if err != nil { |
|
59 |
+ t.Fatal(err) |
|
60 |
+ } |
|
61 |
+ if configInspect.ID != "config_id" { |
|
62 |
+ t.Fatalf("expected `config_id`, got %s", configInspect.ID) |
|
63 |
+ } |
|
64 |
+} |
0 | 65 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,35 @@ |
0 |
+package client |
|
1 |
+ |
|
2 |
+import ( |
|
3 |
+ "encoding/json" |
|
4 |
+ "net/url" |
|
5 |
+ |
|
6 |
+ "github.com/docker/docker/api/types" |
|
7 |
+ "github.com/docker/docker/api/types/filters" |
|
8 |
+ "github.com/docker/docker/api/types/swarm" |
|
9 |
+ "golang.org/x/net/context" |
|
10 |
+) |
|
11 |
+ |
|
12 |
+// ConfigList returns the list of configs. |
|
13 |
+func (cli *Client) ConfigList(ctx context.Context, options types.ConfigListOptions) ([]swarm.Config, error) { |
|
14 |
+ query := url.Values{} |
|
15 |
+ |
|
16 |
+ if options.Filters.Len() > 0 { |
|
17 |
+ filterJSON, err := filters.ToParam(options.Filters) |
|
18 |
+ if err != nil { |
|
19 |
+ return nil, err |
|
20 |
+ } |
|
21 |
+ |
|
22 |
+ query.Set("filters", filterJSON) |
|
23 |
+ } |
|
24 |
+ |
|
25 |
+ resp, err := cli.get(ctx, "/configs", query, nil) |
|
26 |
+ if err != nil { |
|
27 |
+ return nil, err |
|
28 |
+ } |
|
29 |
+ |
|
30 |
+ var configs []swarm.Config |
|
31 |
+ err = json.NewDecoder(resp.body).Decode(&configs) |
|
32 |
+ ensureReaderClosed(resp) |
|
33 |
+ return configs, err |
|
34 |
+} |
0 | 35 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,94 @@ |
0 |
+package client |
|
1 |
+ |
|
2 |
+import ( |
|
3 |
+ "bytes" |
|
4 |
+ "encoding/json" |
|
5 |
+ "fmt" |
|
6 |
+ "io/ioutil" |
|
7 |
+ "net/http" |
|
8 |
+ "strings" |
|
9 |
+ "testing" |
|
10 |
+ |
|
11 |
+ "github.com/docker/docker/api/types" |
|
12 |
+ "github.com/docker/docker/api/types/filters" |
|
13 |
+ "github.com/docker/docker/api/types/swarm" |
|
14 |
+ "golang.org/x/net/context" |
|
15 |
+) |
|
16 |
+ |
|
17 |
+func TestConfigListError(t *testing.T) { |
|
18 |
+ client := &Client{ |
|
19 |
+ client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")), |
|
20 |
+ } |
|
21 |
+ |
|
22 |
+ _, err := client.ConfigList(context.Background(), types.ConfigListOptions{}) |
|
23 |
+ if err == nil || err.Error() != "Error response from daemon: Server error" { |
|
24 |
+ t.Fatalf("expected a Server Error, got %v", err) |
|
25 |
+ } |
|
26 |
+} |
|
27 |
+ |
|
28 |
+func TestConfigList(t *testing.T) { |
|
29 |
+ expectedURL := "/configs" |
|
30 |
+ |
|
31 |
+ filters := filters.NewArgs() |
|
32 |
+ filters.Add("label", "label1") |
|
33 |
+ filters.Add("label", "label2") |
|
34 |
+ |
|
35 |
+ listCases := []struct { |
|
36 |
+ options types.ConfigListOptions |
|
37 |
+ expectedQueryParams map[string]string |
|
38 |
+ }{ |
|
39 |
+ { |
|
40 |
+ options: types.ConfigListOptions{}, |
|
41 |
+ expectedQueryParams: map[string]string{ |
|
42 |
+ "filters": "", |
|
43 |
+ }, |
|
44 |
+ }, |
|
45 |
+ { |
|
46 |
+ options: types.ConfigListOptions{ |
|
47 |
+ Filters: filters, |
|
48 |
+ }, |
|
49 |
+ expectedQueryParams: map[string]string{ |
|
50 |
+ "filters": `{"label":{"label1":true,"label2":true}}`, |
|
51 |
+ }, |
|
52 |
+ }, |
|
53 |
+ } |
|
54 |
+ for _, listCase := range listCases { |
|
55 |
+ client := &Client{ |
|
56 |
+ client: newMockClient(func(req *http.Request) (*http.Response, error) { |
|
57 |
+ if !strings.HasPrefix(req.URL.Path, expectedURL) { |
|
58 |
+ return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) |
|
59 |
+ } |
|
60 |
+ query := req.URL.Query() |
|
61 |
+ for key, expected := range listCase.expectedQueryParams { |
|
62 |
+ actual := query.Get(key) |
|
63 |
+ if actual != expected { |
|
64 |
+ return nil, fmt.Errorf("%s not set in URL query properly. Expected '%s', got %s", key, expected, actual) |
|
65 |
+ } |
|
66 |
+ } |
|
67 |
+ content, err := json.Marshal([]swarm.Config{ |
|
68 |
+ { |
|
69 |
+ ID: "config_id1", |
|
70 |
+ }, |
|
71 |
+ { |
|
72 |
+ ID: "config_id2", |
|
73 |
+ }, |
|
74 |
+ }) |
|
75 |
+ if err != nil { |
|
76 |
+ return nil, err |
|
77 |
+ } |
|
78 |
+ return &http.Response{ |
|
79 |
+ StatusCode: http.StatusOK, |
|
80 |
+ Body: ioutil.NopCloser(bytes.NewReader(content)), |
|
81 |
+ }, nil |
|
82 |
+ }), |
|
83 |
+ } |
|
84 |
+ |
|
85 |
+ configs, err := client.ConfigList(context.Background(), listCase.options) |
|
86 |
+ if err != nil { |
|
87 |
+ t.Fatal(err) |
|
88 |
+ } |
|
89 |
+ if len(configs) != 2 { |
|
90 |
+ t.Fatalf("expected 2 configs, got %v", configs) |
|
91 |
+ } |
|
92 |
+ } |
|
93 |
+} |
0 | 94 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,10 @@ |
0 |
+package client |
|
1 |
+ |
|
2 |
+import "golang.org/x/net/context" |
|
3 |
+ |
|
4 |
+// ConfigRemove removes a Config. |
|
5 |
+func (cli *Client) ConfigRemove(ctx context.Context, id string) error { |
|
6 |
+ resp, err := cli.delete(ctx, "/configs/"+id, nil, nil) |
|
7 |
+ ensureReaderClosed(resp) |
|
8 |
+ return err |
|
9 |
+} |
0 | 10 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,47 @@ |
0 |
+package client |
|
1 |
+ |
|
2 |
+import ( |
|
3 |
+ "bytes" |
|
4 |
+ "fmt" |
|
5 |
+ "io/ioutil" |
|
6 |
+ "net/http" |
|
7 |
+ "strings" |
|
8 |
+ "testing" |
|
9 |
+ |
|
10 |
+ "golang.org/x/net/context" |
|
11 |
+) |
|
12 |
+ |
|
13 |
+func TestConfigRemoveError(t *testing.T) { |
|
14 |
+ client := &Client{ |
|
15 |
+ client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")), |
|
16 |
+ } |
|
17 |
+ |
|
18 |
+ err := client.ConfigRemove(context.Background(), "config_id") |
|
19 |
+ if err == nil || err.Error() != "Error response from daemon: Server error" { |
|
20 |
+ t.Fatalf("expected a Server Error, got %v", err) |
|
21 |
+ } |
|
22 |
+} |
|
23 |
+ |
|
24 |
+func TestConfigRemove(t *testing.T) { |
|
25 |
+ expectedURL := "/configs/config_id" |
|
26 |
+ |
|
27 |
+ client := &Client{ |
|
28 |
+ client: newMockClient(func(req *http.Request) (*http.Response, error) { |
|
29 |
+ if !strings.HasPrefix(req.URL.Path, expectedURL) { |
|
30 |
+ return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) |
|
31 |
+ } |
|
32 |
+ if req.Method != "DELETE" { |
|
33 |
+ return nil, fmt.Errorf("expected DELETE method, got %s", req.Method) |
|
34 |
+ } |
|
35 |
+ return &http.Response{ |
|
36 |
+ StatusCode: http.StatusOK, |
|
37 |
+ Body: ioutil.NopCloser(bytes.NewReader([]byte("body"))), |
|
38 |
+ }, nil |
|
39 |
+ }), |
|
40 |
+ } |
|
41 |
+ |
|
42 |
+ err := client.ConfigRemove(context.Background(), "config_id") |
|
43 |
+ if err != nil { |
|
44 |
+ t.Fatal(err) |
|
45 |
+ } |
|
46 |
+} |
0 | 47 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,18 @@ |
0 |
+package client |
|
1 |
+ |
|
2 |
+import ( |
|
3 |
+ "net/url" |
|
4 |
+ "strconv" |
|
5 |
+ |
|
6 |
+ "github.com/docker/docker/api/types/swarm" |
|
7 |
+ "golang.org/x/net/context" |
|
8 |
+) |
|
9 |
+ |
|
10 |
+// ConfigUpdate attempts to updates a Config |
|
11 |
+func (cli *Client) ConfigUpdate(ctx context.Context, id string, version swarm.Version, config swarm.ConfigSpec) error { |
|
12 |
+ query := url.Values{} |
|
13 |
+ query.Set("version", strconv.FormatUint(version.Index, 10)) |
|
14 |
+ resp, err := cli.post(ctx, "/configs/"+id+"/update", query, config, nil) |
|
15 |
+ ensureReaderClosed(resp) |
|
16 |
+ return err |
|
17 |
+} |
0 | 18 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,49 @@ |
0 |
+package client |
|
1 |
+ |
|
2 |
+import ( |
|
3 |
+ "bytes" |
|
4 |
+ "fmt" |
|
5 |
+ "io/ioutil" |
|
6 |
+ "net/http" |
|
7 |
+ "strings" |
|
8 |
+ "testing" |
|
9 |
+ |
|
10 |
+ "golang.org/x/net/context" |
|
11 |
+ |
|
12 |
+ "github.com/docker/docker/api/types/swarm" |
|
13 |
+) |
|
14 |
+ |
|
15 |
+func TestConfigUpdateError(t *testing.T) { |
|
16 |
+ client := &Client{ |
|
17 |
+ client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")), |
|
18 |
+ } |
|
19 |
+ |
|
20 |
+ err := client.ConfigUpdate(context.Background(), "config_id", swarm.Version{}, swarm.ConfigSpec{}) |
|
21 |
+ if err == nil || err.Error() != "Error response from daemon: Server error" { |
|
22 |
+ t.Fatalf("expected a Server Error, got %v", err) |
|
23 |
+ } |
|
24 |
+} |
|
25 |
+ |
|
26 |
+func TestConfigUpdate(t *testing.T) { |
|
27 |
+ expectedURL := "/configs/config_id/update" |
|
28 |
+ |
|
29 |
+ client := &Client{ |
|
30 |
+ client: newMockClient(func(req *http.Request) (*http.Response, error) { |
|
31 |
+ if !strings.HasPrefix(req.URL.Path, expectedURL) { |
|
32 |
+ return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) |
|
33 |
+ } |
|
34 |
+ if req.Method != "POST" { |
|
35 |
+ return nil, fmt.Errorf("expected POST method, got %s", req.Method) |
|
36 |
+ } |
|
37 |
+ return &http.Response{ |
|
38 |
+ StatusCode: http.StatusOK, |
|
39 |
+ Body: ioutil.NopCloser(bytes.NewReader([]byte("body"))), |
|
40 |
+ }, nil |
|
41 |
+ }), |
|
42 |
+ } |
|
43 |
+ |
|
44 |
+ err := client.ConfigUpdate(context.Background(), "config_id", swarm.Version{}, swarm.ConfigSpec{}) |
|
45 |
+ if err != nil { |
|
46 |
+ t.Fatal(err) |
|
47 |
+ } |
|
48 |
+} |
... | ... |
@@ -256,6 +256,28 @@ func IsErrSecretNotFound(err error) bool { |
256 | 256 |
return ok |
257 | 257 |
} |
258 | 258 |
|
259 |
+// configNotFoundError implements an error returned when a config is not found. |
|
260 |
+type configNotFoundError struct { |
|
261 |
+ name string |
|
262 |
+} |
|
263 |
+ |
|
264 |
+// Error returns a string representation of a configNotFoundError |
|
265 |
+func (e configNotFoundError) Error() string { |
|
266 |
+ return fmt.Sprintf("Error: no such config: %s", e.name) |
|
267 |
+} |
|
268 |
+ |
|
269 |
+// NotFound indicates that this error type is of NotFound |
|
270 |
+func (e configNotFoundError) NotFound() bool { |
|
271 |
+ return true |
|
272 |
+} |
|
273 |
+ |
|
274 |
+// IsErrConfigNotFound returns true if the error is caused |
|
275 |
+// when a config is not found. |
|
276 |
+func IsErrConfigNotFound(err error) bool { |
|
277 |
+ _, ok := err.(configNotFoundError) |
|
278 |
+ return ok |
|
279 |
+} |
|
280 |
+ |
|
259 | 281 |
// pluginNotFoundError implements an error returned when a plugin is not in the docker host. |
260 | 282 |
type pluginNotFoundError struct { |
261 | 283 |
name string |
... | ... |
@@ -18,6 +18,7 @@ import ( |
18 | 18 |
|
19 | 19 |
// CommonAPIClient is the common methods between stable and experimental versions of APIClient. |
20 | 20 |
type CommonAPIClient interface { |
21 |
+ ConfigAPIClient |
|
21 | 22 |
ContainerAPIClient |
22 | 23 |
ImageAPIClient |
23 | 24 |
NodeAPIClient |
... | ... |
@@ -171,3 +172,12 @@ type SecretAPIClient interface { |
171 | 171 |
SecretInspectWithRaw(ctx context.Context, name string) (swarm.Secret, []byte, error) |
172 | 172 |
SecretUpdate(ctx context.Context, id string, version swarm.Version, secret swarm.SecretSpec) error |
173 | 173 |
} |
174 |
+ |
|
175 |
+// ConfigAPIClient defines API client methods for configs |
|
176 |
+type ConfigAPIClient interface { |
|
177 |
+ ConfigList(ctx context.Context, options types.ConfigListOptions) ([]swarm.Config, error) |
|
178 |
+ ConfigCreate(ctx context.Context, config swarm.ConfigSpec) (types.ConfigCreateResponse, error) |
|
179 |
+ ConfigRemove(ctx context.Context, id string) error |
|
180 |
+ ConfigInspectWithRaw(ctx context.Context, name string) (swarm.Config, []byte, error) |
|
181 |
+ ConfigUpdate(ctx context.Context, id string, version swarm.Version, config swarm.ConfigSpec) error |
|
182 |
+} |