Преглед на файлове

Add configs support to client

Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
Aaron Lehmann преди 8 години
родител
ревизия
102738101a

+ 22 - 0
client/config_create.go

@@ -0,0 +1,22 @@
+package client
+
+import (
+	"encoding/json"
+
+	"github.com/docker/docker/api/types"
+	"github.com/docker/docker/api/types/swarm"
+	"golang.org/x/net/context"
+)
+
+// ConfigCreate creates a new Config.
+func (cli *Client) ConfigCreate(ctx context.Context, config swarm.ConfigSpec) (types.ConfigCreateResponse, error) {
+	var response types.ConfigCreateResponse
+	resp, err := cli.post(ctx, "/configs/create", nil, config, nil)
+	if err != nil {
+		return response, err
+	}
+
+	err = json.NewDecoder(resp.body).Decode(&response)
+	ensureReaderClosed(resp)
+	return response, err
+}

+ 57 - 0
client/config_create_test.go

@@ -0,0 +1,57 @@
+package client
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+	"strings"
+	"testing"
+
+	"github.com/docker/docker/api/types"
+	"github.com/docker/docker/api/types/swarm"
+	"golang.org/x/net/context"
+)
+
+func TestConfigCreateError(t *testing.T) {
+	client := &Client{
+		client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
+	}
+	_, err := client.ConfigCreate(context.Background(), swarm.ConfigSpec{})
+	if err == nil || err.Error() != "Error response from daemon: Server error" {
+		t.Fatalf("expected a Server Error, got %v", err)
+	}
+}
+
+func TestConfigCreate(t *testing.T) {
+	expectedURL := "/configs/create"
+	client := &Client{
+		client: newMockClient(func(req *http.Request) (*http.Response, error) {
+			if !strings.HasPrefix(req.URL.Path, expectedURL) {
+				return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
+			}
+			if req.Method != "POST" {
+				return nil, fmt.Errorf("expected POST method, got %s", req.Method)
+			}
+			b, err := json.Marshal(types.ConfigCreateResponse{
+				ID: "test_config",
+			})
+			if err != nil {
+				return nil, err
+			}
+			return &http.Response{
+				StatusCode: http.StatusCreated,
+				Body:       ioutil.NopCloser(bytes.NewReader(b)),
+			}, nil
+		}),
+	}
+
+	r, err := client.ConfigCreate(context.Background(), swarm.ConfigSpec{})
+	if err != nil {
+		t.Fatal(err)
+	}
+	if r.ID != "test_config" {
+		t.Fatalf("expected `test_config`, got %s", r.ID)
+	}
+}

+ 34 - 0
client/config_inspect.go

@@ -0,0 +1,34 @@
+package client
+
+import (
+	"bytes"
+	"encoding/json"
+	"io/ioutil"
+	"net/http"
+
+	"github.com/docker/docker/api/types/swarm"
+	"golang.org/x/net/context"
+)
+
+// ConfigInspectWithRaw returns the config information with raw data
+func (cli *Client) ConfigInspectWithRaw(ctx context.Context, id string) (swarm.Config, []byte, error) {
+	resp, err := cli.get(ctx, "/configs/"+id, nil, nil)
+	if err != nil {
+		if resp.statusCode == http.StatusNotFound {
+			return swarm.Config{}, nil, configNotFoundError{id}
+		}
+		return swarm.Config{}, nil, err
+	}
+	defer ensureReaderClosed(resp)
+
+	body, err := ioutil.ReadAll(resp.body)
+	if err != nil {
+		return swarm.Config{}, nil, err
+	}
+
+	var config swarm.Config
+	rdr := bytes.NewReader(body)
+	err = json.NewDecoder(rdr).Decode(&config)
+
+	return config, body, err
+}

+ 65 - 0
client/config_inspect_test.go

