deploy.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  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. }
  21. func newDeployCommand(dockerCli *client.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. return cmd
  36. }
  37. func runDeploy(dockerCli *client.DockerCli, opts deployOptions) error {
  38. bundle, err := loadBundlefile(dockerCli.Err(), opts.namespace, opts.bundlefile)
  39. if err != nil {
  40. return err
  41. }
  42. networks := getUniqueNetworkNames(bundle.Services)
  43. ctx := context.Background()
  44. if err := updateNetworks(ctx, dockerCli, networks, opts.namespace); err != nil {
  45. return err
  46. }
  47. return deployServices(ctx, dockerCli, bundle.Services, opts.namespace)
  48. }
  49. func getUniqueNetworkNames(services map[string]bundlefile.Service) []string {
  50. networkSet := make(map[string]bool)
  51. for _, service := range services {
  52. for _, network := range service.Networks {
  53. networkSet[network] = true
  54. }
  55. }
  56. networks := []string{}
  57. for network := range networkSet {
  58. networks = append(networks, network)
  59. }
  60. return networks
  61. }
  62. func updateNetworks(
  63. ctx context.Context,
  64. dockerCli *client.DockerCli,
  65. networks []string,
  66. namespace string,
  67. ) error {
  68. client := dockerCli.Client()
  69. existingNetworks, err := getNetworks(ctx, client, namespace)
  70. if err != nil {
  71. return err
  72. }
  73. existingNetworkMap := make(map[string]types.NetworkResource)
  74. for _, network := range existingNetworks {
  75. existingNetworkMap[network.Name] = network
  76. }
  77. createOpts := types.NetworkCreate{
  78. Labels: getStackLabels(namespace, nil),
  79. Driver: defaultNetworkDriver,
  80. // TODO: remove when engine-api uses omitempty for IPAM
  81. IPAM: network.IPAM{Driver: "default"},
  82. }
  83. for _, internalName := range networks {
  84. name := fmt.Sprintf("%s_%s", namespace, internalName)
  85. if _, exists := existingNetworkMap[name]; exists {
  86. continue
  87. }
  88. fmt.Fprintf(dockerCli.Out(), "Creating network %s\n", name)
  89. if _, err := client.NetworkCreate(ctx, name, createOpts); err != nil {
  90. return err
  91. }
  92. }
  93. return nil
  94. }
  95. func convertNetworks(networks []string, namespace string, name string) []swarm.NetworkAttachmentConfig {
  96. nets := []swarm.NetworkAttachmentConfig{}
  97. for _, network := range networks {
  98. nets = append(nets, swarm.NetworkAttachmentConfig{
  99. Target: namespace + "_" + network,
  100. Aliases: []string{name},
  101. })
  102. }
  103. return nets
  104. }
  105. func deployServices(
  106. ctx context.Context,
  107. dockerCli *client.DockerCli,
  108. services map[string]bundlefile.Service,
  109. namespace string,
  110. ) error {
  111. apiClient := dockerCli.Client()
  112. out := dockerCli.Out()
  113. existingServices, err := getServices(ctx, apiClient, namespace)
  114. if err != nil {
  115. return err
  116. }
  117. existingServiceMap := make(map[string]swarm.Service)
  118. for _, service := range existingServices {
  119. existingServiceMap[service.Spec.Name] = service
  120. }
  121. for internalName, service := range services {
  122. name := fmt.Sprintf("%s_%s", namespace, internalName)
  123. var ports []swarm.PortConfig
  124. for _, portSpec := range service.Ports {
  125. ports = append(ports, swarm.PortConfig{
  126. Protocol: swarm.PortConfigProtocol(portSpec.Protocol),
  127. TargetPort: portSpec.Port,
  128. })
  129. }
  130. serviceSpec := swarm.ServiceSpec{
  131. Annotations: swarm.Annotations{
  132. Name: name,
  133. Labels: getStackLabels(namespace, service.Labels),
  134. },
  135. TaskTemplate: swarm.TaskSpec{
  136. ContainerSpec: swarm.ContainerSpec{
  137. Image: service.Image,
  138. Command: service.Command,
  139. Args: service.Args,
  140. Env: service.Env,
  141. },
  142. },
  143. EndpointSpec: &swarm.EndpointSpec{
  144. Ports: ports,
  145. },
  146. Networks: convertNetworks(service.Networks, namespace, internalName),
  147. }
  148. cspec := &serviceSpec.TaskTemplate.ContainerSpec
  149. if service.WorkingDir != nil {
  150. cspec.Dir = *service.WorkingDir
  151. }
  152. if service.User != nil {
  153. cspec.User = *service.User
  154. }
  155. if service, exists := existingServiceMap[name]; exists {
  156. fmt.Fprintf(out, "Updating service %s (id: %s)\n", name, service.ID)
  157. // TODO(nishanttotla): Pass headers with X-Registry-Auth
  158. if err := apiClient.ServiceUpdate(
  159. ctx,
  160. service.ID,
  161. service.Version,
  162. serviceSpec,
  163. nil,
  164. ); err != nil {
  165. return err
  166. }
  167. } else {
  168. fmt.Fprintf(out, "Creating service %s\n", name)
  169. // TODO(nishanttotla): Pass headers with X-Registry-Auth
  170. if _, err := apiClient.ServiceCreate(ctx, serviceSpec, nil); err != nil {
  171. return err
  172. }
  173. }
  174. }
  175. return nil
  176. }