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 }