deploy.go 5.8 KB

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