github.com/itscaro/cli@v0.0.0-20190705081621-c9db0fe93829/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(
    81  	ctx context.Context,
    82  	client dockerclient.NetworkAPIClient,
    83  	externalNetworks []string,
    84  ) error {
    85  	for _, networkName := range externalNetworks {
    86  		if !container.NetworkMode(networkName).IsUserDefined() {
    87  			// Networks that are not user defined always exist on all nodes as
    88  			// local-scoped networks, so there's no need to inspect them.
    89  			continue
    90  		}
    91  		network, err := client.NetworkInspect(ctx, networkName, types.NetworkInspectOptions{})
    92  		switch {
    93  		case dockerclient.IsErrNotFound(err):
    94  			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)
    95  		case err != nil:
    96  			return err
    97  		case network.Scope != "swarm":
    98  			return errors.Errorf("network %q is declared as external, but it is not in the right scope: %q instead of \"swarm\"", networkName, network.Scope)
    99  		}
   100  	}
   101  	return nil
   102  }
   103  
   104  func createSecrets(
   105  	ctx context.Context,
   106  	dockerCli command.Cli,
   107  	secrets []swarm.SecretSpec,
   108  ) error {
   109  	client := dockerCli.Client()
   110  
   111  	for _, secretSpec := range secrets {
   112  		secret, _, err := client.SecretInspectWithRaw(ctx, secretSpec.Name)
   113  		switch {
   114  		case err == nil:
   115  			// secret already exists, then we update that
   116  			if err := client.SecretUpdate(ctx, secret.ID, secret.Meta.Version, secretSpec); err != nil {
   117  				return errors.Wrapf(err, "failed to update secret %s", secretSpec.Name)
   118  			}
   119  		case apiclient.IsErrNotFound(err):
   120  			// secret does not exist, then we create a new one.
   121  			fmt.Fprintf(dockerCli.Out(), "Creating secret %s\n", secretSpec.Name)
   122  			if _, err := client.SecretCreate(ctx, secretSpec); err != nil {
   123  				return errors.Wrapf(err, "failed to create secret %s", secretSpec.Name)
   124  			}
   125  		default:
   126  			return err
   127  		}
   128  	}
   129  	return nil
   130  }
   131  
   132  func createConfigs(
   133  	ctx context.Context,
   134  	dockerCli command.Cli,
   135  	configs []swarm.ConfigSpec,
   136  ) error {
   137  	client := dockerCli.Client()
   138  
   139  	for _, configSpec := range configs {
   140  		config, _, err := client.ConfigInspectWithRaw(ctx, configSpec.Name)
   141  		switch {
   142  		case err == nil:
   143  			// config already exists, then we update that
   144  			if err := client.ConfigUpdate(ctx, config.ID, config.Meta.Version, configSpec); err != nil {
   145  				return errors.Wrapf(err, "failed to update config %s", configSpec.Name)
   146  			}
   147  		case apiclient.IsErrNotFound(err):
   148  			// config does not exist, then we create a new one.
   149  			fmt.Fprintf(dockerCli.Out(), "Creating config %s\n", configSpec.Name)
   150  			if _, err := client.ConfigCreate(ctx, configSpec); err != nil {
   151  				return errors.Wrapf(err, "failed to create config %s", configSpec.Name)
   152  			}
   153  		default:
   154  			return err
   155  		}
   156  	}
   157  	return nil
   158  }
   159  
   160  func createNetworks(
   161  	ctx context.Context,
   162  	dockerCli command.Cli,
   163  	namespace convert.Namespace,
   164  	networks map[string]types.NetworkCreate,
   165  ) error {
   166  	client := dockerCli.Client()
   167  
   168  	existingNetworks, err := getStackNetworks(ctx, client, namespace.Name())
   169  	if err != nil {
   170  		return err
   171  	}
   172  
   173  	existingNetworkMap := make(map[string]types.NetworkResource)
   174  	for _, network := range existingNetworks {
   175  		existingNetworkMap[network.Name] = network
   176  	}
   177  
   178  	for name, createOpts := range networks {
   179  		if _, exists := existingNetworkMap[name]; exists {
   180  			continue
   181  		}
   182  
   183  		if createOpts.Driver == "" {
   184  			createOpts.Driver = defaultNetworkDriver
   185  		}
   186  
   187  		fmt.Fprintf(dockerCli.Out(), "Creating network %s\n", name)
   188  		if _, err := client.NetworkCreate(ctx, name, createOpts); err != nil {
   189  			return errors.Wrapf(err, "failed to create network %s", name)
   190  		}
   191  	}
   192  	return nil
   193  }
   194  
   195  func deployServices(
   196  	ctx context.Context,
   197  	dockerCli command.Cli,
   198  	services map[string]swarm.ServiceSpec,
   199  	namespace convert.Namespace,
   200  	sendAuth bool,
   201  	resolveImage string,
   202  ) error {
   203  	apiClient := dockerCli.Client()
   204  	out := dockerCli.Out()
   205  
   206  	existingServices, err := getStackServices(ctx, apiClient, namespace.Name())
   207  	if err != nil {
   208  		return err
   209  	}
   210  
   211  	existingServiceMap := make(map[string]swarm.Service)
   212  	for _, service := range existingServices {
   213  		existingServiceMap[service.Spec.Name] = service
   214  	}
   215  
   216  	for internalName, serviceSpec := range services {
   217  		name := namespace.Scope(internalName)
   218  
   219  		encodedAuth := ""
   220  		image := serviceSpec.TaskTemplate.ContainerSpec.Image
   221  		if sendAuth {
   222  			// Retrieve encoded auth token from the image reference
   223  			encodedAuth, err = command.RetrieveAuthTokenFromImage(ctx, dockerCli, image)
   224  			if err != nil {
   225  				return err
   226  			}
   227  		}
   228  
   229  		if service, exists := existingServiceMap[name]; exists {
   230  			fmt.Fprintf(out, "Updating service %s (id: %s)\n", name, service.ID)
   231  
   232  			updateOpts := types.ServiceUpdateOptions{EncodedRegistryAuth: encodedAuth}
   233  
   234  			switch {
   235  			case resolveImage == ResolveImageAlways || (resolveImage == ResolveImageChanged && image != service.Spec.Labels[convert.LabelImage]):
   236  				// image should be updated by the server using QueryRegistry
   237  				updateOpts.QueryRegistry = true
   238  			case image == service.Spec.Labels[convert.LabelImage]:
   239  				// image has not changed; update the serviceSpec with the
   240  				// existing information that was set by QueryRegistry on the
   241  				// previous deploy. Otherwise this will trigger an incorrect
   242  				// service update.
   243  				serviceSpec.TaskTemplate.ContainerSpec.Image = service.Spec.TaskTemplate.ContainerSpec.Image
   244  			}
   245  
   246  			// Stack deploy does not have a `--force` option. Preserve existing ForceUpdate
   247  			// value so that tasks are not re-deployed if not updated.
   248  			// TODO move this to API client?
   249  			serviceSpec.TaskTemplate.ForceUpdate = service.Spec.TaskTemplate.ForceUpdate
   250  
   251  			response, err := apiClient.ServiceUpdate(
   252  				ctx,
   253  				service.ID,
   254  				service.Version,
   255  				serviceSpec,
   256  				updateOpts,
   257  			)
   258  			if err != nil {
   259  				return errors.Wrapf(err, "failed to update service %s", name)
   260  			}
   261  
   262  			for _, warning := range response.Warnings {
   263  				fmt.Fprintln(dockerCli.Err(), warning)
   264  			}
   265  		} else {
   266  			fmt.Fprintf(out, "Creating service %s\n", name)
   267  
   268  			createOpts := types.ServiceCreateOptions{EncodedRegistryAuth: encodedAuth}
   269  
   270  			// query registry if flag disabling it was not set
   271  			if resolveImage == ResolveImageAlways || resolveImage == ResolveImageChanged {
   272  				createOpts.QueryRegistry = true
   273  			}
   274  
   275  			if _, err := apiClient.ServiceCreate(ctx, serviceSpec, createOpts); err != nil {
   276  				return errors.Wrapf(err, "failed to create service %s", name)
   277  			}
   278  		}
   279  	}
   280  	return nil
   281  }