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 |
|