Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Docker Network UX & remote API changes #16645

Merged
merged 3 commits into from Oct 7, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
207 changes: 200 additions & 7 deletions api/client/network.go
@@ -1,14 +1,207 @@
// +build experimental

package client

import (
nwclient "github.com/docker/libnetwork/client"
"bytes"
"encoding/json"
"fmt"
"io"
"text/tabwriter"

"github.com/docker/docker/api/types"
Cli "github.com/docker/docker/cli"
flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/pkg/stringid"
)

// CmdNetwork is used to create, display and configure network endpoints.
// CmdNetwork is the parent subcommand for all network commands
//
// Usage: docker network <COMMAND> [OPTIONS]
func (cli *DockerCli) CmdNetwork(args ...string) error {
nCli := nwclient.NewNetworkCli(cli.out, cli.err, nwclient.CallFunc(cli.callWrapper))
args = append([]string{"network"}, args...)
return nCli.Cmd("docker", args...)
cmd := Cli.Subcmd("network", []string{"COMMAND [OPTIONS]"}, networkUsage(), false)
cmd.Require(flag.Min, 1)
err := cmd.ParseFlags(args, true)
cmd.Usage()
return err
}

// CmdNetworkCreate creates a new network with a given name
//
// Usage: docker network create [OPTIONS] <NETWORK-NAME>
func (cli *DockerCli) CmdNetworkCreate(args ...string) error {
cmd := Cli.Subcmd("network create", []string{"NETWORK-NAME"}, "Creates a new network with a name specified by the user", false)
flDriver := cmd.String([]string{"d", "-driver"}, "", "Driver to manage the Network")
cmd.Require(flag.Exact, 1)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}

// Construct network create request body
nc := types.NetworkCreate{Name: cmd.Arg(0), Driver: *flDriver, CheckDuplicate: true}
obj, _, err := readBody(cli.call("POST", "/networks/create", nc, nil))
if err != nil {
return err
}
var resp types.NetworkCreateResponse
err = json.Unmarshal(obj, &resp)
if err != nil {
return err
}
fmt.Fprintf(cli.out, "%s\n", resp.ID)
return nil
}

// CmdNetworkRm deletes a network
//
// Usage: docker network rm <NETWORK-NAME | NETWORK-ID>
func (cli *DockerCli) CmdNetworkRm(args ...string) error {
cmd := Cli.Subcmd("network rm", []string{"NETWORK"}, "Deletes a network", false)
cmd.Require(flag.Exact, 1)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
_, _, err = readBody(cli.call("DELETE", "/networks/"+cmd.Arg(0), nil, nil))
if err != nil {
return err
}
return nil
}

// CmdNetworkConnect connects a container to a network
//
// Usage: docker network connect <NETWORK> <CONTAINER>
func (cli *DockerCli) CmdNetworkConnect(args ...string) error {
cmd := Cli.Subcmd("network connect", []string{"NETWORK CONTAINER"}, "Connects a container to a network", false)
cmd.Require(flag.Exact, 2)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}

nc := types.NetworkConnect{Container: cmd.Arg(1)}
_, _, err = readBody(cli.call("POST", "/networks/"+cmd.Arg(0)+"/connect", nc, nil))
return err
}

// CmdNetworkDisconnect disconnects a container from a network
//
// Usage: docker network disconnect <NETWORK> <CONTAINER>
func (cli *DockerCli) CmdNetworkDisconnect(args ...string) error {
cmd := Cli.Subcmd("network disconnect", []string{"NETWORK CONTAINER"}, "Disconnects container from a network", false)
cmd.Require(flag.Exact, 2)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}

nc := types.NetworkConnect{Container: cmd.Arg(1)}
_, _, err = readBody(cli.call("POST", "/networks/"+cmd.Arg(0)+"/disconnect", nc, nil))
return err
}

// CmdNetworkLs lists all the netorks managed by docker daemon
//
// Usage: docker network ls [OPTIONS]
func (cli *DockerCli) CmdNetworkLs(args ...string) error {
cmd := Cli.Subcmd("network ls", []string{""}, "Lists all the networks created by the user", false)
quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only display numeric IDs")
noTrunc := cmd.Bool([]string{"", "-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
}
obj, _, err := readBody(cli.call("GET", "/networks", nil, nil))
if err != nil {
return err
}
if *last == -1 && *nLatest {
*last = 1
}

var networkResources []types.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\tDRIVER")
}

for _, networkResource := range networkResources {
ID := networkResource.ID
netName := networkResource.Name
if !*noTrunc {
ID = stringid.TruncateID(ID)
}
if *quiet {
fmt.Fprintln(wr, ID)
continue
}
driver := networkResource.Driver
fmt.Fprintf(wr, "%s\t%s\t%s\t",
ID,
netName,
driver)
fmt.Fprint(wr, "\n")
}
wr.Flush()
return nil
}

