deploy.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. // +build experimental
  2. package stack
  3. import (
  4. "fmt"
  5. "github.com/spf13/cobra"
  6. "golang.org/x/net/context"
  7. "github.com/docker/docker/api/client"
  8. "github.com/docker/docker/api/client/bundlefile"
  9. "github.com/docker/docker/cli"
  10. "github.com/docker/engine-api/types"
  11. "github.com/docker/engine-api/types/network"
  12. "github.com/docker/engine-api/types/swarm"
  13. )
  14. const (
  15. defaultNetworkDriver = "overlay"
  16. )
  17. type deployOptions struct {
  18. bundlefile string
  19. namespace string
  20. sendRegistryAuth bool
  21. }
  22. func newDeployCommand(dockerCli *client.DockerCli) *cobra.Command {
  23. var opts deployOptions
  24. cmd := &cobra.Command{
  25. Use: "deploy [OPTIONS] STACK",
  26. Aliases: []string{"up"},
  27. Short: "Create and update a stack from a Distributed Application Bundle (DAB)",
  28. Args: cli.ExactArgs(1),
  29. RunE: func(cmd *cobra.Command, args []string) error {
  30. opts.namespace = args[0]
  31. return runDeploy(dockerCli, opts)
  32. },
  33. }
  34. flags := cmd.Flags()
  35. addBundlefileFlag(&opts.bundlefile, flags)
  36. addRegistryAuthFlag(&opts.sendRegistryAuth, flags)
  37. return cmd
  38. }
  39. func runDeploy(dockerCli *client.DockerCli, opts deployOptions) error {
  40. bundle, err := loadBundlefile(dockerCli.Err(), opts.namespace, opts.bundlefile)
  41. if err != nil {
  42. return err
  43. }
  44. networks := getUniqueNetworkNames(bundle.Services)
  45. ctx := context.Background()
  46. if err := updateNetworks(ctx, dockerCli, networks, opts.namespace); err != nil {
  47. return err
  48. }
  49. return deployServices(ctx, dockerCli, bundle.Services, opts.namespace, opts.sendRegistryAuth)
  50. }
  51. func getUniqueNetworkNames(services map[string]bundlefile.Service) []string {
  52. networkSet := make(map[string]bool)
  53. for _, service := range services {
  54. for _, network := range service.Networks {
  55. networkSet[network] = true
  56. }
  57. }
  58. networks := []string{}
  59. for network := range networkSet {
  60. networks = append(networks, network)
  61. }
  62. return networks
  63. }
  64. func updateNetworks(
  65. ctx context.Context,
  66. dockerCli *client.DockerCli,
  67. networks []string,
  68. namespace string,
  69. ) error {
  70. client := dockerCli.Client()
  71. existingNetworks, err := getNetworks(ctx, client, namespace)
  72. if err != nil {
  73. return err
  74. }
  75. existingNetworkMap := make(map[string]types.NetworkResource)
  76. for _, network := range existingNetworks {
  77. existingNetworkMap[network.Name] = network
  78. }
  79. createOpts := types.NetworkCreate{
  80. Labels: getStackLabels(namespace, nil),
  81. Driver: defaultNetworkDriver,
  82. // TODO: remove when engine-api uses omitempty for IPAM
  83. IPAM: network.IPAM{Driver: "default"},
  84. }
  85. for _, internalName := range networks {
  86. name := fmt.Sprintf("%s_%s", namespace, internalName)
  87. if _, exists := existingNetworkMap[name]; exists {
  88. continue
  89. }
  90. fmt.Fprintf(dockerCli.Out(), "Creating network %s\n", name)
  91. if _, err := client.NetworkCreate(ctx, name, createOpts); err != nil {
  92. return err
  93. }
  94. }
  95. return nil
  96. }
  97. func convertNetworks(networks []string, namespace string, name string) []swarm.NetworkAttachmentConfig {
  98. nets := []swarm.NetworkAttachmentConfig{}
  99. for _, network := range networks {
  100. nets = append(nets, swarm.NetworkAttachmentConfig{
  101. Target: namespace + "_" + network,
  102. Aliases: []string{name},
  103. })
  104. }
  105. return nets
  106. }
  107. func deployServices(
  108. ctx context.Context,
  109. dockerCli *client.DockerCli,
  110. services map[string]bundlefile.Service,
  111. namespace string,
  112. sendAuth bool,
  113. ) error {
  114. apiClient := dockerCli.Client()
  115. out := dockerCli.Out()
  116. existingServices, err := getServices(ctx, apiClient, namespace)
  117. if err != nil {
  118. return err
  119. }
  120. existingServiceMap := make(map[string]swarm.Service)
  121. for _, service := range existingServices {
  122. existingServiceMap[service.Spec.Name] = service
  123. }
  124. for internalName, service := range services {
  125. name := fmt.Sprintf("%s_%s", namespace, internalName)
  126. var ports []swarm.PortConfig
  127. for _, portSpec := range service.Ports {
  128. ports = append(ports, swarm.PortConfig{
  129. Protocol: swarm.PortConfigProtocol(portSpec.Protocol),
  130. TargetPort: portSpec.Port,
  131. })
  132. }
  133. serviceSpec := swarm.ServiceSpec{
  134. Annotations: swarm.Annotations{
  135. Name: name,
  136. Labels: getStackLabels(namespace, service.Labels),
  137. },
  138. TaskTemplate: swarm.TaskSpec{
  139. ContainerSpec: swarm.ContainerSpec{
  140. Image: service.Image,
  141. Command: service.Command,
  142. Args: service.Args,
  143. Env: service.Env,
  144. },
  145. },
  146. EndpointSpec: &swarm.EndpointSpec{
  147. Ports: ports,
  148. },
  149. Networks: convertNetworks(service.Networks, namespace, internalName),
  150. }
  151. cspec := &serviceSpec.TaskTemplate.ContainerSpec
  152. if service.WorkingDir != nil {
  153. cspec.Dir = *service.WorkingDir
  154. }
  155. if service.User != nil {
  156. cspec.User = *service.User
  157. }
  158. encodedAuth := ""
  159. if sendAuth {
  160. // Retrieve encoded auth token from the image reference
  161. image := serviceSpec.TaskTemplate.ContainerSpec.Image
  162. encodedAuth, err = dockerCli.RetrieveAuthTokenFromImage(ctx, image)
  163. if err != nil {
  164. return err
  165. }
  166. }
  167. if service, exists := existingServiceMap[name]; exists {
  168. fmt.Fprintf(out, "Updating service %s (id: %s)\n", name, service.ID)
  169. updateOpts := types.ServiceUpdateOptions{}
  170. if sendAuth {
  171. updateOpts.EncodedRegistryAuth = encodedAuth
  172. }
  173. if err := apiClient.ServiceUpdate(
  174. ctx,
  175. service.ID,
  176. service.Version,
  177. serviceSpec,
  178. updateOpts,
  179. ); err != nil {
  180. return err
  181. }
  182. } else {
  183. fmt.Fprintf(out, "Creating service %s\n", name)
  184. createOpts := types.ServiceCreateOptions{}
  185. if sendAuth {
  186. createOpts.EncodedRegistryAuth = encodedAuth
  187. }
  188. if _, err := apiClient.ServiceCreate(ctx, serviceSpec, createOpts); err != nil {
  189. return err
  190. }
  191. }
  192. }
  193. return nil
  194. }