service.go 10 KB

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