github.com/vieux/docker@v0.6.3-0.20161004191708-e097c2a938c7/cli/command/stack/deploy.go (about)

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