github.com/kobeld/docker@v1.12.0-rc1/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  }
    27  
    28  func newDeployCommand(dockerCli *client.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",
    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  	return cmd
    45  }
    46  
    47  func runDeploy(dockerCli *client.DockerCli, opts deployOptions) error {
    48  	bundle, err := loadBundlefile(dockerCli.Err(), opts.namespace, opts.bundlefile)
    49  	if err != nil {
    50  		return err
    51  	}
    52  
    53  	networks := getUniqueNetworkNames(bundle.Services)
    54  	ctx := context.Background()
    55  
    56  	if err := updateNetworks(ctx, dockerCli, networks, opts.namespace); err != nil {
    57  		return err
    58  	}
    59  	return deployServices(ctx, dockerCli, bundle.Services, opts.namespace)
    60  }
    61  
    62  func getUniqueNetworkNames(services map[string]bundlefile.Service) []string {
    63  	networkSet := make(map[string]bool)
    64  	for _, service := range services {
    65  		for _, network := range service.Networks {
    66  			networkSet[network] = true
    67  		}
    68  	}
    69  
    70  	networks := []string{}
    71  	for network := range networkSet {
    72  		networks = append(networks, network)
    73  	}
    74  	return networks
    75  }
    76  
    77  func updateNetworks(
    78  	ctx context.Context,
    79  	dockerCli *client.DockerCli,
    80  	networks []string,
    81  	namespace string,
    82  ) error {
    83  	client := dockerCli.Client()
    84  
    85  	existingNetworks, err := getNetworks(ctx, client, namespace)
    86  	if err != nil {
    87  		return err
    88  	}
    89  
    90  	existingNetworkMap := make(map[string]types.NetworkResource)
    91  	for _, network := range existingNetworks {
    92  		existingNetworkMap[network.Name] = network
    93  	}
    94  
    95  	createOpts := types.NetworkCreate{
    96  		Labels: getStackLabels(namespace, nil),
    97  		Driver: defaultNetworkDriver,
    98  		// TODO: remove when engine-api uses omitempty for IPAM
    99  		IPAM: network.IPAM{Driver: "default"},
   100  	}
   101  
   102  	for _, internalName := range networks {
   103  		name := fmt.Sprintf("%s_%s", namespace, internalName)
   104  
   105  		if _, exists := existingNetworkMap[name]; exists {
   106  			continue
   107  		}
   108  		fmt.Fprintf(dockerCli.Out(), "Creating network %s\n", name)
   109  		if _, err := client.NetworkCreate(ctx, name, createOpts); err != nil {
   110  			return err
   111  		}
   112  	}
   113  	return nil
   114  }
   115  
   116  func convertNetworks(networks []string, namespace string, name string) []swarm.NetworkAttachmentConfig {
   117  	nets := []swarm.NetworkAttachmentConfig{}
   118  	for _, network := range networks {
   119  		nets = append(nets, swarm.NetworkAttachmentConfig{
   120  			Target:  namespace + "_" + network,
   121  			Aliases: []string{name},
   122  		})
   123  	}
   124  	return nets
   125  }
   126  
   127  func deployServices(
   128  	ctx context.Context,
   129  	dockerCli *client.DockerCli,
   130  	services map[string]bundlefile.Service,
   131  	namespace string,
   132  ) error {
   133  	apiClient := dockerCli.Client()
   134  	out := dockerCli.Out()
   135  
   136  	existingServices, err := getServices(ctx, apiClient, namespace)
   137  	if err != nil {
   138  		return err
   139  	}
   140  
   141  	existingServiceMap := make(map[string]swarm.Service)
   142  	for _, service := range existingServices {
   143  		existingServiceMap[service.Spec.Name] = service
   144  	}
   145  
   146  	for internalName, service := range services {
   147  		name := fmt.Sprintf("%s_%s", namespace, internalName)
   148  
   149  		var ports []swarm.PortConfig
   150  		for _, portSpec := range service.Ports {
   151  			ports = append(ports, swarm.PortConfig{
   152  				Protocol:   swarm.PortConfigProtocol(portSpec.Protocol),
   153  				TargetPort: portSpec.Port,
   154  			})
   155  		}
   156  
   157  		serviceSpec := swarm.ServiceSpec{
   158  			Annotations: swarm.Annotations{
   159  				Name:   name,
   160  				Labels: getStackLabels(namespace, service.Labels),
   161  			},
   162  			TaskTemplate: swarm.TaskSpec{
   163  				ContainerSpec: swarm.ContainerSpec{
   164  					Image:   service.Image,
   165  					Command: service.Command,
   166  					Args:    service.Args,
   167  					Env:     service.Env,
   168  				},
   169  			},
   170  			EndpointSpec: &swarm.EndpointSpec{
   171  				Ports: ports,
   172  			},
   173  			Networks: convertNetworks(service.Networks, namespace, internalName),
   174  		}
   175  
   176  		cspec := &serviceSpec.TaskTemplate.ContainerSpec
   177  		if service.WorkingDir != nil {
   178  			cspec.Dir = *service.WorkingDir
   179  		}
   180  		if service.User != nil {
   181  			cspec.User = *service.User
   182  		}
   183  
   184  		if service, exists := existingServiceMap[name]; exists {
   185  			fmt.Fprintf(out, "Updating service %s (id: %s)\n", name, service.ID)
   186  
   187  			if err := apiClient.ServiceUpdate(
   188  				ctx,
   189  				service.ID,
   190  				service.Version,
   191  				serviceSpec,
   192  			); err != nil {
   193  				return err
   194  			}
   195  		} else {
   196  			fmt.Fprintf(out, "Creating service %s\n", name)
   197  
   198  			if _, err := apiClient.ServiceCreate(ctx, serviceSpec); err != nil {
   199  				return err
   200  			}
   201  		}
   202  	}
   203  
   204  	return nil
   205  }