This commit also brings in the ability to specify a default network and its
corresponding driver as daemon flags. This helps in existing clients to
make use of newer networking features provided by libnetwork.
Signed-off-by: Madhu Venugopal <madhu@docker.com>
1 | 1 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,15 @@ |
0 |
+// +build experimental |
|
1 |
+ |
|
2 |
+package client |
|
3 |
+ |
|
4 |
+import ( |
|
5 |
+ "os" |
|
6 |
+ |
|
7 |
+ nwclient "github.com/docker/libnetwork/client" |
|
8 |
+) |
|
9 |
+ |
|
10 |
+func (cli *DockerCli) CmdNetwork(args ...string) error { |
|
11 |
+ nCli := nwclient.NewNetworkCli(cli.out, cli.err, nwclient.CallFunc(cli.call)) |
|
12 |
+ args = append([]string{"network"}, args...) |
|
13 |
+ return nCli.Cmd(os.Args[0], args...) |
|
14 |
+} |
0 | 15 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,12 @@ |
0 |
+// +build experimental |
|
1 |
+ |
|
2 |
+package server |
|
3 |
+ |
|
4 |
+func (s *Server) registerSubRouter() { |
|
5 |
+ httpHandler := s.daemon.NetworkApiRouter() |
|
6 |
+ |
|
7 |
+ subrouter := s.router.PathPrefix("/v{version:[0-9.]+}/networks").Subrouter() |
|
8 |
+ subrouter.Methods("GET", "POST", "PUT", "DELETE").HandlerFunc(httpHandler) |
|
9 |
+ subrouter = s.router.PathPrefix("/networks").Subrouter() |
|
10 |
+ subrouter.Methods("GET", "POST", "PUT", "DELETE").HandlerFunc(httpHandler) |
|
11 |
+} |
... | ... |
@@ -70,6 +70,7 @@ func (s *Server) newServer(proto, addr string) ([]serverCloser, error) { |
70 | 70 |
func (s *Server) AcceptConnections(d *daemon.Daemon) { |
71 | 71 |
// Tell the init daemon we are accepting requests |
72 | 72 |
s.daemon = d |
73 |
+ s.registerSubRouter() |
|
73 | 74 |
go systemd.SdNotify("READY=1") |
74 | 75 |
// close the lock so the listeners start accepting connections |
75 | 76 |
select { |
... | ... |
@@ -45,6 +45,7 @@ func (s *Server) newServer(proto, addr string) ([]serverCloser, error) { |
45 | 45 |
|
46 | 46 |
func (s *Server) AcceptConnections(d *daemon.Daemon) { |
47 | 47 |
s.daemon = d |
48 |
+ s.registerSubRouter() |
|
48 | 49 |
// close the lock so the listeners start accepting connections |
49 | 50 |
select { |
50 | 51 |
case <-s.start: |
... | ... |
@@ -32,6 +32,7 @@ type CommonConfig struct { |
32 | 32 |
Pidfile string |
33 | 33 |
Root string |
34 | 34 |
TrustKeyPath string |
35 |
+ DefaultNetwork string |
|
35 | 36 |
} |
36 | 37 |
|
37 | 38 |
// InstallCommonFlags adds command-line options to the top-level flag parser for |
... | ... |
@@ -50,6 +51,7 @@ func (config *Config) InstallCommonFlags() { |
50 | 50 |
flag.IntVar(&config.Mtu, []string{"#mtu", "-mtu"}, 0, "Set the containers network MTU") |
51 | 51 |
flag.BoolVar(&config.EnableCors, []string{"#api-enable-cors", "#-api-enable-cors"}, false, "Enable CORS headers in the remote API, this is deprecated by --api-cors-header") |
52 | 52 |
flag.StringVar(&config.CorsHeaders, []string{"-api-cors-header"}, "", "Set CORS headers in the remote API") |
53 |
+ flag.StringVar(&config.DefaultNetwork, []string{"-default-network"}, "", "Set default network") |
|
53 | 54 |
// FIXME: why the inconsistency between "hosts" and "sockets"? |
54 | 55 |
opts.IPListVar(&config.Dns, []string{"#dns", "-dns"}, "DNS server to use") |
55 | 56 |
opts.DnsSearchListVar(&config.DnsSearch, []string{"-dns-search"}, "DNS search domains to use") |
... | ... |
@@ -737,17 +737,47 @@ func (container *Container) buildCreateEndpointOptions() ([]libnetwork.EndpointO |
737 | 737 |
return createOptions, nil |
738 | 738 |
} |
739 | 739 |
|
740 |
+func createDefaultNetwork(controller libnetwork.NetworkController) (libnetwork.Network, error) { |
|
741 |
+ createOptions := []libnetwork.NetworkOption{} |
|
742 |
+ genericOption := options.Generic{} |
|
743 |
+ dnet := controller.Config().Daemon.DefaultNetwork |
|
744 |
+ driver := controller.Config().Daemon.DefaultDriver |
|
745 |
+ |
|
746 |
+ // Bridge driver is special due to legacy reasons |
|
747 |
+ if runconfig.NetworkMode(driver).IsBridge() { |
|
748 |
+ genericOption[netlabel.GenericData] = map[string]interface{}{ |
|
749 |
+ "BridgeName": dnet, |
|
750 |
+ "AllowNonDefaultBridge": "true", |
|
751 |
+ } |
|
752 |
+ networkOption := libnetwork.NetworkOptionGeneric(genericOption) |
|
753 |
+ createOptions = append(createOptions, networkOption) |
|
754 |
+ } |
|
755 |
+ |
|
756 |
+ return controller.NewNetwork(driver, dnet, createOptions...) |
|
757 |
+} |
|
758 |
+ |
|
740 | 759 |
func (container *Container) AllocateNetwork() error { |
741 | 760 |
mode := container.hostConfig.NetworkMode |
761 |
+ controller := container.daemon.netController |
|
742 | 762 |
if container.Config.NetworkDisabled || mode.IsContainer() { |
743 | 763 |
return nil |
744 | 764 |
} |
745 | 765 |
|
766 |
+ networkName := mode.NetworkName() |
|
767 |
+ if mode.IsDefault() { |
|
768 |
+ networkName = controller.Config().Daemon.DefaultNetwork |
|
769 |
+ } |
|
770 |
+ |
|
746 | 771 |
var err error |
747 | 772 |
|
748 |
- n, err := container.daemon.netController.NetworkByName(string(mode)) |
|
773 |
+ n, err := controller.NetworkByName(networkName) |
|
749 | 774 |
if err != nil { |
750 |
- return fmt.Errorf("error locating network with name %s: %v", string(mode), err) |
|
775 |
+ if !mode.IsDefault() { |
|
776 |
+ return fmt.Errorf("error locating network with name %s: %v", networkName, err) |
|
777 |
+ } |
|
778 |
+ if n, err = createDefaultNetwork(controller); err != nil { |
|
779 |
+ return err |
|
780 |
+ } |
|
751 | 781 |
} |
752 | 782 |
|
753 | 783 |
createOptions, err := container.buildCreateEndpointOptions() |
... | ... |
@@ -790,9 +820,8 @@ func (container *Container) initializeNetworking() error { |
790 | 790 |
// Make sure NetworkMode has an acceptable value before |
791 | 791 |
// initializing networking. |
792 | 792 |
if container.hostConfig.NetworkMode == runconfig.NetworkMode("") { |
793 |
- container.hostConfig.NetworkMode = runconfig.NetworkMode("bridge") |
|
793 |
+ container.hostConfig.NetworkMode = runconfig.NetworkMode("default") |
|
794 | 794 |
} |
795 |
- |
|
796 | 795 |
if container.hostConfig.NetworkMode.IsContainer() { |
797 | 796 |
// we need to get the hosts files from the container to join |
798 | 797 |
nc, err := container.getNetworkedContainer() |
... | ... |
@@ -5,6 +5,7 @@ package daemon |
5 | 5 |
import ( |
6 | 6 |
"fmt" |
7 | 7 |
"net" |
8 |
+ "net/http" |
|
8 | 9 |
"os" |
9 | 10 |
"path/filepath" |
10 | 11 |
"runtime" |
... | ... |
@@ -24,6 +25,8 @@ import ( |
24 | 24 |
"github.com/docker/docker/volume/local" |
25 | 25 |
"github.com/docker/libcontainer/label" |
26 | 26 |
"github.com/docker/libnetwork" |
27 |
+ nwapi "github.com/docker/libnetwork/api" |
|
28 |
+ nwconfig "github.com/docker/libnetwork/config" |
|
27 | 29 |
"github.com/docker/libnetwork/netlabel" |
28 | 30 |
"github.com/docker/libnetwork/options" |
29 | 31 |
) |
... | ... |
@@ -264,8 +267,35 @@ func isNetworkDisabled(config *Config) bool { |
264 | 264 |
return config.Bridge.Iface == disableNetworkBridge |
265 | 265 |
} |
266 | 266 |
|
267 |
+func networkOptions(dconfig *Config) ([]nwconfig.Option, error) { |
|
268 |
+ options := []nwconfig.Option{} |
|
269 |
+ if dconfig == nil { |
|
270 |
+ return options, nil |
|
271 |
+ } |
|
272 |
+ if strings.TrimSpace(dconfig.DefaultNetwork) != "" { |
|
273 |
+ dn := strings.Split(dconfig.DefaultNetwork, ":") |
|
274 |
+ if len(dn) < 2 { |
|
275 |
+ return nil, fmt.Errorf("default network daemon config must be of the form NETWORKDRIVER:NETWORKNAME") |
|
276 |
+ } |
|
277 |
+ options = append(options, nwconfig.OptionDefaultDriver(dn[0])) |
|
278 |
+ options = append(options, nwconfig.OptionDefaultNetwork(strings.Join(dn[1:], ":"))) |
|
279 |
+ } else { |
|
280 |
+ dd := runconfig.DefaultDaemonNetworkMode() |
|
281 |
+ dn := runconfig.DefaultDaemonNetworkMode().NetworkName() |
|
282 |
+ options = append(options, nwconfig.OptionDefaultDriver(string(dd))) |
|
283 |
+ options = append(options, nwconfig.OptionDefaultNetwork(dn)) |
|
284 |
+ } |
|
285 |
+ options = append(options, nwconfig.OptionLabels(dconfig.Labels)) |
|
286 |
+ return options, nil |
|
287 |
+} |
|
288 |
+ |
|
267 | 289 |
func initNetworkController(config *Config) (libnetwork.NetworkController, error) { |
268 |
- controller, err := libnetwork.New() |
|
290 |
+ netOptions, err := networkOptions(config) |
|
291 |
+ if err != nil { |
|
292 |
+ return nil, err |
|
293 |
+ } |
|
294 |
+ |
|
295 |
+ controller, err := libnetwork.New(netOptions...) |
|
269 | 296 |
if err != nil { |
270 | 297 |
return nil, fmt.Errorf("error obtaining controller instance: %v", err) |
271 | 298 |
} |
... | ... |
@@ -419,3 +449,7 @@ func setupInitLayer(initLayer string) error { |
419 | 419 |
// Layer is ready to use, if it wasn't before. |
420 | 420 |
return nil |
421 | 421 |
} |
422 |
+ |
|
423 |
+func (daemon *Daemon) NetworkApiRouter() func(w http.ResponseWriter, req *http.Request) { |
|
424 |
+ return nwapi.NewHTTPHandler(daemon.netController) |
|
425 |
+} |
... | ... |
@@ -18,7 +18,7 @@ clone git golang.org/x/net 3cffabab72adf04f8e3b01c5baf775361837b5fe https://gith |
18 | 18 |
clone hg code.google.com/p/gosqlite 74691fb6f837 |
19 | 19 |
|
20 | 20 |
#get libnetwork packages |
21 |
-clone git github.com/docker/libnetwork 3be488927db8d719568917203deddd630a194564 |
|
21 |
+clone git github.com/docker/libnetwork fc7abaa93fd33a77cc37845adbbc4adf03676dd5 |
|
22 | 22 |
clone git github.com/docker/libkv e8cde779d58273d240c1eff065352a6cd67027dd |
23 | 23 |
clone git github.com/vishvananda/netns 5478c060110032f972e86a1f844fdb9a2f008f2c |
24 | 24 |
clone git github.com/vishvananda/netlink 8eb64238879fed52fd51c5b30ad20b928fb4c36c |
... | ... |
@@ -869,7 +869,7 @@ func (s *DockerSuite) TestContainerApiCreate(c *check.C) { |
869 | 869 |
|
870 | 870 |
out, err := exec.Command(dockerBinary, "start", "-a", container.Id).CombinedOutput() |
871 | 871 |
if err != nil { |
872 |
- c.Fatal(out, err) |
|
872 |
+ c.Fatal(string(out), err) |
|
873 | 873 |
} |
874 | 874 |
if strings.TrimSpace(string(out)) != "/test" { |
875 | 875 |
c.Fatalf("expected output `/test`, got %q", out) |
876 | 876 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,72 @@ |
0 |
+// +build experimental |
|
1 |
+ |
|
2 |
+package main |
|
3 |
+ |
|
4 |
+import ( |
|
5 |
+ "encoding/json" |
|
6 |
+ "fmt" |
|
7 |
+ "net/http" |
|
8 |
+ |
|
9 |
+ "github.com/go-check/check" |
|
10 |
+) |
|
11 |
+ |
|
12 |
+func isNetworkAvailable(c *check.C, name string) bool { |
|
13 |
+ status, body, err := sockRequest("GET", "/networks", nil) |
|
14 |
+ c.Assert(status, check.Equals, http.StatusOK) |
|
15 |
+ c.Assert(err, check.IsNil) |
|
16 |
+ |
|
17 |
+ var inspectJSON []struct { |
|
18 |
+ Name string |
|
19 |
+ ID string |
|
20 |
+ Type string |
|
21 |
+ } |
|
22 |
+ if err = json.Unmarshal(body, &inspectJSON); err != nil { |
|
23 |
+ c.Fatalf("unable to unmarshal response body: %v", err) |
|
24 |
+ } |
|
25 |
+ for _, n := range inspectJSON { |
|
26 |
+ if n.Name == name { |
|
27 |
+ return true |
|
28 |
+ } |
|
29 |
+ } |
|
30 |
+ return false |
|
31 |
+ |
|
32 |
+} |
|
33 |
+ |
|
34 |
+func (s *DockerSuite) TestNetworkApiGetAll(c *check.C) { |
|
35 |
+ defaults := []string{"bridge", "host", "none"} |
|
36 |
+ for _, nn := range defaults { |
|
37 |
+ if !isNetworkAvailable(c, nn) { |
|
38 |
+ c.Fatalf("Missing Default network : %s", nn) |
|
39 |
+ } |
|
40 |
+ } |
|
41 |
+} |
|
42 |
+ |
|
43 |
+func (s *DockerSuite) TestNetworkApiCreateDelete(c *check.C) { |
|
44 |
+ name := "testnetwork" |
|
45 |
+ config := map[string]interface{}{ |
|
46 |
+ "name": name, |
|
47 |
+ "network_type": "bridge", |
|
48 |
+ } |
|
49 |
+ |
|
50 |
+ status, resp, err := sockRequest("POST", "/networks", config) |
|
51 |
+ c.Assert(status, check.Equals, http.StatusCreated) |
|
52 |
+ c.Assert(err, check.IsNil) |
|
53 |
+ |
|
54 |
+ if !isNetworkAvailable(c, name) { |
|
55 |
+ c.Fatalf("Network %s not found", name) |
|
56 |
+ } |
|
57 |
+ |
|
58 |
+ var id string |
|
59 |
+ err = json.Unmarshal(resp, &id) |
|
60 |
+ if err != nil { |
|
61 |
+ c.Fatal(err) |
|
62 |
+ } |
|
63 |
+ |
|
64 |
+ status, _, err = sockRequest("DELETE", fmt.Sprintf("/networks/%s", id), nil) |
|
65 |
+ c.Assert(status, check.Equals, http.StatusOK) |
|
66 |
+ c.Assert(err, check.IsNil) |
|
67 |
+ |
|
68 |
+ if isNetworkAvailable(c, name) { |
|
69 |
+ c.Fatalf("Network %s not deleted", name) |
|
70 |
+ } |
|
71 |
+} |
0 | 72 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,54 @@ |
0 |
+// +build experimental |
|
1 |
+ |
|
2 |
+package main |
|
3 |
+ |
|
4 |
+import ( |
|
5 |
+ "os/exec" |
|
6 |
+ "strings" |
|
7 |
+ |
|
8 |
+ "github.com/go-check/check" |
|
9 |
+) |
|
10 |
+ |
|
11 |
+func isNetworkPresent(c *check.C, name string) bool { |
|
12 |
+ runCmd := exec.Command(dockerBinary, "network", "ls") |
|
13 |
+ out, _, _, err := runCommandWithStdoutStderr(runCmd) |
|
14 |
+ if err != nil { |
|
15 |
+ c.Fatal(out, err) |
|
16 |
+ } |
|
17 |
+ lines := strings.Split(out, "\n") |
|
18 |
+ for i := 1; i < len(lines)-1; i++ { |
|
19 |
+ if strings.Contains(lines[i], name) { |
|
20 |
+ return true |
|
21 |
+ } |
|
22 |
+ } |
|
23 |
+ return false |
|
24 |
+} |
|
25 |
+ |
|
26 |
+func (s *DockerSuite) TestDockerNetworkLsDefault(c *check.C) { |
|
27 |
+ defaults := []string{"bridge", "host", "none"} |
|
28 |
+ for _, nn := range defaults { |
|
29 |
+ if !isNetworkPresent(c, nn) { |
|
30 |
+ c.Fatalf("Missing Default network : %s", nn) |
|
31 |
+ } |
|
32 |
+ } |
|
33 |
+} |
|
34 |
+ |
|
35 |
+func (s *DockerSuite) TestDockerNetworkCreateDelete(c *check.C) { |
|
36 |
+ runCmd := exec.Command(dockerBinary, "network", "create", "test") |
|
37 |
+ out, _, _, err := runCommandWithStdoutStderr(runCmd) |
|
38 |
+ if err != nil { |
|
39 |
+ c.Fatal(out, err) |
|
40 |
+ } |
|
41 |
+ if !isNetworkPresent(c, "test") { |
|
42 |
+ c.Fatalf("Network test not found") |
|
43 |
+ } |
|
44 |
+ |
|
45 |
+ runCmd = exec.Command(dockerBinary, "network", "rm", "test") |
|
46 |
+ out, _, _, err = runCommandWithStdoutStderr(runCmd) |
|
47 |
+ if err != nil { |
|
48 |
+ c.Fatal(out, err) |
|
49 |
+ } |
|
50 |
+ if isNetworkPresent(c, "test") { |
|
51 |
+ c.Fatalf("Network test is not removed") |
|
52 |
+ } |
|
53 |
+} |
... | ... |
@@ -21,6 +21,29 @@ func (n NetworkMode) IsPrivate() bool { |
21 | 21 |
return !(n.IsHost() || n.IsContainer()) |
22 | 22 |
} |
23 | 23 |
|
24 |
+func (n NetworkMode) IsDefault() bool { |
|
25 |
+ return n == "default" |
|
26 |
+} |
|
27 |
+ |
|
28 |
+func DefaultDaemonNetworkMode() NetworkMode { |
|
29 |
+ return NetworkMode("bridge") |
|
30 |
+} |
|
31 |
+ |
|
32 |
+func (n NetworkMode) NetworkName() string { |
|
33 |
+ if n.IsBridge() { |
|
34 |
+ return "bridge" |
|
35 |
+ } else if n.IsHost() { |
|
36 |
+ return "host" |
|
37 |
+ } else if n.IsContainer() { |
|
38 |
+ return "container" |
|
39 |
+ } else if n.IsNone() { |
|
40 |
+ return "none" |
|
41 |
+ } else if n.IsDefault() { |
|
42 |
+ return "default" |
|
43 |
+ } |
|
44 |
+ return "" |
|
45 |
+} |
|
46 |
+ |
|
24 | 47 |
func (n NetworkMode) IsBridge() bool { |
25 | 48 |
return n == "bridge" |
26 | 49 |
} |
... | ... |
@@ -72,7 +72,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe |
72 | 72 |
flCpusetMems = cmd.String([]string{"-cpuset-mems"}, "", "MEMs in which to allow execution (0-3, 0,1)") |
73 | 73 |
flCpuQuota = cmd.Int64([]string{"-cpu-quota"}, 0, "Limit the CPU CFS quota") |
74 | 74 |
flBlkioWeight = cmd.Int64([]string{"-blkio-weight"}, 0, "Block IO (relative weight), between 10 and 1000") |
75 |
- flNetMode = cmd.String([]string{"-net"}, "bridge", "Set the Network mode for the container") |
|
75 |
+ flNetMode = cmd.String([]string{"-net"}, "default", "Set the Network mode for the container") |
|
76 | 76 |
flMacAddress = cmd.String([]string{"-mac-address"}, "", "Container MAC address (e.g. 92:d0:c6:0a:29:33)") |
77 | 77 |
flIpcMode = cmd.String([]string{"-ipc"}, "", "IPC namespace to use") |
78 | 78 |
flRestartPolicy = cmd.String([]string{"-restart"}, "no", "Restart policy to apply when a container exits") |
... | ... |
@@ -485,7 +485,7 @@ func parseKeyValueOpts(opts opts.ListOpts) ([]KeyValuePair, error) { |
485 | 485 |
func parseNetMode(netMode string) (NetworkMode, error) { |
486 | 486 |
parts := strings.Split(netMode, ":") |
487 | 487 |
switch mode := parts[0]; mode { |
488 |
- case "bridge", "none", "host": |
|
488 |
+ case "default", "bridge", "none", "host": |
|
489 | 489 |
case "container": |
490 | 490 |
if len(parts) < 2 || parts[1] == "" { |
491 | 491 |
return "", fmt.Errorf("invalid container format container:<name|id>") |
492 | 492 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,807 @@ |
0 |
+package api |
|
1 |
+ |
|
2 |
+import ( |
|
3 |
+ "encoding/json" |
|
4 |
+ "fmt" |
|
5 |
+ "io/ioutil" |
|
6 |
+ "net/http" |
|
7 |
+ "strings" |
|
8 |
+ |
|
9 |
+ "github.com/docker/libnetwork" |
|
10 |
+ "github.com/docker/libnetwork/netlabel" |
|
11 |
+ "github.com/docker/libnetwork/types" |
|
12 |
+ "github.com/gorilla/mux" |
|
13 |
+) |
|
14 |
+ |
|
15 |
+var ( |
|
16 |
+ successResponse = responseStatus{Status: "Success", StatusCode: http.StatusOK} |
|
17 |
+ createdResponse = responseStatus{Status: "Created", StatusCode: http.StatusCreated} |
|
18 |
+ mismatchResponse = responseStatus{Status: "Body/URI parameter mismatch", StatusCode: http.StatusBadRequest} |
|
19 |
+ badQueryResponse = responseStatus{Status: "Unsupported query", StatusCode: http.StatusBadRequest} |
|
20 |
+) |
|
21 |
+ |
|
22 |
+const ( |
|
23 |
+ // Resource name regex |
|
24 |
+ regex = "[a-zA-Z_0-9-]+" |
|
25 |
+ // Router URL variable definition |
|
26 |
+ nwName = "{" + urlNwName + ":" + regex + "}" |
|
27 |
+ nwID = "{" + urlNwID + ":" + regex + "}" |
|
28 |
+ nwPID = "{" + urlNwPID + ":" + regex + "}" |
|
29 |
+ epName = "{" + urlEpName + ":" + regex + "}" |
|
30 |
+ epID = "{" + urlEpID + ":" + regex + "}" |
|
31 |
+ epPID = "{" + urlEpPID + ":" + regex + "}" |
|
32 |
+ cnID = "{" + urlCnID + ":" + regex + "}" |
|
33 |
+ |
|
34 |
+ // Though this name can be anything, in order to support default network, |
|
35 |
+ // we will keep it as name |
|
36 |
+ urlNwName = "name" |
|
37 |
+ // Internal URL variable name, they can be anything |
|
38 |
+ urlNwID = "network-id" |
|
39 |
+ urlNwPID = "network-partial-id" |
|
40 |
+ urlEpName = "endpoint-name" |
|
41 |
+ urlEpID = "endpoint-id" |
|
42 |
+ urlEpPID = "endpoint-partial-id" |
|
43 |
+ urlCnID = "container-id" |
|
44 |
+ |
|
45 |
+ // BridgeNetworkDriver is the built-in default for Network Driver |
|
46 |
+ BridgeNetworkDriver = "bridge" |
|
47 |
+) |
|
48 |
+ |
|
49 |
+// NewHTTPHandler creates and initialize the HTTP handler to serve the requests for libnetwork |
|
50 |
+func NewHTTPHandler(c libnetwork.NetworkController) func(w http.ResponseWriter, req *http.Request) { |
|
51 |
+ h := &httpHandler{c: c} |
|
52 |
+ h.initRouter() |
|
53 |
+ return h.handleRequest |
|
54 |
+} |
|
55 |
+ |
|
56 |
+type responseStatus struct { |
|
57 |
+ Status string |
|
58 |
+ StatusCode int |
|
59 |
+} |
|
60 |
+ |
|
61 |
+func (r *responseStatus) isOK() bool { |
|
62 |
+ return r.StatusCode == http.StatusOK || r.StatusCode == http.StatusCreated |
|
63 |
+} |
|
64 |
+ |
|
65 |
+type processor func(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) |
|
66 |
+ |
|
67 |
+type httpHandler struct { |
|
68 |
+ c libnetwork.NetworkController |
|
69 |
+ r *mux.Router |
|
70 |
+} |
|
71 |
+ |
|
72 |
+func (h *httpHandler) handleRequest(w http.ResponseWriter, req *http.Request) { |
|
73 |
+ // Make sure the service is there |
|
74 |
+ if h.c == nil { |
|
75 |
+ http.Error(w, "NetworkController is not available", http.StatusServiceUnavailable) |
|
76 |
+ return |
|
77 |
+ } |
|
78 |
+ |
|
79 |
+ // Get handler from router and execute it |
|
80 |
+ h.r.ServeHTTP(w, req) |
|
81 |
+} |
|
82 |
+ |
|
83 |
+func (h *httpHandler) initRouter() { |
|
84 |
+ m := map[string][]struct { |
|
85 |
+ url string |
|
86 |
+ qrs []string |
|
87 |
+ fct processor |
|
88 |
+ }{ |
|
89 |
+ "GET": { |
|
90 |
+ // Order matters |
|
91 |
+ {"/networks", []string{"name", nwName}, procGetNetworks}, |
|
92 |
+ {"/networks", []string{"partial-id", nwPID}, procGetNetworks}, |
|
93 |
+ {"/networks", nil, procGetNetworks}, |
|
94 |
+ {"/networks/" + nwID, nil, procGetNetwork}, |
|
95 |
+ {"/networks/" + nwID + "/endpoints", []string{"name", epName}, procGetEndpoints}, |
|
96 |
+ {"/networks/" + nwID + "/endpoints", []string{"partial-id", epPID}, procGetEndpoints}, |
|
97 |
+ {"/networks/" + nwID + "/endpoints", nil, procGetEndpoints}, |
|
98 |
+ {"/networks/" + nwID + "/endpoints/" + epID, nil, procGetEndpoint}, |
|
99 |
+ {"/services", []string{"network", nwName}, procGetServices}, |
|
100 |
+ {"/services", []string{"name", epName}, procGetServices}, |
|
101 |
+ {"/services", []string{"partial-id", epPID}, procGetServices}, |
|
102 |
+ {"/services", nil, procGetServices}, |
|
103 |
+ {"/services/" + epID, nil, procGetService}, |
|
104 |
+ {"/services/" + epID + "/backend", nil, procGetContainers}, |
|
105 |
+ }, |
|
106 |
+ "POST": { |
|
107 |
+ {"/networks", nil, procCreateNetwork}, |
|
108 |
+ {"/networks/" + nwID + "/endpoints", nil, procCreateEndpoint}, |
|
109 |
+ {"/networks/" + nwID + "/endpoints/" + epID + "/containers", nil, procJoinEndpoint}, |
|
110 |
+ {"/services", nil, procPublishService}, |
|
111 |
+ {"/services/" + epID + "/backend", nil, procAttachBackend}, |
|
112 |
+ }, |
|
113 |
+ "DELETE": { |
|
114 |
+ {"/networks/" + nwID, nil, procDeleteNetwork}, |
|
115 |
+ {"/networks/" + nwID + "/endpoints/" + epID, nil, procDeleteEndpoint}, |
|
116 |
+ {"/networks/" + nwID + "/endpoints/" + epID + "/containers/" + cnID, nil, procLeaveEndpoint}, |
|
117 |
+ {"/services/" + epID, nil, procUnpublishService}, |
|
118 |
+ {"/services/" + epID + "/backend/" + cnID, nil, procDetachBackend}, |
|
119 |
+ }, |
|
120 |
+ } |
|
121 |
+ |
|
122 |
+ h.r = mux.NewRouter() |
|
123 |
+ for method, routes := range m { |
|
124 |
+ for _, route := range routes { |
|
125 |
+ r := h.r.Path("/{.*}" + route.url).Methods(method).HandlerFunc(makeHandler(h.c, route.fct)) |
|
126 |
+ if route.qrs != nil { |
|
127 |
+ r.Queries(route.qrs...) |
|
128 |
+ } |
|
129 |
+ |
|
130 |
+ r = h.r.Path(route.url).Methods(method).HandlerFunc(makeHandler(h.c, route.fct)) |
|
131 |
+ if route.qrs != nil { |
|
132 |
+ r.Queries(route.qrs...) |
|
133 |
+ } |
|
134 |
+ } |
|
135 |
+ } |
|
136 |
+} |
|
137 |
+ |
|
138 |
+func makeHandler(ctrl libnetwork.NetworkController, fct processor) http.HandlerFunc { |
|
139 |
+ return func(w http.ResponseWriter, req *http.Request) { |
|
140 |
+ var ( |
|
141 |
+ body []byte |
|
142 |
+ err error |
|
143 |
+ ) |
|
144 |
+ if req.Body != nil { |
|
145 |
+ body, err = ioutil.ReadAll(req.Body) |
|
146 |
+ if err != nil { |
|
147 |
+ http.Error(w, "Invalid body: "+err.Error(), http.StatusBadRequest) |
|
148 |
+ return |
|
149 |
+ } |
|
150 |
+ } |
|
151 |
+ |
|
152 |
+ mvars := mux.Vars(req) |
|
153 |
+ rvars := req.URL.Query() |
|
154 |
+ // workaround a mux issue which filters out valid queries with empty value |
|
155 |
+ for k := range rvars { |
|
156 |
+ if _, ok := mvars[k]; !ok { |
|
157 |
+ if rvars.Get(k) == "" { |
|
158 |
+ mvars[k] = "" |
|
159 |
+ } |
|
160 |
+ } |
|
161 |
+ } |
|
162 |
+ |
|
163 |
+ res, rsp := fct(ctrl, mvars, body) |
|
164 |
+ if !rsp.isOK() { |
|
165 |
+ http.Error(w, rsp.Status, rsp.StatusCode) |
|
166 |
+ return |
|
167 |
+ } |
|
168 |
+ if res != nil { |
|
169 |
+ writeJSON(w, rsp.StatusCode, res) |
|
170 |
+ } |
|
171 |
+ } |
|
172 |
+} |
|
173 |
+ |
|
174 |
+/***************** |
|
175 |
+ Resource Builders |
|
176 |
+******************/ |
|
177 |
+ |
|
178 |
+func buildNetworkResource(nw libnetwork.Network) *networkResource { |
|
179 |
+ r := &networkResource{} |
|
180 |
+ if nw != nil { |
|
181 |
+ r.Name = nw.Name() |
|
182 |
+ r.ID = nw.ID() |
|
183 |
+ r.Type = nw.Type() |
|
184 |
+ epl := nw.Endpoints() |
|
185 |
+ r.Endpoints = make([]*endpointResource, 0, len(epl)) |
|
186 |
+ for _, e := range epl { |
|
187 |
+ epr := buildEndpointResource(e) |
|
188 |
+ r.Endpoints = append(r.Endpoints, epr) |
|
189 |
+ } |
|
190 |
+ } |
|
191 |
+ return r |
|
192 |
+} |
|
193 |
+ |
|
194 |
+func buildEndpointResource(ep libnetwork.Endpoint) *endpointResource { |
|
195 |
+ r := &endpointResource{} |
|
196 |
+ if ep != nil { |
|
197 |
+ r.Name = ep.Name() |
|
198 |
+ r.ID = ep.ID() |
|
199 |
+ r.Network = ep.Network() |
|
200 |
+ } |
|
201 |
+ return r |
|
202 |
+} |
|
203 |
+ |
|
204 |
+func buildContainerResource(ci libnetwork.ContainerInfo) *containerResource { |
|
205 |
+ r := &containerResource{} |
|
206 |
+ if ci != nil { |
|
207 |
+ r.ID = ci.ID() |
|
208 |
+ } |
|
209 |
+ return r |
|
210 |
+} |
|
211 |
+ |
|
212 |
+/**************** |
|
213 |
+ Options Parsers |
|
214 |
+*****************/ |
|
215 |
+ |
|
216 |
+func (nc *networkCreate) parseOptions() []libnetwork.NetworkOption { |
|
217 |
+ var setFctList []libnetwork.NetworkOption |
|
218 |
+ |
|
219 |
+ if nc.Options != nil { |
|
220 |
+ setFctList = append(setFctList, libnetwork.NetworkOptionGeneric(nc.Options)) |
|
221 |
+ } |
|
222 |
+ |
|
223 |
+ return setFctList |
|
224 |
+} |
|
225 |
+ |
|
226 |
+func (ej *endpointJoin) parseOptions() []libnetwork.EndpointOption { |
|
227 |
+ var setFctList []libnetwork.EndpointOption |
|
228 |
+ if ej.HostName != "" { |
|
229 |
+ setFctList = append(setFctList, libnetwork.JoinOptionHostname(ej.HostName)) |
|
230 |
+ } |
|
231 |
+ if ej.DomainName != "" { |
|
232 |
+ setFctList = append(setFctList, libnetwork.JoinOptionDomainname(ej.DomainName)) |
|
233 |
+ } |
|
234 |
+ if ej.HostsPath != "" { |
|
235 |
+ setFctList = append(setFctList, libnetwork.JoinOptionHostsPath(ej.HostsPath)) |
|
236 |
+ } |
|
237 |
+ if ej.ResolvConfPath != "" { |
|
238 |
+ setFctList = append(setFctList, libnetwork.JoinOptionResolvConfPath(ej.ResolvConfPath)) |
|
239 |
+ } |
|
240 |
+ if ej.UseDefaultSandbox { |
|
241 |
+ setFctList = append(setFctList, libnetwork.JoinOptionUseDefaultSandbox()) |
|
242 |
+ } |
|
243 |
+ if ej.DNS != nil { |
|
244 |
+ for _, d := range ej.DNS { |
|
245 |
+ setFctList = append(setFctList, libnetwork.JoinOptionDNS(d)) |
|
246 |
+ } |
|
247 |
+ } |
|
248 |
+ if ej.ExtraHosts != nil { |
|
249 |
+ for _, e := range ej.ExtraHosts { |
|
250 |
+ setFctList = append(setFctList, libnetwork.JoinOptionExtraHost(e.Name, e.Address)) |
|
251 |
+ } |
|
252 |
+ } |
|
253 |
+ if ej.ParentUpdates != nil { |
|
254 |
+ for _, p := range ej.ParentUpdates { |
|
255 |
+ setFctList = append(setFctList, libnetwork.JoinOptionParentUpdate(p.EndpointID, p.Name, p.Address)) |
|
256 |
+ } |
|
257 |
+ } |
|
258 |
+ return setFctList |
|
259 |
+} |
|
260 |
+ |
|
261 |
+/****************** |
|
262 |
+ Process functions |
|
263 |
+*******************/ |
|
264 |
+ |
|
265 |
+func processCreateDefaults(c libnetwork.NetworkController, nc *networkCreate) { |
|
266 |
+ if nc.NetworkType == "" { |
|
267 |
+ nc.NetworkType = c.Config().Daemon.DefaultDriver |
|
268 |
+ } |
|
269 |
+ if nc.NetworkType == BridgeNetworkDriver { |
|
270 |
+ if nc.Options == nil { |
|
271 |
+ nc.Options = make(map[string]interface{}) |
|
272 |
+ } |
|
273 |
+ genericData, ok := nc.Options[netlabel.GenericData] |
|
274 |
+ if !ok { |
|
275 |
+ genericData = make(map[string]interface{}) |
|
276 |
+ } |
|
277 |
+ gData := genericData.(map[string]interface{}) |
|
278 |
+ |
|
279 |
+ if _, ok := gData["BridgeName"]; !ok { |
|
280 |
+ gData["BridgeName"] = nc.Name |
|
281 |
+ } |
|
282 |
+ if _, ok := gData["AllowNonDefaultBridge"]; !ok { |
|
283 |
+ gData["AllowNonDefaultBridge"] = "true" |
|
284 |
+ } |
|
285 |
+ nc.Options[netlabel.GenericData] = genericData |
|
286 |
+ } |
|
287 |
+} |
|
288 |
+ |
|
289 |
+/*************************** |
|
290 |
+ NetworkController interface |
|
291 |
+****************************/ |
|
292 |
+func procCreateNetwork(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) { |
|
293 |
+ var create networkCreate |
|
294 |
+ |
|
295 |
+ err := json.Unmarshal(body, &create) |
|
296 |
+ if err != nil { |
|
297 |
+ return "", &responseStatus{Status: "Invalid body: " + err.Error(), StatusCode: http.StatusBadRequest} |
|
298 |
+ } |
|
299 |
+ processCreateDefaults(c, &create) |
|
300 |
+ |
|
301 |
+ nw, err := c.NewNetwork(create.NetworkType, create.Name, create.parseOptions()...) |
|
302 |
+ if err != nil { |
|
303 |
+ return "", convertNetworkError(err) |
|
304 |
+ } |
|
305 |
+ |
|
306 |
+ return nw.ID(), &createdResponse |
|
307 |
+} |
|
308 |
+ |
|
309 |
+func procGetNetwork(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) { |
|
310 |
+ t, by := detectNetworkTarget(vars) |
|
311 |
+ nw, errRsp := findNetwork(c, t, by) |
|
312 |
+ if !errRsp.isOK() { |
|
313 |
+ return nil, errRsp |
|
314 |
+ } |
|
315 |
+ return buildNetworkResource(nw), &successResponse |
|
316 |
+} |
|
317 |
+ |
|
318 |
+func procGetNetworks(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) { |
|
319 |
+ var list []*networkResource |
|
320 |
+ |
|
321 |
+ // Look for query filters and validate |
|
322 |
+ name, queryByName := vars[urlNwName] |
|
323 |
+ shortID, queryByPid := vars[urlNwPID] |
|
324 |
+ if queryByName && queryByPid { |
|
325 |
+ return nil, &badQueryResponse |
|
326 |
+ } |
|
327 |
+ |
|
328 |
+ if queryByName { |
|
329 |
+ if nw, errRsp := findNetwork(c, name, byName); errRsp.isOK() { |
|
330 |
+ list = append(list, buildNetworkResource(nw)) |
|
331 |
+ } |
|
332 |
+ } else if queryByPid { |
|
333 |
+ // Return all the prefix-matching networks |
|
334 |
+ l := func(nw libnetwork.Network) bool { |
|
335 |
+ if strings.HasPrefix(nw.ID(), shortID) { |
|
336 |
+ list = append(list, buildNetworkResource(nw)) |
|
337 |
+ } |
|
338 |
+ return false |
|
339 |
+ } |
|
340 |
+ c.WalkNetworks(l) |
|
341 |
+ } else { |
|
342 |
+ for _, nw := range c.Networks() { |
|
343 |
+ list = append(list, buildNetworkResource(nw)) |
|
344 |
+ } |
|
345 |
+ } |
|
346 |
+ |
|
347 |
+ return list, &successResponse |
|
348 |
+} |
|
349 |
+ |
|
350 |
+/****************** |
|
351 |
+ Network interface |
|
352 |
+*******************/ |
|
353 |
+func procCreateEndpoint(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) { |
|
354 |
+ var ec endpointCreate |
|
355 |
+ |
|
356 |
+ err := json.Unmarshal(body, &ec) |
|
357 |
+ if err != nil { |
|
358 |
+ return "", &responseStatus{Status: "Invalid body: " + err.Error(), StatusCode: http.StatusBadRequest} |
|
359 |
+ } |
|
360 |
+ |
|
361 |
+ nwT, nwBy := detectNetworkTarget(vars) |
|
362 |
+ n, errRsp := findNetwork(c, nwT, nwBy) |
|
363 |
+ if !errRsp.isOK() { |
|
364 |
+ return "", errRsp |
|
365 |
+ } |
|
366 |
+ |
|
367 |
+ var setFctList []libnetwork.EndpointOption |
|
368 |
+ if ec.ExposedPorts != nil { |
|
369 |
+ setFctList = append(setFctList, libnetwork.CreateOptionExposedPorts(ec.ExposedPorts)) |
|
370 |
+ } |
|
371 |
+ if ec.PortMapping != nil { |
|
372 |
+ setFctList = append(setFctList, libnetwork.CreateOptionPortMapping(ec.PortMapping)) |
|
373 |
+ } |
|
374 |
+ |
|
375 |
+ ep, err := n.CreateEndpoint(ec.Name, setFctList...) |
|
376 |
+ if err != nil { |
|
377 |
+ return "", convertNetworkError(err) |
|
378 |
+ } |
|
379 |
+ |
|
380 |
+ return ep.ID(), &createdResponse |
|
381 |
+} |
|
382 |
+ |
|
383 |
+func procGetEndpoint(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) { |
|
384 |
+ nwT, nwBy := detectNetworkTarget(vars) |
|
385 |
+ epT, epBy := detectEndpointTarget(vars) |
|
386 |
+ |
|
387 |
+ ep, errRsp := findEndpoint(c, nwT, epT, nwBy, epBy) |
|
388 |
+ if !errRsp.isOK() { |
|
389 |
+ return nil, errRsp |
|
390 |
+ } |
|
391 |
+ |
|
392 |
+ return buildEndpointResource(ep), &successResponse |
|
393 |
+} |
|
394 |
+ |
|
395 |
+func procGetEndpoints(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) { |
|
396 |
+ // Look for query filters and validate |
|
397 |
+ name, queryByName := vars[urlEpName] |
|
398 |
+ shortID, queryByPid := vars[urlEpPID] |
|
399 |
+ if queryByName && queryByPid { |
|
400 |
+ return nil, &badQueryResponse |
|
401 |
+ } |
|
402 |
+ |
|
403 |
+ nwT, nwBy := detectNetworkTarget(vars) |
|
404 |
+ nw, errRsp := findNetwork(c, nwT, nwBy) |
|
405 |
+ if !errRsp.isOK() { |
|
406 |
+ return nil, errRsp |
|
407 |
+ } |
|
408 |
+ |
|
409 |
+ var list []*endpointResource |
|
410 |
+ |
|
411 |
+ // If query parameter is specified, return a filtered collection |
|
412 |
+ if queryByName { |
|
413 |
+ if ep, errRsp := findEndpoint(c, nwT, name, nwBy, byName); errRsp.isOK() { |
|
414 |
+ list = append(list, buildEndpointResource(ep)) |
|
415 |
+ } |
|
416 |
+ } else if queryByPid { |
|
417 |
+ // Return all the prefix-matching endpoints |
|
418 |
+ l := func(ep libnetwork.Endpoint) bool { |
|
419 |
+ if strings.HasPrefix(ep.ID(), shortID) { |
|
420 |
+ list = append(list, buildEndpointResource(ep)) |
|
421 |
+ } |
|
422 |
+ return false |
|
423 |
+ } |
|
424 |
+ nw.WalkEndpoints(l) |
|
425 |
+ } else { |
|
426 |
+ for _, ep := range nw.Endpoints() { |
|
427 |
+ epr := buildEndpointResource(ep) |
|
428 |
+ list = append(list, epr) |
|
429 |
+ } |
|
430 |
+ } |
|
431 |
+ |
|
432 |
+ return list, &successResponse |
|
433 |
+} |
|
434 |
+ |
|
435 |
+func procDeleteNetwork(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) { |
|
436 |
+ target, by := detectNetworkTarget(vars) |
|
437 |
+ |
|
438 |
+ nw, errRsp := findNetwork(c, target, by) |
|
439 |
+ if !errRsp.isOK() { |
|
440 |
+ return nil, errRsp |
|
441 |
+ } |
|
442 |
+ |
|
443 |
+ err := nw.Delete() |
|
444 |
+ if err != nil { |
|
445 |
+ return nil, convertNetworkError(err) |
|
446 |
+ } |
|
447 |
+ |
|
448 |
+ return nil, &successResponse |
|
449 |
+} |
|
450 |
+ |
|
451 |
+/****************** |
|
452 |
+ Endpoint interface |
|
453 |
+*******************/ |
|
454 |
+func procJoinEndpoint(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) { |
|
455 |
+ var ej endpointJoin |
|
456 |
+ err := json.Unmarshal(body, &ej) |
|
457 |
+ if err != nil { |
|
458 |
+ return nil, &responseStatus{Status: "Invalid body: " + err.Error(), StatusCode: http.StatusBadRequest} |
|
459 |
+ } |
|
460 |
+ |
|
461 |
+ nwT, nwBy := detectNetworkTarget(vars) |
|
462 |
+ epT, epBy := detectEndpointTarget(vars) |
|
463 |
+ |
|
464 |
+ ep, errRsp := findEndpoint(c, nwT, epT, nwBy, epBy) |
|
465 |
+ if !errRsp.isOK() { |
|
466 |
+ return nil, errRsp |
|
467 |
+ } |
|
468 |
+ |
|
469 |
+ err = ep.Join(ej.ContainerID, ej.parseOptions()...) |
|
470 |
+ if err != nil { |
|
471 |
+ return nil, convertNetworkError(err) |
|
472 |
+ } |
|
473 |
+ return ep.Info().SandboxKey(), &successResponse |
|
474 |
+} |
|
475 |
+ |
|
476 |
+func procLeaveEndpoint(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) { |
|
477 |
+ nwT, nwBy := detectNetworkTarget(vars) |
|
478 |
+ epT, epBy := detectEndpointTarget(vars) |
|
479 |
+ |
|
480 |
+ ep, errRsp := findEndpoint(c, nwT, epT, nwBy, epBy) |
|
481 |
+ if !errRsp.isOK() { |
|
482 |
+ return nil, errRsp |
|
483 |
+ } |
|
484 |
+ |
|
485 |
+ err := ep.Leave(vars[urlCnID]) |
|
486 |
+ if err != nil { |
|
487 |
+ return nil, convertNetworkError(err) |
|
488 |
+ } |
|
489 |
+ |
|
490 |
+ return nil, &successResponse |
|
491 |
+} |
|
492 |
+ |
|
493 |
+func procDeleteEndpoint(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) { |
|
494 |
+ nwT, nwBy := detectNetworkTarget(vars) |
|
495 |
+ epT, epBy := detectEndpointTarget(vars) |
|
496 |
+ |
|
497 |
+ ep, errRsp := findEndpoint(c, nwT, epT, nwBy, epBy) |
|
498 |
+ if !errRsp.isOK() { |
|
499 |
+ return nil, errRsp |
|
500 |
+ } |
|
501 |
+ |
|
502 |
+ err := ep.Delete() |
|
503 |
+ if err != nil { |
|
504 |
+ return nil, convertNetworkError(err) |
|
505 |
+ } |
|
506 |
+ |
|
507 |
+ return nil, &successResponse |
|
508 |
+} |
|
509 |
+ |
|
510 |
+/****************** |
|
511 |
+ Service interface |
|
512 |
+*******************/ |
|
513 |
+func procGetServices(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) { |
|
514 |
+ // Look for query filters and validate |
|
515 |
+ nwName, filterByNwName := vars[urlNwName] |
|
516 |
+ svName, queryBySvName := vars[urlEpName] |
|
517 |
+ shortID, queryBySvPID := vars[urlEpPID] |
|
518 |
+ |
|
519 |
+ if filterByNwName && queryBySvName || filterByNwName && queryBySvPID || queryBySvName && queryBySvPID { |
|
520 |
+ return nil, &badQueryResponse |
|
521 |
+ } |
|
522 |
+ |
|
523 |
+ var list []*endpointResource |
|
524 |
+ |
|
525 |
+ switch { |
|
526 |
+ case filterByNwName: |
|
527 |
+ // return all service present on the specified network |
|
528 |
+ nw, errRsp := findNetwork(c, nwName, byName) |
|
529 |
+ if !errRsp.isOK() { |
|
530 |
+ return list, &successResponse |
|
531 |
+ } |
|
532 |
+ for _, ep := range nw.Endpoints() { |
|
533 |
+ epr := buildEndpointResource(ep) |
|
534 |
+ list = append(list, epr) |
|
535 |
+ } |
|
536 |
+ case queryBySvName: |
|
537 |
+ // Look in each network for the service with the specified name |
|
538 |
+ l := func(ep libnetwork.Endpoint) bool { |
|
539 |
+ if ep.Name() == svName { |
|
540 |
+ list = append(list, buildEndpointResource(ep)) |
|
541 |
+ return true |
|
542 |
+ } |
|
543 |
+ return false |
|
544 |
+ } |
|
545 |
+ for _, nw := range c.Networks() { |
|
546 |
+ nw.WalkEndpoints(l) |
|
547 |
+ } |
|
548 |
+ case queryBySvPID: |
|
549 |
+ // Return all the prefix-matching services |
|
550 |
+ l := func(ep libnetwork.Endpoint) bool { |
|
551 |
+ if strings.HasPrefix(ep.ID(), shortID) { |
|
552 |
+ list = append(list, buildEndpointResource(ep)) |
|
553 |
+ } |
|
554 |
+ return false |
|
555 |
+ } |
|
556 |
+ for _, nw := range c.Networks() { |
|
557 |
+ nw.WalkEndpoints(l) |
|
558 |
+ } |
|
559 |
+ default: |
|
560 |
+ for _, nw := range c.Networks() { |
|
561 |
+ for _, ep := range nw.Endpoints() { |
|
562 |
+ epr := buildEndpointResource(ep) |
|
563 |
+ list = append(list, epr) |
|
564 |
+ } |
|
565 |
+ } |
|
566 |
+ } |
|
567 |
+ |
|
568 |
+ return list, &successResponse |
|
569 |
+} |
|
570 |
+ |
|
571 |
+func procGetService(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) { |
|
572 |
+ epT, epBy := detectEndpointTarget(vars) |
|
573 |
+ sv, errRsp := findService(c, epT, epBy) |
|
574 |
+ if !errRsp.isOK() { |
|
575 |
+ return nil, endpointToService(errRsp) |
|
576 |
+ } |
|
577 |
+ return buildEndpointResource(sv), &successResponse |
|
578 |
+} |
|
579 |
+ |
|
580 |
+func procGetContainers(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) { |
|
581 |
+ epT, epBy := detectEndpointTarget(vars) |
|
582 |
+ sv, errRsp := findService(c, epT, epBy) |
|
583 |
+ if !errRsp.isOK() { |
|
584 |
+ return nil, endpointToService(errRsp) |
|
585 |
+ } |
|
586 |
+ var list []*containerResource |
|
587 |
+ if sv.ContainerInfo() != nil { |
|
588 |
+ list = append(list, buildContainerResource(sv.ContainerInfo())) |
|
589 |
+ } |
|
590 |
+ return list, &successResponse |
|
591 |
+} |
|
592 |
+ |
|
593 |
+func procPublishService(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) { |
|
594 |
+ var sp servicePublish |
|
595 |
+ |
|
596 |
+ err := json.Unmarshal(body, &sp) |
|
597 |
+ if err != nil { |
|
598 |
+ return "", &responseStatus{Status: "Invalid body: " + err.Error(), StatusCode: http.StatusBadRequest} |
|
599 |
+ } |
|
600 |
+ |
|
601 |
+ n, errRsp := findNetwork(c, sp.Network, byName) |
|
602 |
+ if !errRsp.isOK() { |
|
603 |
+ return "", errRsp |
|
604 |
+ } |
|
605 |
+ |
|
606 |
+ var setFctList []libnetwork.EndpointOption |
|
607 |
+ if sp.ExposedPorts != nil { |
|
608 |
+ setFctList = append(setFctList, libnetwork.CreateOptionExposedPorts(sp.ExposedPorts)) |
|
609 |
+ } |
|
610 |
+ if sp.PortMapping != nil { |
|
611 |
+ setFctList = append(setFctList, libnetwork.CreateOptionPortMapping(sp.PortMapping)) |
|
612 |
+ } |
|
613 |
+ |
|
614 |
+ ep, err := n.CreateEndpoint(sp.Name, setFctList...) |
|
615 |
+ if err != nil { |
|
616 |
+ return "", endpointToService(convertNetworkError(err)) |
|
617 |
+ } |
|
618 |
+ |
|
619 |
+ return ep.ID(), &createdResponse |
|
620 |
+} |
|
621 |
+ |
|
622 |
+func procUnpublishService(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) { |
|
623 |
+ epT, epBy := detectEndpointTarget(vars) |
|
624 |
+ sv, errRsp := findService(c, epT, epBy) |
|
625 |
+ if !errRsp.isOK() { |
|
626 |
+ return nil, errRsp |
|
627 |
+ } |
|
628 |
+ err := sv.Delete() |
|
629 |
+ if err != nil { |
|
630 |
+ return nil, endpointToService(convertNetworkError(err)) |
|
631 |
+ } |
|
632 |
+ return nil, &successResponse |
|
633 |
+} |
|
634 |
+ |
|
635 |
+func procAttachBackend(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) { |
|
636 |
+ var bk endpointJoin |
|
637 |
+ err := json.Unmarshal(body, &bk) |
|
638 |
+ if err != nil { |
|
639 |
+ return nil, &responseStatus{Status: "Invalid body: " + err.Error(), StatusCode: http.StatusBadRequest} |
|
640 |
+ } |
|
641 |
+ |
|
642 |
+ epT, epBy := detectEndpointTarget(vars) |
|
643 |
+ sv, errRsp := findService(c, epT, epBy) |
|
644 |
+ if !errRsp.isOK() { |
|
645 |
+ return nil, errRsp |
|
646 |
+ } |
|
647 |
+ |
|
648 |
+ err = sv.Join(bk.ContainerID, bk.parseOptions()...) |
|
649 |
+ if err != nil { |
|
650 |
+ return nil, convertNetworkError(err) |
|
651 |
+ } |
|
652 |
+ return sv.Info().SandboxKey(), &successResponse |
|
653 |
+} |
|
654 |
+ |
|
655 |
+func procDetachBackend(c libnetwork.NetworkController, vars map[string]string, body []byte) (interface{}, *responseStatus) { |
|
656 |
+ epT, epBy := detectEndpointTarget(vars) |
|
657 |
+ sv, errRsp := findService(c, epT, epBy) |
|
658 |
+ if !errRsp.isOK() { |
|
659 |
+ return nil, errRsp |
|
660 |
+ } |
|
661 |
+ |
|
662 |
+ err := sv.Leave(vars[urlCnID]) |
|
663 |
+ if err != nil { |
|
664 |
+ return nil, convertNetworkError(err) |
|
665 |
+ } |
|
666 |
+ |
|
667 |
+ return nil, &successResponse |
|
668 |
+} |
|
669 |
+ |
|
670 |
+/*********** |
|
671 |
+ Utilities |
|
672 |
+************/ |
|
673 |
+const ( |
|
674 |
+ byID = iota |
|
675 |
+ byName |
|
676 |
+) |
|
677 |
+ |
|
678 |
+func detectNetworkTarget(vars map[string]string) (string, int) { |
|
679 |
+ if target, ok := vars[urlNwName]; ok { |
|
680 |
+ return target, byName |
|
681 |
+ } |
|
682 |
+ if target, ok := vars[urlNwID]; ok { |
|
683 |
+ return target, byID |
|
684 |
+ } |
|
685 |
+ // vars are populated from the URL, following cannot happen |
|
686 |
+ panic("Missing URL variable parameter for network") |
|
687 |
+} |
|
688 |
+ |
|
689 |
+func detectEndpointTarget(vars map[string]string) (string, int) { |
|
690 |
+ if target, ok := vars[urlEpName]; ok { |
|
691 |
+ return target, byName |
|
692 |
+ } |
|
693 |
+ if target, ok := vars[urlEpID]; ok { |
|
694 |
+ return target, byID |
|
695 |
+ } |
|
696 |
+ // vars are populated from the URL, following cannot happen |
|
697 |
+ panic("Missing URL variable parameter for endpoint") |
|
698 |
+} |
|
699 |
+ |
|
700 |
+func findNetwork(c libnetwork.NetworkController, s string, by int) (libnetwork.Network, *responseStatus) { |
|
701 |
+ var ( |
|
702 |
+ nw libnetwork.Network |
|
703 |
+ err error |
|
704 |
+ ) |
|
705 |
+ switch by { |
|
706 |
+ case byID: |
|
707 |
+ nw, err = c.NetworkByID(s) |
|
708 |
+ case byName: |
|
709 |
+ if s == "" { |
|
710 |
+ s = c.Config().Daemon.DefaultNetwork |
|
711 |
+ } |
|
712 |
+ nw, err = c.NetworkByName(s) |
|
713 |
+ default: |
|
714 |
+ panic(fmt.Sprintf("unexpected selector for network search: %d", by)) |
|
715 |
+ } |
|
716 |
+ if err != nil { |
|
717 |
+ if _, ok := err.(types.NotFoundError); ok { |
|
718 |
+ return nil, &responseStatus{Status: "Resource not found: Network", StatusCode: http.StatusNotFound} |
|
719 |
+ } |
|
720 |
+ return nil, &responseStatus{Status: err.Error(), StatusCode: http.StatusBadRequest} |
|
721 |
+ } |
|
722 |
+ return nw, &successResponse |
|
723 |
+} |
|
724 |
+ |
|
725 |
+func findEndpoint(c libnetwork.NetworkController, ns, es string, nwBy, epBy int) (libnetwork.Endpoint, *responseStatus) { |
|
726 |
+ nw, errRsp := findNetwork(c, ns, nwBy) |
|
727 |
+ if !errRsp.isOK() { |
|
728 |
+ return nil, errRsp |
|
729 |
+ } |
|
730 |
+ var ( |
|
731 |
+ err error |
|
732 |
+ ep libnetwork.Endpoint |
|
733 |
+ ) |
|
734 |
+ switch epBy { |
|
735 |
+ case byID: |
|
736 |
+ ep, err = nw.EndpointByID(es) |
|
737 |
+ case byName: |
|
738 |
+ ep, err = nw.EndpointByName(es) |
|
739 |
+ default: |
|
740 |
+ panic(fmt.Sprintf("unexpected selector for endpoint search: %d", epBy)) |
|
741 |
+ } |
|
742 |
+ if err != nil { |
|
743 |
+ if _, ok := err.(types.NotFoundError); ok { |
|
744 |
+ return nil, &responseStatus{Status: "Resource not found: Endpoint", StatusCode: http.StatusNotFound} |
|
745 |
+ } |
|
746 |
+ return nil, &responseStatus{Status: err.Error(), StatusCode: http.StatusBadRequest} |
|
747 |
+ } |
|
748 |
+ return ep, &successResponse |
|
749 |
+} |
|
750 |
+ |
|
751 |
+func findService(c libnetwork.NetworkController, svs string, svBy int) (libnetwork.Endpoint, *responseStatus) { |
|
752 |
+ for _, nw := range c.Networks() { |
|
753 |
+ var ( |
|
754 |
+ ep libnetwork.Endpoint |
|
755 |
+ err error |
|
756 |
+ ) |
|
757 |
+ switch svBy { |
|
758 |
+ case byID: |
|
759 |
+ ep, err = nw.EndpointByID(svs) |
|
760 |
+ case byName: |
|
761 |
+ ep, err = nw.EndpointByName(svs) |
|
762 |
+ default: |
|
763 |
+ panic(fmt.Sprintf("unexpected selector for service search: %d", svBy)) |
|
764 |
+ } |
|
765 |
+ if err == nil { |
|
766 |
+ return ep, &successResponse |
|
767 |
+ } else if _, ok := err.(types.NotFoundError); !ok { |
|
768 |
+ return nil, convertNetworkError(err) |
|
769 |
+ } |
|
770 |
+ } |
|
771 |
+ return nil, &responseStatus{Status: "Service not found", StatusCode: http.StatusNotFound} |
|
772 |
+} |
|
773 |
+ |
|
774 |
+func endpointToService(rsp *responseStatus) *responseStatus { |
|
775 |
+ rsp.Status = strings.Replace(rsp.Status, "endpoint", "service", -1) |
|
776 |
+ return rsp |
|
777 |
+} |
|
778 |
+ |
|
779 |
+func convertNetworkError(err error) *responseStatus { |
|
780 |
+ var code int |
|
781 |
+ switch err.(type) { |
|
782 |
+ case types.BadRequestError: |
|
783 |
+ code = http.StatusBadRequest |
|
784 |
+ case types.ForbiddenError: |
|
785 |
+ code = http.StatusForbidden |
|
786 |
+ case types.NotFoundError: |
|
787 |
+ code = http.StatusNotFound |
|
788 |
+ case types.TimeoutError: |
|
789 |
+ code = http.StatusRequestTimeout |
|
790 |
+ case types.NotImplementedError: |
|
791 |
+ code = http.StatusNotImplemented |
|
792 |
+ case types.NoServiceError: |
|
793 |
+ code = http.StatusServiceUnavailable |
|
794 |
+ case types.InternalError: |
|
795 |
+ code = http.StatusInternalServerError |
|
796 |
+ default: |
|
797 |
+ code = http.StatusInternalServerError |
|
798 |
+ } |
|
799 |
+ return &responseStatus{Status: err.Error(), StatusCode: code} |
|
800 |
+} |
|
801 |
+ |
|
802 |
+func writeJSON(w http.ResponseWriter, code int, v interface{}) error { |
|
803 |
+ w.Header().Set("Content-Type", "application/json") |
|
804 |
+ w.WriteHeader(code) |
|
805 |
+ return json.NewEncoder(w).Encode(v) |
|
806 |
+} |
0 | 807 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,81 @@ |
0 |
+package api |
|
1 |
+ |
|
2 |
+import "github.com/docker/libnetwork/types" |
|
3 |
+ |
|
4 |
+/*********** |
|
5 |
+ Resources |
|
6 |
+************/ |
|
7 |
+ |
|
8 |
+// networkResource is the body of the "get network" http response message |
|
9 |
+type networkResource struct { |
|
10 |
+ Name string `json:"name"` |
|
11 |
+ ID string `json:"id"` |
|
12 |
+ Type string `json:"type"` |
|
13 |
+ Endpoints []*endpointResource `json:"endpoints"` |
|
14 |
+} |
|
15 |
+ |
|
16 |
+// endpointResource is the body of the "get endpoint" http response message |
|
17 |
+type endpointResource struct { |
|
18 |
+ Name string `json:"name"` |
|
19 |
+ ID string `json:"id"` |
|
20 |
+ Network string `json:"network"` |
|
21 |
+} |
|
22 |
+ |
|
23 |
+// containerResource is the body of "get service backend" response message |
|
24 |
+type containerResource struct { |
|
25 |
+ ID string `json:"id"` |
|
26 |
+ // will add more fields once labels change is in |
|
27 |
+} |
|
28 |
+ |
|
29 |
+/*********** |
|
30 |
+ Body types |
|
31 |
+ ************/ |
|
32 |
+ |
|
33 |
+// networkCreate is the expected body of the "create network" http request message |
|
34 |
+type networkCreate struct { |
|
35 |
+ Name string `json:"name"` |
|
36 |
+ NetworkType string `json:"network_type"` |
|
37 |
+ Options map[string]interface{} `json:"options"` |
|
38 |
+} |
|
39 |
+ |
|
40 |
+// endpointCreate represents the body of the "create endpoint" http request message |
|
41 |
+type endpointCreate struct { |
|
42 |
+ Name string `json:"name"` |
|
43 |
+ ExposedPorts []types.TransportPort `json:"exposed_ports"` |
|
44 |
+ PortMapping []types.PortBinding `json:"port_mapping"` |
|
45 |
+} |
|
46 |
+ |
|
47 |
+// endpointJoin represents the expected body of the "join endpoint" or "leave endpoint" http request messages |
|
48 |
+type endpointJoin struct { |
|
49 |
+ ContainerID string `json:"container_id"` |
|
50 |
+ HostName string `json:"host_name"` |
|
51 |
+ DomainName string `json:"domain_name"` |
|
52 |
+ HostsPath string `json:"hosts_path"` |
|
53 |
+ ResolvConfPath string `json:"resolv_conf_path"` |
|
54 |
+ DNS []string `json:"dns"` |
|
55 |
+ ExtraHosts []endpointExtraHost `json:"extra_hosts"` |
|
56 |
+ ParentUpdates []endpointParentUpdate `json:"parent_updates"` |
|
57 |
+ UseDefaultSandbox bool `json:"use_default_sandbox"` |
|
58 |
+} |
|
59 |
+ |
|
60 |
+// servicePublish represents the body of the "publish service" http request message |
|
61 |
+type servicePublish struct { |
|
62 |
+ Name string `json:"name"` |
|
63 |
+ Network string `json:"network_name"` |
|
64 |
+ ExposedPorts []types.TransportPort `json:"exposed_ports"` |
|
65 |
+ PortMapping []types.PortBinding `json:"port_mapping"` |
|
66 |
+} |
|
67 |
+ |
|
68 |
+// EndpointExtraHost represents the extra host object |
|
69 |
+type endpointExtraHost struct { |
|
70 |
+ Name string `json:"name"` |
|
71 |
+ Address string `json:"address"` |
|
72 |
+} |
|
73 |
+ |
|
74 |
+// EndpointParentUpdate is the object carrying the information about the |
|
75 |
+// endpoint parent that needs to be updated |
|
76 |
+type endpointParentUpdate struct { |
|
77 |
+ EndpointID string `json:"endpoint_id"` |
|
78 |
+ Name string `json:"name"` |
|
79 |
+ Address string `json:"address"` |
|
80 |
+} |
0 | 81 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,115 @@ |
0 |
+package client |
|
1 |
+ |
|
2 |
+import ( |
|
3 |
+ "fmt" |
|
4 |
+ "io" |
|
5 |
+ "io/ioutil" |
|
6 |
+ "net/http" |
|
7 |
+ "reflect" |
|
8 |
+ "strings" |
|
9 |
+ |
|
10 |
+ flag "github.com/docker/docker/pkg/mflag" |
|
11 |
+) |
|
12 |
+ |
|
13 |
+// CallFunc provides environment specific call utility to invoke backend functions from UI |
|
14 |
+type CallFunc func(string, string, interface{}, map[string][]string) (io.ReadCloser, http.Header, int, error) |
|
15 |
+ |
|
16 |
+// NetworkCli is the UI object for network subcmds |
|
17 |
+type NetworkCli struct { |
|
18 |
+ out io.Writer |
|
19 |
+ err io.Writer |
|
20 |
+ call CallFunc |
|
21 |
+} |
|
22 |
+ |
|
23 |
+// NewNetworkCli is a convenient function to create a NetworkCli object |
|
24 |
+func NewNetworkCli(out, err io.Writer, call CallFunc) *NetworkCli { |
|
25 |
+ return &NetworkCli{ |
|
26 |
+ out: out, |
|
27 |
+ err: err, |
|
28 |
+ call: call, |
|
29 |
+ } |
|
30 |
+} |
|
31 |
+ |
|
32 |
+// getMethod is Borrowed from Docker UI which uses reflection to identify the UI Handler |
|
33 |
+func (cli *NetworkCli) getMethod(args ...string) (func(string, ...string) error, bool) { |
|
34 |
+ camelArgs := make([]string, len(args)) |
|
35 |
+ for i, s := range args { |
|
36 |
+ if len(s) == 0 { |
|
37 |
+ return nil, false |
|
38 |
+ } |
|
39 |
+ camelArgs[i] = strings.ToUpper(s[:1]) + strings.ToLower(s[1:]) |
|
40 |
+ } |
|
41 |
+ methodName := "Cmd" + strings.Join(camelArgs, "") |
|
42 |
+ method := reflect.ValueOf(cli).MethodByName(methodName) |
|
43 |
+ if !method.IsValid() { |
|
44 |
+ return nil, false |
|
45 |
+ } |
|
46 |
+ return method.Interface().(func(string, ...string) error), true |
|
47 |
+} |
|
48 |
+ |
|
49 |
+// Cmd is borrowed from Docker UI and acts as the entry point for network UI commands. |
|
50 |
+// network UI commands are designed to be invoked from multiple parent chains |
|
51 |
+func (cli *NetworkCli) Cmd(chain string, args ...string) error { |
|
52 |
+ if len(args) > 2 { |
|
53 |
+ method, exists := cli.getMethod(args[:3]...) |
|
54 |
+ if exists { |
|
55 |
+ return method(chain+" "+args[0]+" "+args[1], args[3:]...) |
|
56 |
+ } |
|
57 |
+ } |
|
58 |
+ if len(args) > 1 { |
|
59 |
+ method, exists := cli.getMethod(args[:2]...) |
|
60 |
+ if exists { |
|
61 |
+ return method(chain+" "+args[0], args[2:]...) |
|
62 |
+ } |
|
63 |
+ } |
|
64 |
+ if len(args) > 0 { |
|
65 |
+ method, exists := cli.getMethod(args[0]) |
|
66 |
+ if !exists { |
|
67 |
+ return fmt.Errorf("%s: '%s' is not a %s command. See '%s --help'.\n", chain, args[0], chain, chain) |
|
68 |
+ } |
|
69 |
+ return method(chain, args[1:]...) |
|
70 |
+ } |
|
71 |
+ flag.Usage() |
|
72 |
+ return nil |
|
73 |
+} |
|
74 |
+ |
|
75 |
+// Subcmd is borrowed from Docker UI and performs the same function of configuring the subCmds |
|
76 |
+func (cli *NetworkCli) Subcmd(chain, name, signature, description string, exitOnError bool) *flag.FlagSet { |
|
77 |
+ var errorHandling flag.ErrorHandling |
|
78 |
+ if exitOnError { |
|
79 |
+ errorHandling = flag.ExitOnError |
|
80 |
+ } else { |
|
81 |
+ errorHandling = flag.ContinueOnError |
|
82 |
+ } |
|
83 |
+ flags := flag.NewFlagSet(name, errorHandling) |
|
84 |
+ flags.Usage = func() { |
|
85 |
+ flags.ShortUsage() |
|
86 |
+ flags.PrintDefaults() |
|
87 |
+ } |
|
88 |
+ flags.ShortUsage = func() { |
|
89 |
+ options := "" |
|
90 |
+ if signature != "" { |
|
91 |
+ signature = " " + signature |
|
92 |
+ } |
|
93 |
+ if flags.FlagCountUndeprecated() > 0 { |
|
94 |
+ options = " [OPTIONS]" |
|
95 |
+ } |
|
96 |
+ fmt.Fprintf(cli.out, "\nUsage: %s %s%s%s\n\n%s\n\n", chain, name, options, signature, description) |
|
97 |
+ flags.SetOutput(cli.out) |
|
98 |
+ } |
|
99 |
+ return flags |
|
100 |
+} |
|
101 |
+ |
|
102 |
+func readBody(stream io.ReadCloser, hdr http.Header, statusCode int, err error) ([]byte, int, error) { |
|
103 |
+ if stream != nil { |
|
104 |
+ defer stream.Close() |
|
105 |
+ } |
|
106 |
+ if err != nil { |
|
107 |
+ return nil, statusCode, err |
|
108 |
+ } |
|
109 |
+ body, err := ioutil.ReadAll(stream) |
|
110 |
+ if err != nil { |
|
111 |
+ return nil, -1, err |
|
112 |
+ } |
|
113 |
+ return body, statusCode, nil |
|
114 |
+} |
0 | 115 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,231 @@ |
0 |
+package client |
|
1 |
+ |
|
2 |
+import ( |
|
3 |
+ "bytes" |
|
4 |
+ "encoding/json" |
|
5 |
+ "fmt" |
|
6 |
+ "net/http" |
|
7 |
+ "text/tabwriter" |
|
8 |
+ |
|
9 |
+ flag "github.com/docker/docker/pkg/mflag" |
|
10 |
+ "github.com/docker/docker/pkg/stringid" |
|
11 |
+) |
|
12 |
+ |
|
13 |
+type command struct { |
|
14 |
+ name string |
|
15 |
+ description string |
|
16 |
+} |
|
17 |
+ |
|
18 |
+var ( |
|
19 |
+ networkCommands = []command{ |
|
20 |
+ {"create", "Create a network"}, |
|
21 |
+ {"rm", "Remove a network"}, |
|
22 |
+ {"ls", "List all networks"}, |
|
23 |
+ {"info", "Display information of a network"}, |
|
24 |
+ } |
|
25 |
+) |
|
26 |
+ |
|
27 |
+// CmdNetwork handles the root Network UI |
|
28 |
+func (cli *NetworkCli) CmdNetwork(chain string, args ...string) error { |
|
29 |
+ cmd := cli.Subcmd(chain, "network", "COMMAND [OPTIONS] [arg...]", networkUsage(chain), false) |
|
30 |
+ cmd.Require(flag.Min, 1) |
|
31 |
+ err := cmd.ParseFlags(args, true) |
|
32 |
+ if err == nil { |
|
33 |
+ cmd.Usage() |
|
34 |
+ return fmt.Errorf("invalid command : %v", args) |
|
35 |
+ } |
|
36 |
+ return err |
|
37 |
+} |
|
38 |
+ |
|
39 |
+// CmdNetworkCreate handles Network Create UI |
|
40 |
+func (cli *NetworkCli) CmdNetworkCreate(chain string, args ...string) error { |
|
41 |
+ cmd := cli.Subcmd(chain, "create", "NETWORK-NAME", "Creates a new network with a name specified by the user", false) |
|
42 |
+ flDriver := cmd.String([]string{"d", "-driver"}, "", "Driver to manage the Network") |
|
43 |
+ cmd.Require(flag.Exact, 1) |
|
44 |
+ err := cmd.ParseFlags(args, true) |
|
45 |
+ if err != nil { |
|
46 |
+ return err |
|
47 |
+ } |
|
48 |
+ |
|
49 |
+ // Construct network create request body |
|
50 |
+ ops := make(map[string]interface{}) |
|
51 |
+ nc := networkCreate{Name: cmd.Arg(0), NetworkType: *flDriver, Options: ops} |
|
52 |
+ obj, _, err := readBody(cli.call("POST", "/networks", nc, nil)) |
|
53 |
+ if err != nil { |
|
54 |
+ return err |
|
55 |
+ } |
|
56 |
+ var replyID string |
|
57 |
+ err = json.Unmarshal(obj, &replyID) |
|
58 |
+ if err != nil { |
|
59 |
+ return err |
|
60 |
+ } |
|
61 |
+ fmt.Fprintf(cli.out, "%s\n", replyID) |
|
62 |
+ return nil |
|
63 |
+} |
|
64 |
+ |
|
65 |
+// CmdNetworkRm handles Network Delete UI |
|
66 |
+func (cli *NetworkCli) CmdNetworkRm(chain string, args ...string) error { |
|
67 |
+ cmd := cli.Subcmd(chain, "rm", "NETWORK", "Deletes a network", false) |
|
68 |
+ cmd.Require(flag.Exact, 1) |
|
69 |
+ err := cmd.ParseFlags(args, true) |
|
70 |
+ if err != nil { |
|
71 |
+ return err |
|
72 |
+ } |
|
73 |
+ id, err := lookupNetworkID(cli, cmd.Arg(0)) |
|
74 |
+ if err != nil { |
|
75 |
+ return err |
|
76 |
+ } |
|
77 |
+ _, _, err = readBody(cli.call("DELETE", "/networks/"+id, nil, nil)) |
|
78 |
+ if err != nil { |
|
79 |
+ return err |
|
80 |
+ } |
|
81 |
+ return nil |
|
82 |
+} |
|
83 |
+ |
|
84 |
+// CmdNetworkLs handles Network List UI |
|
85 |
+func (cli *NetworkCli) CmdNetworkLs(chain string, args ...string) error { |
|
86 |
+ cmd := cli.Subcmd(chain, "ls", "", "Lists all the networks created by the user", false) |
|
87 |
+ quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only display numeric IDs") |
|
88 |
+ noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Do not truncate the output") |
|
89 |
+ nLatest := cmd.Bool([]string{"l", "-latest"}, false, "Show the latest network created") |
|
90 |
+ last := cmd.Int([]string{"n"}, -1, "Show n last created networks") |
|
91 |
+ err := cmd.ParseFlags(args, true) |
|
92 |
+ if err != nil { |
|
93 |
+ return err |
|
94 |
+ } |
|
95 |
+ obj, _, err := readBody(cli.call("GET", "/networks", nil, nil)) |
|
96 |
+ if err != nil { |
|
97 |
+ return err |
|
98 |
+ } |
|
99 |
+ if *last == -1 && *nLatest { |
|
100 |
+ *last = 1 |
|
101 |
+ } |
|
102 |
+ |
|
103 |
+ var networkResources []networkResource |
|
104 |
+ err = json.Unmarshal(obj, &networkResources) |
|
105 |
+ if err != nil { |
|
106 |
+ return err |
|
107 |
+ } |
|
108 |
+ |
|
109 |
+ wr := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) |
|
110 |
+ |
|
111 |
+ // unless quiet (-q) is specified, print field titles |
|
112 |
+ if !*quiet { |
|
113 |
+ fmt.Fprintln(wr, "NETWORK ID\tNAME\tTYPE") |
|
114 |
+ } |
|
115 |
+ |
|
116 |
+ for _, networkResource := range networkResources { |
|
117 |
+ ID := networkResource.ID |
|
118 |
+ netName := networkResource.Name |
|
119 |
+ if !*noTrunc { |
|
120 |
+ ID = stringid.TruncateID(ID) |
|
121 |
+ } |
|
122 |
+ if *quiet { |
|
123 |
+ fmt.Fprintln(wr, ID) |
|
124 |
+ continue |
|
125 |
+ } |
|
126 |
+ netType := networkResource.Type |
|
127 |
+ fmt.Fprintf(wr, "%s\t%s\t%s\t", |
|
128 |
+ ID, |
|
129 |
+ netName, |
|
130 |
+ netType) |
|
131 |
+ fmt.Fprint(wr, "\n") |
|
132 |
+ } |
|
133 |
+ wr.Flush() |
|
134 |
+ return nil |
|
135 |
+} |
|
136 |
+ |
|
137 |
+// CmdNetworkInfo handles Network Info UI |
|
138 |
+func (cli *NetworkCli) CmdNetworkInfo(chain string, args ...string) error { |
|
139 |
+ cmd := cli.Subcmd(chain, "info", "NETWORK", "Displays detailed information on a network", false) |
|
140 |
+ cmd.Require(flag.Exact, 1) |
|
141 |
+ err := cmd.ParseFlags(args, true) |
|
142 |
+ if err != nil { |
|
143 |
+ return err |
|
144 |
+ } |
|
145 |
+ |
|
146 |
+ id, err := lookupNetworkID(cli, cmd.Arg(0)) |
|
147 |
+ if err != nil { |
|
148 |
+ return err |
|
149 |
+ } |
|
150 |
+ |
|
151 |
+ obj, _, err := readBody(cli.call("GET", "/networks/"+id, nil, nil)) |
|
152 |
+ if err != nil { |
|
153 |
+ return err |
|
154 |
+ } |
|
155 |
+ networkResource := &networkResource{} |
|
156 |
+ if err := json.NewDecoder(bytes.NewReader(obj)).Decode(networkResource); err != nil { |
|
157 |
+ return err |
|
158 |
+ } |
|
159 |
+ fmt.Fprintf(cli.out, "Network Id: %s\n", networkResource.ID) |
|
160 |
+ fmt.Fprintf(cli.out, "Name: %s\n", networkResource.Name) |
|
161 |
+ fmt.Fprintf(cli.out, "Type: %s\n", networkResource.Type) |
|
162 |
+ if networkResource.Services != nil { |
|
163 |
+ for _, serviceResource := range networkResource.Services { |
|
164 |
+ fmt.Fprintf(cli.out, " Service Id: %s\n", serviceResource.ID) |
|
165 |
+ fmt.Fprintf(cli.out, "\tName: %s\n", serviceResource.Name) |
|
166 |
+ } |
|
167 |
+ } |
|
168 |
+ |
|
169 |
+ return nil |
|
170 |
+} |
|
171 |
+ |
|
172 |
+// Helper function to predict if a string is a name or id or partial-id |
|
173 |
+// This provides a best-effort mechanism to identify a id with the help of GET Filter APIs |
|
174 |
+// Being a UI, its most likely that name will be used by the user, which is used to lookup |
|
175 |
+// the corresponding ID. If ID is not found, this function will assume that the passed string |
|
176 |
+// is an ID by itself. |
|
177 |
+ |
|
178 |
+func lookupNetworkID(cli *NetworkCli, nameID string) (string, error) { |
|
179 |
+ obj, statusCode, err := readBody(cli.call("GET", "/networks?name="+nameID, nil, nil)) |
|
180 |
+ if err != nil { |
|
181 |
+ return "", err |
|
182 |
+ } |
|
183 |
+ |
|
184 |
+ if statusCode != http.StatusOK { |
|
185 |
+ return "", fmt.Errorf("name query failed for %s due to : statuscode(%d) %v", nameID, statusCode, string(obj)) |
|
186 |
+ } |
|
187 |
+ |
|
188 |
+ var list []*networkResource |
|
189 |
+ err = json.Unmarshal(obj, &list) |
|
190 |
+ if err != nil { |
|
191 |
+ return "", err |
|
192 |
+ } |
|
193 |
+ if len(list) > 0 { |
|
194 |
+ // name query filter will always return a single-element collection |
|
195 |
+ return list[0].ID, nil |
|
196 |
+ } |
|
197 |
+ |
|
198 |
+ // Check for Partial-id |
|
199 |
+ obj, statusCode, err = readBody(cli.call("GET", "/networks?partial-id="+nameID, nil, nil)) |
|
200 |
+ if err != nil { |
|
201 |
+ return "", err |
|
202 |
+ } |
|
203 |
+ |
|
204 |
+ if statusCode != http.StatusOK { |
|
205 |
+ return "", fmt.Errorf("partial-id match query failed for %s due to : statuscode(%d) %v", nameID, statusCode, string(obj)) |
|
206 |
+ } |
|
207 |
+ |
|
208 |
+ err = json.Unmarshal(obj, &list) |
|
209 |
+ if err != nil { |
|
210 |
+ return "", err |
|
211 |
+ } |
|
212 |
+ if len(list) == 0 { |
|
213 |
+ return "", fmt.Errorf("resource not found %s", nameID) |
|
214 |
+ } |
|
215 |
+ if len(list) > 1 { |
|
216 |
+ return "", fmt.Errorf("multiple Networks matching the partial identifier (%s). Please use full identifier", nameID) |
|
217 |
+ } |
|
218 |
+ return list[0].ID, nil |
|
219 |
+} |
|
220 |
+ |
|
221 |
+func networkUsage(chain string) string { |
|
222 |
+ help := "Commands:\n" |
|
223 |
+ |
|
224 |
+ for _, cmd := range networkCommands { |
|
225 |
+ help += fmt.Sprintf(" %-25.25s%s\n", cmd.name, cmd.description) |
|
226 |
+ } |
|
227 |
+ |
|
228 |
+ help += fmt.Sprintf("\nRun '%s network COMMAND --help' for more information on a command.", chain) |
|
229 |
+ return help |
|
230 |
+} |
0 | 231 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,362 @@ |
0 |
+package client |
|
1 |
+ |
|
2 |
+import ( |
|
3 |
+ "bytes" |
|
4 |
+ "encoding/json" |
|
5 |
+ "fmt" |
|
6 |
+ "net/http" |
|
7 |
+ "strings" |
|
8 |
+ "text/tabwriter" |
|
9 |
+ |
|
10 |
+ flag "github.com/docker/docker/pkg/mflag" |
|
11 |
+ "github.com/docker/docker/pkg/stringid" |
|
12 |
+) |
|
13 |
+ |
|
14 |
+var ( |
|
15 |
+ serviceCommands = []command{ |
|
16 |
+ {"publish", "Publish a service"}, |
|
17 |
+ {"unpublish", "Remove a service"}, |
|
18 |
+ {"attach", "Attach a backend (container) to the service"}, |
|
19 |
+ {"detach", "Detach the backend from the service"}, |
|
20 |
+ {"ls", "Lists all services"}, |
|
21 |
+ {"info", "Display information about a service"}, |
|
22 |
+ } |
|
23 |
+) |
|
24 |
+ |
|
25 |
+func lookupServiceID(cli *NetworkCli, nwName, svNameID string) (string, error) { |
|
26 |
+ // Sanity Check |
|
27 |
+ obj, _, err := readBody(cli.call("GET", fmt.Sprintf("/networks?name=%s", nwName), nil, nil)) |
|
28 |
+ if err != nil { |
|
29 |
+ return "", err |
|
30 |
+ } |
|
31 |
+ var nwList []networkResource |
|
32 |
+ if err = json.Unmarshal(obj, &nwList); err != nil { |
|
33 |
+ return "", err |
|
34 |
+ } |
|
35 |
+ if len(nwList) == 0 { |
|
36 |
+ return "", fmt.Errorf("Network %s does not exist", nwName) |
|
37 |
+ } |
|
38 |
+ |
|
39 |
+ if nwName == "" { |
|
40 |
+ obj, _, err := readBody(cli.call("GET", "/networks/"+nwList[0].ID, nil, nil)) |
|
41 |
+ if err != nil { |
|
42 |
+ return "", err |
|
43 |
+ } |
|
44 |
+ networkResource := &networkResource{} |
|
45 |
+ if err := json.NewDecoder(bytes.NewReader(obj)).Decode(networkResource); err != nil { |
|
46 |
+ return "", err |
|
47 |
+ } |
|
48 |
+ nwName = networkResource.Name |
|
49 |
+ } |
|
50 |
+ |
|
51 |
+ // Query service by name |
|
52 |
+ obj, statusCode, err := readBody(cli.call("GET", fmt.Sprintf("/services?name=%s", svNameID), nil, nil)) |
|
53 |
+ if err != nil { |
|
54 |
+ return "", err |
|
55 |
+ } |
|
56 |
+ |
|
57 |
+ if statusCode != http.StatusOK { |
|
58 |
+ return "", fmt.Errorf("name query failed for %s due to: (%d) %s", svNameID, statusCode, string(obj)) |
|
59 |
+ } |
|
60 |
+ |
|
61 |
+ var list []*serviceResource |
|
62 |
+ if err = json.Unmarshal(obj, &list); err != nil { |
|
63 |
+ return "", err |
|
64 |
+ } |
|
65 |
+ for _, sr := range list { |
|
66 |
+ if sr.Network == nwName { |
|
67 |
+ return sr.ID, nil |
|
68 |
+ } |
|
69 |
+ } |
|
70 |
+ |
|
71 |
+ // Query service by Partial-id (this covers full id as well) |
|
72 |
+ obj, statusCode, err = readBody(cli.call("GET", fmt.Sprintf("/services?partial-id=%s", svNameID), nil, nil)) |
|
73 |
+ if err != nil { |
|
74 |
+ return "", err |
|
75 |
+ } |
|
76 |
+ |
|
77 |
+ if statusCode != http.StatusOK { |
|
78 |
+ return "", fmt.Errorf("partial-id match query failed for %s due to: (%d) %s", svNameID, statusCode, string(obj)) |
|
79 |
+ } |
|
80 |
+ |
|
81 |
+ if err = json.Unmarshal(obj, &list); err != nil { |
|
82 |
+ return "", err |
|
83 |
+ } |
|
84 |
+ for _, sr := range list { |
|
85 |
+ if sr.Network == nwName { |
|
86 |
+ return sr.ID, nil |
|
87 |
+ } |
|
88 |
+ } |
|
89 |
+ |
|
90 |
+ return "", fmt.Errorf("Service %s not found on network %s", svNameID, nwName) |
|
91 |
+} |
|
92 |
+ |
|
93 |
+func lookupContainerID(cli *NetworkCli, cnNameID string) (string, error) { |
|
94 |
+ // Container is a Docker resource, ask docker about it. |
|
95 |
+ // In case of connecton error, we assume we are running in dnet and return whatever was passed to us |
|
96 |
+ obj, _, err := readBody(cli.call("GET", fmt.Sprintf("/containers/%s/json", cnNameID), nil, nil)) |
|
97 |
+ if err != nil { |
|
98 |
+ // We are probably running outside of docker |
|
99 |
+ return cnNameID, nil |
|
100 |
+ } |
|
101 |
+ |
|
102 |
+ var x map[string]interface{} |
|
103 |
+ err = json.Unmarshal(obj, &x) |
|
104 |
+ if err != nil { |
|
105 |
+ return "", err |
|
106 |
+ } |
|
107 |
+ if iid, ok := x["Id"]; ok { |
|
108 |
+ if id, ok := iid.(string); ok { |
|
109 |
+ return id, nil |
|
110 |
+ } |
|
111 |
+ return "", fmt.Errorf("Unexpected data type for container ID in json response") |
|
112 |
+ } |
|
113 |
+ return "", fmt.Errorf("Cannot find container ID in json response") |
|
114 |
+} |
|
115 |
+ |
|
116 |
+// CmdService handles the service UI |
|
117 |
+func (cli *NetworkCli) CmdService(chain string, args ...string) error { |
|
118 |
+ cmd := cli.Subcmd(chain, "service", "COMMAND [OPTIONS] [arg...]", serviceUsage(chain), false) |
|
119 |
+ cmd.Require(flag.Min, 1) |
|
120 |
+ err := cmd.ParseFlags(args, true) |
|
121 |
+ if err == nil { |
|
122 |
+ cmd.Usage() |
|
123 |
+ return fmt.Errorf("Invalid command : %v", args) |
|
124 |
+ } |
|
125 |
+ return err |
|
126 |
+} |
|
127 |
+ |
|
128 |
+// Parse service name for "SERVICE[.NETWORK]" format |
|
129 |
+func parseServiceName(name string) (string, string) { |
|
130 |
+ s := strings.Split(name, ".") |
|
131 |
+ var sName, nName string |
|
132 |
+ if len(s) > 1 { |
|
133 |
+ nName = s[len(s)-1] |
|
134 |
+ sName = strings.Join(s[:len(s)-1], ".") |
|
135 |
+ } else { |
|
136 |
+ sName = s[0] |
|
137 |
+ } |
|
138 |
+ return sName, nName |
|
139 |
+} |
|
140 |
+ |
|
141 |
+// CmdServicePublish handles service create UI |
|
142 |
+func (cli *NetworkCli) CmdServicePublish(chain string, args ...string) error { |
|
143 |
+ cmd := cli.Subcmd(chain, "publish", "SERVICE[.NETWORK]", "Publish a new service on a network", false) |
|
144 |
+ cmd.Require(flag.Exact, 1) |
|
145 |
+ err := cmd.ParseFlags(args, true) |
|
146 |
+ if err != nil { |
|
147 |
+ return err |
|
148 |
+ } |
|
149 |
+ |
|
150 |
+ sn, nn := parseServiceName(cmd.Arg(0)) |
|
151 |
+ sc := serviceCreate{Name: sn, Network: nn} |
|
152 |
+ obj, _, err := readBody(cli.call("POST", "/services", sc, nil)) |
|
153 |
+ if err != nil { |
|
154 |
+ return err |
|
155 |
+ } |
|
156 |
+ |
|
157 |
+ var replyID string |
|
158 |
+ err = json.Unmarshal(obj, &replyID) |
|
159 |
+ if err != nil { |
|
160 |
+ return err |
|
161 |
+ } |
|
162 |
+ |
|
163 |
+ fmt.Fprintf(cli.out, "%s\n", replyID) |
|
164 |
+ return nil |
|
165 |
+} |
|
166 |
+ |
|
167 |
+// CmdServiceUnpublish handles service delete UI |
|
168 |
+func (cli *NetworkCli) CmdServiceUnpublish(chain string, args ...string) error { |
|
169 |
+ cmd := cli.Subcmd(chain, "unpublish", "SERVICE[.NETWORK]", "Removes a service", false) |
|
170 |
+ cmd.Require(flag.Exact, 1) |
|
171 |
+ err := cmd.ParseFlags(args, true) |
|
172 |
+ if err != nil { |
|
173 |
+ return err |
|
174 |
+ } |
|
175 |
+ |
|
176 |
+ sn, nn := parseServiceName(cmd.Arg(0)) |
|
177 |
+ serviceID, err := lookupServiceID(cli, nn, sn) |
|
178 |
+ if err != nil { |
|
179 |
+ return err |
|
180 |
+ } |
|
181 |
+ |
|
182 |
+ _, _, err = readBody(cli.call("DELETE", "/services/"+serviceID, nil, nil)) |
|
183 |
+ |
|
184 |
+ return err |
|
185 |
+} |
|
186 |
+ |
|
187 |
+// CmdServiceLs handles service list UI |
|
188 |
+func (cli *NetworkCli) CmdServiceLs(chain string, args ...string) error { |
|
189 |
+ cmd := cli.Subcmd(chain, "ls", "SERVICE", "Lists all the services on a network", false) |
|
190 |
+ flNetwork := cmd.String([]string{"net", "-network"}, "", "Only show the services that are published on the specified network") |
|
191 |
+ quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only display numeric IDs") |
|
192 |
+ noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Do not truncate the output") |
|
193 |
+ |
|
194 |
+ err := cmd.ParseFlags(args, true) |
|
195 |
+ if err != nil { |
|
196 |
+ return err |
|
197 |
+ } |
|
198 |
+ |
|
199 |
+ var obj []byte |
|
200 |
+ if *flNetwork == "" { |
|
201 |
+ obj, _, err = readBody(cli.call("GET", "/services", nil, nil)) |
|
202 |
+ } else { |
|
203 |
+ obj, _, err = readBody(cli.call("GET", "/services?network="+*flNetwork, nil, nil)) |
|
204 |
+ } |
|
205 |
+ if err != nil { |
|
206 |
+ return err |
|
207 |
+ } |
|
208 |
+ |
|
209 |
+ var serviceResources []serviceResource |
|
210 |
+ err = json.Unmarshal(obj, &serviceResources) |
|
211 |
+ if err != nil { |
|
212 |
+ fmt.Println(err) |
|
213 |
+ return err |
|
214 |
+ } |
|
215 |
+ |
|
216 |
+ wr := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) |
|
217 |
+ // unless quiet (-q) is specified, print field titles |
|
218 |
+ if !*quiet { |
|
219 |
+ fmt.Fprintln(wr, "SERVICE ID\tNAME\tNETWORK\tCONTAINER") |
|
220 |
+ } |
|
221 |
+ |
|
222 |
+ for _, sr := range serviceResources { |
|
223 |
+ ID := sr.ID |
|
224 |
+ bkID, err := getBackendID(cli, ID) |
|
225 |
+ if err != nil { |
|
226 |
+ return err |
|
227 |
+ } |
|
228 |
+ if !*noTrunc { |
|
229 |
+ ID = stringid.TruncateID(ID) |
|
230 |
+ bkID = stringid.TruncateID(bkID) |
|
231 |
+ } |
|
232 |
+ if !*quiet { |
|
233 |
+ fmt.Fprintf(wr, "%s\t%s\t%s\t%s\n", ID, sr.Name, sr.Network, bkID) |
|
234 |
+ } else { |
|
235 |
+ fmt.Fprintln(wr, ID) |
|
236 |
+ } |
|
237 |
+ } |
|
238 |
+ wr.Flush() |
|
239 |
+ |
|
240 |
+ return nil |
|
241 |
+} |
|
242 |
+ |
|
243 |
+func getBackendID(cli *NetworkCli, servID string) (string, error) { |
|
244 |
+ var ( |
|
245 |
+ obj []byte |
|
246 |
+ err error |
|
247 |
+ bk string |
|
248 |
+ ) |
|
249 |
+ |
|
250 |
+ if obj, _, err = readBody(cli.call("GET", "/services/"+servID+"/backend", nil, nil)); err == nil { |
|
251 |
+ var bkl []backendResource |
|
252 |
+ if err := json.NewDecoder(bytes.NewReader(obj)).Decode(&bkl); err == nil { |
|
253 |
+ if len(bkl) > 0 { |
|
254 |
+ bk = bkl[0].ID |
|
255 |
+ } |
|
256 |
+ } else { |
|
257 |
+ // Only print a message, don't make the caller cli fail for this |
|
258 |
+ fmt.Fprintf(cli.out, "Failed to retrieve backend list for service %s (%v)", servID, err) |
|
259 |
+ } |
|
260 |
+ } |
|
261 |
+ |
|
262 |
+ return bk, err |
|
263 |
+} |
|
264 |
+ |
|
265 |
+// CmdServiceInfo handles service info UI |
|
266 |
+func (cli *NetworkCli) CmdServiceInfo(chain string, args ...string) error { |
|
267 |
+ cmd := cli.Subcmd(chain, "info", "SERVICE[.NETWORK]", "Displays detailed information about a service", false) |
|
268 |
+ cmd.Require(flag.Min, 1) |
|
269 |
+ |
|
270 |
+ err := cmd.ParseFlags(args, true) |
|
271 |
+ if err != nil { |
|
272 |
+ return err |
|
273 |
+ } |
|
274 |
+ |
|
275 |
+ sn, nn := parseServiceName(cmd.Arg(0)) |
|
276 |
+ serviceID, err := lookupServiceID(cli, nn, sn) |
|
277 |
+ if err != nil { |
|
278 |
+ return err |
|
279 |
+ } |
|
280 |
+ |
|
281 |
+ obj, _, err := readBody(cli.call("GET", "/services/"+serviceID, nil, nil)) |
|
282 |
+ if err != nil { |
|
283 |
+ return err |
|
284 |
+ } |
|
285 |
+ |
|
286 |
+ sr := &serviceResource{} |
|
287 |
+ if err := json.NewDecoder(bytes.NewReader(obj)).Decode(sr); err != nil { |
|
288 |
+ return err |
|
289 |
+ } |
|
290 |
+ |
|
291 |
+ fmt.Fprintf(cli.out, "Service Id: %s\n", sr.ID) |
|
292 |
+ fmt.Fprintf(cli.out, "\tName: %s\n", sr.Name) |
|
293 |
+ fmt.Fprintf(cli.out, "\tNetwork: %s\n", sr.Network) |
|
294 |
+ |
|
295 |
+ return nil |
|
296 |
+} |
|
297 |
+ |
|
298 |
+// CmdServiceAttach handles service attach UI |
|
299 |
+func (cli *NetworkCli) CmdServiceAttach(chain string, args ...string) error { |
|
300 |
+ cmd := cli.Subcmd(chain, "attach", "CONTAINER SERVICE[.NETWORK]", "Sets a container as a service backend", false) |
|
301 |
+ cmd.Require(flag.Min, 2) |
|
302 |
+ err := cmd.ParseFlags(args, true) |
|
303 |
+ if err != nil { |
|
304 |
+ return err |
|
305 |
+ } |
|
306 |
+ |
|
307 |
+ containerID, err := lookupContainerID(cli, cmd.Arg(0)) |
|
308 |
+ if err != nil { |
|
309 |
+ return err |
|
310 |
+ } |
|
311 |
+ |
|
312 |
+ sn, nn := parseServiceName(cmd.Arg(1)) |
|
313 |
+ serviceID, err := lookupServiceID(cli, nn, sn) |
|
314 |
+ if err != nil { |
|
315 |
+ return err |
|
316 |
+ } |
|
317 |
+ |
|
318 |
+ nc := serviceAttach{ContainerID: containerID} |
|
319 |
+ |
|
320 |
+ _, _, err = readBody(cli.call("POST", "/services/"+serviceID+"/backend", nc, nil)) |
|
321 |
+ |
|
322 |
+ return err |
|
323 |
+} |
|
324 |
+ |
|
325 |
+// CmdServiceDetach handles service detach UI |
|
326 |
+func (cli *NetworkCli) CmdServiceDetach(chain string, args ...string) error { |
|
327 |
+ cmd := cli.Subcmd(chain, "detach", "CONTAINER SERVICE", "Removes a container from service backend", false) |
|
328 |
+ cmd.Require(flag.Min, 2) |
|
329 |
+ err := cmd.ParseFlags(args, true) |
|
330 |
+ if err != nil { |
|
331 |
+ return err |
|
332 |
+ } |
|
333 |
+ |
|
334 |
+ sn, nn := parseServiceName(cmd.Arg(1)) |
|
335 |
+ containerID, err := lookupContainerID(cli, cmd.Arg(0)) |
|
336 |
+ if err != nil { |
|
337 |
+ return err |
|
338 |
+ } |
|
339 |
+ |
|
340 |
+ serviceID, err := lookupServiceID(cli, nn, sn) |
|
341 |
+ if err != nil { |
|
342 |
+ return err |
|
343 |
+ } |
|
344 |
+ |
|
345 |
+ _, _, err = readBody(cli.call("DELETE", "/services/"+serviceID+"/backend/"+containerID, nil, nil)) |
|
346 |
+ if err != nil { |
|
347 |
+ return err |
|
348 |
+ } |
|
349 |
+ return nil |
|
350 |
+} |
|
351 |
+ |
|
352 |
+func serviceUsage(chain string) string { |
|
353 |
+ help := "Commands:\n" |
|
354 |
+ |
|
355 |
+ for _, cmd := range serviceCommands { |
|
356 |
+ help += fmt.Sprintf(" %-10.10s%s\n", cmd.name, cmd.description) |
|
357 |
+ } |
|
358 |
+ |
|
359 |
+ help += fmt.Sprintf("\nRun '%s service COMMAND --help' for more information on a command.", chain) |
|
360 |
+ return help |
|
361 |
+} |
0 | 362 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,73 @@ |
0 |
+package client |
|
1 |
+ |
|
2 |
+import "github.com/docker/libnetwork/types" |
|
3 |
+ |
|
4 |
+/*********** |
|
5 |
+ Resources |
|
6 |
+************/ |
|
7 |
+ |
|
8 |
+// networkResource is the body of the "get network" http response message |
|
9 |
+type networkResource struct { |
|
10 |
+ Name string `json:"name"` |
|
11 |
+ ID string `json:"id"` |
|
12 |
+ Type string `json:"type"` |
|
13 |
+ Services []*serviceResource `json:"services"` |
|
14 |
+} |
|
15 |
+ |
|
16 |
+// serviceResource is the body of the "get service" http response message |
|
17 |
+type serviceResource struct { |
|
18 |
+ Name string `json:"name"` |
|
19 |
+ ID string `json:"id"` |
|
20 |
+ Network string `json:"network"` |
|
21 |
+} |
|
22 |
+ |
|
23 |
+// backendResource is the body of "get service backend" response message |
|
24 |
+type backendResource struct { |
|
25 |
+ ID string `json:"id"` |
|
26 |
+} |
|
27 |
+ |
|
28 |
+/*********** |
|
29 |
+ Body types |
|
30 |
+ ************/ |
|
31 |
+ |
|
32 |
+// networkCreate is the expected body of the "create network" http request message |
|
33 |
+type networkCreate struct { |
|
34 |
+ Name string `json:"name"` |
|
35 |
+ NetworkType string `json:"network_type"` |
|
36 |
+ Options map[string]interface{} `json:"options"` |
|
37 |
+} |
|
38 |
+ |
|
39 |
+// serviceCreate represents the body of the "publish service" http request message |
|
40 |
+type serviceCreate struct { |
|
41 |
+ Name string `json:"name"` |
|
42 |
+ Network string `json:"network_name"` |
|
43 |
+ ExposedPorts []types.TransportPort `json:"exposed_ports"` |
|
44 |
+ PortMapping []types.PortBinding `json:"port_mapping"` |
|
45 |
+} |
|
46 |
+ |
|
47 |
+// serviceAttach represents the expected body of the "attach/detach backend to/from service" http request messages |
|
48 |
+type serviceAttach struct { |
|
49 |
+ ContainerID string `json:"container_id"` |
|
50 |
+ HostName string `json:"host_name"` |
|
51 |
+ DomainName string `json:"domain_name"` |
|
52 |
+ HostsPath string `json:"hosts_path"` |
|
53 |
+ ResolvConfPath string `json:"resolv_conf_path"` |
|
54 |
+ DNS []string `json:"dns"` |
|
55 |
+ ExtraHosts []serviceExtraHost `json:"extra_hosts"` |
|
56 |
+ ParentUpdates []serviceParentUpdate `json:"parent_updates"` |
|
57 |
+ UseDefaultSandbox bool `json:"use_default_sandbox"` |
|
58 |
+} |
|
59 |
+ |
|
60 |
+// serviceExtraHost represents the extra host object |
|
61 |
+type serviceExtraHost struct { |
|
62 |
+ Name string `json:"name"` |
|
63 |
+ Address string `json:"address"` |
|
64 |
+} |
|
65 |
+ |
|
66 |
+// EndpointParentUpdate is the object carrying the information about the |
|
67 |
+// endpoint parent that needs to be updated |
|
68 |
+type serviceParentUpdate struct { |
|
69 |
+ EndpointID string `json:"service_id"` |
|
70 |
+ Name string `json:"name"` |
|
71 |
+ Address string `json:"address"` |
|
72 |
+} |
... | ... |
@@ -4,6 +4,7 @@ import ( |
4 | 4 |
"strings" |
5 | 5 |
|
6 | 6 |
"github.com/BurntSushi/toml" |
7 |
+ "github.com/docker/libnetwork/netlabel" |
|
7 | 8 |
) |
8 | 9 |
|
9 | 10 |
// Config encapsulates configurations of various Libnetwork components |
... | ... |
@@ -18,6 +19,7 @@ type DaemonCfg struct { |
18 | 18 |
Debug bool |
19 | 19 |
DefaultNetwork string |
20 | 20 |
DefaultDriver string |
21 |
+ Labels []string |
|
21 | 22 |
} |
22 | 23 |
|
23 | 24 |
// ClusterCfg represents cluster configuration |
... | ... |
@@ -66,6 +68,17 @@ func OptionDefaultDriver(dd string) Option { |
66 | 66 |
} |
67 | 67 |
} |
68 | 68 |
|
69 |
+// OptionLabels function returns an option setter for labels |
|
70 |
+func OptionLabels(labels []string) Option { |
|
71 |
+ return func(c *Config) { |
|
72 |
+ for _, label := range labels { |
|
73 |
+ if strings.HasPrefix(label, netlabel.Prefix) { |
|
74 |
+ c.Daemon.Labels = append(c.Daemon.Labels, label) |
|
75 |
+ } |
|
76 |
+ } |
|
77 |
+ } |
|
78 |
+} |
|
79 |
+ |
|
69 | 80 |
// OptionKVProvider function returns an option setter for kvstore provider |
70 | 81 |
func OptionKVProvider(provider string) Option { |
71 | 82 |
return func(c *Config) { |
... | ... |
@@ -88,3 +101,11 @@ func (c *Config) ProcessOptions(options ...Option) { |
88 | 88 |
} |
89 | 89 |
} |
90 | 90 |
} |
91 |
+ |
|
92 |
+// IsValidName validates configuration objects supported by libnetwork |
|
93 |
+func IsValidName(name string) bool { |
|
94 |
+ if name == "" || strings.Contains(name, ".") { |
|
95 |
+ return false |
|
96 |
+ } |
|
97 |
+ return true |
|
98 |
+} |
... | ... |
@@ -169,6 +169,9 @@ func (c *controller) hostLeaveCallback(hosts []net.IP) { |
169 | 169 |
func (c *controller) Config() config.Config { |
170 | 170 |
c.Lock() |
171 | 171 |
defer c.Unlock() |
172 |
+ if c.cfg == nil { |
|
173 |
+ return config.Config{} |
|
174 |
+ } |
|
172 | 175 |
return *c.cfg |
173 | 176 |
} |
174 | 177 |
|
... | ... |
@@ -185,6 +188,9 @@ func (c *controller) ConfigureNetworkDriver(networkType string, options map[stri |
185 | 185 |
func (c *controller) RegisterDriver(networkType string, driver driverapi.Driver, capability driverapi.Capability) error { |
186 | 186 |
c.Lock() |
187 | 187 |
defer c.Unlock() |
188 |
+ if !config.IsValidName(networkType) { |
|
189 |
+ return ErrInvalidName(networkType) |
|
190 |
+ } |
|
188 | 191 |
if _, ok := c.drivers[networkType]; ok { |
189 | 192 |
return driverapi.ErrActiveRegistration(networkType) |
190 | 193 |
} |
... | ... |
@@ -195,7 +201,7 @@ func (c *controller) RegisterDriver(networkType string, driver driverapi.Driver, |
195 | 195 |
// NewNetwork creates a new network of the specified network type. The options |
196 | 196 |
// are network specific and modeled in a generic way. |
197 | 197 |
func (c *controller) NewNetwork(networkType, name string, options ...NetworkOption) (Network, error) { |
198 |
- if name == "" { |
|
198 |
+ if !config.IsValidName(name) { |
|
199 | 199 |
return nil, ErrInvalidName(name) |
200 | 200 |
} |
201 | 201 |
// Check if a network already exists with the specified network name |
... | ... |
@@ -1,18 +1,24 @@ |
1 | 1 |
package netlabel |
2 | 2 |
|
3 | 3 |
const ( |
4 |
+ // Prefix constant marks the reserved label space for libnetwork |
|
5 |
+ Prefix = "com.docker.network" |
|
6 |
+ |
|
7 |
+ // DriverPrefix constant marks the reserved label space for libnetwork drivers |
|
8 |
+ DriverPrefix = Prefix + ".driver" |
|
9 |
+ |
|
4 | 10 |
// GenericData constant that helps to identify an option as a Generic constant |
5 |
- GenericData = "io.docker.network.generic" |
|
11 |
+ GenericData = Prefix + ".generic" |
|
6 | 12 |
|
7 | 13 |
// PortMap constant represents Port Mapping |
8 |
- PortMap = "io.docker.network.endpoint.portmap" |
|
14 |
+ PortMap = Prefix + ".portmap" |
|
9 | 15 |
|
10 | 16 |
// MacAddress constant represents Mac Address config of a Container |
11 |
- MacAddress = "io.docker.network.endpoint.macaddress" |
|
17 |
+ MacAddress = Prefix + ".endpoint.macaddress" |
|
12 | 18 |
|
13 | 19 |
// ExposedPorts constant represents exposedports of a Container |
14 |
- ExposedPorts = "io.docker.network.endpoint.exposedports" |
|
20 |
+ ExposedPorts = Prefix + ".endpoint.exposedports" |
|
15 | 21 |
|
16 | 22 |
//EnableIPv6 constant represents enabling IPV6 at network level |
17 |
- EnableIPv6 = "io.docker.network.enable_ipv6" |
|
23 |
+ EnableIPv6 = Prefix + ".enable_ipv6" |
|
18 | 24 |
) |
... | ... |
@@ -6,6 +6,7 @@ import ( |
6 | 6 |
|
7 | 7 |
log "github.com/Sirupsen/logrus" |
8 | 8 |
"github.com/docker/docker/pkg/stringid" |
9 |
+ "github.com/docker/libnetwork/config" |
|
9 | 10 |
"github.com/docker/libnetwork/datastore" |
10 | 11 |
"github.com/docker/libnetwork/driverapi" |
11 | 12 |
"github.com/docker/libnetwork/netlabel" |
... | ... |
@@ -274,7 +275,7 @@ func (n *network) addEndpoint(ep *endpoint) error { |
274 | 274 |
|
275 | 275 |
func (n *network) CreateEndpoint(name string, options ...EndpointOption) (Endpoint, error) { |
276 | 276 |
var err error |
277 |
- if name == "" { |
|
277 |
+ if !config.IsValidName(name) { |
|
278 | 278 |
return nil, ErrInvalidName(name) |
279 | 279 |
} |
280 | 280 |
|