Files
docker-cli/cli/command/stack/deploy.go
Sebastiaan van Stijn 4f7c07cfc2 update local code for updated modules
Some tests had to be skipped as there's some issues to address, and
some of the result-types cannot be mocked / stubbed.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-24 10:28:54 +02:00

123 lines
4.4 KiB
Go

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/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"})
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 {
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`)
}
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)
}