11package stack
22
33import (
4+ "context"
5+ "fmt"
6+
47 "github.com/docker/cli/cli"
58 "github.com/docker/cli/cli/command"
6- "github.com/docker/cli/cli/command/stack/loader"
7- "github.com/docker/cli/cli/command/stack/options"
8- "github.com/docker/cli/cli/command/stack/swarm"
9+ "github.com/docker/cli/cli/compose/convert"
10+ composetypes "github.com/docker/cli/cli/compose/types"
11+ "github.com/moby/moby/api/types/swarm"
12+ "github.com/moby/moby/api/types/versions"
13+ "github.com/pkg/errors"
914 "github.com/spf13/cobra"
15+ "github.com/spf13/pflag"
1016)
1117
18+ // deployOptions holds docker stack deploy options
19+ type deployOptions struct {
20+ composefiles []string
21+ namespace string
22+ resolveImage string
23+ sendRegistryAuth bool
24+ prune bool
25+ detach bool
26+ quiet bool
27+ }
28+
1229func newDeployCommand (dockerCLI command.Cli ) * cobra.Command {
13- var opts options. Deploy
30+ var opts deployOptions
1431
1532 cmd := & cobra.Command {
1633 Use : "deploy [OPTIONS] STACK" ,
1734 Aliases : []string {"up" },
1835 Short : "Deploy a new stack or update an existing stack" ,
1936 Args : cli .ExactArgs (1 ),
2037 RunE : func (cmd * cobra.Command , args []string ) error {
21- opts .Namespace = args [0 ]
22- if err := validateStackName (opts .Namespace ); err != nil {
38+ opts .namespace = args [0 ]
39+ if err := validateStackName (opts .namespace ); err != nil {
2340 return err
2441 }
25- config , err := loader . LoadComposefile (dockerCLI , opts )
42+ config , err := loadComposeFile (dockerCLI , opts )
2643 if err != nil {
2744 return err
2845 }
29- return swarm . RunDeploy (cmd .Context (), dockerCLI , cmd .Flags (), & opts , config )
46+ return runDeploy (cmd .Context (), dockerCLI , cmd .Flags (), & opts , config )
3047 },
3148 ValidArgsFunction : func (cmd * cobra.Command , args []string , toComplete string ) ([]string , cobra.ShellCompDirective ) {
3249 return completeNames (dockerCLI )(cmd , args , toComplete )
@@ -35,15 +52,81 @@ func newDeployCommand(dockerCLI command.Cli) *cobra.Command {
3552 }
3653
3754 flags := cmd .Flags ()
38- flags .StringSliceVarP (& opts .Composefiles , "compose-file" , "c" , []string {}, `Path to a Compose file, or "-" to read from stdin` )
55+ flags .StringSliceVarP (& opts .composefiles , "compose-file" , "c" , []string {}, `Path to a Compose file, or "-" to read from stdin` )
3956 flags .SetAnnotation ("compose-file" , "version" , []string {"1.25" })
40- flags .BoolVar (& opts .SendRegistryAuth , "with-registry-auth" , false , "Send registry authentication details to Swarm agents" )
41- flags .BoolVar (& opts .Prune , "prune" , false , "Prune services that are no longer referenced" )
57+ flags .BoolVar (& opts .sendRegistryAuth , "with-registry-auth" , false , "Send registry authentication details to Swarm agents" )
58+ flags .BoolVar (& opts .prune , "prune" , false , "Prune services that are no longer referenced" )
4259 flags .SetAnnotation ("prune" , "version" , []string {"1.27" })
43- flags .StringVar (& opts .ResolveImage , "resolve-image" , swarm . ResolveImageAlways ,
44- `Query the registry to resolve image digest and supported platforms ("` + swarm . ResolveImageAlways + `", "` + swarm . ResolveImageChanged + `", "` + swarm . ResolveImageNever + `")` )
60+ flags .StringVar (& opts .resolveImage , "resolve-image" , resolveImageAlways ,
61+ `Query the registry to resolve image digest and supported platforms ("` + resolveImageAlways + `", "` + resolveImageChanged + `", "` + resolveImageNever + `")` )
4562 flags .SetAnnotation ("resolve-image" , "version" , []string {"1.30" })
46- flags .BoolVarP (& opts .Detach , "detach" , "d" , true , "Exit immediately instead of waiting for the stack services to converge" )
47- flags .BoolVarP (& opts .Quiet , "quiet" , "q" , false , "Suppress progress output" )
63+ flags .BoolVarP (& opts .detach , "detach" , "d" , true , "Exit immediately instead of waiting for the stack services to converge" )
64+ flags .BoolVarP (& opts .quiet , "quiet" , "q" , false , "Suppress progress output" )
4865 return cmd
4966}
67+
68+ // Resolve image constants
69+ const (
70+ resolveImageAlways = "always"
71+ resolveImageChanged = "changed"
72+ resolveImageNever = "never"
73+ )
74+
75+ const defaultNetworkDriver = "overlay"
76+
77+ // runDeploy is the swarm implementation of docker stack deploy
78+ func runDeploy (ctx context.Context , dockerCLI command.Cli , flags * pflag.FlagSet , opts * deployOptions , cfg * composetypes.Config ) error {
79+ switch opts .resolveImage {
80+ case resolveImageAlways , resolveImageChanged , resolveImageNever :
81+ // valid options.
82+ default :
83+ return errors .Errorf ("Invalid option %s for flag --resolve-image" , opts .resolveImage )
84+ }
85+
86+ // client side image resolution should not be done when the supported
87+ // server version is older than 1.30
88+ if versions .LessThan (dockerCLI .Client ().ClientVersion (), "1.30" ) {
89+ // TODO(thaJeztah): should this error if "opts.ResolveImage" is already other (unsupported) values?
90+ opts .resolveImage = resolveImageNever
91+ }
92+
93+ if opts .detach && ! flags .Changed ("detach" ) {
94+ _ , _ = fmt .Fprintln (dockerCLI .Err (), "Since --detach=false was not specified, tasks will be created in the background.\n " +
95+ "In a future release, --detach=false will become the default." )
96+ }
97+
98+ return deployCompose (ctx , dockerCLI , opts , cfg )
99+ }
100+
101+ // checkDaemonIsSwarmManager does an Info API call to verify that the daemon is
102+ // a swarm manager. This is necessary because we must create networks before we
103+ // create services, but the API call for creating a network does not return a
104+ // proper status code when it can't create a network in the "global" scope.
105+ func checkDaemonIsSwarmManager (ctx context.Context , dockerCli command.Cli ) error {
106+ info , err := dockerCli .Client ().Info (ctx )
107+ if err != nil {
108+ return err
109+ }
110+ if ! info .Swarm .ControlAvailable {
111+ 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" )
112+ }
113+ return nil
114+ }
115+
116+ // pruneServices removes services that are no longer referenced in the source
117+ func pruneServices (ctx context.Context , dockerCLI command.Cli , namespace convert.Namespace , services map [string ]struct {}) {
118+ apiClient := dockerCLI .Client ()
119+
120+ oldServices , err := getStackServices (ctx , apiClient , namespace .Name ())
121+ if err != nil {
122+ _ , _ = fmt .Fprintln (dockerCLI .Err (), "Failed to list services:" , err )
123+ }
124+
125+ toRemove := make ([]swarm.Service , 0 , len (oldServices ))
126+ for _ , service := range oldServices {
127+ if _ , exists := services [namespace .Descope (service .Spec .Name )]; ! exists {
128+ toRemove = append (toRemove , service )
129+ }
130+ }
131+ removeServices (ctx , dockerCLI , toRemove )
132+ }
0 commit comments