diff --git a/libnetwork/Makefile b/libnetwork/Makefile index 892d827dbf..59c181ecdc 100644 --- a/libnetwork/Makefile +++ b/libnetwork/Makefile @@ -22,7 +22,7 @@ build: ${build_image}.created ${docker} make build-local build-local: - $(shell which godep) go build ./... + $(shell which godep) go build -tags experimental ./... check: ${build_image}.created ${docker} make check-local diff --git a/libnetwork/client/client_experimental_test.go b/libnetwork/client/client_experimental_test.go new file mode 100644 index 0000000000..9592b3ca77 --- /dev/null +++ b/libnetwork/client/client_experimental_test.go @@ -0,0 +1,124 @@ +// +build experimental + +package client + +import ( + "bytes" + "testing" + + _ "github.com/docker/libnetwork/netutils" +) + +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()) + } +} + +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()) + } +} + +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()) + } +} + +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 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, callbackFunc) + + err := cli.Cmd("docker", "network", "create", "--help") + if err != nil { + t.Fatalf(err.Error()) + } +} +*/ + +// 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 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, callbackFunc) + + err := cli.Cmd("docker", "network", "create") + if err != nil { + t.Fatal(err.Error()) + } +} +*/ diff --git a/libnetwork/client/client_test.go b/libnetwork/client/client_test.go index 6a8c675de2..3b2f3a8eb9 100644 --- a/libnetwork/client/client_test.go +++ b/libnetwork/client/client_test.go @@ -27,10 +27,10 @@ func TestMain(m *testing.M) { 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 mockNwID = "2a3456789" var mockServiceName = "testSrv" -var mockServiceID = "23456789" -var mockContainerID = "23456789" +var mockServiceID = "2a3456789" +var mockContainerID = "2a3456789" func setupMockHTTPCallback() { var list []networkResource @@ -75,13 +75,15 @@ func setupMockHTTPCallback() { rsp = string(mockServiceJSON) } case "POST": + var data []byte if strings.HasSuffix(path, "networks") { - rsp = mockNwID + data, _ = json.Marshal(mockNwID) } else if strings.HasSuffix(path, "endpoints") { - rsp = mockServiceID + data, _ = json.Marshal(mockServiceID) } else if strings.HasSuffix(path, "containers") { - rsp = mockContainerID + data, _ = json.Marshal(mockContainerID) } + rsp = string(data) case "PUT": case "DELETE": rsp = "" @@ -153,9 +155,6 @@ func TestClientNetworkLs(t *testing.T) { if err != nil { t.Fatal(err.Error()) } - if out.String() != string(mockNwListJSON) { - t.Fatal("Network List command fail to return the expected list") - } } func TestClientNetworkInfo(t *testing.T) { @@ -166,9 +165,6 @@ func TestClientNetworkInfo(t *testing.T) { if err != nil { t.Fatal(err.Error()) } - if out.String() != string(mockNwJSON) { - t.Fatal("Network info command fail to return the expected object") - } } func TestClientNetworkInfoById(t *testing.T) { @@ -179,98 +175,6 @@ func TestClientNetworkInfoById(t *testing.T) { 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 diff --git a/libnetwork/client/network.go b/libnetwork/client/network.go index 2b57bede4d..4e0232941c 100644 --- a/libnetwork/client/network.go +++ b/libnetwork/client/network.go @@ -4,11 +4,11 @@ import ( "bytes" "encoding/json" "fmt" - "io" "net/http" - "strings" + "text/tabwriter" flag "github.com/docker/docker/pkg/mflag" + "github.com/docker/docker/pkg/stringid" ) const ( @@ -26,12 +26,6 @@ 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"}, } ) @@ -66,9 +60,12 @@ func (cli *NetworkCli) CmdNetworkCreate(chain string, args ...string) error { if err != nil { return err } - if _, err := io.Copy(cli.out, bytes.NewReader(obj)); err != nil { + var replyID string + err = json.Unmarshal(obj, &replyID) + if err != nil { return err } + fmt.Fprintf(cli.out, "%s\n", replyID) return nil } @@ -84,19 +81,20 @@ func (cli *NetworkCli) CmdNetworkRm(chain string, args ...string) error { if err != nil { return err } - obj, _, err := readBody(cli.call("DELETE", "/networks/"+id, nil, nil)) + _, _, err = readBody(cli.call("DELETE", "/networks/"+id, nil, nil)) if err != nil { return err } - if _, err := io.Copy(cli.out, bytes.NewReader(obj)); err != nil { - return err - } return nil } // CmdNetworkLs handles Network List UI func (cli *NetworkCli) CmdNetworkLs(chain string, args ...string) error { cmd := cli.Subcmd(chain, "ls", "", "Lists all the networks created by the user", false) + quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only display numeric IDs") + noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Do not truncate the output") + nLatest := cmd.Bool([]string{"l", "-latest"}, false, "Show the latest network created") + last := cmd.Int([]string{"n"}, -1, "Show n last created networks") err := cmd.ParseFlags(args, true) if err != nil { return err @@ -105,9 +103,41 @@ func (cli *NetworkCli) CmdNetworkLs(chain string, args ...string) error { if err != nil { return err } - if _, err := io.Copy(cli.out, bytes.NewReader(obj)); err != nil { + if *last == -1 && *nLatest { + *last = 1 + } + + var networkResources []networkResource + err = json.Unmarshal(obj, &networkResources) + if err != nil { return err } + + wr := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) + + // unless quiet (-q) is specified, print field titles + if !*quiet { + fmt.Fprintln(wr, "NETWORK ID\tNAME\tTYPE") + } + + for _, networkResource := range networkResources { + ID := networkResource.ID + netName := networkResource.Name + if !*noTrunc { + ID = stringid.TruncateID(ID) + } + if *quiet { + fmt.Fprintln(wr, ID) + continue + } + netType := networkResource.Type + fmt.Fprintf(wr, "%s\t%s\t%s\t", + ID, + netName, + netType) + fmt.Fprint(wr, "\n") + } + wr.Flush() return nil } @@ -129,9 +159,20 @@ func (cli *NetworkCli) CmdNetworkInfo(chain string, args ...string) error { if err != nil { return err } - if _, err := io.Copy(cli.out, bytes.NewReader(obj)); err != nil { + networkResource := &networkResource{} + if err := json.NewDecoder(bytes.NewReader(obj)).Decode(networkResource); err != nil { return err } + fmt.Fprintf(cli.out, "Network Id: %s\n", networkResource.ID) + fmt.Fprintf(cli.out, "Name: %s\n", networkResource.Name) + fmt.Fprintf(cli.out, "Type: %s\n", networkResource.Type) + if networkResource.Endpoints != nil { + for _, endpointResource := range networkResource.Endpoints { + fmt.Fprintf(cli.out, " Service Id: %s\n", endpointResource.ID) + fmt.Fprintf(cli.out, "\tName: %s\n", endpointResource.Name) + } + } + return nil } @@ -184,249 +225,6 @@ func lookupNetworkID(cli *NetworkCli, nameID string) (string, error) { 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 - } - if _, err := io.Copy(cli.out, bytes.NewReader(obj)); err != nil { - return err - } - return nil -} - func networkUsage(chain string) string { help := "Commands:\n" @@ -434,20 +232,10 @@ func networkUsage(chain string) string { help += fmt.Sprintf(" %-25.25s%s\n", cmd.name, cmd.description) } + for _, cmd := range serviceCommands { + help += fmt.Sprintf(" %-25.25s%s\n", "service "+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/libnetwork/client/service.go b/libnetwork/client/service.go new file mode 100644 index 0000000000..afdbb7f84b --- /dev/null +++ b/libnetwork/client/service.go @@ -0,0 +1,7 @@ +// +build !experimental + +package client + +var ( + serviceCommands = []command{} +) diff --git a/libnetwork/client/service_experimental.go b/libnetwork/client/service_experimental.go new file mode 100644 index 0000000000..02555fc197 --- /dev/null +++ b/libnetwork/client/service_experimental.go @@ -0,0 +1,317 @@ +// +build experimental + +package client + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "text/tabwriter" + + flag "github.com/docker/docker/pkg/mflag" + "github.com/docker/docker/pkg/stringid" +) + +var ( + serviceCommands = []command{ + {"create", "Create a service endpoint"}, + {"rm", "Remove a service endpoint"}, + {"join", "Join a container to a service endpoint"}, + {"leave", "Leave a container from a service endpoint"}, + {"ls", "Lists all service endpoints on a network"}, + {"info", "Display information of a service endpoint"}, + } +) + +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 + } + + var replyID string + err = json.Unmarshal(obj, &replyID) + if err != nil { + return err + } + + fmt.Fprintf(cli.out, "%s\n", replyID) + 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 + } + + _, _, err = readBody(cli.call("DELETE", "/networks/"+networkID+"/endpoints/"+serviceID, nil, nil)) + if 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) + quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only display numeric IDs") + noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Do not truncate the output") + nLatest := cmd.Bool([]string{"l", "-latest"}, false, "Show the latest network created") + last := cmd.Int([]string{"n"}, -1, "Show n last created networks") + 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 *last == -1 && *nLatest { + *last = 1 + } + + var endpointResources []endpointResource + err = json.Unmarshal(obj, &endpointResources) + if err != nil { + return err + } + + wr := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) + // unless quiet (-q) is specified, print field titles + if !*quiet { + fmt.Fprintln(wr, "NETWORK SERVICE ID\tNAME\tNETWORK") + } + + for _, networkResource := range endpointResources { + ID := networkResource.ID + netName := networkResource.Name + if !*noTrunc { + ID = stringid.TruncateID(ID) + } + if *quiet { + fmt.Fprintln(wr, ID) + continue + } + network := networkResource.Network + fmt.Fprintf(wr, "%s\t%s\t%s", + ID, + netName, + network) + fmt.Fprint(wr, "\n") + } + wr.Flush() + + 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 + } + + endpointResource := &endpointResource{} + if err := json.NewDecoder(bytes.NewReader(obj)).Decode(endpointResource); err != nil { + return err + } + fmt.Fprintf(cli.out, "Service Id: %s\n", endpointResource.ID) + fmt.Fprintf(cli.out, "\tName: %s\n", endpointResource.Name) + fmt.Fprintf(cli.out, "\tNetwork: %s\n", endpointResource.Network) + + 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} + + _, _, err = readBody(cli.call("POST", "/networks/"+networkID+"/endpoints/"+serviceID+"/containers", nc, nil)) + if err != nil { + fmt.Fprintf(cli.err, "%s", err.Error()) + 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 + } + + _, _, 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 + } + return nil +} + +func serviceUsage(chain string) string { + help := "Commands:\n" + + for _, cmd := range serviceCommands { + help += fmt.Sprintf(" %-10.10s%s\n", cmd, cmd.description) + } + + help += fmt.Sprintf("\nRun '%s service COMMAND --help' for more information on a command.", chain) + return help +}