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