github.com/kim0/docker@v0.6.2-0.20161130212042-4addda3f07e7/cli/command/stack/deploy.go (about)

     1  package stack
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  
     7  	"github.com/spf13/cobra"
     8  	"golang.org/x/net/context"
     9  
    10  	"github.com/docker/docker/api/types"
    11  	"github.com/docker/docker/api/types/swarm"
    12  	"github.com/docker/docker/cli"
    13  	"github.com/docker/docker/cli/command"
    14  	"github.com/docker/docker/cli/command/bundlefile"
    15  )
    16  
    17  const (
    18  	defaultNetworkDriver = "overlay"
    19  )
    20  
    21  type deployOptions struct {
    22  	bundlefile       string
    23  	namespace        string
    24  	sendRegistryAuth bool
    25  }
    26  
    27  func newDeployCommand(dockerCli *command.DockerCli) *cobra.Command {
    28  	var opts deployOptions
    29  
    30  	cmd := &cobra.Command{
    31  		Use:     "deploy [OPTIONS] STACK",
    32  		Aliases: []string{"up"},
    33  		Short:   "Create and update a stack from a Distributed Application Bundle (DAB)",
    34  		Args:    cli.ExactArgs(1),
    35  		RunE: func(cmd *cobra.Command, args []string) error {
    36  			opts.namespace = strings.TrimSuffix(args[0], ".dab")
    37  			return runDeploy(dockerCli, opts)
    38  		},
    39  	}
    40  
    41  	flags := cmd.Flags()
    42  	addBundlefileFlag(&opts.bundlefile, flags)
    43  	addRegistryAuthFlag(&opts.sendRegistryAuth, flags)
    44  	return cmd
    45  }
    46  
    47  func runDeploy(dockerCli *command.DockerCli, opts deployOptions) error {
    48  	bundle, err := loadBundlefile(dockerCli.Err(), opts.namespace, opts.bundlefile)
    49  	if err != nil {
    50  		return err
    51  	}
    52  
    53  	info, err := dockerCli.Client().Info(context.Background())
    54  	if err != nil {
    55  		return err
    56  	}
    57  	if !info.Swarm.ControlAvailable {
    58  		return fmt.Errorf("This node is not a swarm manager. Use \"docker swarm init\" or \"docker swarm join\" to connect this node to swarm and try again.")
    59  	}
    60  
    61  	networks := getUniqueNetworkNames(bundle.Services)
    62  	ctx := context.Background()
    63  
    64  	if err := updateNetworks(ctx, dockerCli, networks, opts.namespace); err != nil {
    65  		return err
    66  	}
    67  	return deployServices(ctx, dockerCli, bundle.Services, opts.namespace, opts.sendRegistryAuth)
    68  }
    69  
    70  func getUniqueNetworkNames(services map[string]bundlefile.Service) []string {
    71  	networkSet := make(map[string]bool)
    72  	for _, service := range services {
    73  		for _, network := range service.Networks {
    74  			networkSet[network] = true
    75  		}
    76  	}
    77  
    78  	networks := []string{}
    79  	for network := range networkSet {
    80  		networks = append(networks, network)
    81  	}
    82  	return networks
    83  }
    84  
    85  func updateNetworks(
    86  	ctx context.Context,
    87  	dockerCli *command.DockerCli,
    88  	networks []string,
    89  	namespace string,
    90  ) error {
    91  	client := dockerCli.Client()
    92  
    93  	existingNetworks, err := getNetworks(ctx, client, namespace)
    94  	if err != nil {
    95  		return err
    96  	}
    97  
    98  	existingNetworkMap := make(map[string]types.NetworkResource)
    99  	for _, network := range existingNetworks {
   100  		existingNetworkMap[network.Name] = network
   101  	}
   102  
   103  	createOpts := types.NetworkCreate{
   104  		Labels: getStackLabels(namespace, nil),
   105  		Driver: defaultNetworkDriver,
   106  	}
   107  
   108  	for _, internalName := range networks {
   109  		name := fmt.Sprintf("%s_%s", namespace, internalName)
   110  
   111  		if _, exists := existingNetworkMap[name]; exists {
   112  			continue
   113  		}
   114  		fmt.Fprintf(dockerCli.Out(), "Creating network %s\n", name)
   115  		if _, err := client.NetworkCreate(ctx, name, createOpts); err != nil {
   116  			return err
   117  		}
   118  	}
   119  	return nil
   120  }
   121  
   122  func convertNetworks(networks []string, namespace string, name string) []swarm.NetworkAttachmentConfig {
   123  	nets := []swarm.NetworkAttachmentConfig{}
   124  	for _, network := range networks {
   125  		nets = append(nets, swarm.NetworkAttachmentConfig{
   126  			Target:  namespace + "_" + network,
   127  			Aliases: []string{name},
   128  		})
   129  	}
   130  	return nets
   131  }
   132  
   133  func deployServices(
   134  	ctx context.Context,
   135  	dockerCli *command.DockerCli,
   136  	services map[string]bundlefile.Service,
   137  	namespace string,
   138  	sendAuth bool,
   139  ) error {
   140  	apiClient := dockerCli.Client()
   141  	out := dockerCli.Out()
   142  
   143  	existingServices, err := getServices(ctx, apiClient, namespace)
   144  	if err != nil {
   145  		return err
   146  	}
   147  
   148  	existingServiceMap := make(map[string]swarm.Service)
   149  	for _, service := range existingServices {
   150  		existingServiceMap[service.Spec.Name] = service
   151  	}
   152  
   153  	for internalName, service := range services {
   154  		name := fmt.Sprintf("%s_%s", namespace, internalName)
   155  
   156  		var ports []swarm.PortConfig
   157  		for _, portSpec := range service.Ports {
   158  			ports = append(ports, swarm.PortConfig{
   159  				Protocol:   swarm.PortConfigProtocol(portSpec.Protocol),
   160  				TargetPort: portSpec.Port,
   161  			})
   162  		}
   163  
   164  		serviceSpec := swarm.ServiceSpec{
   165  			Annotations: swarm.Annotations{
   166  				Name:   name,
   167  				Labels: getStackLabels(namespace, service.Labels),
   168  			},
   169  			TaskTemplate: swarm.TaskSpec{
   170  				ContainerSpec: swarm.ContainerSpec{
   171  					Image:   service.Image,
   172  					Command: service.Command,
   173  					Args:    service.Args,
   174  					Env:     service.Env,
   175  					// Service Labels will not be copied to Containers
   176  					// automatically during the deployment so we apply
   177  					// it here.
   178  					Labels: getStackLabels(namespace, nil),
   179  				},
   180  			},
   181  			EndpointSpec: &swarm.EndpointSpec{
   182  				Ports: ports,
   183  			},
   184  			Networks: convertNetworks(service.Networks, namespace, internalName),
   185  		}
   186  
   187  		cspec := &serviceSpec.TaskTemplate.ContainerSpec
   188  		if service.WorkingDir != nil {
   189  			cspec.Dir = *service.WorkingDir
   190  		}
   191  		if service.User != nil {
   192  			cspec.User = *service.User
   193  		}
   194  
   195  		encodedAuth := ""
   196  		if sendAuth {
   197  			// Retrieve encoded auth token from the image reference
   198  			image := serviceSpec.TaskTemplate.ContainerSpec.Image
   199  			encodedAuth, err = command.RetrieveAuthTokenFromImage(ctx, dockerCli, image)
   200  			if err != nil {
   201  				return err
   202  			}
   203  		}
   204  
   205  		if service, exists := existingServiceMap[name]; exists {
   206  			fmt.Fprintf(out, "Updating service %s (id: %s)\n", name, service.ID)
   207  
   208  			updateOpts := types.ServiceUpdateOptions{}
   209  			if sendAuth {
   210  				updateOpts.EncodedRegistryAuth = encodedAuth
   211  			}
   212  			if err := apiClient.ServiceUpdate(
   213  				ctx,
   214  				service.ID,
   215  				service.Version,
   216  				serviceSpec,
   217  				updateOpts,
   218  			); err != nil {
   219  				return err
   220  			}
   221  		} else {
   222  			fmt.Fprintf(out, "Creating service %s\n", name)
   223  
   224  			createOpts := types.ServiceCreateOptions{}
   225  			if sendAuth {
   226  				createOpts.EncodedRegistryAuth = encodedAuth
   227  			}
   228  			if _, err := apiClient.ServiceCreate(ctx, serviceSpec, createOpts); err != nil {
   229  				return err
   230  			}
   231  		}
   232  	}
   233  
   234  	return nil
   235  }