-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Expand file tree
/
Copy pathdeploy.go
More file actions
128 lines (112 loc) · 4.67 KB
/
deploy.go
File metadata and controls
128 lines (112 loc) · 4.67 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
package stack
import (
"context"
"errors"
"fmt"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/compose/convert"
composetypes "github.com/docker/cli/cli/compose/types"
"github.com/moby/moby/api/types/swarm"
"github.com/moby/moby/client"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
// deployOptions holds docker stack deploy options
type deployOptions struct {
composefiles []string
namespace string
resolveImage string
sendRegistryAuth bool
prune bool
detach bool
quiet bool
}
func newDeployCommand(dockerCLI command.Cli) *cobra.Command {
var opts deployOptions
cmd := &cobra.Command{
Use: "deploy [OPTIONS] STACK",
Aliases: []string{"up"},
Short: "Deploy a new stack or update an existing stack",
Args: cli.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
opts.namespace = args[0]
if err := validateStackName(opts.namespace); err != nil {
return err
}
config, err := loadComposeFile(dockerCLI, opts)
if err != nil {
return err
}
return runDeploy(cmd.Context(), dockerCLI, cmd.Flags(), &opts, config)
},
ValidArgsFunction: completeNames(dockerCLI),
DisableFlagsInUseLine: true,
}
flags := cmd.Flags()
flags.StringSliceVarP(&opts.composefiles, "compose-file", "c", []string{}, `Path to a Compose file, or "-" to read from stdin`)
_ = flags.SetAnnotation("compose-file", "version", []string{"1.25"})
// Provide tab-completion for filenames. On Bash, this is constrained to the
// ".yaml" and ".yml" file-extensions, but this doesn't appear to be supported
// by other shells.
_ = cmd.MarkFlagFilename("compose-file", "yaml", "yml")
flags.BoolVar(&opts.sendRegistryAuth, "with-registry-auth", false, "Send registry authentication details to Swarm agents")
flags.BoolVar(&opts.prune, "prune", false, "Prune services that are no longer referenced")
flags.SetAnnotation("prune", "version", []string{"1.27"})
flags.StringVar(&opts.resolveImage, "resolve-image", resolveImageAlways,
`Query the registry to resolve image digest and supported platforms ("`+resolveImageAlways+`", "`+resolveImageChanged+`", "`+resolveImageNever+`")`)
flags.SetAnnotation("resolve-image", "version", []string{"1.30"})
flags.BoolVarP(&opts.detach, "detach", "d", true, "Exit immediately instead of waiting for the stack services to converge")
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Suppress progress output")
return cmd
}
// Resolve image constants
const (
resolveImageAlways = "always"
resolveImageChanged = "changed"
resolveImageNever = "never"
)
const defaultNetworkDriver = "overlay"
// runDeploy is the swarm implementation of docker stack deploy
func runDeploy(ctx context.Context, dockerCLI command.Cli, flags *pflag.FlagSet, opts *deployOptions, cfg *composetypes.Config) error {
switch opts.resolveImage {
case resolveImageAlways, resolveImageChanged, resolveImageNever:
// valid options.
default:
return fmt.Errorf("invalid option %s for flag --resolve-image", opts.resolveImage)
}
if opts.detach && !flags.Changed("detach") {
_, _ = fmt.Fprintln(dockerCLI.Err(), "Since --detach=false was not specified, tasks will be created in the background.\n"+
"In a future release, --detach=false will become the default.")
}
return deployCompose(ctx, dockerCLI, opts, cfg)
}
// 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.Cli) error {
res, err := dockerCli.Client().Info(ctx, client.InfoOptions{})
if err != nil {
return err
}
if !res.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`)
}
return nil
}
// 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{}) {
apiClient := dockerCLI.Client()
oldServices, err := getStackServices(ctx, apiClient, namespace.Name())
if err != nil {
_, _ = fmt.Fprintln(dockerCLI.Err(), "Failed to list services:", err)
}
toRemove := make([]swarm.Service, 0, len(oldServices.Items))
for _, service := range oldServices.Items {
if _, exists := services[namespace.Descope(service.Spec.Name)]; !exists {
toRemove = append(toRemove, service)
}
}
removeServices(ctx, dockerCLI, toRemove)
}