2016-09-08 13:11:39 -04:00
|
|
|
package stack
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
|
2017-04-17 18:07:56 -04:00
|
|
|
"github.com/docker/cli/cli"
|
|
|
|
"github.com/docker/cli/cli/command"
|
|
|
|
"github.com/docker/cli/cli/compose/convert"
|
2017-02-22 15:43:13 -05:00
|
|
|
"github.com/docker/docker/api/types/swarm"
|
2017-01-13 11:26:29 -05:00
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/spf13/cobra"
|
|
|
|
"golang.org/x/net/context"
|
2016-09-08 13:11:39 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
defaultNetworkDriver = "overlay"
|
|
|
|
)
|
|
|
|
|
|
|
|
type deployOptions struct {
|
2016-11-08 12:05:23 -05:00
|
|
|
bundlefile string
|
2016-11-02 14:57:40 -04:00
|
|
|
composefile string
|
2016-09-08 13:11:39 -04:00
|
|
|
namespace string
|
|
|
|
sendRegistryAuth bool
|
2017-02-22 15:43:13 -05:00
|
|
|
prune bool
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
|
2016-09-08 14:54:01 -04:00
|
|
|
func newDeployCommand(dockerCli *command.DockerCli) *cobra.Command {
|
2016-09-08 13:11:39 -04:00
|
|
|
var opts deployOptions
|
|
|
|
|
|
|
|
cmd := &cobra.Command{
|
|
|
|
Use: "deploy [OPTIONS] STACK",
|
|
|
|
Aliases: []string{"up"},
|
2016-11-02 14:57:40 -04:00
|
|
|
Short: "Deploy a new stack or update an existing stack",
|
2016-09-08 13:11:39 -04:00
|
|
|
Args: cli.ExactArgs(1),
|
|
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
2016-11-02 14:57:40 -04:00
|
|
|
opts.namespace = args[0]
|
2016-09-08 13:11:39 -04:00
|
|
|
return runDeploy(dockerCli, opts)
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
flags := cmd.Flags()
|
2016-11-08 12:05:23 -05:00
|
|
|
addBundlefileFlag(&opts.bundlefile, flags)
|
2016-11-02 14:57:40 -04:00
|
|
|
addComposefileFlag(&opts.composefile, flags)
|
2016-09-08 13:11:39 -04:00
|
|
|
addRegistryAuthFlag(&opts.sendRegistryAuth, flags)
|
2017-02-22 15:43:13 -05:00
|
|
|
flags.BoolVar(&opts.prune, "prune", false, "Prune services that are no longer referenced")
|
|
|
|
flags.SetAnnotation("prune", "version", []string{"1.27"})
|
2016-09-08 13:11:39 -04:00
|
|
|
return cmd
|
|
|
|
}
|
|
|
|
|
2016-09-08 14:54:01 -04:00
|
|
|
func runDeploy(dockerCli *command.DockerCli, opts deployOptions) error {
|
2016-11-21 15:03:43 -05:00
|
|
|
ctx := context.Background()
|
|
|
|
|
2016-11-08 15:20:16 -05:00
|
|
|
switch {
|
|
|
|
case opts.bundlefile == "" && opts.composefile == "":
|
2017-03-09 13:23:45 -05:00
|
|
|
return errors.Errorf("Please specify either a bundle file (with --bundle-file) or a Compose file (with --compose-file).")
|
2016-11-08 15:20:16 -05:00
|
|
|
case opts.bundlefile != "" && opts.composefile != "":
|
2017-03-09 13:23:45 -05:00
|
|
|
return errors.Errorf("You cannot specify both a bundle file and a Compose file.")
|
2016-11-08 15:20:16 -05:00
|
|
|
case opts.bundlefile != "":
|
2016-11-21 15:03:43 -05:00
|
|
|
return deployBundle(ctx, dockerCli, opts)
|
2016-11-08 15:20:16 -05:00
|
|
|
default:
|
2016-11-21 15:03:43 -05:00
|
|
|
return deployCompose(ctx, dockerCli, opts)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// checkDaemonIsSwarmManager does an Info API call to verify that the daemon is
|
|
|
|
// a swarm manager. This is necessary because we must create networks before we
|
|
|
|
// create services, but the API call for creating a network does not return a
|
|
|
|
// proper status code when it can't create a network in the "global" scope.
|
|
|
|
func checkDaemonIsSwarmManager(ctx context.Context, dockerCli *command.DockerCli) error {
|
|
|
|
info, err := dockerCli.Client().Info(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if !info.Swarm.ControlAvailable {
|
|
|
|
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.")
|
2016-11-08 12:05:23 -05:00
|
|
|
}
|
2016-11-21 15:03:43 -05:00
|
|
|
return nil
|
2016-11-08 12:05:23 -05:00
|
|
|
}
|
2017-02-22 15:43:13 -05:00
|
|
|
|
|
|
|
// pruneServices removes services that are no longer referenced in the source
|
|
|
|
func pruneServices(ctx context.Context, dockerCli command.Cli, namespace convert.Namespace, services map[string]struct{}) bool {
|
|
|
|
client := dockerCli.Client()
|
|
|
|
|
|
|
|
oldServices, err := getServices(ctx, client, namespace.Name())
|
|
|
|
if err != nil {
|
|
|
|
fmt.Fprintf(dockerCli.Err(), "Failed to list services: %s", err)
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
pruneServices := []swarm.Service{}
|
|
|
|
for _, service := range oldServices {
|
|
|
|
if _, exists := services[namespace.Descope(service.Spec.Name)]; !exists {
|
|
|
|
pruneServices = append(pruneServices, service)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return removeServices(ctx, dockerCli, pruneServices)
|
|
|
|
}
|