github.com/ali-iotechsys/cli@v20.10.0+incompatible/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(ctx context.Context, client dockerclient.NetworkAPIClient, externalNetworks []string) error { 81 for _, networkName := range externalNetworks { 82 if !container.NetworkMode(networkName).IsUserDefined() { 83 // Networks that are not user defined always exist on all nodes as 84 // local-scoped networks, so there's no need to inspect them. 85 continue 86 } 87 network, err := client.NetworkInspect(ctx, networkName, types.NetworkInspectOptions{}) 88 switch { 89 case dockerclient.IsErrNotFound(err): 90 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) 91 case err != nil: 92 return err 93 case network.Scope != "swarm": 94 return errors.Errorf("network %q is declared as external, but it is not in the right scope: %q instead of \"swarm\"", networkName, network.Scope) 95 } 96 } 97 return nil 98 } 99 100 func createSecrets(ctx context.Context, dockerCli command.Cli, secrets []swarm.SecretSpec) error { 101 client := dockerCli.Client() 102 103 for _, secretSpec := range secrets { 104 secret, _, err := client.SecretInspectWithRaw(ctx, secretSpec.Name) 105 switch { 106 case err == nil: 107 // secret already exists, then we update that 108 if err := client.SecretUpdate(ctx, secret.ID, secret.Meta.Version, secretSpec); err != nil { 109 return errors.Wrapf(err, "failed to update secret %s", secretSpec.Name) 110 } 111 case apiclient.IsErrNotFound(err): 112 // secret does not exist, then we create a new one. 113 fmt.Fprintf(dockerCli.Out(), "Creating secret %s\n", secretSpec.Name) 114 if _, err := client.SecretCreate(ctx, secretSpec); err != nil { 115 return errors.Wrapf(err, "failed to create secret %s", secretSpec.Name) 116 } 117 default: 118 return err 119 } 120 } 121 return nil 122 } 123 124 func createConfigs(ctx context.Context, dockerCli command.Cli, configs []swarm.ConfigSpec) error { 125 client := dockerCli.Client() 126 127 for _, configSpec := range configs { 128 config, _, err := client.ConfigInspectWithRaw(ctx, configSpec.Name) 129 switch { 130 case err == nil: 131 // config already exists, then we update that 132 if err := client.ConfigUpdate(ctx, config.ID, config.Meta.Version, configSpec); err != nil { 133 return errors.Wrapf(err, "failed to update config %s", configSpec.Name) 134 } 135 case apiclient.IsErrNotFound(err): 136 // config does not exist, then we create a new one. 137 fmt.Fprintf(dockerCli.Out(), "Creating config %s\n", configSpec.Name) 138 if _, err := client.ConfigCreate(ctx, configSpec); err != nil { 139 return errors.Wrapf(err, "failed to create config %s", configSpec.Name) 140 } 141 default: 142 return err 143 } 144 } 145 return nil 146 } 147 148 func createNetworks(ctx context.Context, dockerCli command.Cli, namespace convert.Namespace, networks map[string]types.NetworkCreate) error { 149 client := dockerCli.Client() 150 151 existingNetworks, err := getStackNetworks(ctx, client, namespace.Name()) 152 if err != nil { 153 return err 154 } 155 156 existingNetworkMap := make(map[string]types.NetworkResource) 157 for _, network := range existingNetworks { 158 existingNetworkMap[network.Name] = network 159 } 160 161 for name, createOpts := range networks { 162 if _, exists := existingNetworkMap[name]; exists { 163 continue 164 } 165 166 if createOpts.Driver == "" { 167 createOpts.Driver = defaultNetworkDriver 168 } 169 170 fmt.Fprintf(dockerCli.Out(), "Creating network %s\n", name) 171 if _, err := client.NetworkCreate(ctx, name, createOpts); err != nil { 172 return errors.Wrapf(err, "failed to create network %s", name) 173 } 174 } 175 return nil 176 } 177 178 // nolint: gocyclo 179 func deployServices(ctx context.Context, dockerCli command.Cli, services map[string]swarm.ServiceSpec, namespace convert.Namespace, sendAuth bool, resolveImage string) error { 180 apiClient := dockerCli.Client() 181 out := dockerCli.Out() 182 183 existingServices, err := getStackServices(ctx, apiClient, namespace.Name()) 184 if err != nil { 185 return err 186 } 187 188 existingServiceMap := make(map[string]swarm.Service) 189 for _, service := range existingServices { 190 existingServiceMap[service.Spec.Name] = service 191 } 192 193 for internalName, serviceSpec := range services { 194 var ( 195 name = namespace.Scope(internalName) 196 image = serviceSpec.TaskTemplate.ContainerSpec.Image 197 encodedAuth string 198 ) 199 200 if sendAuth { 201 // Retrieve encoded auth token from the image reference 202 encodedAuth, err = command.RetrieveAuthTokenFromImage(ctx, dockerCli, image) 203 if err != nil { 204 return err 205 } 206 } 207 208 if service, exists := existingServiceMap[name]; exists { 209 fmt.Fprintf(out, "Updating service %s (id: %s)\n", name, service.ID) 210 211 updateOpts := types.ServiceUpdateOptions{EncodedRegistryAuth: encodedAuth} 212 213 switch resolveImage { 214 case ResolveImageAlways: 215 // image should be updated by the server using QueryRegistry 216 updateOpts.QueryRegistry = true 217 case ResolveImageChanged: 218 if image != service.Spec.Labels[convert.LabelImage] { 219 // Query the registry to resolve digest for the updated image 220 updateOpts.QueryRegistry = true 221 } else { 222 // image has not changed; update the serviceSpec with the 223 // existing information that was set by QueryRegistry on the 224 // previous deploy. Otherwise this will trigger an incorrect 225 // service update. 226 serviceSpec.TaskTemplate.ContainerSpec.Image = service.Spec.TaskTemplate.ContainerSpec.Image 227 } 228 default: 229 if image == service.Spec.Labels[convert.LabelImage] { 230 // image has not changed; update the serviceSpec with the 231 // existing information that was set by QueryRegistry on the 232 // previous deploy. Otherwise this will trigger an incorrect 233 // service update. 234 serviceSpec.TaskTemplate.ContainerSpec.Image = service.Spec.TaskTemplate.ContainerSpec.Image 235 } 236 } 237 238 // Stack deploy does not have a `--force` option. Preserve existing 239 // ForceUpdate value so that tasks are not re-deployed if not updated. 240 // TODO move this to API client? 241 serviceSpec.TaskTemplate.ForceUpdate = service.Spec.TaskTemplate.ForceUpdate 242 243 response, err := apiClient.ServiceUpdate(ctx, service.ID, service.Version, serviceSpec, updateOpts) 244 if err != nil { 245 return errors.Wrapf(err, "failed to update service %s", name) 246 } 247 248 for _, warning := range response.Warnings { 249 fmt.Fprintln(dockerCli.Err(), warning) 250 } 251 } else { 252 fmt.Fprintf(out, "Creating service %s\n", name) 253 254 createOpts := types.ServiceCreateOptions{EncodedRegistryAuth: encodedAuth} 255 256 // query registry if flag disabling it was not set 257 if resolveImage == ResolveImageAlways || resolveImage == ResolveImageChanged { 258 createOpts.QueryRegistry = true 259 } 260 261 if _, err := apiClient.ServiceCreate(ctx, serviceSpec, createOpts); err != nil { 262 return errors.Wrapf(err, "failed to create service %s", name) 263 } 264 } 265 } 266 return nil 267 }