github.com/ali-iotechsys/cli@v20.10.0+incompatible/cli/command/stack/swarm/deploy_composefile.go (about)

     1  package swarm
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  
     7  	"github.com/docker/cli/cli/command"
     8  	"github.com/docker/cli/cli/command/stack/options"
     9  	"github.com/docker/cli/cli/compose/convert"
    10  	composetypes "github.com/docker/cli/cli/compose/types"
    11  	"github.com/docker/docker/api/types"
    12  	"github.com/docker/docker/api/types/container"
    13  	"github.com/docker/docker/api/types/swarm"
    14  	apiclient "github.com/docker/docker/client"
    15  	dockerclient "github.com/docker/docker/client"
    16  	"github.com/pkg/errors"
    17  )
    18  
    19  func deployCompose(ctx context.Context, dockerCli command.Cli, opts options.Deploy, config *composetypes.Config) error {
    20  	if err := checkDaemonIsSwarmManager(ctx, dockerCli); err != nil {
    21  		return err
    22  	}
    23  
    24  	namespace := convert.NewNamespace(opts.Namespace)
    25  
    26  	if opts.Prune {
    27  		services := map[string]struct{}{}
    28  		for _, service := range config.Services {
    29  			services[service.Name] = struct{}{}
    30  		}
    31  		pruneServices(ctx, dockerCli, namespace, services)
    32  	}
    33  
    34  	serviceNetworks := getServicesDeclaredNetworks(config.Services)
    35  	networks, externalNetworks := convert.Networks(namespace, config.Networks, serviceNetworks)
    36  	if err := validateExternalNetworks(ctx, dockerCli.Client(), externalNetworks); err != nil {
    37  		return err
    38  	}
    39  	if err := createNetworks(ctx, dockerCli, namespace, networks); err != nil {
    40  		return err
    41  	}
    42  
    43  	secrets, err := convert.Secrets(namespace, config.Secrets)
    44  	if err != nil {
    45  		return err
    46  	}
    47  	if err := createSecrets(ctx, dockerCli, secrets); err != nil {
    48  		return err
    49  	}
    50  
    51  	configs, err := convert.Configs(namespace, config.Configs)
    52  	if err != nil {
    53  		return err
    54  	}
    55  	if err := createConfigs(ctx, dockerCli, configs); err != nil {
    56  		return err
    57  	}
    58  
    59  	services, err := convert.Services(namespace, config, dockerCli.Client())
    60  	if err != nil {
    61  		return err
    62  	}
    63  	return deployServices(ctx, dockerCli, services, namespace, opts.SendRegistryAuth, opts.ResolveImage)
    64  }
    65  
    66  func getServicesDeclaredNetworks(serviceConfigs []composetypes.ServiceConfig) map[string]struct{} {
    67  	serviceNetworks := map[string]struct{}{}
    68  	for _, serviceConfig := range serviceConfigs {
    69  		if len(serviceConfig.Networks) == 0 {
    70  			serviceNetworks["default"] = struct{}{}
    71  			continue
    72  		}
    73  		for network := range serviceConfig.Networks {
    74  			serviceNetworks[network] = struct{}{}
    75  		}
    76  	}
    77  	return serviceNetworks
    78  }
    79  
    80  func validateExternalNetworks(ctx context.Context, client dockerclient.NetworkAPIClient, externalNetworks []string) error {
    81  	for _, networkName := range externalNetworks {
    82  		if !container.NetworkMode(networkName).IsUserDefined() {
    83  			// Networks that are not user defined always exist on all nodes as
    84  			// local-scoped networks, so there's no need to inspect them.
    85  			continue
    86  		}
    87  		network, err := client.NetworkInspect(ctx, networkName, types.NetworkInspectOptions{})
    88  		switch {
    89  		case dockerclient.IsErrNotFound(err):
    90  			return errors.Errorf("network %q is declared as external, but could not be found. You need to create a swarm-scoped network before the stack is deployed", networkName)
    91  		case err != nil:
    92  			return err
    93  		case network.Scope != "swarm":
    94  			return errors.Errorf("network %q is declared as external, but it is not in the right scope: %q instead of \"swarm\"", networkName, network.Scope)
    95  		}
    96  	}
    97  	return nil
    98  }
    99  
   100  func createSecrets(ctx context.Context, dockerCli command.Cli, secrets []swarm.SecretSpec) error {
   101  	client := dockerCli.Client()
   102  
   103  	for _, secretSpec := range secrets {
   104  		secret, _, err := client.SecretInspectWithRaw(ctx, secretSpec.Name)
   105  		switch {
   106  		case err == nil:
   107  			// secret already exists, then we update that
   108  			if err := client.SecretUpdate(ctx, secret.ID, secret.Meta.Version, secretSpec); err != nil {
   109  				return errors.Wrapf(err, "failed to update secret %s", secretSpec.Name)
   110  			}
   111  		case apiclient.IsErrNotFound(err):
   112  			// secret does not exist, then we create a new one.
   113  			fmt.Fprintf(dockerCli.Out(), "Creating secret %s\n", secretSpec.Name)
   114  			if _, err := client.SecretCreate(ctx, secretSpec); err != nil {
   115  				return errors.Wrapf(err, "failed to create secret %s", secretSpec.Name)
   116  			}
   117  		default:
   118  			return err
   119  		}
   120  	}
   121  	return nil
   122  }
   123  
   124  func createConfigs(ctx context.Context, dockerCli command.Cli, configs []swarm.ConfigSpec) error {
   125  	client := dockerCli.Client()
   126  
   127  	for _, configSpec := range configs {
   128  		config, _, err := client.ConfigInspectWithRaw(ctx, configSpec.Name)
   129  		switch {
   130  		case err == nil:
   131  			// config already exists, then we update that
   132  			if err := client.ConfigUpdate(ctx, config.ID, config.Meta.Version, configSpec); err != nil {
   133  				return errors.Wrapf(err, "failed to update config %s", configSpec.Name)
   134  			}
   135  		case apiclient.IsErrNotFound(err):
   136  			// config does not exist, then we create a new one.
   137  			fmt.Fprintf(dockerCli.Out(), "Creating config %s\n", configSpec.Name)
   138  			if _, err := client.ConfigCreate(ctx, configSpec); err != nil {
   139  				return errors.Wrapf(err, "failed to create config %s", configSpec.Name)
   140  			}
   141  		default:
   142  			return err
   143  		}
   144  	}
   145  	return nil
   146  }
   147  
   148  func createNetworks(ctx context.Context, dockerCli command.Cli, namespace convert.Namespace, networks map[string]types.NetworkCreate) error {
   149  	client := dockerCli.Client()
   150  
   151  	existingNetworks, err := getStackNetworks(ctx, client, namespace.Name())
   152  	if err != nil {
   153  		return err
   154  	}
   155  
   156  	existingNetworkMap := make(map[string]types.NetworkResource)
   157  	for _, network := range existingNetworks {
   158  		existingNetworkMap[network.Name] = network
   159  	}
   160  
   161  	for name, createOpts := range networks {
   162  		if _, exists := existingNetworkMap[name]; exists {
   163  			continue
   164  		}
   165  
   166  		if createOpts.Driver == "" {
   167  			createOpts.Driver = defaultNetworkDriver
   168  		}
   169  
   170  		fmt.Fprintf(dockerCli.Out(), "Creating network %s\n", name)
   171  		if _, err := client.NetworkCreate(ctx, name, createOpts); err != nil {
   172  			return errors.Wrapf(err, "failed to create network %s", name)
   173  		}
   174  	}
   175  	return nil
   176  }
   177  
   178  // nolint: gocyclo
   179  func deployServices(ctx context.Context, dockerCli command.Cli, services map[string]swarm.ServiceSpec, namespace convert.Namespace, sendAuth bool, resolveImage string) error {
   180  	apiClient := dockerCli.Client()
   181  	out := dockerCli.Out()
   182  
   183  	existingServices, err := getStackServices(ctx, apiClient, namespace.Name())
   184  	if err != nil {
   185  		return err
   186  	}
   187  
   188  	existingServiceMap := make(map[string]swarm.Service)
   189  	for _, service := range existingServices {
   190  		existingServiceMap[service.Spec.Name] = service
   191  	}
   192  
   193  	for internalName, serviceSpec := range services {
   194  		var (
   195  			name        = namespace.Scope(internalName)
   196  			image       = serviceSpec.TaskTemplate.ContainerSpec.Image
   197  			encodedAuth string
   198  		)
   199  
   200  		if sendAuth {
   201  			// Retrieve encoded auth token from the image reference
   202  			encodedAuth, err = command.RetrieveAuthTokenFromImage(ctx, dockerCli, image)
   203  			if err != nil {
   204  				return err
   205  			}
   206  		}
   207  
   208  		if service, exists := existingServiceMap[name]; exists {
   209  			fmt.Fprintf(out, "Updating service %s (id: %s)\n", name, service.ID)
   210  
   211  			updateOpts := types.ServiceUpdateOptions{EncodedRegistryAuth: encodedAuth}
   212  
   213  			switch resolveImage {
   214  			case ResolveImageAlways:
   215  				// image should be updated by the server using QueryRegistry
   216  				updateOpts.QueryRegistry = true
   217  			case ResolveImageChanged:
   218  				if image != service.Spec.Labels[convert.LabelImage] {
   219  					// Query the registry to resolve digest for the updated image
   220  					updateOpts.QueryRegistry = true
   221  				} else {
   222  					// image has not changed; update the serviceSpec with the
   223  					// existing information that was set by QueryRegistry on the
   224  					// previous deploy. Otherwise this will trigger an incorrect
   225  					// service update.
   226  					serviceSpec.TaskTemplate.ContainerSpec.Image = service.Spec.TaskTemplate.ContainerSpec.Image
   227  				}
   228  			default:
   229  				if image == service.Spec.Labels[convert.LabelImage] {
   230  					// image has not changed; update the serviceSpec with the
   231  					// existing information that was set by QueryRegistry on the
   232  					// previous deploy. Otherwise this will trigger an incorrect
   233  					// service update.
   234  					serviceSpec.TaskTemplate.ContainerSpec.Image = service.Spec.TaskTemplate.ContainerSpec.Image
   235  				}
   236  			}
   237  
   238  			// Stack deploy does not have a `--force` option. Preserve existing
   239  			// ForceUpdate value so that tasks are not re-deployed if not updated.
   240  			// TODO move this to API client?
   241  			serviceSpec.TaskTemplate.ForceUpdate = service.Spec.TaskTemplate.ForceUpdate
   242  
   243  			response, err := apiClient.ServiceUpdate(ctx, service.ID, service.Version, serviceSpec, updateOpts)
   244  			if err != nil {
   245  				return errors.Wrapf(err, "failed to update service %s", name)
   246  			}
   247  
   248  			for _, warning := range response.Warnings {
   249  				fmt.Fprintln(dockerCli.Err(), warning)
   250  			}
   251  		} else {
   252  			fmt.Fprintf(out, "Creating service %s\n", name)
   253  
   254  			createOpts := types.ServiceCreateOptions{EncodedRegistryAuth: encodedAuth}
   255  
   256  			// query registry if flag disabling it was not set
   257  			if resolveImage == ResolveImageAlways || resolveImage == ResolveImageChanged {
   258  				createOpts.QueryRegistry = true
   259  			}
   260  
   261  			if _, err := apiClient.ServiceCreate(ctx, serviceSpec, createOpts); err != nil {
   262  				return errors.Wrapf(err, "failed to create service %s", name)
   263  			}
   264  		}
   265  	}
   266  	return nil
   267  }