diff --git a/cli/command/stack/deploy.go b/cli/command/stack/deploy.go index d18a43484d..a5edd2bd67 100644 --- a/cli/command/stack/deploy.go +++ b/cli/command/stack/deploy.go @@ -7,6 +7,7 @@ import ( "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/compose/convert" "github.com/docker/docker/api/types/swarm" + "github.com/docker/docker/api/types/versions" "github.com/pkg/errors" "github.com/spf13/cobra" "golang.org/x/net/context" @@ -21,6 +22,7 @@ type deployOptions struct { composefile string namespace string sendRegistryAuth bool + noResolveImage bool prune bool } @@ -44,12 +46,19 @@ func newDeployCommand(dockerCli command.Cli) *cobra.Command { addRegistryAuthFlag(&opts.sendRegistryAuth, flags) flags.BoolVar(&opts.prune, "prune", false, "Prune services that are no longer referenced") flags.SetAnnotation("prune", "version", []string{"1.27"}) + flags.BoolVar(&opts.noResolveImage, "no-resolve-image", false, "Do not query the registry to resolve image digest and supported platforms") + flags.SetAnnotation("no-resolve-image", "version", []string{"1.30"}) return cmd } func runDeploy(dockerCli command.Cli, opts deployOptions) error { ctx := context.Background() + // image resolution should not happen for clients older than v1.30 + if versions.LessThan(dockerCli.Client().ClientVersion(), "1.30") { + opts.noResolveImage = true + } + switch { case opts.bundlefile == "" && opts.composefile == "": return errors.Errorf("Please specify either a bundle file (with --bundle-file) or a Compose file (with --compose-file).") diff --git a/cli/command/stack/deploy_bundlefile.go b/cli/command/stack/deploy_bundlefile.go index 2f2a9aa042..9c3ba25954 100644 --- a/cli/command/stack/deploy_bundlefile.go +++ b/cli/command/stack/deploy_bundlefile.go @@ -87,5 +87,5 @@ func deployBundle(ctx context.Context, dockerCli command.Cli, opts deployOptions if err := createNetworks(ctx, dockerCli, namespace, networks); err != nil { return err } - return deployServices(ctx, dockerCli, services, namespace, opts.sendRegistryAuth) + return deployServices(ctx, dockerCli, services, namespace, opts.sendRegistryAuth, opts.noResolveImage) } diff --git a/cli/command/stack/deploy_composefile.go b/cli/command/stack/deploy_composefile.go index 145da67daa..297594dfee 100644 --- a/cli/command/stack/deploy_composefile.go +++ b/cli/command/stack/deploy_composefile.go @@ -92,7 +92,7 @@ func deployCompose(ctx context.Context, dockerCli command.Cli, opts deployOption if err != nil { return err } - return deployServices(ctx, dockerCli, services, namespace, opts.sendRegistryAuth) + return deployServices(ctx, dockerCli, services, namespace, opts.sendRegistryAuth, opts.noResolveImage) } func getServicesDeclaredNetworks(serviceConfigs []composetypes.ServiceConfig) map[string]struct{} { @@ -283,6 +283,7 @@ func deployServices( services map[string]swarm.ServiceSpec, namespace convert.Namespace, sendAuth bool, + noResolveImage bool, ) error { apiClient := dockerCli.Client() out := dockerCli.Out() @@ -301,9 +302,9 @@ func deployServices( name := namespace.Scope(internalName) encodedAuth := "" + image := serviceSpec.TaskTemplate.ContainerSpec.Image if sendAuth { // Retrieve encoded auth token from the image reference - image := serviceSpec.TaskTemplate.ContainerSpec.Image encodedAuth, err = command.RetrieveAuthTokenFromImage(ctx, dockerCli, image) if err != nil { return err @@ -313,12 +314,20 @@ func deployServices( if service, exists := existingServiceMap[name]; exists { fmt.Fprintf(out, "Updating service %s (id: %s)\n", name, service.ID) + updateOpts := types.ServiceUpdateOptions{EncodedRegistryAuth: encodedAuth} + + if image != service.Spec.Labels["com.docker.stack.image"] { + if !noResolveImage { + updateOpts.QueryRegistry = true + } + } + response, err := apiClient.ServiceUpdate( ctx, service.ID, service.Version, serviceSpec, - types.ServiceUpdateOptions{EncodedRegistryAuth: encodedAuth}, + updateOpts, ) if err != nil { return errors.Wrapf(err, "failed to update service %s", name) @@ -331,6 +340,12 @@ func deployServices( fmt.Fprintf(out, "Creating service %s\n", name) createOpts := types.ServiceCreateOptions{EncodedRegistryAuth: encodedAuth} + + // query registry if flag disabling it was not set + if !noResolveImage { + createOpts.QueryRegistry = true + } + if _, err := apiClient.ServiceCreate(ctx, serviceSpec, createOpts); err != nil { return errors.Wrapf(err, "failed to create service %s", name) } diff --git a/cli/compose/convert/service.go b/cli/compose/convert/service.go index 0fef936a22..cf919488f9 100644 --- a/cli/compose/convert/service.go +++ b/cli/compose/convert/service.go @@ -45,6 +45,12 @@ func Services( if err != nil { return nil, errors.Wrapf(err, "service %s", service.Name) } + // add an image label to serviceSpec + if serviceSpec.Labels == nil { + serviceSpec.Labels = make(map[string]string) + } + serviceSpec.Labels["com.docker.stack.image"] = service.Image + result[service.Name] = serviceSpec }