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 }