Add configs support to client

Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
This commit is contained in:
Aaron Lehmann 2017-03-15 15:04:32 -07:00
parent 7728557687
commit 102738101a
12 changed files with 463 additions and 0 deletions

22
client/config_create.go Normal file
View File

@ -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
}

View File

@ -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
client/config_inspect.go Normal file
View File

@ -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
}

View File

@ -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
client/config_list.go Normal file
View File

@ -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
}

View File

@ -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
client/config_remove.go Normal file
View File

@ -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
}

View File

@ -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
client/config_update.go Normal file
View File

@ -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
}

View File

@ -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)
}
}

View File

@ -256,6 +256,28 @@ func IsErrSecretNotFound(err error) bool {
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.
type pluginNotFoundError struct {
name string

View File

@ -18,6 +18,7 @@ import (
// CommonAPIClient is the common methods between stable and experimental versions of APIClient.
type CommonAPIClient interface {
ConfigAPIClient
ContainerAPIClient
ImageAPIClient
NodeAPIClient
@ -171,3 +172,12 @@ type SecretAPIClient interface {
SecretInspectWithRaw(ctx context.Context, name string) (swarm.Secret, []byte, 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
}