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 }