github.com/itscaro/cli@v0.0.0-20190705081621-c9db0fe93829/cli/command/stack/swarm/deploy_composefile.go (about) 1 package swarm 2 3 import ( 4 "context" 5 "fmt" 6 7 "github.com/docker/cli/cli/command" 8 "github.com/docker/cli/cli/command/stack/options" 9 "github.com/docker/cli/cli/compose/convert" 10 composetypes "github.com/docker/cli/cli/compose/types" 11 "github.com/docker/docker/api/types" 12 "github.com/docker/docker/api/types/container" 13 "github.com/docker/docker/api/types/swarm" 14 apiclient "github.com/docker/docker/client" 15 dockerclient "github.com/docker/docker/client" 16 "github.com/pkg/errors" 17 ) 18 19 func deployCompose(ctx context.Context, dockerCli command.Cli, opts options.Deploy, config *composetypes.Config) error { 20 if err := checkDaemonIsSwarmManager(ctx, dockerCli); err != nil { 21 return err 22 } 23 24 namespace := convert.NewNamespace(opts.Namespace) 25 26 if opts.Prune { 27 services := map[string]struct{}{} 28 for _, service := range config.Services { 29 services[service.Name] = struct{}{} 30 } 31 pruneServices(ctx, dockerCli, namespace, services) 32 } 33 34 serviceNetworks := getServicesDeclaredNetworks(config.Services) 35 networks, externalNetworks := convert.Networks(namespace, config.Networks, serviceNetworks) 36 if err := validateExternalNetworks(ctx, dockerCli.Client(), externalNetworks); err != nil { 37 return err 38 } 39 if err := createNetworks(ctx, dockerCli, namespace, networks); err != nil { 40 return err 41 } 42 43 secrets, err := convert.Secrets(namespace, config.Secrets) 44 if err != nil { 45 return err 46 } 47 if err := createSecrets(ctx, dockerCli, secrets); err != nil { 48 return err 49 } 50 51 configs, err := convert.Configs(namespace, config.Configs) 52 if err != nil { 53 return err 54 } 55 if err := createConfigs(ctx, dockerCli, configs); err != nil { 56 return err 57 } 58 59 services, err := convert.Services(namespace, config, dockerCli.Client()) 60 if err != nil { 61 return err 62 } 63 return deployServices(ctx, dockerCli, services, namespace, opts.SendRegistryAuth, opts.ResolveImage) 64 } 65 66 func getServicesDeclaredNetworks(serviceConfigs []composetypes.ServiceConfig) map[string]struct{} { 67 serviceNetworks := map[string]struct{}{} 68 for _, serviceConfig := range serviceConfigs { 69 if len(serviceConfig.Networks) == 0 { 70 serviceNetworks["default"] = struct{}{} 71 continue 72 } 73 for network := range serviceConfig.Networks { 74 serviceNetworks[network] = struct{}{} 75 } 76 } 77 return serviceNetworks 78 } 79 80 func validateExternalNetworks( 81 ctx context.Context, 82 client dockerclient.NetworkAPIClient, 83 externalNetworks []string, 84 ) error { 85 for _, networkName := range externalNetworks { 86 if !container.NetworkMode(networkName).IsUserDefined() { 87 // Networks that are not user defined always exist on all nodes as 88 // local-scoped networks, so there's no need to inspect them. 89 continue 90 } 91 network, err := client.NetworkInspect(ctx, networkName, types.NetworkInspectOptions{}) 92 switch { 93 case dockerclient.IsErrNotFound(err): 94 return errors.Errorf("network %q is declared as external, but could not be found. You need to create a swarm-scoped network before the stack is deployed", networkName) 95 case err != nil: 96 return err 97 case network.Scope != "swarm": 98 return errors.Errorf("network %q is declared as external, but it is not in the right scope: %q instead of \"swarm\"", networkName, network.Scope) 99 } 100 } 101 return nil 102 } 103 104 func createSecrets( 105 ctx context.Context, 106 dockerCli command.Cli, 107 secrets []swarm.SecretSpec, 108 ) error { 109 client := dockerCli.Client() 110 111 for _, secretSpec := range secrets { 112 secret, _, err := client.SecretInspectWithRaw(ctx, secretSpec.Name) 113 switch { 114 case err == nil: 115 // secret already exists, then we update that 116 if err := client.SecretUpdate(ctx, secret.ID, secret.Meta.Version, secretSpec); err != nil { 117 return errors.Wrapf(err, "failed to update secret %s", secretSpec.Name) 118 } 119 case apiclient.IsErrNotFound(err): 120 // secret does not exist, then we create a new one. 121 fmt.Fprintf(dockerCli.Out(), "Creating secret %s\n", secretSpec.Name) 122 if _, err := client.SecretCreate(ctx, secretSpec); err != nil { 123 return errors.Wrapf(err, "failed to create secret %s", secretSpec.Name) 124 } 125 default: 126 return err 127 } 128 } 129 return nil 130 } 131 132 func createConfigs( 133 ctx context.Context, 134 dockerCli command.Cli, 135 configs []swarm.ConfigSpec, 136 ) error { 137 client := dockerCli.Client() 138 139 for _, configSpec := range configs { 140 config, _, err := client.ConfigInspectWithRaw(ctx, configSpec.Name) 141 switch { 142 case err == nil: 143 // config already exists, then we update that 144 if err := client.ConfigUpdate(ctx, config.ID, config.Meta.Version, configSpec); err != nil { 145 return errors.Wrapf(err, "failed to update config %s", configSpec.Name) 146 } 147 case apiclient.IsErrNotFound(err): 148 // config does not exist, then we create a new one. 149 fmt.Fprintf(dockerCli.Out(), "Creating config %s\n", configSpec.Name) 150 if _, err := client.ConfigCreate(ctx, configSpec); err != nil { 151 return errors.Wrapf(err, "failed to create config %s", configSpec.Name) 152 } 153 default: 154 return err 155 } 156 } 157 return nil 158 } 159 160 func createNetworks( 161 ctx context.Context, 162 dockerCli command.Cli, 163 namespace convert.Namespace, 164 networks map[string]types.NetworkCreate, 165 ) error { 166 client := dockerCli.Client() 167 168 existingNetworks, err := getStackNetworks(ctx, client, namespace.Name()) 169 if err != nil { 170 return err 171 } 172 173 existingNetworkMap := make(map[string]types.NetworkResource) 174 for _, network := range existingNetworks { 175 existingNetworkMap[network.Name] = network 176 } 177 178 for name, createOpts := range networks { 179 if _, exists := existingNetworkMap[name]; exists { 180 continue 181 } 182 183 if createOpts.Driver == "" { 184 createOpts.Driver = defaultNetworkDriver 185 } 186 187 fmt.Fprintf(dockerCli.Out(), "Creating network %s\n", name) 188 if _, err := client.NetworkCreate(ctx, name, createOpts); err != nil { 189 return errors.Wrapf(err, "failed to create network %s", name) 190 } 191 } 192 return nil 193 } 194 195 func deployServices( 196 ctx context.Context, 197 dockerCli command.Cli, 198 services map[string]swarm.ServiceSpec, 199 namespace convert.Namespace, 200 sendAuth bool, 201 resolveImage string, 202 ) error { 203 apiClient := dockerCli.Client() 204 out := dockerCli.Out() 205 206 existingServices, err := getStackServices(ctx, apiClient, namespace.Name()) 207 if err != nil { 208 return err 209 } 210 211 existingServiceMap := make(map[string]swarm.Service) 212 for _, service := range existingServices { 213 existingServiceMap[service.Spec.Name] = service 214 } 215 216 for internalName, serviceSpec := range services { 217 name := namespace.Scope(internalName) 218 219 encodedAuth := "" 220 image := serviceSpec.TaskTemplate.ContainerSpec.Image 221 if sendAuth { 222 // Retrieve encoded auth token from the image reference 223 encodedAuth, err = command.RetrieveAuthTokenFromImage(ctx, dockerCli, image) 224 if err != nil { 225 return err 226 } 227 } 228 229 if service, exists := existingServiceMap[name]; exists { 230 fmt.Fprintf(out, "Updating service %s (id: %s)\n", name, service.ID) 231 232 updateOpts := types.ServiceUpdateOptions{EncodedRegistryAuth: encodedAuth} 233 234 switch { 235 case resolveImage == ResolveImageAlways || (resolveImage == ResolveImageChanged && image != service.Spec.Labels[convert.LabelImage]): 236 // image should be updated by the server using QueryRegistry 237 updateOpts.QueryRegistry = true 238 case image == service.Spec.Labels[convert.LabelImage]: 239 // image has not changed; update the serviceSpec with the 240 // existing information that was set by QueryRegistry on the 241 // previous deploy. Otherwise this will trigger an incorrect 242 // service update. 243 serviceSpec.TaskTemplate.ContainerSpec.Image = service.Spec.TaskTemplate.ContainerSpec.Image 244 } 245 246 // Stack deploy does not have a `--force` option. Preserve existing ForceUpdate 247 // value so that tasks are not re-deployed if not updated. 248 // TODO move this to API client? 249 serviceSpec.TaskTemplate.ForceUpdate = service.Spec.TaskTemplate.ForceUpdate 250 251 response, err := apiClient.ServiceUpdate( 252 ctx, 253 service.ID, 254 service.Version, 255 serviceSpec, 256 updateOpts, 257 ) 258 if err != nil { 259 return errors.Wrapf(err, "failed to update service %s", name) 260 } 261 262 for _, warning := range response.Warnings { 263 fmt.Fprintln(dockerCli.Err(), warning) 264 } 265 } else { 266 fmt.Fprintf(out, "Creating service %s\n", name) 267 268 createOpts := types.ServiceCreateOptions{EncodedRegistryAuth: encodedAuth} 269 270 // query registry if flag disabling it was not set 271 if resolveImage == ResolveImageAlways || resolveImage == ResolveImageChanged { 272 createOpts.QueryRegistry = true 273 } 274 275 if _, err := apiClient.ServiceCreate(ctx, serviceSpec, createOpts); err != nil { 276 return errors.Wrapf(err, "failed to create service %s", name) 277 } 278 } 279 } 280 return nil 281 }