mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
libnetwork client base infra
This is an experiment by modularizing the client UI handler in libnetwork while the actual UI hook to the docker chain can come from Docker Project. Signed-off-by: Madhu Venugopal <madhu@docker.com>
This commit is contained in:
parent
d152bb91c4
commit
263ee2dbbb
3 changed files with 426 additions and 0 deletions
112
libnetwork/client/client.go
Normal file
112
libnetwork/client/client.go
Normal file
|
@ -0,0 +1,112 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
flag "github.com/docker/docker/pkg/mflag"
|
||||
)
|
||||
|
||||
// CallFunc provides environment specific call utility to invoke backend functions from UI
|
||||
type CallFunc func(string, string, interface{}, map[string][]string) (io.ReadCloser, int, error)
|
||||
|
||||
// NetworkCli is the UI object for network subcmds
|
||||
type NetworkCli struct {
|
||||
out io.Writer
|
||||
err io.Writer
|
||||
call CallFunc
|
||||
}
|
||||
|
||||
// NewNetworkCli is a conveninent function to create a NetworkCli object
|
||||
func NewNetworkCli(out, err io.Writer, call CallFunc) *NetworkCli {
|
||||
return &NetworkCli{
|
||||
out: out,
|
||||
err: err,
|
||||
call: call,
|
||||
}
|
||||
}
|
||||
|
||||
// getMethod is Borrowed from Docker UI which uses reflection to identify the UI Handler
|
||||
func (cli *NetworkCli) getMethod(args ...string) (func(string, ...string) error, bool) {
|
||||
camelArgs := make([]string, len(args))
|
||||
for i, s := range args {
|
||||
if len(s) == 0 {
|
||||
return nil, false
|
||||
}
|
||||
camelArgs[i] = strings.ToUpper(s[:1]) + strings.ToLower(s[1:])
|
||||
}
|
||||
methodName := "Cmd" + strings.Join(camelArgs, "")
|
||||
method := reflect.ValueOf(cli).MethodByName(methodName)
|
||||
if !method.IsValid() {
|
||||
return nil, false
|
||||
}
|
||||
return method.Interface().(func(string, ...string) error), true
|
||||
}
|
||||
|
||||
// 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 {
|
||||
var errStr string
|
||||
if len(args) > 1 {
|
||||
method, exists := cli.getMethod(args[:2]...)
|
||||
if exists {
|
||||
return method(chain, args[2:]...)
|
||||
}
|
||||
}
|
||||
if len(args) > 0 {
|
||||
method, exists := cli.getMethod(args[0])
|
||||
if !exists {
|
||||
errStr = fmt.Sprintf("%s: '%s' is not a %s command. See '%s --help'.\n", chain, args[0], chain, chain)
|
||||
fmt.Fprintf(cli.err, errStr)
|
||||
return errors.New(errStr)
|
||||
}
|
||||
return method(chain, args[1:]...)
|
||||
}
|
||||
errStr = fmt.Sprintf("'%s' is not a valid command. See '%s --help'.\n", chain, chain)
|
||||
fmt.Fprintf(cli.err, errStr)
|
||||
return errors.New(errStr)
|
||||
}
|
||||
|
||||
// Subcmd is borrowed from Docker UI and performs the same function of configuring the subCmds
|
||||
func (cli *NetworkCli) Subcmd(chain, name, signature, description string, exitOnError bool) *flag.FlagSet {
|
||||
var errorHandling flag.ErrorHandling
|
||||
if exitOnError {
|
||||
errorHandling = flag.ExitOnError
|
||||
} else {
|
||||
errorHandling = flag.ContinueOnError
|
||||
}
|
||||
flags := flag.NewFlagSet(name, errorHandling)
|
||||
flags.Usage = func() {
|
||||
options := ""
|
||||
if signature != "" {
|
||||
signature = " " + signature
|
||||
}
|
||||
if flags.FlagCountUndeprecated() > 0 {
|
||||
options = " [OPTIONS]"
|
||||
}
|
||||
fmt.Fprintf(cli.out, "\nUsage: %s %s%s%s\n\n%s\n\n", chain, name, options, signature, description)
|
||||
flags.SetOutput(cli.out)
|
||||
flags.PrintDefaults()
|
||||
os.Exit(0)
|
||||
}
|
||||
return flags
|
||||
}
|
||||
|
||||
func readBody(stream io.ReadCloser, statusCode int, err error) ([]byte, int, error) {
|
||||
if stream != nil {
|
||||
defer stream.Close()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, statusCode, err
|
||||
}
|
||||
body, err := ioutil.ReadAll(stream)
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
return body, statusCode, nil
|
||||
}
|
178
libnetwork/client/client_test.go
Normal file
178
libnetwork/client/client_test.go
Normal file
|
@ -0,0 +1,178 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// nopCloser is used to provide a dummy CallFunc for Cmd()
|
||||
type nopCloser struct {
|
||||
io.Reader
|
||||
}
|
||||
|
||||
func (nopCloser) Close() error { return 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)
|
||||
|
||||
err := cli.Cmd("docker", "dummy")
|
||||
if err == nil {
|
||||
t.Fatalf("Incorrect Command must fail")
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientNoCommand(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)
|
||||
|
||||
err := cli.Cmd("docker")
|
||||
if err == nil {
|
||||
t.Fatalf("Incorrect Command must fail")
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
err := cli.Cmd("docker", "network", "create", "test")
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
err := cli.Cmd("docker", "network", "create", "-f=dummy", "test")
|
||||
if err == nil {
|
||||
t.Fatalf("Passing incorrect flags to the create command must fail")
|
||||
}
|
||||
|
||||
err = cli.Cmd("docker", "network", "create", "-d=dummy", "test")
|
||||
if err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
err := cli.Cmd("docker", "network", "rm", "test")
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
err := cli.Cmd("docker", "network", "info", "test")
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
if out.String() != info {
|
||||
t.Fatal("Network List command fail to return the intended list")
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientNetworkJoin(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)
|
||||
|
||||
err := cli.Cmd("docker", "network", "join", "db1", "dbnet", "db1-ep")
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientNetworkLeave(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)
|
||||
|
||||
err := cli.Cmd("docker", "network", "leave", "db1", "dbnet")
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// Docker Flag processing in flag.go uses os.Exit(0) for --help
|
||||
// TODO : Handle the --help test-case in the IT when CLI is available
|
||||
/*
|
||||
func TestClientNetworkCreateHelp(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)
|
||||
|
||||
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 paramater case.
|
||||
// TODO : Handle the missing argument case in the IT when CLI is available
|
||||
/*
|
||||
func TestClientNetworkCreateMissingArgument(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)
|
||||
|
||||
err := cli.Cmd("docker", "network", "create")
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
}
|
||||
*/
|
136
libnetwork/client/network.go
Normal file
136
libnetwork/client/network.go
Normal file
|
@ -0,0 +1,136 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
flag "github.com/docker/docker/pkg/mflag"
|
||||
)
|
||||
|
||||
// CmdNetworkCreate handles Network Create UI
|
||||
func (cli *NetworkCli) CmdNetworkCreate(chain string, args ...string) error {
|
||||
cmd := cli.Subcmd(chain, "create", "NETWORK-NAME", chain+" create", false)
|
||||
flDriver := cmd.String([]string{"d", "-driver"}, "", "Driver to manage the Network")
|
||||
cmd.Require(flag.Min, 1)
|
||||
err := cmd.ParseFlags(args, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if *flDriver == "" {
|
||||
*flDriver = "bridge"
|
||||
}
|
||||
// TODO : Proper Backend handling
|
||||
obj, _, err := readBody(cli.call("POST", "/networks/"+args[0], 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
|
||||
}
|
||||
|
||||
// CmdNetworkRm handles Network Delete UI
|
||||
func (cli *NetworkCli) CmdNetworkRm(chain string, args ...string) error {
|
||||
cmd := cli.Subcmd(chain, "rm", "NETWORK-NAME", chain+" rm", false)
|
||||
cmd.Require(flag.Min, 1)
|
||||
err := cmd.ParseFlags(args, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// TODO : Proper Backend handling
|
||||
obj, _, err := readBody(cli.call("DELETE", "/networks/"+args[0], 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
|
||||
}
|
||||
|
||||
// CmdNetworkLs handles Network List UI
|
||||
func (cli *NetworkCli) CmdNetworkLs(chain string, args ...string) error {
|
||||
cmd := cli.Subcmd(chain, "ls", "", chain+" ls", false)
|
||||
err := cmd.ParseFlags(args, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// TODO : Proper Backend handling
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CmdNetworkInfo handles Network Info UI
|
||||
func (cli *NetworkCli) CmdNetworkInfo(chain string, args ...string) error {
|
||||
cmd := cli.Subcmd(chain, "info", "NETWORK-NAME", chain+" info", false)
|
||||
cmd.Require(flag.Min, 1)
|
||||
err := cmd.ParseFlags(args, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// TODO : Proper Backend handling
|
||||
obj, _, err := readBody(cli.call("GET", "/networks/"+args[0], 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
|
||||
}
|
||||
|
||||
// CmdNetworkJoin handles the UI to let a Container join a Network via an endpoint
|
||||
// Sample UI : <chain> network join <container-name/id> <network-name/id> [<endpoint-name>]
|
||||
func (cli *NetworkCli) CmdNetworkJoin(chain string, args ...string) error {
|
||||
cmd := cli.Subcmd(chain, "join", "CONTAINER-NAME/ID NETWORK-NAME/ID [ENDPOINT-NAME]",
|
||||
chain+" join", false)
|
||||
cmd.Require(flag.Min, 2)
|
||||
err := cmd.ParseFlags(args, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// TODO : Proper Backend handling
|
||||
obj, _, err := readBody(cli.call("POST", "/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
|
||||
}
|
||||
|
||||
// CmdNetworkLeave handles the UI to let a Container disconnect from a Network
|
||||
// Sample UI : <chain> network leave <container-name/id> <network-name/id>
|
||||
func (cli *NetworkCli) CmdNetworkLeave(chain string, args ...string) error {
|
||||
cmd := cli.Subcmd(chain, "leave", "CONTAINER-NAME/ID NETWORK-NAME/ID",
|
||||
chain+" leave", false)
|
||||
cmd.Require(flag.Min, 2)
|
||||
err := cmd.ParseFlags(args, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// TODO : Proper Backend handling
|
||||
obj, _, err := readBody(cli.call("PUT", "/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
|
||||
}
|
Loading…
Reference in a new issue