@@ -0,0 +1,65 @@
+package client
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+	"strings"
+	"testing"
+
+	"github.com/docker/docker/api/types/swarm"
+	"golang.org/x/net/context"
+)
+
+func TestConfigInspectError(t *testing.T) {
+	client := &Client{
+		client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
+	}
+
+	_, _, err := client.ConfigInspectWithRaw(context.Background(), "nothing")
+	if err == nil || err.Error() != "Error response from daemon: Server error" {
+		t.Fatalf("expected a Server Error, got %v", err)
+	}
+}
+
+func TestConfigInspectConfigNotFound(t *testing.T) {
+	client := &Client{
+		client: newMockClient(errorMock(http.StatusNotFound, "Server error")),
+	}
+
+	_, _, err := client.ConfigInspectWithRaw(context.Background(), "unknown")
+	if err == nil || !IsErrConfigNotFound(err) {
+		t.Fatalf("expected a configNotFoundError error, got %v", err)
+	}
+}
+
+func TestConfigInspect(t *testing.T) {
+	expectedURL := "/configs/config_id"
+	client := &Client{
+		client: newMockClient(func(req *http.Request) (*http.Response, error) {
+			if !strings.HasPrefix(req.URL.Path, expectedURL) {
+				return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
+			}
+			content, err := json.Marshal(swarm.Config{
+				ID: "config_id",
+			})
+			if err != nil {
+				return nil, err
+			}
+			return &http.Response{
+				StatusCode: http.StatusOK,
+				Body:       ioutil.NopCloser(bytes.NewReader(content)),
+			}, nil
+		}),
+	}
+
+	configInspect, _, err := client.ConfigInspectWithRaw(context.Background(), "config_id")
+	if err != nil {
+		t.Fatal(err)
+	}
+	if configInspect.ID != "config_id" {
+		t.Fatalf("expected `config_id`, got %s", configInspect.ID)
+	}
+}

+ 35 - 0
client/config_list.go

@@ -0,0 +1,35 @@
+package client
+
+import (
+	"encoding/json"
+	"net/url"
+
+	"github.com/docker/docker/api/types"
+	"github.com/docker/docker/api/types/filters"
+	"github.com/docker/docker/api/types/swarm"
+	"golang.org/x/net/context"
+)
+
+// ConfigList returns the list of configs.
+func (cli *Client) ConfigList(ctx context.Context, options types.ConfigListOptions) ([]swarm.Config, error) {
+	query := url.Values{}
+
+	if options.Filters.Len() > 0 {
+		filterJSON, err := filters.ToParam(options.Filters)
+		if err != nil {
+			return nil, err
+		}
+
+		query.Set("filters", filterJSON)
+	}
+
+	resp, err := cli.get(ctx, "/configs", query, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	var configs []swarm.Config
+	err = json.NewDecoder(resp.body).Decode(&configs)
+	ensureReaderClosed(resp)
+	return configs, err
+}

+ 94 - 0
client/config_list_test.go

@@ -0,0 +1,94 @@
+package client
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+	"strings"
+	"testing"
+
+	"github.com/docker/docker/api/types"
+	"github.com/docker/docker/api/types/filters"
+	"github.com/docker/docker/api/types/swarm"
+	"golang.org/x/net/context"
+)
+
+func TestConfigListError(t *testing.T) {
+	client := &Client{
+		client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
+	}
+
+	_, err := client.ConfigList(context.Background(), types.ConfigListOptions{})
+	if err == nil || err.Error() != "Error response from daemon: Server error" {
+		t.Fatalf("expected a Server Error, got %v", err)
+	}
+}
+
+func TestConfigList(t *testing.T) {
+	expectedURL := "/configs"
+
+	filters := filters.NewArgs()
+	filters.Add("label", "label1")
+	filters.Add("label", "label2")
+
+	listCases := []struct {
+		options             types.ConfigListOptions
+		expectedQueryParams map[string]string
+	}{
+		{
+			options: types.ConfigListOptions{},
+			expectedQueryParams: map[string]string{
+				"filters": "",
+			},
+		},
+		{
+			options: types.ConfigListOptions{
+				Filters: filters,
+			},
+			expectedQueryParams: map[string]string{
+				"filters": `{"label":{"label1":true,"label2":true}}`,
+			},
+		},
+	}
+	for _, listCase := range listCases {
+		client := &Client{
+			client: newMockClient(func(req *http.Request) (*http.Response, error) {
+				if !strings.HasPrefix(req.URL.Path, expectedURL) {
+					return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
+				}
+				query := req.URL.Query()
+				for key, expected := range listCase.expectedQueryParams {
+					actual := query.Get(key)
+					if actual != expected {
+						return nil, fmt.Errorf("%s not set in URL query properly. Expected '%s', got %s", key, expected, actual)
+					}
+				}
+				content, err := json.Marshal([]swarm.Config{
+					{
+						ID: "config_id1",
+					},
+					{
+						ID: "config_id2",
+					},
+				})
+				if err != nil {
+					return nil, err
+				}
+				return &http.Response{
+					StatusCode: http.StatusOK,
+					Body:       ioutil.NopCloser(bytes.NewReader(content)),
+				}, nil
+			}),
+		}
+
+		configs, err := client.ConfigList(context.Background(), listCase.options)
+		if err != nil {
+			t.Fatal(err)
+		}
+		if len(configs) != 2 {
+			t.Fatalf("expected 2 configs, got %v", configs)
+		}
+	}
+}

