diff --git a/api/api.go b/api/api.go index 8981f5f..a3ab77e 100644 --- a/api/api.go +++ b/api/api.go @@ -89,7 +89,7 @@ func (h *httpHandler) initRouter() { {"/networks/" + nwID, nil, procGetNetwork}, {"/networks/" + nwID + "/endpoints", []string{"name", epName}, procGetEndpoints}, {"/networks/" + nwID + "/endpoints", []string{"partial-id", epPID}, procGetEndpoints}, - + {"/networks/" + nwID + "/endpoints", nil, procGetEndpoints}, {"/networks/" + nwID + "/endpoints/" + epID, nil, procGetEndpoint}, }, "POST": { diff --git a/api/api_test.go b/api/api_test.go index 416cebb..cb35ecf 100644 --- a/api/api_test.go +++ b/api/api_test.go @@ -1391,8 +1391,8 @@ func TestEndToEnd(t *testing.T) { t.Fatal(err) } handleRequest(rsp, req) - if rsp.statusCode != http.StatusNotFound { - t.Fatalf("Expected StatusNotFound. Got (%d): %s", rsp.statusCode, rsp.body) + if rsp.statusCode != http.StatusOK { + t.Fatalf("Expected StatusOK. Got (%d): %s", rsp.statusCode, rsp.body) } req, err = http.NewRequest("GET", "/networks/"+nid+"/endpoints?name=bla", nil) diff --git a/client/client.go b/client/client.go index 64c6f7d..4bc86da 100644 --- a/client/client.go +++ b/client/client.go @@ -49,6 +49,12 @@ func (cli *NetworkCli) getMethod(args ...string) (func(string, ...string) error, // Cmd is borrowed from Docker UI and acts as the entry point for network UI commands. // network UI commands are designed to be invoked from multiple parent chains func (cli *NetworkCli) Cmd(chain string, args ...string) error { + if len(args) > 2 { + method, exists := cli.getMethod(args[:3]...) + if exists { + return method(chain+" "+args[0]+" "+args[1], args[3:]...) + } + } if len(args) > 1 { method, exists := cli.getMethod(args[:2]...) if exists { diff --git a/client/client_test.go b/client/client_test.go index fbb5ba2..6a8c675 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -2,7 +2,11 @@ package client import ( "bytes" + "encoding/json" + "fmt" "io" + "os" + "strings" "testing" _ "github.com/docker/libnetwork/netutils" @@ -15,12 +19,80 @@ type nopCloser struct { func (nopCloser) Close() error { return nil } +func TestMain(m *testing.M) { + setupMockHTTPCallback() + os.Exit(m.Run()) +} + +var callbackFunc func(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, int, error) +var mockNwJSON, mockNwListJSON, mockServiceJSON, mockServiceListJSON []byte +var mockNwName = "test" +var mockNwID = "23456789" +var mockServiceName = "testSrv" +var mockServiceID = "23456789" +var mockContainerID = "23456789" + +func setupMockHTTPCallback() { + var list []networkResource + nw := networkResource{Name: mockNwName, ID: mockNwID} + mockNwJSON, _ = json.Marshal(nw) + list = append(list, nw) + mockNwListJSON, _ = json.Marshal(list) + + var srvList []endpointResource + ep := endpointResource{Name: mockServiceName, ID: mockServiceID, Network: mockNwName} + mockServiceJSON, _ = json.Marshal(ep) + srvList = append(srvList, ep) + mockServiceListJSON, _ = json.Marshal(srvList) + + callbackFunc = func(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, int, error) { + var rsp string + switch method { + case "GET": + if strings.Contains(path, fmt.Sprintf("networks?name=%s", mockNwName)) { + rsp = string(mockNwListJSON) + } else if strings.Contains(path, "networks?name=") { + rsp = "[]" + } else if strings.Contains(path, fmt.Sprintf("networks?partial-id=%s", mockNwID)) { + rsp = string(mockNwListJSON) + } else if strings.Contains(path, "networks?partial-id=") { + rsp = "[]" + } else if strings.HasSuffix(path, "networks") { + rsp = string(mockNwListJSON) + } else if strings.HasSuffix(path, "networks/"+mockNwID) { + rsp = string(mockNwJSON) + } else if strings.Contains(path, fmt.Sprintf("endpoints?name=%s", mockServiceName)) { + rsp = string(mockServiceListJSON) + } else if strings.Contains(path, "endpoints?name=") { + rsp = "[]" + } else if strings.Contains(path, fmt.Sprintf("endpoints?partial-id=%s", mockServiceID)) { + rsp = string(mockServiceListJSON) + } else if strings.Contains(path, "endpoints?partial-id=") { + rsp = "[]" + } else if strings.HasSuffix(path, "endpoints") { + rsp = string(mockServiceListJSON) + } else if strings.HasSuffix(path, "endpoints/"+mockServiceID) { + rsp = string(mockServiceJSON) + } + case "POST": + if strings.HasSuffix(path, "networks") { + rsp = mockNwID + } else if strings.HasSuffix(path, "endpoints") { + rsp = mockServiceID + } else if strings.HasSuffix(path, "containers") { + rsp = mockContainerID + } + case "PUT": + case "DELETE": + rsp = "" + } + return nopCloser{bytes.NewBufferString(rsp)}, 200, nil + } +} + func TestClientDummyCommand(t *testing.T) { var out, errOut bytes.Buffer - cFunc := func(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, int, error) { - return nopCloser{bytes.NewBufferString("")}, 200, nil - } - cli := NewNetworkCli(&out, &errOut, cFunc) + cli := NewNetworkCli(&out, &errOut, callbackFunc) err := cli.Cmd("docker", "dummy") if err == nil { @@ -30,10 +102,7 @@ func TestClientDummyCommand(t *testing.T) { func TestClientNetworkInvalidCommand(t *testing.T) { var out, errOut bytes.Buffer - cFunc := func(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, int, error) { - return nopCloser{bytes.NewBufferString("")}, 200, nil - } - cli := NewNetworkCli(&out, &errOut, cFunc) + cli := NewNetworkCli(&out, &errOut, callbackFunc) err := cli.Cmd("docker", "network", "invalid") if err == nil { @@ -43,12 +112,9 @@ func TestClientNetworkInvalidCommand(t *testing.T) { func TestClientNetworkCreate(t *testing.T) { var out, errOut bytes.Buffer - cFunc := func(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, int, error) { - return nopCloser{bytes.NewBufferString("")}, 200, nil - } - cli := NewNetworkCli(&out, &errOut, cFunc) + cli := NewNetworkCli(&out, &errOut, callbackFunc) - err := cli.Cmd("docker", "network", "create", "test") + err := cli.Cmd("docker", "network", "create", mockNwName) if err != nil { t.Fatal(err.Error()) } @@ -56,17 +122,14 @@ func TestClientNetworkCreate(t *testing.T) { func TestClientNetworkCreateWithDriver(t *testing.T) { var out, errOut bytes.Buffer - cFunc := func(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, int, error) { - return nopCloser{bytes.NewBufferString("")}, 200, nil - } - cli := NewNetworkCli(&out, &errOut, cFunc) + cli := NewNetworkCli(&out, &errOut, callbackFunc) - err := cli.Cmd("docker", "network", "create", "-f=dummy", "test") + err := cli.Cmd("docker", "network", "create", "-f=dummy", mockNwName) if err == nil { t.Fatalf("Passing incorrect flags to the create command must fail") } - err = cli.Cmd("docker", "network", "create", "-d=dummy", "test") + err = cli.Cmd("docker", "network", "create", "-d=dummy", mockNwName) if err != nil { t.Fatalf(err.Error()) } @@ -74,12 +137,9 @@ func TestClientNetworkCreateWithDriver(t *testing.T) { func TestClientNetworkRm(t *testing.T) { var out, errOut bytes.Buffer - cFunc := func(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, int, error) { - return nopCloser{bytes.NewBufferString("")}, 200, nil - } - cli := NewNetworkCli(&out, &errOut, cFunc) + cli := NewNetworkCli(&out, &errOut, callbackFunc) - err := cli.Cmd("docker", "network", "rm", "test") + err := cli.Cmd("docker", "network", "rm", mockNwName) if err != nil { t.Fatal(err.Error()) } @@ -87,47 +147,141 @@ func TestClientNetworkRm(t *testing.T) { func TestClientNetworkLs(t *testing.T) { var out, errOut bytes.Buffer - networks := "db,web,test" - cFunc := func(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, int, error) { - return nopCloser{bytes.NewBufferString(networks)}, 200, nil - } - cli := NewNetworkCli(&out, &errOut, cFunc) + cli := NewNetworkCli(&out, &errOut, callbackFunc) err := cli.Cmd("docker", "network", "ls") if err != nil { t.Fatal(err.Error()) } - if out.String() != networks { - t.Fatal("Network List command fail to return the intended list") + if out.String() != string(mockNwListJSON) { + t.Fatal("Network List command fail to return the expected list") } } func TestClientNetworkInfo(t *testing.T) { var out, errOut bytes.Buffer - info := "dummy info" - cFunc := func(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, int, error) { - return nopCloser{bytes.NewBufferString(info)}, 200, nil - } - cli := NewNetworkCli(&out, &errOut, cFunc) + cli := NewNetworkCli(&out, &errOut, callbackFunc) - err := cli.Cmd("docker", "network", "info", "test") + err := cli.Cmd("docker", "network", "info", mockNwName) if err != nil { t.Fatal(err.Error()) } - if out.String() != info { - t.Fatal("Network List command fail to return the intended list") + if out.String() != string(mockNwJSON) { + t.Fatal("Network info command fail to return the expected object") + } +} + +func TestClientNetworkInfoById(t *testing.T) { + var out, errOut bytes.Buffer + cli := NewNetworkCli(&out, &errOut, callbackFunc) + + err := cli.Cmd("docker", "network", "info", mockNwID) + if err != nil { + t.Fatal(err.Error()) + } + if out.String() != string(mockNwJSON) { + t.Fatal("Network info command fail to return the expected object") + } +} + +func TestClientNetworkServiceInvalidCommand(t *testing.T) { + var out, errOut bytes.Buffer + cli := NewNetworkCli(&out, &errOut, callbackFunc) + + err := cli.Cmd("docker", "network", "service", "invalid") + if err == nil { + t.Fatalf("Passing invalid commands must fail") + } +} + +func TestClientNetworkServiceCreate(t *testing.T) { + var out, errOut bytes.Buffer + cli := NewNetworkCli(&out, &errOut, callbackFunc) + + err := cli.Cmd("docker", "network", "service", "create", mockServiceName, mockNwName) + if err != nil { + t.Fatal(err.Error()) + } +} + +func TestClientNetworkServiceRm(t *testing.T) { + var out, errOut bytes.Buffer + cli := NewNetworkCli(&out, &errOut, callbackFunc) + + err := cli.Cmd("docker", "network", "service", "rm", mockServiceName, mockNwName) + if err != nil { + t.Fatal(err.Error()) + } +} + +func TestClientNetworkServiceLs(t *testing.T) { + var out, errOut bytes.Buffer + cli := NewNetworkCli(&out, &errOut, callbackFunc) + + err := cli.Cmd("docker", "network", "service", "ls", mockNwName) + if err != nil { + t.Fatal(err.Error()) + } + if out.String() != string(mockServiceListJSON) { + t.Fatal("Network service ls command fail to return the expected list") + } +} + +func TestClientNetworkServiceInfo(t *testing.T) { + var out, errOut bytes.Buffer + cli := NewNetworkCli(&out, &errOut, callbackFunc) + + err := cli.Cmd("docker", "network", "service", "info", mockServiceName, mockNwName) + if err != nil { + t.Fatal(err.Error()) + } + if out.String() != string(mockServiceJSON) { + t.Fatal("Network info command fail to return the expected object") + } +} + +func TestClientNetworkServiceInfoById(t *testing.T) { + var out, errOut bytes.Buffer + cli := NewNetworkCli(&out, &errOut, callbackFunc) + + err := cli.Cmd("docker", "network", "service", "info", mockServiceID, mockNwID) + if err != nil { + t.Fatal(err.Error()) + } + if out.String() != string(mockServiceJSON) { + t.Fatal("Network info command fail to return the expected object") + } +} + +func TestClientNetworkServiceJoin(t *testing.T) { + var out, errOut bytes.Buffer + cli := NewNetworkCli(&out, &errOut, callbackFunc) + + err := cli.Cmd("docker", "network", "service", "join", mockContainerID, mockServiceName, mockNwName) + if err != nil { + t.Fatal(err.Error()) + } +} + +func TestClientNetworkServiceLeave(t *testing.T) { + var out, errOut bytes.Buffer + cli := NewNetworkCli(&out, &errOut, callbackFunc) + + err := cli.Cmd("docker", "network", "service", "leave", mockContainerID, mockServiceName, mockNwName) + if err != nil { + t.Fatal(err.Error()) } } // Docker Flag processing in flag.go uses os.Exit() frequently, even for --help // TODO : Handle the --help test-case in the IT when CLI is available /* -func TestClientNetworkCreateHelp(t *testing.T) { +func TestClientNetworkServiceCreateHelp(t *testing.T) { var out, errOut bytes.Buffer cFunc := func(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, int, error) { return nil, 0, nil } - cli := NewNetworkCli(&out, &errOut, cFunc) + cli := NewNetworkCli(&out, &errOut, callbackFunc) err := cli.Cmd("docker", "network", "create", "--help") if err != nil { @@ -139,12 +293,12 @@ func TestClientNetworkCreateHelp(t *testing.T) { // Docker flag processing in flag.go uses os.Exit(1) for incorrect parameter case. // TODO : Handle the missing argument case in the IT when CLI is available /* -func TestClientNetworkCreateMissingArgument(t *testing.T) { +func TestClientNetworkServiceCreateMissingArgument(t *testing.T) { var out, errOut bytes.Buffer cFunc := func(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, int, error) { return nil, 0, nil } - cli := NewNetworkCli(&out, &errOut, cFunc) + cli := NewNetworkCli(&out, &errOut, callbackFunc) err := cli.Cmd("docker", "network", "create") if err != nil { diff --git a/client/network.go b/client/network.go index 3da71fe..2b57bed 100644 --- a/client/network.go +++ b/client/network.go @@ -2,8 +2,11 @@ package client import ( "bytes" + "encoding/json" "fmt" "io" + "net/http" + "strings" flag "github.com/docker/docker/pkg/mflag" ) @@ -23,6 +26,12 @@ var ( {"rm", "Remove a network"}, {"ls", "List all networks"}, {"info", "Display information of a network"}, + {"service create", "Create a service endpoint"}, + {"service rm", "Remove a service endpoint"}, + {"service join", "Join a container to a service endpoint"}, + {"service leave", "Leave a container from a service endpoint"}, + {"service ls", "Lists all service endpoints on a network"}, + {"service info", "Display information of a service endpoint"}, } ) @@ -33,7 +42,7 @@ func (cli *NetworkCli) CmdNetwork(chain string, args ...string) error { err := cmd.ParseFlags(args, true) if err == nil { cmd.Usage() - return fmt.Errorf("Invalid command : %v", args) + return fmt.Errorf("invalid command : %v", args) } return err } @@ -55,7 +64,6 @@ func (cli *NetworkCli) CmdNetworkCreate(chain string, args ...string) error { obj, _, err := readBody(cli.call("POST", "/networks", nc, nil)) if err != nil { - fmt.Fprintf(cli.err, "%s", err.Error()) return err } if _, err := io.Copy(cli.out, bytes.NewReader(obj)); err != nil { @@ -66,15 +74,18 @@ func (cli *NetworkCli) CmdNetworkCreate(chain string, args ...string) error { // CmdNetworkRm handles Network Delete UI func (cli *NetworkCli) CmdNetworkRm(chain string, args ...string) error { - cmd := cli.Subcmd(chain, "rm", "NETWORK-NAME", "Deletes a network", false) + cmd := cli.Subcmd(chain, "rm", "NETWORK", "Deletes a network", false) cmd.Require(flag.Min, 1) err := cmd.ParseFlags(args, true) if err != nil { return err } - obj, _, err := readBody(cli.call("DELETE", "/networks/name/"+cmd.Arg(0), nil, nil)) + id, err := lookupNetworkID(cli, cmd.Arg(0)) + if err != nil { + return err + } + obj, _, err := readBody(cli.call("DELETE", "/networks/"+id, nil, nil)) if err != nil { - fmt.Fprintf(cli.err, "%s", err.Error()) return err } if _, err := io.Copy(cli.out, bytes.NewReader(obj)); err != nil { @@ -92,7 +103,6 @@ func (cli *NetworkCli) CmdNetworkLs(chain string, args ...string) error { } obj, _, err := readBody(cli.call("GET", "/networks", nil, nil)) if err != nil { - fmt.Fprintf(cli.err, "%s", err.Error()) return err } if _, err := io.Copy(cli.out, bytes.NewReader(obj)); err != nil { @@ -103,13 +113,310 @@ func (cli *NetworkCli) CmdNetworkLs(chain string, args ...string) error { // CmdNetworkInfo handles Network Info UI func (cli *NetworkCli) CmdNetworkInfo(chain string, args ...string) error { - cmd := cli.Subcmd(chain, "info", "NETWORK-NAME", "Displays detailed information on a network", false) + cmd := cli.Subcmd(chain, "info", "NETWORK", "Displays detailed information on a network", false) cmd.Require(flag.Min, 1) err := cmd.ParseFlags(args, true) if err != nil { return err } - obj, _, err := readBody(cli.call("GET", "/networks/name/"+cmd.Arg(0), nil, nil)) + + id, err := lookupNetworkID(cli, cmd.Arg(0)) + if err != nil { + return err + } + + obj, _, err := readBody(cli.call("GET", "/networks/"+id, nil, nil)) + if err != nil { + return err + } + if _, err := io.Copy(cli.out, bytes.NewReader(obj)); err != nil { + return err + } + return nil +} + +// Helper function to predict if a string is a name or id or partial-id +// This provides a best-effort mechanism to identify a id with the help of GET Filter APIs +// Being a UI, its most likely that name will be used by the user, which is used to lookup +// the corresponding ID. If ID is not found, this function will assume that the passed string +// is an ID by itself. + +func lookupNetworkID(cli *NetworkCli, nameID string) (string, error) { + obj, statusCode, err := readBody(cli.call("GET", "/networks?name="+nameID, nil, nil)) + if err != nil { + return "", err + } + + if statusCode != http.StatusOK { + return "", fmt.Errorf("name query failed for %s due to : statuscode(%d) %v", nameID, statusCode, string(obj)) + } + + var list []*networkResource + err = json.Unmarshal(obj, &list) + if err != nil { + return "", err + } + if len(list) > 0 { + // name query filter will always return a single-element collection + return list[0].ID, nil + } + + // Check for Partial-id + obj, statusCode, err = readBody(cli.call("GET", "/networks?partial-id="+nameID, nil, nil)) + if err != nil { + return "", err + } + + if statusCode != http.StatusOK { + return "", fmt.Errorf("partial-id match query failed for %s due to : statuscode(%d) %v", nameID, statusCode, string(obj)) + } + + err = json.Unmarshal(obj, &list) + if err != nil { + return "", err + } + if len(list) == 0 { + return "", fmt.Errorf("resource not found %s", nameID) + } + if len(list) > 1 { + return "", fmt.Errorf("multiple Networks matching the partial identifier (%s). Please use full identifier", nameID) + } + return list[0].ID, nil +} + +func lookupServiceID(cli *NetworkCli, networkID string, nameID string) (string, error) { + obj, statusCode, err := readBody(cli.call("GET", fmt.Sprintf("/networks/%s/endpoints?name=%s", networkID, nameID), nil, nil)) + if err != nil { + return "", err + } + + if statusCode != http.StatusOK { + return "", fmt.Errorf("name query failed for %s due to : statuscode(%d) %v", nameID, statusCode, string(obj)) + } + + var list []*networkResource + err = json.Unmarshal(obj, &list) + if err != nil { + return "", err + } + if len(list) > 0 { + // name query filter will always return a single-element collection + return list[0].ID, nil + } + + // Check for Partial-id + obj, statusCode, err = readBody(cli.call("GET", fmt.Sprintf("/networks/%s/endpoints?partial-id=%s", networkID, nameID), nil, nil)) + if err != nil { + return "", err + } + + if statusCode != http.StatusOK { + return "", fmt.Errorf("partial-id match query failed for %s due to : statuscode(%d) %v", nameID, statusCode, string(obj)) + } + + err = json.Unmarshal(obj, &list) + if err != nil { + return "", err + } + if len(list) == 0 { + return "", fmt.Errorf("resource not found %s", nameID) + } + if len(list) > 1 { + return "", fmt.Errorf("multiple services matching the partial identifier (%s). Please use full identifier", nameID) + } + return list[0].ID, nil +} + +func lookupContainerID(cli *NetworkCli, nameID string) (string, error) { + // TODO : containerID to sandbox-key ? + return nameID, nil +} + +// CmdNetworkService handles the network service UI +func (cli *NetworkCli) CmdNetworkService(chain string, args ...string) error { + cmd := cli.Subcmd(chain, "service", "COMMAND [OPTIONS] [arg...]", serviceUsage(chain), false) + cmd.Require(flag.Min, 1) + err := cmd.ParseFlags(args, true) + if err == nil { + cmd.Usage() + return fmt.Errorf("Invalid command : %v", args) + } + return err +} + +// CmdNetworkServiceCreate handles service create UI +func (cli *NetworkCli) CmdNetworkServiceCreate(chain string, args ...string) error { + cmd := cli.Subcmd(chain, "create", "SERVICE NETWORK", "Creates a new service on a network", false) + cmd.Require(flag.Min, 2) + err := cmd.ParseFlags(args, true) + if err != nil { + return err + } + + networkID, err := lookupNetworkID(cli, cmd.Arg(1)) + if err != nil { + return err + } + + ec := endpointCreate{Name: cmd.Arg(0), NetworkID: networkID} + + obj, _, err := readBody(cli.call("POST", "/networks/"+networkID+"/endpoints", ec, nil)) + if err != nil { + return err + } + if _, err := io.Copy(cli.out, bytes.NewReader(obj)); err != nil { + return err + } + return nil +} + +// CmdNetworkServiceRm handles service delete UI +func (cli *NetworkCli) CmdNetworkServiceRm(chain string, args ...string) error { + cmd := cli.Subcmd(chain, "rm", "SERVICE NETWORK", "Deletes a service", false) + cmd.Require(flag.Min, 2) + err := cmd.ParseFlags(args, true) + if err != nil { + return err + } + + networkID, err := lookupNetworkID(cli, cmd.Arg(1)) + if err != nil { + return err + } + + serviceID, err := lookupServiceID(cli, networkID, cmd.Arg(0)) + if err != nil { + return err + } + + obj, _, err := readBody(cli.call("DELETE", "/networks/"+networkID+"/endpoints/"+serviceID, nil, nil)) + if err != nil { + return err + } + if _, err := io.Copy(cli.out, bytes.NewReader(obj)); err != nil { + return err + } + return nil +} + +// CmdNetworkServiceLs handles service list UI +func (cli *NetworkCli) CmdNetworkServiceLs(chain string, args ...string) error { + cmd := cli.Subcmd(chain, "ls", "NETWORK", "Lists all the services on a network", false) + err := cmd.ParseFlags(args, true) + if err != nil { + return err + } + + cmd.Require(flag.Min, 1) + + networkID, err := lookupNetworkID(cli, cmd.Arg(0)) + if err != nil { + return err + } + + obj, _, err := readBody(cli.call("GET", "/networks/"+networkID+"/endpoints", nil, nil)) + if err != nil { + fmt.Fprintf(cli.err, "%s", err.Error()) + return err + } + if _, err := io.Copy(cli.out, bytes.NewReader(obj)); err != nil { + return err + } + return nil +} + +// CmdNetworkServiceInfo handles service info UI +func (cli *NetworkCli) CmdNetworkServiceInfo(chain string, args ...string) error { + cmd := cli.Subcmd(chain, "info", "SERVICE NETWORK", "Displays detailed information on a service", false) + cmd.Require(flag.Min, 2) + err := cmd.ParseFlags(args, true) + if err != nil { + return err + } + + networkID, err := lookupNetworkID(cli, cmd.Arg(1)) + if err != nil { + return err + } + + serviceID, err := lookupServiceID(cli, networkID, cmd.Arg(0)) + if err != nil { + return err + } + + obj, _, err := readBody(cli.call("GET", "/networks/"+networkID+"/endpoints/"+serviceID, nil, nil)) + if err != nil { + fmt.Fprintf(cli.err, "%s", err.Error()) + return err + } + if _, err := io.Copy(cli.out, bytes.NewReader(obj)); err != nil { + return err + } + return nil +} + +// CmdNetworkServiceJoin handles service join UI +func (cli *NetworkCli) CmdNetworkServiceJoin(chain string, args ...string) error { + cmd := cli.Subcmd(chain, "join", "CONTAINER SERVICE NETWORK", "Sets a container as a service backend", false) + cmd.Require(flag.Min, 3) + err := cmd.ParseFlags(args, true) + if err != nil { + return err + } + + containerID, err := lookupContainerID(cli, cmd.Arg(0)) + if err != nil { + return err + } + + networkID, err := lookupNetworkID(cli, cmd.Arg(2)) + if err != nil { + return err + } + + serviceID, err := lookupServiceID(cli, networkID, cmd.Arg(1)) + if err != nil { + return err + } + + nc := endpointJoin{ContainerID: containerID} + + obj, _, err := readBody(cli.call("POST", "/networks/"+networkID+"/endpoints/"+serviceID+"/containers", nc, nil)) + if err != nil { + fmt.Fprintf(cli.err, "%s", err.Error()) + return err + } + if _, err := io.Copy(cli.out, bytes.NewReader(obj)); err != nil { + return err + } + return nil +} + +// CmdNetworkServiceLeave handles service leave UI +func (cli *NetworkCli) CmdNetworkServiceLeave(chain string, args ...string) error { + cmd := cli.Subcmd(chain, "leave", "CONTAINER SERVICE NETWORK", "Removes a container from service backend", false) + cmd.Require(flag.Min, 3) + err := cmd.ParseFlags(args, true) + if err != nil { + return err + } + + containerID, err := lookupContainerID(cli, cmd.Arg(0)) + if err != nil { + return err + } + + networkID, err := lookupNetworkID(cli, cmd.Arg(2)) + if err != nil { + return err + } + + serviceID, err := lookupServiceID(cli, networkID, cmd.Arg(1)) + if err != nil { + return err + } + + obj, _, err := readBody(cli.call("DELETE", "/networks/"+networkID+"/endpoints/"+serviceID+"/containers/"+containerID, nil, nil)) if err != nil { fmt.Fprintf(cli.err, "%s", err.Error()) return err @@ -124,9 +431,23 @@ func networkUsage(chain string) string { help := "Commands:\n" for _, cmd := range networkCommands { - help += fmt.Sprintf(" %-10.10s%s\n", cmd.name, cmd.description) + help += fmt.Sprintf(" %-25.25s%s\n", cmd.name, cmd.description) } help += fmt.Sprintf("\nRun '%s network COMMAND --help' for more information on a command.", chain) return help } + +func serviceUsage(chain string) string { + help := "Commands:\n" + + for _, cmd := range networkCommands { + if strings.HasPrefix(cmd.name, "service ") { + command := strings.SplitAfter(cmd.name, "service ") + help += fmt.Sprintf(" %-10.10s%s\n", command[1], cmd.description) + } + } + + help += fmt.Sprintf("\nRun '%s service COMMAND --help' for more information on a command.", chain) + return help +} diff --git a/client/types.go b/client/types.go index 27d17b9..972ed43 100644 --- a/client/types.go +++ b/client/types.go @@ -1,6 +1,6 @@ package client -import "github.com/docker/libnetwork/sandbox" +import "github.com/docker/libnetwork/types" /*********** Resources @@ -19,7 +19,6 @@ type endpointResource struct { Name string ID string Network string - Info sandbox.Info } /*********** @@ -32,3 +31,38 @@ type networkCreate struct { NetworkType string Options map[string]interface{} } + +// endpointCreate represents the body of the "create endpoint" http request message +type endpointCreate struct { + Name string + NetworkID string + ExposedPorts []types.TransportPort + PortMapping []types.PortBinding +} + +// endpointJoin represents the expected body of the "join endpoint" or "leave endpoint" http request messages +type endpointJoin struct { + ContainerID string + HostName string + DomainName string + HostsPath string + ResolvConfPath string + DNS []string + ExtraHosts []endpointExtraHost + ParentUpdates []endpointParentUpdate + UseDefaultSandbox bool +} + +// EndpointExtraHost represents the extra host object +type endpointExtraHost struct { + Name string + Address string +} + +// EndpointParentUpdate is the object carrying the information about the +// endpoint parent that needs to be updated +type endpointParentUpdate struct { + EndpointID string + Name string + Address string +} diff --git a/cmd/dnet/dnet.go b/cmd/dnet/dnet.go index c34c38c..41448db 100644 --- a/cmd/dnet/dnet.go +++ b/cmd/dnet/dnet.go @@ -158,7 +158,7 @@ func (d *dnetConnection) httpCall(method, path string, data interface{}, headers statusCode = resp.StatusCode } if err != nil { - return nil, statusCode, fmt.Errorf("An error occurred trying to connect: %v", err) + return nil, statusCode, fmt.Errorf("error when trying to connect: %v", err) } if statusCode < 200 || statusCode >= 400 { @@ -166,7 +166,7 @@ func (d *dnetConnection) httpCall(method, path string, data interface{}, headers if err != nil { return nil, statusCode, err } - return nil, statusCode, fmt.Errorf("Error response from daemon: %s", bytes.TrimSpace(body)) + return nil, statusCode, fmt.Errorf("error : %s", bytes.TrimSpace(body)) } return resp.Body, statusCode, nil