// CmdNetworkInspect inspects the network object for more details
//
// Usage: docker network inspect <NETWORK>
// CmdNetworkInspect handles Network inspect UI
func (cli *DockerCli) CmdNetworkInspect(args ...string) error {
cmd := Cli.Subcmd("network inspect", []string{"NETWORK"}, "Displays detailed information on a network", false)
cmd.Require(flag.Exact, 1)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice to have: --format on docker inspect is particularly useful, we should try to have the same here if it's not too much of an effort.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. the inspect output atm is simple. But with the introduction of ipam driver (moby/libnetwork#525), we are adding more stuffs to the network inspect and that might make the need for --format useful. I think we can decide on that in the subsequent PR for the ipam UX changes. WDYT ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't block this PR on that as long as it is in the plans. What is the timeline for the IPAM UX changes?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

immediately after this and Discovery PR are merged :)

err := cmd.ParseFlags(args, true)
if err != nil {
return err
}

obj, _, err := readBody(cli.call("GET", "/networks/"+cmd.Arg(0), nil, nil))
if err != nil {
return err
}
networkResource := &types.NetworkResource{}
if err := json.NewDecoder(bytes.NewReader(obj)).Decode(networkResource); err != nil {
return err
}

indented := new(bytes.Buffer)
if err := json.Indent(indented, obj, "", " "); err != nil {
return err
}
if _, err := io.Copy(cli.out, indented); err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's surprising to have this one dump raw JSON instead of a structured table.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the network inspect output will certainly expand to accomodate more data in it (such as stats) & more network resources. Structured table doesn't suit well. network inspect (similar to docker inspect) will be highly appreciated by operators and devs alike.

return err
}
return nil
}

func networkUsage() string {
networkCommands := map[string]string{
"create": "Create a network",
"connect": "Connect container to a network",
"disconnect": "Disconnect container from a network",
"inspect": "Display detailed network information",
"ls": "List all networks",
"rm": "Remove a network",
}

help := "Commands:\n"

for cmd, description := range networkCommands {
help += fmt.Sprintf(" %-25.25s%s\n", cmd, description)
}

help += fmt.Sprintf("\nRun 'docker network COMMAND --help' for more information on a command.")
return help
}
17 changes: 0 additions & 17 deletions api/client/service.go

This file was deleted.

15 changes: 8 additions & 7 deletions api/server/router/local/local.go
Expand Up @@ -5,11 +5,9 @@ import (

"golang.org/x/net/context"

"github.com/Sirupsen/logrus"
"github.com/docker/docker/api/server/httputils"
dkrouter "github.com/docker/docker/api/server/router"
"github.com/docker/docker/daemon"
"github.com/gorilla/mux"
)

// router is a docker router that talks with the local docker daemon.
Expand All @@ -31,11 +29,14 @@ func (l localRoute) Handler() httputils.APIFunc {
return l.handler
}

// Register adds the filtered handler to the mux.
func (l localRoute) Register(m *mux.Router, handler http.Handler) {
logrus.Debugf("Registering %s, %s", l.method, l.path)
m.Path(dkrouter.VersionMatcher + l.path).Methods(l.method).Handler(handler)
m.Path(l.path).Methods(l.method).Handler(handler)
// Method returns the http method that the route responds to.
func (l localRoute) Method() string {
return l.method
}

// Path returns the subpath where the route responds to.
func (l localRoute) Path() string {
return l.path
}

// NewRoute initialies a new local route for the reouter
Expand Down
35 changes: 25 additions & 10 deletions api/server/router/network/network.go
@@ -1,26 +1,41 @@
package network

import (
"github.com/docker/docker/api/server/httputils"
"github.com/docker/docker/api/server/router"
"github.com/docker/docker/api/server/router/local"
"github.com/docker/docker/daemon"
)

// networkRouter is a router to talk with the network controller
type networkRouter struct {
daemon *daemon.Daemon
routes []router.Route
}

// Routes returns the available routes to the network controller
func (n networkRouter) Routes() []router.Route {
return n.routes
// NewRouter initializes a new network router
func NewRouter(d *daemon.Daemon) router.Router {
r := &networkRouter{
daemon: d,
}
r.initRoutes()
return r
}

type networkRoute struct {
path string
handler httputils.APIFunc
// Routes returns the available routes to the network controller
func (r *networkRouter) Routes() []router.Route {
return r.routes
}

// Handler returns the APIFunc to let the server wrap it in middlewares
func (l networkRoute) Handler() httputils.APIFunc {
return l.handler
func (r *networkRouter) initRoutes() {
r.routes = []router.Route{
// GET
local.NewGetRoute("/networks", r.getNetworksList),
local.NewGetRoute("/networks/{id:.*}", r.getNetwork),
// POST
local.NewPostRoute("/networks/create", r.postNetworkCreate),
local.NewPostRoute("/networks/{id:.*}/connect", r.postNetworkConnect),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor:

since this post is going to connect a container to a network, it might be better to have the container-id in the url itself i.e.

"/networks/{id:.*}/connect/{id:.*}",

same will be true for disconnect

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

its highly likely that we will expand the connect data to more than container-id (such as alias and other endpoint specific data). When it comes, we dont want to change the API again. So, I prefer to keep the current structure and it can easily be expanded by having backward compatible clients.

local.NewPostRoute("/networks/{id:.*}/disconnect", r.postNetworkDisconnect),
// DELETE
local.NewDeleteRoute("/networks/{id:.*}", r.deleteNetwork),
}
}
51 changes: 0 additions & 51 deletions api/server/router/network/network_experimental.go

This file was deleted.