+ 10 - 0
client/config_remove.go

@@ -0,0 +1,10 @@
+package client
+
+import "golang.org/x/net/context"
+
+// ConfigRemove removes a Config.
+func (cli *Client) ConfigRemove(ctx context.Context, id string) error {
+	resp, err := cli.delete(ctx, "/configs/"+id, nil, nil)
+	ensureReaderClosed(resp)
+	return err
+}

+ 47 - 0
client/config_remove_test.go

@@ -0,0 +1,47 @@
+package client
+
+import (
+	"bytes"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+	"strings"
+	"testing"
+
+	"golang.org/x/net/context"
+)
+
+func TestConfigRemoveError(t *testing.T) {
+	client := &Client{
+		client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
+	}
+
+	err := client.ConfigRemove(context.Background(), "config_id")
+	if err == nil || err.Error() != "Error response from daemon: Server error" {
+		t.Fatalf("expected a Server Error, got %v", err)
+	}
+}
+
+func TestConfigRemove(t *testing.T) {
+	expectedURL := "/configs/config_id"
+
+	client := &Client{
+		client: newMockClient(func(req *http.Request) (*http.Response, error) {
+			if !strings.HasPrefix(req.URL.Path, expectedURL) {
+				return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
+			}
+			if req.Method != "DELETE" {
+				return nil, fmt.Errorf("expected DELETE method, got %s", req.Method)
+			}
+			return &http.Response{
+				StatusCode: http.StatusOK,
+				Body:       ioutil.NopCloser(bytes.NewReader([]byte("body"))),
+			}, nil
+		}),
+	}
+
+	err := client.ConfigRemove(context.Background(), "config_id")
+	if err != nil {
+		t.Fatal(err)
+	}
+}

+ 18 - 0
client/config_update.go

@@ -0,0 +1,18 @@
+package client
+
+import (
+	"net/url"
+	"strconv"
+
+	"github.com/docker/docker/api/types/swarm"
+	"golang.org/x/net/context"
+)
+
+// ConfigUpdate attempts to updates a Config
+func (cli *Client) ConfigUpdate(ctx context.Context, id string, version swarm.Version, config swarm.ConfigSpec) error {
+	query := url.Values{}
+	query.Set("version", strconv.FormatUint(version.Index, 10))
+	resp, err := cli.post(ctx, "/configs/"+id+"/update", query, config, nil)
+	ensureReaderClosed(resp)
+	return err
+}

+ 49 - 0
client/config_update_test.go

