github.com/kaisenlinux/docker.io@v0.0.0-20230510090727-ea55db55fac7/libnetwork/client/service.go (about)

     1  package client
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"net/http"
     9  	"strings"
    10  	"text/tabwriter"
    11  
    12  	"github.com/docker/docker/pkg/stringid"
    13  	flag "github.com/docker/libnetwork/client/mflag"
    14  	"github.com/docker/libnetwork/netutils"
    15  )
    16  
    17  var (
    18  	serviceCommands = []command{
    19  		{"publish", "Publish a service"},
    20  		{"unpublish", "Remove a service"},
    21  		{"attach", "Attach a backend (container) to the service"},
    22  		{"detach", "Detach the backend from the service"},
    23  		{"ls", "Lists all services"},
    24  		{"info", "Display information about a service"},
    25  	}
    26  )
    27  
    28  func lookupServiceID(cli *NetworkCli, nwName, svNameID string) (string, error) {
    29  	// Sanity Check
    30  	obj, _, err := readBody(cli.call("GET", fmt.Sprintf("/networks?name=%s", nwName), nil, nil))
    31  	if err != nil {
    32  		return "", err
    33  	}
    34  	var nwList []networkResource
    35  	if err = json.Unmarshal(obj, &nwList); err != nil {
    36  		return "", err
    37  	}
    38  	if len(nwList) == 0 {
    39  		return "", fmt.Errorf("Network %s does not exist", nwName)
    40  	}
    41  
    42  	if nwName == "" {
    43  		obj, _, err := readBody(cli.call("GET", "/networks/"+nwList[0].ID, nil, nil))
    44  		if err != nil {
    45  			return "", err
    46  		}
    47  		networkResource := &networkResource{}
    48  		if err := json.NewDecoder(bytes.NewReader(obj)).Decode(networkResource); err != nil {
    49  			return "", err
    50  		}
    51  		nwName = networkResource.Name
    52  	}
    53  
    54  	// Query service by name
    55  	obj, statusCode, err := readBody(cli.call("GET", fmt.Sprintf("/services?name=%s", svNameID), nil, nil))
    56  	if err != nil {
    57  		return "", err
    58  	}
    59  
    60  	if statusCode != http.StatusOK {
    61  		return "", fmt.Errorf("name query failed for %s due to: (%d) %s", svNameID, statusCode, string(obj))
    62  	}
    63  
    64  	var list []*serviceResource
    65  	if err = json.Unmarshal(obj, &list); err != nil {
    66  		return "", err
    67  	}
    68  	for _, sr := range list {
    69  		if sr.Network == nwName {
    70  			return sr.ID, nil
    71  		}
    72  	}
    73  
    74  	// Query service by Partial-id (this covers full id as well)
    75  	obj, statusCode, err = readBody(cli.call("GET", fmt.Sprintf("/services?partial-id=%s", svNameID), nil, nil))
    76  	if err != nil {
    77  		return "", err
    78  	}
    79  
    80  	if statusCode != http.StatusOK {
    81  		return "", fmt.Errorf("partial-id match query failed for %s due to: (%d) %s", svNameID, statusCode, string(obj))
    82  	}
    83  
    84  	if err = json.Unmarshal(obj, &list); err != nil {
    85  		return "", err
    86  	}
    87  	for _, sr := range list {
    88  		if sr.Network == nwName {
    89  			return sr.ID, nil
    90  		}
    91  	}
    92  
    93  	return "", fmt.Errorf("Service %s not found on network %s", svNameID, nwName)
    94  }
    95  
    96  func lookupContainerID(cli *NetworkCli, cnNameID string) (string, error) {
    97  	// Container is a Docker resource, ask docker about it.
    98  	// In case of connection error, we assume we are running in dnet and return whatever was passed to us
    99  	obj, _, err := readBody(cli.call("GET", fmt.Sprintf("/containers/%s/json", cnNameID), nil, nil))
   100  	if err != nil {
   101  		// We are probably running outside of docker
   102  		return cnNameID, nil
   103  	}
   104  
   105  	var x map[string]interface{}
   106  	err = json.Unmarshal(obj, &x)
   107  	if err != nil {
   108  		return "", err
   109  	}
   110  	if iid, ok := x["Id"]; ok {
   111  		if id, ok := iid.(string); ok {
   112  			return id, nil
   113  		}
   114  		return "", errors.New("Unexpected data type for container ID in json response")
   115  	}
   116  	return "", errors.New("Cannot find container ID in json response")
   117  }
   118  
   119  func lookupSandboxID(cli *NetworkCli, containerID string) (string, error) {
   120  	obj, _, err := readBody(cli.call("GET", fmt.Sprintf("/sandboxes?partial-container-id=%s", containerID), nil, nil))
   121  	if err != nil {
   122  		return "", err
   123  	}
   124  
   125  	var sandboxList []SandboxResource
   126  	err = json.Unmarshal(obj, &sandboxList)
   127  	if err != nil {
   128  		return "", err
   129  	}
   130  
   131  	if len(sandboxList) == 0 {
   132  		return "", fmt.Errorf("cannot find sandbox for container: %s", containerID)
   133  	}
   134  
   135  	return sandboxList[0].ID, nil
   136  }
   137  
   138  // CmdService handles the service UI
   139  func (cli *NetworkCli) CmdService(chain string, args ...string) error {
   140  	cmd := cli.Subcmd(chain, "service", "COMMAND [OPTIONS] [arg...]", serviceUsage(chain), false)
   141  	cmd.Require(flag.Min, 1)
   142  	err := cmd.ParseFlags(args, true)
   143  	if err == nil {
   144  		cmd.Usage()
   145  		return fmt.Errorf("Invalid command : %v", args)
   146  	}
   147  	return err
   148  }
   149  
   150  // Parse service name for "SERVICE[.NETWORK]" format
   151  func parseServiceName(name string) (string, string) {
   152  	s := strings.Split(name, ".")
   153  	var sName, nName string
   154  	if len(s) > 1 {
   155  		nName = s[len(s)-1]
   156  		sName = strings.Join(s[:len(s)-1], ".")
   157  	} else {
   158  		sName = s[0]
   159  	}
   160  	return sName, nName
   161  }
   162  
   163  // CmdServicePublish handles service create UI
   164  func (cli *NetworkCli) CmdServicePublish(chain string, args ...string) error {
   165  	cmd := cli.Subcmd(chain, "publish", "SERVICE[.NETWORK]", "Publish a new service on a network", false)
   166  	flAlias := flag.NewListOpts(netutils.ValidateAlias)
   167  	cmd.Var(&flAlias, []string{"-alias"}, "Add alias to self")
   168  	cmd.Require(flag.Exact, 1)
   169  	err := cmd.ParseFlags(args, true)
   170  	if err != nil {
   171  		return err
   172  	}
   173  
   174  	sn, nn := parseServiceName(cmd.Arg(0))
   175  	sc := serviceCreate{Name: sn, Network: nn, MyAliases: flAlias.GetAll()}
   176  	obj, _, err := readBody(cli.call("POST", "/services", sc, nil))
   177  	if err != nil {
   178  		return err
   179  	}
   180  
   181  	var replyID string
   182  	err = json.Unmarshal(obj, &replyID)
   183  	if err != nil {
   184  		return err
   185  	}
   186  
   187  	fmt.Fprintf(cli.out, "%s\n", replyID)
   188  	return nil
   189  }
   190  
   191  // CmdServiceUnpublish handles service delete UI
   192  func (cli *NetworkCli) CmdServiceUnpublish(chain string, args ...string) error {
   193  	cmd := cli.Subcmd(chain, "unpublish", "SERVICE[.NETWORK]", "Removes a service", false)
   194  	force := cmd.Bool([]string{"f", "-force"}, false, "force unpublish service")
   195  	cmd.Require(flag.Exact, 1)
   196  	err := cmd.ParseFlags(args, true)
   197  	if err != nil {
   198  		return err
   199  	}
   200  
   201  	sn, nn := parseServiceName(cmd.Arg(0))
   202  	serviceID, err := lookupServiceID(cli, nn, sn)
   203  	if err != nil {
   204  		return err
   205  	}
   206  
   207  	sd := serviceDelete{Name: sn, Force: *force}
   208  	_, _, err = readBody(cli.call("DELETE", "/services/"+serviceID, sd, nil))
   209  
   210  	return err
   211  }
   212  
   213  // CmdServiceLs handles service list UI
   214  func (cli *NetworkCli) CmdServiceLs(chain string, args ...string) error {
   215  	cmd := cli.Subcmd(chain, "ls", "SERVICE", "Lists all the services on a network", false)
   216  	flNetwork := cmd.String([]string{"net", "-network"}, "", "Only show the services that are published on the specified network")
   217  	quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only display numeric IDs")
   218  	noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Do not truncate the output")
   219  
   220  	err := cmd.ParseFlags(args, true)
   221  	if err != nil {
   222  		return err
   223  	}
   224  
   225  	var obj []byte
   226  	if *flNetwork == "" {
   227  		obj, _, err = readBody(cli.call("GET", "/services", nil, nil))
   228  	} else {
   229  		obj, _, err = readBody(cli.call("GET", "/services?network="+*flNetwork, nil, nil))
   230  	}
   231  	if err != nil {
   232  		return err
   233  	}
   234  
   235  	var serviceResources []serviceResource
   236  	err = json.Unmarshal(obj, &serviceResources)
   237  	if err != nil {
   238  		fmt.Println(err)
   239  		return err
   240  	}
   241  
   242  	wr := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
   243  	// unless quiet (-q) is specified, print field titles
   244  	if !*quiet {
   245  		fmt.Fprintln(wr, "SERVICE ID\tNAME\tNETWORK\tCONTAINER\tSANDBOX")
   246  	}
   247  
   248  	for _, sr := range serviceResources {
   249  		ID := sr.ID
   250  		bkID, sbID, err := getBackendID(cli, ID)
   251  		if err != nil {
   252  			return err
   253  		}
   254  		if !*noTrunc {
   255  			ID = stringid.TruncateID(ID)
   256  			bkID = stringid.TruncateID(bkID)
   257  			sbID = stringid.TruncateID(sbID)
   258  		}
   259  		if !*quiet {
   260  			fmt.Fprintf(wr, "%s\t%s\t%s\t%s\t%s\n", ID, sr.Name, sr.Network, bkID, sbID)
   261  		} else {
   262  			fmt.Fprintln(wr, ID)
   263  		}
   264  	}
   265  	wr.Flush()
   266  
   267  	return nil
   268  }
   269  
   270  func getBackendID(cli *NetworkCli, servID string) (string, string, error) {
   271  	var (
   272  		obj []byte
   273  		err error
   274  		bk  string
   275  		sb  string
   276  	)
   277  
   278  	if obj, _, err = readBody(cli.call("GET", "/services/"+servID+"/backend", nil, nil)); err == nil {
   279  		var sr SandboxResource
   280  		if err := json.NewDecoder(bytes.NewReader(obj)).Decode(&sr); err == nil {
   281  			bk = sr.ContainerID
   282  			sb = sr.ID
   283  		} else {
   284  			// Only print a message, don't make the caller cli fail for this
   285  			fmt.Fprintf(cli.out, "Failed to retrieve backend list for service %s (%v)\n", servID, err)
   286  		}
   287  	}
   288  
   289  	return bk, sb, err
   290  }
   291  
   292  // CmdServiceInfo handles service info UI
   293  func (cli *NetworkCli) CmdServiceInfo(chain string, args ...string) error {
   294  	cmd := cli.Subcmd(chain, "info", "SERVICE[.NETWORK]", "Displays detailed information about a service", false)
   295  	cmd.Require(flag.Min, 1)
   296  
   297  	err := cmd.ParseFlags(args, true)
   298  	if err != nil {
   299  		return err
   300  	}
   301  
   302  	sn, nn := parseServiceName(cmd.Arg(0))
   303  	serviceID, err := lookupServiceID(cli, nn, sn)
   304  	if err != nil {
   305  		return err
   306  	}
   307  
   308  	obj, _, err := readBody(cli.call("GET", "/services/"+serviceID, nil, nil))
   309  	if err != nil {
   310  		return err
   311  	}
   312  
   313  	sr := &serviceResource{}
   314  	if err := json.NewDecoder(bytes.NewReader(obj)).Decode(sr); err != nil {
   315  		return err
   316  	}
   317  
   318  	fmt.Fprintf(cli.out, "Service Id: %s\n", sr.ID)
   319  	fmt.Fprintf(cli.out, "\tName: %s\n", sr.Name)
   320  	fmt.Fprintf(cli.out, "\tNetwork: %s\n", sr.Network)
   321  
   322  	return nil
   323  }
   324  
   325  // CmdServiceAttach handles service attach UI
   326  func (cli *NetworkCli) CmdServiceAttach(chain string, args ...string) error {
   327  	cmd := cli.Subcmd(chain, "attach", "CONTAINER SERVICE[.NETWORK]", "Sets a container as a service backend", false)
   328  	flAlias := flag.NewListOpts(netutils.ValidateAlias)
   329  	cmd.Var(&flAlias, []string{"-alias"}, "Add alias for another container")
   330  	cmd.Require(flag.Min, 2)
   331  	err := cmd.ParseFlags(args, true)
   332  	if err != nil {
   333  		return err
   334  	}
   335  
   336  	containerID, err := lookupContainerID(cli, cmd.Arg(0))
   337  	if err != nil {
   338  		return err
   339  	}
   340  
   341  	sandboxID, err := lookupSandboxID(cli, containerID)
   342  	if err != nil {
   343  		return err
   344  	}
   345  
   346  	sn, nn := parseServiceName(cmd.Arg(1))
   347  	serviceID, err := lookupServiceID(cli, nn, sn)
   348  	if err != nil {
   349  		return err
   350  	}
   351  
   352  	nc := serviceAttach{SandboxID: sandboxID, Aliases: flAlias.GetAll()}
   353  
   354  	_, _, err = readBody(cli.call("POST", "/services/"+serviceID+"/backend", nc, nil))
   355  
   356  	return err
   357  }
   358  
   359  // CmdServiceDetach handles service detach UI
   360  func (cli *NetworkCli) CmdServiceDetach(chain string, args ...string) error {
   361  	cmd := cli.Subcmd(chain, "detach", "CONTAINER SERVICE", "Removes a container from service backend", false)
   362  	cmd.Require(flag.Min, 2)
   363  	err := cmd.ParseFlags(args, true)
   364  	if err != nil {
   365  		return err
   366  	}
   367  
   368  	sn, nn := parseServiceName(cmd.Arg(1))
   369  	containerID, err := lookupContainerID(cli, cmd.Arg(0))
   370  	if err != nil {
   371  		return err
   372  	}
   373  
   374  	sandboxID, err := lookupSandboxID(cli, containerID)
   375  	if err != nil {
   376  		return err
   377  	}
   378  
   379  	serviceID, err := lookupServiceID(cli, nn, sn)
   380  	if err != nil {
   381  		return err
   382  	}
   383  
   384  	_, _, err = readBody(cli.call("DELETE", "/services/"+serviceID+"/backend/"+sandboxID, nil, nil))
   385  	if err != nil {
   386  		return err
   387  	}
   388  	return nil
   389  }
   390  
   391  func serviceUsage(chain string) string {
   392  	help := "Commands:\n"
   393  
   394  	for _, cmd := range serviceCommands {
   395  		help += fmt.Sprintf("    %-10.10s%s\n", cmd.name, cmd.description)
   396  	}
   397  
   398  	help += fmt.Sprintf("\nRun '%s service COMMAND --help' for more information on a command.", chain)
   399  	return help
   400  }