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 }