@@ -0,0 +1,49 @@
+package client
+
+import (
+	"bytes"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+	"strings"
+	"testing"
+
+	"golang.org/x/net/context"
+
+	"github.com/docker/docker/api/types/swarm"
+)
+
+func TestConfigUpdateError(t *testing.T) {
+	client := &Client{
+		client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
+	}
+
+	err := client.ConfigUpdate(context.Background(), "config_id", swarm.Version{}, swarm.ConfigSpec{})
+	if err == nil || err.Error() != "Error response from daemon: Server error" {
+		t.Fatalf("expected a Server Error, got %v", err)
+	}
+}
+
+func TestConfigUpdate(t *testing.T) {
+	expectedURL := "/configs/config_id/update"
+
+	client := &Client{
+		client: newMockClient(func(req *http.Request) (*http.Response, error) {
+			if !strings.HasPrefix(req.URL.Path, expectedURL) {
+				return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
+			}
+			if req.Method != "POST" {
+				return nil, fmt.Errorf("expected POST method, got %s", req.Method)
+			}
+			return &http.Response{
+				StatusCode: http.StatusOK,
+				Body:       ioutil.NopCloser(bytes.NewReader([]byte("body"))),
+			}, nil
+		}),
+	}
+
+	err := client.ConfigUpdate(context.Background(), "config_id", swarm.Version{}, swarm.ConfigSpec{})
+	if err != nil {
+		t.Fatal(err)
+	}
+}

+ 22 - 0
client/errors.go

@@ -256,6 +256,28 @@ func IsErrSecretNotFound(err error) bool {
 	return ok
 	return ok
 }
 }
 
 
+// configNotFoundError implements an error returned when a config is not found.
+type configNotFoundError struct {
+	name string
+}
+
+// Error returns a string representation of a configNotFoundError
+func (e configNotFoundError) Error() string {
+	return fmt.Sprintf("Error: no such config: %s", e.name)
+}
+
+// NotFound indicates that this error type is of NotFound
+func (e configNotFoundError) NotFound() bool {
+	return true
+}
+
+// IsErrConfigNotFound returns true if the error is caused
+// when a config is not found.
+func IsErrConfigNotFound(err error) bool {
+	_, ok := err.(configNotFoundError)
+	return ok
+}
+
 // pluginNotFoundError implements an error returned when a plugin is not in the docker host.
 // pluginNotFoundError implements an error returned when a plugin is not in the docker host.
 type pluginNotFoundError struct {
 type pluginNotFoundError struct {
 	name string
 	name string

+ 10 - 0
client/interface.go

@@ -18,6 +18,7 @@ import (
 
 
 // CommonAPIClient is the common methods between stable and experimental versions of APIClient.
 // CommonAPIClient is the common methods between stable and experimental versions of APIClient.
 type CommonAPIClient interface {
 type CommonAPIClient interface {
+	ConfigAPIClient
 	ContainerAPIClient
 	ContainerAPIClient
 	ImageAPIClient
 	ImageAPIClient
 	NodeAPIClient
 	NodeAPIClient
@@ -171,3 +172,12 @@ type SecretAPIClient interface {
 	SecretInspectWithRaw(ctx context.Context, name string) (swarm.Secret, []byte, error)
 	SecretInspectWithRaw(ctx context.Context, name string) (swarm.Secret, []byte, error)
 	SecretUpdate(ctx context.Context, id string, version swarm.Version, secret swarm.SecretSpec) error
 	SecretUpdate(ctx context.Context, id string, version swarm.Version, secret swarm.SecretSpec) error
 }
 }
+
+// ConfigAPIClient defines API client methods for configs
+type ConfigAPIClient interface {
+	ConfigList(ctx context.Context, options types.ConfigListOptions) ([]swarm.Config, error)
+	ConfigCreate(ctx context.Context, config swarm.ConfigSpec) (types.ConfigCreateResponse, error)
+	ConfigRemove(ctx context.Context, id string) error
+	ConfigInspectWithRaw(ctx context.Context, name string) (swarm.Config, []byte, error)
+	ConfigUpdate(ctx context.Context, id string, version swarm.Version, config swarm.ConfigSpec) error
+}