deploy.go 3.1 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. package stack
  2. import (
  3. "fmt"
  4. "github.com/docker/docker/api/types/swarm"
  5. "github.com/docker/docker/cli"
  6. "github.com/docker/docker/cli/command"
  7. "github.com/docker/docker/cli/compose/convert"
  8. "github.com/pkg/errors"
  9. "github.com/spf13/cobra"
  10. "golang.org/x/net/context"
  11. )
  12. const (
  13. defaultNetworkDriver = "overlay"
  14. )
  15. type deployOptions struct {
  16. bundlefile string
  17. composefile string
  18. namespace string
  19. sendRegistryAuth bool
  20. prune bool
  21. }
  22. func newDeployCommand(dockerCli *command.DockerCli) *cobra.Command {
  23. var opts deployOptions
  24. cmd := &cobra.Command{
  25. Use: "deploy [OPTIONS] STACK",
  26. Aliases: []string{"up"},
  27. Short: "Deploy a new stack or update an existing stack",
  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. addComposefileFlag(&opts.composefile, flags)
  37. addRegistryAuthFlag(&opts.sendRegistryAuth, flags)
  38. flags.BoolVar(&opts.prune, "prune", false, "Prune services that are no longer referenced")
  39. flags.SetAnnotation("prune", "version", []string{"1.27"})
  40. return cmd
  41. }
  42. func runDeploy(dockerCli *command.DockerCli, opts deployOptions) error {
  43. ctx := context.Background()
  44. switch {
  45. case opts.bundlefile == "" && opts.composefile == "":
  46. return errors.Errorf("Please specify either a bundle file (with --bundle-file) or a Compose file (with --compose-file).")
  47. case opts.bundlefile != "" && opts.composefile != "":
  48. return errors.Errorf("You cannot specify both a bundle file and a Compose file.")
  49. case opts.bundlefile != "":
  50. return deployBundle(ctx, dockerCli, opts)
  51. default:
  52. return deployCompose(ctx, dockerCli, opts)
  53. }
  54. }
  55. // checkDaemonIsSwarmManager does an Info API call to verify that the daemon is
  56. // a swarm manager. This is necessary because we must create networks before we
  57. // create services, but the API call for creating a network does not return a
  58. // proper status code when it can't create a network in the "global" scope.
  59. func checkDaemonIsSwarmManager(ctx context.Context, dockerCli *command.DockerCli) error {
  60. info, err := dockerCli.Client().Info(ctx)
  61. if err != nil {
  62. return err
  63. }
  64. if !info.Swarm.ControlAvailable {
  65. return errors.New("This node is not a swarm manager. Use \"docker swarm init\" or \"docker swarm join\" to connect this node to swarm and try again.")
  66. }
  67. return nil
  68. }
  69. // pruneServices removes services that are no longer referenced in the source
  70. func pruneServices(ctx context.Context, dockerCli command.Cli, namespace convert.Namespace, services map[string]struct{}) bool {
  71. client := dockerCli.Client()
  72. oldServices, err := getServices(ctx, client, namespace.Name())
  73. if err != nil {
  74. fmt.Fprintf(dockerCli.Err(), "Failed to list services: %s", err)
  75. return true
  76. }
  77. pruneServices := []swarm.Service{}
  78. for _, service := range oldServices {
  79. if _, exists := services[namespace.Descope(service.Spec.Name)]; !exists {
  80. pruneServices = append(pruneServices, service)
  81. }
  82. }
  83. return removeServices(ctx, dockerCli, pruneServices)
  84. }