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 }