github.com/DaoCloud/dao@v0.0.0-20161212064103-c3dbfd13ee36/api/client/service/update.go (about) 1 package service 2 3 import ( 4 "fmt" 5 "sort" 6 "strings" 7 "time" 8 9 "golang.org/x/net/context" 10 11 "github.com/docker/docker/api/client" 12 "github.com/docker/docker/cli" 13 "github.com/docker/docker/opts" 14 runconfigopts "github.com/docker/docker/runconfig/opts" 15 "github.com/docker/engine-api/types" 16 "github.com/docker/engine-api/types/swarm" 17 "github.com/docker/go-connections/nat" 18 shlex "github.com/flynn-archive/go-shlex" 19 "github.com/spf13/cobra" 20 "github.com/spf13/pflag" 21 ) 22 23 func newUpdateCommand(dockerCli *client.DockerCli) *cobra.Command { 24 opts := newServiceOptions() 25 26 cmd := &cobra.Command{ 27 Use: "update [OPTIONS] SERVICE", 28 Short: "更新一个服务", 29 Args: cli.ExactArgs(1), 30 RunE: func(cmd *cobra.Command, args []string) error { 31 return runUpdate(dockerCli, cmd.Flags(), args[0]) 32 }, 33 } 34 35 flags := cmd.Flags() 36 flags.String("image", "", "服务镜像的标签") 37 flags.String("args", "", "服务的启动命令") 38 addServiceFlags(cmd, opts) 39 flags.Var(newListOptsVar(), flagEnvRemove, "删除一个环境变量") 40 flags.Var(newListOptsVar(), flagLabelRemove, "通过键值删除一个标签") 41 flags.Var(newListOptsVar(), flagContainerLabelRemove, "通过键值删除一个容器的标签") 42 flags.Var(newListOptsVar(), flagMountRemove, "通过目标路径删除一个挂载") 43 flags.Var(newListOptsVar(), flagPublishRemove, "通过目标端口删除一个对外暴露的端口") 44 flags.Var(newListOptsVar(), flagConstraintRemove, "删除一条限制条件") 45 flags.Var(&opts.labels, flagLabelAdd, "添加或更新服务标签") 46 flags.Var(&opts.containerLabels, flagContainerLabelAdd, "添加或更新容器标签") 47 flags.Var(&opts.env, flagEnvAdd, "添加或更新环境变量") 48 flags.Var(&opts.mounts, flagMountAdd, "添加或更新一个服务的挂载项") 49 flags.StringSliceVar(&opts.constraints, flagConstraintAdd, []string{}, "添加或更新放置策略与限制") 50 flags.Var(&opts.endpoint.ports, flagPublishAdd, "添加或更新一个对外暴露的端口") 51 return cmd 52 } 53 54 func newListOptsVar() *opts.ListOpts { 55 return opts.NewListOptsRef(&[]string{}, nil) 56 } 57 58 func runUpdate(dockerCli *client.DockerCli, flags *pflag.FlagSet, serviceID string) error { 59 apiClient := dockerCli.Client() 60 ctx := context.Background() 61 updateOpts := types.ServiceUpdateOptions{} 62 63 service, _, err := apiClient.ServiceInspectWithRaw(ctx, serviceID) 64 if err != nil { 65 return err 66 } 67 68 err = updateService(flags, &service.Spec) 69 if err != nil { 70 return err 71 } 72 73 // only send auth if flag was set 74 sendAuth, err := flags.GetBool(flagRegistryAuth) 75 if err != nil { 76 return err 77 } 78 if sendAuth { 79 // Retrieve encoded auth token from the image reference 80 // This would be the old image if it didn't change in this update 81 image := service.Spec.TaskTemplate.ContainerSpec.Image 82 encodedAuth, err := dockerCli.RetrieveAuthTokenFromImage(ctx, image) 83 if err != nil { 84 return err 85 } 86 updateOpts.EncodedRegistryAuth = encodedAuth 87 } 88 89 err = apiClient.ServiceUpdate(ctx, service.ID, service.Version, service.Spec, updateOpts) 90 if err != nil { 91 return err 92 } 93 94 fmt.Fprintf(dockerCli.Out(), "%s\n", serviceID) 95 return nil 96 } 97 98 func updateService(flags *pflag.FlagSet, spec *swarm.ServiceSpec) error { 99 updateString := func(flag string, field *string) { 100 if flags.Changed(flag) { 101 *field, _ = flags.GetString(flag) 102 } 103 } 104 105 updateInt64Value := func(flag string, field *int64) { 106 if flags.Changed(flag) { 107 *field = flags.Lookup(flag).Value.(int64Value).Value() 108 } 109 } 110 111 updateDuration := func(flag string, field *time.Duration) { 112 if flags.Changed(flag) { 113 *field, _ = flags.GetDuration(flag) 114 } 115 } 116 117 updateDurationOpt := func(flag string, field **time.Duration) { 118 if flags.Changed(flag) { 119 val := *flags.Lookup(flag).Value.(*DurationOpt).Value() 120 *field = &val 121 } 122 } 123 124 updateUint64 := func(flag string, field *uint64) { 125 if flags.Changed(flag) { 126 *field, _ = flags.GetUint64(flag) 127 } 128 } 129 130 updateUint64Opt := func(flag string, field **uint64) { 131 if flags.Changed(flag) { 132 val := *flags.Lookup(flag).Value.(*Uint64Opt).Value() 133 *field = &val 134 } 135 } 136 137 cspec := &spec.TaskTemplate.ContainerSpec 138 task := &spec.TaskTemplate 139 140 taskResources := func() *swarm.ResourceRequirements { 141 if task.Resources == nil { 142 task.Resources = &swarm.ResourceRequirements{} 143 } 144 return task.Resources 145 } 146 147 updateString(flagName, &spec.Name) 148 updateLabels(flags, &spec.Labels) 149 updateContainerLabels(flags, &cspec.Labels) 150 updateString("image", &cspec.Image) 151 updateStringToSlice(flags, "args", &cspec.Args) 152 updateEnvironment(flags, &cspec.Env) 153 updateString("workdir", &cspec.Dir) 154 updateString(flagUser, &cspec.User) 155 updateMounts(flags, &cspec.Mounts) 156 157 if flags.Changed(flagLimitCPU) || flags.Changed(flagLimitMemory) { 158 taskResources().Limits = &swarm.Resources{} 159 updateInt64Value(flagLimitCPU, &task.Resources.Limits.NanoCPUs) 160 updateInt64Value(flagLimitMemory, &task.Resources.Limits.MemoryBytes) 161 } 162 if flags.Changed(flagReserveCPU) || flags.Changed(flagReserveMemory) { 163 taskResources().Reservations = &swarm.Resources{} 164 updateInt64Value(flagReserveCPU, &task.Resources.Reservations.NanoCPUs) 165 updateInt64Value(flagReserveMemory, &task.Resources.Reservations.MemoryBytes) 166 } 167 168 updateDurationOpt(flagStopGracePeriod, &cspec.StopGracePeriod) 169 170 if anyChanged(flags, flagRestartCondition, flagRestartDelay, flagRestartMaxAttempts, flagRestartWindow) { 171 if task.RestartPolicy == nil { 172 task.RestartPolicy = &swarm.RestartPolicy{} 173 } 174 175 if flags.Changed(flagRestartCondition) { 176 value, _ := flags.GetString(flagRestartCondition) 177 task.RestartPolicy.Condition = swarm.RestartPolicyCondition(value) 178 } 179 updateDurationOpt(flagRestartDelay, &task.RestartPolicy.Delay) 180 updateUint64Opt(flagRestartMaxAttempts, &task.RestartPolicy.MaxAttempts) 181 updateDurationOpt(flagRestartWindow, &task.RestartPolicy.Window) 182 } 183 184 if anyChanged(flags, flagConstraintAdd, flagConstraintRemove) { 185 if task.Placement == nil { 186 task.Placement = &swarm.Placement{} 187 } 188 updatePlacement(flags, task.Placement) 189 } 190 191 if err := updateReplicas(flags, &spec.Mode); err != nil { 192 return err 193 } 194 195 if anyChanged(flags, flagUpdateParallelism, flagUpdateDelay, flagUpdateFailureAction) { 196 if spec.UpdateConfig == nil { 197 spec.UpdateConfig = &swarm.UpdateConfig{} 198 } 199 updateUint64(flagUpdateParallelism, &spec.UpdateConfig.Parallelism) 200 updateDuration(flagUpdateDelay, &spec.UpdateConfig.Delay) 201 updateString(flagUpdateFailureAction, &spec.UpdateConfig.FailureAction) 202 } 203 204 if flags.Changed(flagEndpointMode) { 205 value, _ := flags.GetString(flagEndpointMode) 206 if spec.EndpointSpec == nil { 207 spec.EndpointSpec = &swarm.EndpointSpec{} 208 } 209 spec.EndpointSpec.Mode = swarm.ResolutionMode(value) 210 } 211 212 if anyChanged(flags, flagPublishAdd, flagPublishRemove) { 213 if spec.EndpointSpec == nil { 214 spec.EndpointSpec = &swarm.EndpointSpec{} 215 } 216 if err := updatePorts(flags, &spec.EndpointSpec.Ports); err != nil { 217 return err 218 } 219 } 220 221 if err := updateLogDriver(flags, &spec.TaskTemplate); err != nil { 222 return err 223 } 224 225 return nil 226 } 227 228 func updateStringToSlice(flags *pflag.FlagSet, flag string, field *[]string) error { 229 if !flags.Changed(flag) { 230 return nil 231 } 232 233 value, _ := flags.GetString(flag) 234 valueSlice, err := shlex.Split(value) 235 *field = valueSlice 236 return err 237 } 238 239 func anyChanged(flags *pflag.FlagSet, fields ...string) bool { 240 for _, flag := range fields { 241 if flags.Changed(flag) { 242 return true 243 } 244 } 245 return false 246 } 247 248 func updatePlacement(flags *pflag.FlagSet, placement *swarm.Placement) { 249 field, _ := flags.GetStringSlice(flagConstraintAdd) 250 placement.Constraints = append(placement.Constraints, field...) 251 252 toRemove := buildToRemoveSet(flags, flagConstraintRemove) 253 placement.Constraints = removeItems(placement.Constraints, toRemove, itemKey) 254 } 255 256 func updateContainerLabels(flags *pflag.FlagSet, field *map[string]string) { 257 if flags.Changed(flagContainerLabelAdd) { 258 if *field == nil { 259 *field = map[string]string{} 260 } 261 262 values := flags.Lookup(flagContainerLabelAdd).Value.(*opts.ListOpts).GetAll() 263 for key, value := range runconfigopts.ConvertKVStringsToMap(values) { 264 (*field)[key] = value 265 } 266 } 267 268 if *field != nil && flags.Changed(flagContainerLabelRemove) { 269 toRemove := flags.Lookup(flagContainerLabelRemove).Value.(*opts.ListOpts).GetAll() 270 for _, label := range toRemove { 271 delete(*field, label) 272 } 273 } 274 } 275 276 func updateLabels(flags *pflag.FlagSet, field *map[string]string) { 277 if flags.Changed(flagLabelAdd) { 278 if *field == nil { 279 *field = map[string]string{} 280 } 281 282 values := flags.Lookup(flagLabelAdd).Value.(*opts.ListOpts).GetAll() 283 for key, value := range runconfigopts.ConvertKVStringsToMap(values) { 284 (*field)[key] = value 285 } 286 } 287 288 if *field != nil && flags.Changed(flagLabelRemove) { 289 toRemove := flags.Lookup(flagLabelRemove).Value.(*opts.ListOpts).GetAll() 290 for _, label := range toRemove { 291 delete(*field, label) 292 } 293 } 294 } 295 296 func updateEnvironment(flags *pflag.FlagSet, field *[]string) { 297 envSet := map[string]string{} 298 for _, v := range *field { 299 envSet[envKey(v)] = v 300 } 301 if flags.Changed(flagEnvAdd) { 302 value := flags.Lookup(flagEnvAdd).Value.(*opts.ListOpts) 303 for _, v := range value.GetAll() { 304 envSet[envKey(v)] = v 305 } 306 } 307 308 *field = []string{} 309 for _, v := range envSet { 310 *field = append(*field, v) 311 } 312 313 toRemove := buildToRemoveSet(flags, flagEnvRemove) 314 *field = removeItems(*field, toRemove, envKey) 315 } 316 317 func envKey(value string) string { 318 kv := strings.SplitN(value, "=", 2) 319 return kv[0] 320 } 321 322 func itemKey(value string) string { 323 return value 324 } 325 326 func buildToRemoveSet(flags *pflag.FlagSet, flag string) map[string]struct{} { 327 var empty struct{} 328 toRemove := make(map[string]struct{}) 329 330 if !flags.Changed(flag) { 331 return toRemove 332 } 333 334 toRemoveSlice := flags.Lookup(flag).Value.(*opts.ListOpts).GetAll() 335 for _, key := range toRemoveSlice { 336 toRemove[key] = empty 337 } 338 return toRemove 339 } 340 341 func removeItems( 342 seq []string, 343 toRemove map[string]struct{}, 344 keyFunc func(string) string, 345 ) []string { 346 newSeq := []string{} 347 for _, item := range seq { 348 if _, exists := toRemove[keyFunc(item)]; !exists { 349 newSeq = append(newSeq, item) 350 } 351 } 352 return newSeq 353 } 354 355 func updateMounts(flags *pflag.FlagSet, mounts *[]swarm.Mount) { 356 if flags.Changed(flagMountAdd) { 357 values := flags.Lookup(flagMountAdd).Value.(*MountOpt).Value() 358 *mounts = append(*mounts, values...) 359 } 360 toRemove := buildToRemoveSet(flags, flagMountRemove) 361 362 newMounts := []swarm.Mount{} 363 for _, mount := range *mounts { 364 if _, exists := toRemove[mount.Target]; !exists { 365 newMounts = append(newMounts, mount) 366 } 367 } 368 *mounts = newMounts 369 } 370 371 type byPortConfig []swarm.PortConfig 372 373 func (r byPortConfig) Len() int { return len(r) } 374 func (r byPortConfig) Swap(i, j int) { r[i], r[j] = r[j], r[i] } 375 func (r byPortConfig) Less(i, j int) bool { 376 // We convert PortConfig into `port/protocol`, e.g., `80/tcp` 377 // In updatePorts we already filter out with map so there is duplicate entries 378 return portConfigToString(&r[i]) < portConfigToString(&r[j]) 379 } 380 381 func portConfigToString(portConfig *swarm.PortConfig) string { 382 protocol := portConfig.Protocol 383 if protocol == "" { 384 protocol = "tcp" 385 } 386 return fmt.Sprintf("%v/%s", portConfig.PublishedPort, protocol) 387 } 388 389 func updatePorts(flags *pflag.FlagSet, portConfig *[]swarm.PortConfig) error { 390 // The key of the map is `port/protocol`, e.g., `80/tcp` 391 portSet := map[string]swarm.PortConfig{} 392 // Check to see if there are any conflict in flags. 393 if flags.Changed(flagPublishAdd) { 394 values := flags.Lookup(flagPublishAdd).Value.(*opts.ListOpts).GetAll() 395 ports, portBindings, _ := nat.ParsePortSpecs(values) 396 397 for port := range ports { 398 newConfigs := convertPortToPortConfig(port, portBindings) 399 for _, entry := range newConfigs { 400 if v, ok := portSet[portConfigToString(&entry)]; ok && v != entry { 401 return fmt.Errorf("conflicting port mapping between %v:%v/%s and %v:%v/%s", entry.PublishedPort, entry.TargetPort, entry.Protocol, v.PublishedPort, v.TargetPort, v.Protocol) 402 } 403 portSet[portConfigToString(&entry)] = entry 404 } 405 } 406 } 407 408 // Override previous PortConfig in service if there is any duplicate 409 for _, entry := range *portConfig { 410 if _, ok := portSet[portConfigToString(&entry)]; !ok { 411 portSet[portConfigToString(&entry)] = entry 412 } 413 } 414 415 toRemove := flags.Lookup(flagPublishRemove).Value.(*opts.ListOpts).GetAll() 416 newPorts := []swarm.PortConfig{} 417 portLoop: 418 for _, port := range portSet { 419 for _, rawTargetPort := range toRemove { 420 targetPort := nat.Port(rawTargetPort) 421 if equalPort(targetPort, port) { 422 continue portLoop 423 } 424 } 425 newPorts = append(newPorts, port) 426 } 427 // Sort the PortConfig to avoid unnecessary updates 428 sort.Sort(byPortConfig(newPorts)) 429 *portConfig = newPorts 430 return nil 431 } 432 433 func equalPort(targetPort nat.Port, port swarm.PortConfig) bool { 434 return (string(port.Protocol) == targetPort.Proto() && 435 port.TargetPort == uint32(targetPort.Int())) 436 } 437 438 func updateReplicas(flags *pflag.FlagSet, serviceMode *swarm.ServiceMode) error { 439 if !flags.Changed(flagReplicas) { 440 return nil 441 } 442 443 if serviceMode == nil || serviceMode.Replicated == nil { 444 return fmt.Errorf("replicas can only be used with replicated mode") 445 } 446 serviceMode.Replicated.Replicas = flags.Lookup(flagReplicas).Value.(*Uint64Opt).Value() 447 return nil 448 } 449 450 // updateLogDriver updates the log driver only if the log driver flag is set. 451 // All options will be replaced with those provided on the command line. 452 func updateLogDriver(flags *pflag.FlagSet, taskTemplate *swarm.TaskSpec) error { 453 if !flags.Changed(flagLogDriver) { 454 return nil 455 } 456 457 name, err := flags.GetString(flagLogDriver) 458 if err != nil { 459 return err 460 } 461 462 if name == "" { 463 return nil 464 } 465 466 taskTemplate.LogDriver = &swarm.Driver{ 467 Name: name, 468 Options: runconfigopts.ConvertKVStringsToMap(flags.Lookup(flagLogOpt).Value.(*opts.ListOpts).GetAll()), 469 } 470 471 return nil 472 }