diff --git a/client/interface.go b/client/interface.go index 2e0491a..1b4fa42 100644 --- a/client/interface.go +++ b/client/interface.go @@ -94,7 +94,7 @@ type NetworkAPIClient interface { type NodeAPIClient interface { NodeInspectWithRaw(ctx context.Context, nodeID string) (swarm.Node, []byte, error) NodeList(ctx context.Context, options types.NodeListOptions) ([]swarm.Node, error) - NodeRemove(ctx context.Context, nodeID string) error + NodeRemove(ctx context.Context, nodeID string, options types.NodeRemoveOptions) error NodeUpdate(ctx context.Context, nodeID string, version swarm.Version, node swarm.NodeSpec) error } diff --git a/client/node_remove.go b/client/node_remove.go index a22ee93..a9cf8ba 100644 --- a/client/node_remove.go +++ b/client/node_remove.go @@ -1,10 +1,21 @@ package client -import "golang.org/x/net/context" +import ( + "net/url" + + "github.com/docker/engine-api/types" + + "golang.org/x/net/context" +) // NodeRemove removes a Node. -func (cli *Client) NodeRemove(ctx context.Context, nodeID string) error { - resp, err := cli.delete(ctx, "/nodes/"+nodeID, nil, nil) +func (cli *Client) NodeRemove(ctx context.Context, nodeID string, options types.NodeRemoveOptions) error { + query := url.Values{} + if options.Force { + query.Set("force", "1") + } + + resp, err := cli.delete(ctx, "/nodes/"+nodeID, query, nil) ensureReaderClosed(resp) return err } diff --git a/client/node_remove_test.go b/client/node_remove_test.go index 7d3e00e..5ebd64f 100644 --- a/client/node_remove_test.go +++ b/client/node_remove_test.go @@ -8,6 +8,8 @@ import ( "strings" "testing" + "github.com/docker/engine-api/types" + "golang.org/x/net/context" ) @@ -16,7 +18,7 @@ func TestNodeRemoveError(t *testing.T) { transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), } - err := client.NodeRemove(context.Background(), "node_id") + err := client.NodeRemove(context.Background(), "node_id", types.NodeRemoveOptions{Force: false}) if err == nil || err.Error() != "Error response from daemon: Server error" { t.Fatalf("expected a Server Error, got %v", err) } @@ -25,23 +27,43 @@ func TestNodeRemoveError(t *testing.T) { func TestNodeRemove(t *testing.T) { expectedURL := "/nodes/node_id" - client := &Client{ - transport: newMockClient(nil, 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 - }), + removeCases := []struct { + force bool + expectedForce string + }{ + { + expectedForce: "", + }, + { + force: true, + expectedForce: "1", + }, } - err := client.NodeRemove(context.Background(), "node_id") - if err != nil { - t.Fatal(err) + for _, removeCase := range removeCases { + client := &Client{ + transport: newMockClient(nil, 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) + } + force := req.URL.Query().Get("force") + if force != removeCase.expectedForce { + return nil, fmt.Errorf("force not set in URL query properly. expected '%s', got %s", removeCase.expectedForce, force) + } + + return &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader([]byte("body"))), + }, nil + }), + } + + err := client.NodeRemove(context.Background(), "node_id", types.NodeRemoveOptions{Force: removeCase.force}) + if err != nil { + t.Fatal(err) + } } } diff --git a/types/client.go b/types/client.go index def3f06..c6d244d 100644 --- a/types/client.go +++ b/types/client.go @@ -241,11 +241,16 @@ func (v VersionResponse) ServerOK() bool { return v.Server != nil } -// NodeListOptions holds parameters to list nodes with. +// NodeListOptions holds parameters to list nodes with. type NodeListOptions struct { Filter filters.Args } +// NodeRemoveOptions holds parameters to remove nodes with. +type NodeRemoveOptions struct { + Force bool +} + // ServiceCreateOptions contains the options to use when creating a service. type ServiceCreateOptions struct { // EncodedRegistryAuth is the encoded registry authorization credentials to