github.com/kim0/docker@v0.6.2-0.20161130212042-4addda3f07e7/cli/command/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/types" 12 "github.com/docker/docker/api/types/container" 13 mounttypes "github.com/docker/docker/api/types/mount" 14 "github.com/docker/docker/api/types/swarm" 15 "github.com/docker/docker/cli" 16 "github.com/docker/docker/cli/command" 17 "github.com/docker/docker/opts" 18 runconfigopts "github.com/docker/docker/runconfig/opts" 19 "github.com/docker/go-connections/nat" 20 shlex "github.com/flynn-archive/go-shlex" 21 "github.com/spf13/cobra" 22 "github.com/spf13/pflag" 23 ) 24 25 func newUpdateCommand(dockerCli *command.DockerCli) *cobra.Command { 26 opts := newServiceOptions() 27 28 cmd := &cobra.Command{ 29 Use: "update [OPTIONS] SERVICE", 30 Short: "Update a service", 31 Args: cli.ExactArgs(1), 32 RunE: func(cmd *cobra.Command, args []string) error { 33 return runUpdate(dockerCli, cmd.Flags(), args[0]) 34 }, 35 } 36 37 flags := cmd.Flags() 38 flags.String("image", "", "Service image tag") 39 flags.String("args", "", "Service command args") 40 flags.Bool("rollback", false, "Rollback to previous specification") 41 flags.Bool("force", false, "Force update even if no changes require it") 42 addServiceFlags(cmd, opts) 43 44 flags.Var(newListOptsVar(), flagEnvRemove, "Remove an environment variable") 45 flags.Var(newListOptsVar(), flagGroupRemove, "Remove previously added supplementary user groups from the container") 46 flags.Var(newListOptsVar(), flagLabelRemove, "Remove a label by its key") 47 flags.Var(newListOptsVar(), flagContainerLabelRemove, "Remove a container label by its key") 48 flags.Var(newListOptsVar(), flagMountRemove, "Remove a mount by its target path") 49 flags.Var(newListOptsVar(), flagPublishRemove, "Remove a published port by its target port") 50 flags.Var(newListOptsVar(), flagConstraintRemove, "Remove a constraint") 51 flags.Var(&opts.labels, flagLabelAdd, "Add or update service labels") 52 flags.Var(&opts.containerLabels, flagContainerLabelAdd, "Add or update container labels") 53 flags.Var(&opts.env, flagEnvAdd, "Add or update environment variables") 54 flags.Var(&opts.mounts, flagMountAdd, "Add or update a mount on a service") 55 flags.StringSliceVar(&opts.constraints, flagConstraintAdd, []string{}, "Add or update placement constraints") 56 flags.Var(&opts.endpoint.ports, flagPublishAdd, "Add or update a published port") 57 flags.StringSliceVar(&opts.groups, flagGroupAdd, []string{}, "Add additional supplementary user groups to the container") 58 return cmd 59 } 60 61 func newListOptsVar() *opts.ListOpts { 62 return opts.NewListOptsRef(&[]string{}, nil) 63 } 64 65 func runUpdate(dockerCli *command.DockerCli, flags *pflag.FlagSet, serviceID string) error { 66 apiClient := dockerCli.Client() 67 ctx := context.Background() 68 updateOpts := types.ServiceUpdateOptions{} 69 70 service, _, err := apiClient.ServiceInspectWithRaw(ctx, serviceID) 71 if err != nil { 72 return err 73 } 74 75 rollback, err := flags.GetBool("rollback") 76 if err != nil { 77 return err 78 } 79 80 spec := &service.Spec 81 if rollback { 82 spec = service.PreviousSpec 83 if spec == nil { 84 return fmt.Errorf("service does not have a previous specification to roll back to") 85 } 86 } 87 88 err = updateService(flags, spec) 89 if err != nil { 90 return err 91 } 92 93 // only send auth if flag was set 94 sendAuth, err := flags.GetBool(flagRegistryAuth) 95 if err != nil { 96 return err 97 } 98 if sendAuth { 99 // Retrieve encoded auth token from the image reference 100 // This would be the old image if it didn't change in this update 101 image := spec.TaskTemplate.ContainerSpec.Image 102 encodedAuth, err := command.RetrieveAuthTokenFromImage(ctx, dockerCli, image) 103 if err != nil { 104 return err 105 } 106 updateOpts.EncodedRegistryAuth = encodedAuth 107 } else if rollback { 108 updateOpts.RegistryAuthFrom = types.RegistryAuthFromPreviousSpec 109 } else { 110 updateOpts.RegistryAuthFrom = types.RegistryAuthFromSpec 111 } 112 113 err = apiClient.ServiceUpdate(ctx, service.ID, service.Version, *spec, updateOpts) 114 if err != nil { 115 return err 116 } 117 118 fmt.Fprintf(dockerCli.Out(), "%s\n", serviceID) 119 return nil 120 } 121 122 func updateService(flags *pflag.FlagSet, spec *swarm.ServiceSpec) error { 123 updateString := func(flag string, field *string) { 124 if flags.Changed(flag) { 125 *field, _ = flags.GetString(flag) 126 } 127 } 128 129 updateInt64Value := func(flag string, field *int64) { 130 if flags.Changed(flag) { 131 *field = flags.Lookup(flag).Value.(int64Value).Value() 132 } 133 } 134 135 updateFloat32 := func(flag string, field *float32) { 136 if flags.Changed(flag) { 137 *field, _ = flags.GetFloat32(flag) 138 } 139 } 140 141 updateDuration := func(flag string, field *time.Duration) { 142 if flags.Changed(flag) { 143 *field, _ = flags.GetDuration(flag) 144 } 145 } 146 147 updateDurationOpt := func(flag string, field **time.Duration) { 148 if flags.Changed(flag) { 149 val := *flags.Lookup(flag).Value.(*DurationOpt).Value() 150 *field = &val 151 } 152 } 153 154 updateUint64 := func(flag string, field *uint64) { 155 if flags.Changed(flag) { 156 *field, _ = flags.GetUint64(flag) 157 } 158 } 159 160 updateUint64Opt := func(flag string, field **uint64) { 161 if flags.Changed(flag) { 162 val := *flags.Lookup(flag).Value.(*Uint64Opt).Value() 163 *field = &val 164 } 165 } 166 167 cspec := &spec.TaskTemplate.ContainerSpec 168 task := &spec.TaskTemplate 169 170 taskResources := func() *swarm.ResourceRequirements { 171 if task.Resources == nil { 172 task.Resources = &swarm.ResourceRequirements{} 173 } 174 return task.Resources 175 } 176 177 updateLabels(flags, &spec.Labels) 178 updateContainerLabels(flags, &cspec.Labels) 179 updateString("image", &cspec.Image) 180 updateStringToSlice(flags, "args", &cspec.Args) 181 updateEnvironment(flags, &cspec.Env) 182 updateString(flagWorkdir, &cspec.Dir) 183 updateString(flagUser, &cspec.User) 184 updateMounts(flags, &cspec.Mounts) 185 186 if flags.Changed(flagLimitCPU) || flags.Changed(flagLimitMemory) { 187 taskResources().Limits = &swarm.Resources{} 188 updateInt64Value(flagLimitCPU, &task.Resources.Limits.NanoCPUs) 189 updateInt64Value(flagLimitMemory, &task.Resources.Limits.MemoryBytes) 190 } 191 if flags.Changed(flagReserveCPU) || flags.Changed(flagReserveMemory) { 192 taskResources().Reservations = &swarm.Resources{} 193 updateInt64Value(flagReserveCPU, &task.Resources.Reservations.NanoCPUs) 194 updateInt64Value(flagReserveMemory, &task.Resources.Reservations.MemoryBytes) 195 } 196 197 updateDurationOpt(flagStopGracePeriod, &cspec.StopGracePeriod) 198 199 if anyChanged(flags, flagRestartCondition, flagRestartDelay, flagRestartMaxAttempts, flagRestartWindow) { 200 if task.RestartPolicy == nil { 201 task.RestartPolicy = &swarm.RestartPolicy{} 202 } 203 204 if flags.Changed(flagRestartCondition) { 205 value, _ := flags.GetString(flagRestartCondition) 206 task.RestartPolicy.Condition = swarm.RestartPolicyCondition(value) 207 } 208 updateDurationOpt(flagRestartDelay, &task.RestartPolicy.Delay) 209 updateUint64Opt(flagRestartMaxAttempts, &task.RestartPolicy.MaxAttempts) 210 updateDurationOpt(flagRestartWindow, &task.RestartPolicy.Window) 211 } 212 213 if anyChanged(flags, flagConstraintAdd, flagConstraintRemove) { 214 if task.Placement == nil { 215 task.Placement = &swarm.Placement{} 216 } 217 updatePlacement(flags, task.Placement) 218 } 219 220 if err := updateReplicas(flags, &spec.Mode); err != nil { 221 return err 222 } 223 224 if anyChanged(flags, flagUpdateParallelism, flagUpdateDelay, flagUpdateMonitor, flagUpdateFailureAction, flagUpdateMaxFailureRatio) { 225 if spec.UpdateConfig == nil { 226 spec.UpdateConfig = &swarm.UpdateConfig{} 227 } 228 updateUint64(flagUpdateParallelism, &spec.UpdateConfig.Parallelism) 229 updateDuration(flagUpdateDelay, &spec.UpdateConfig.Delay) 230 updateDuration(flagUpdateMonitor, &spec.UpdateConfig.Monitor) 231 updateString(flagUpdateFailureAction, &spec.UpdateConfig.FailureAction) 232 updateFloat32(flagUpdateMaxFailureRatio, &spec.UpdateConfig.MaxFailureRatio) 233 } 234 235 if flags.Changed(flagEndpointMode) { 236 value, _ := flags.GetString(flagEndpointMode) 237 if spec.EndpointSpec == nil { 238 spec.EndpointSpec = &swarm.EndpointSpec{} 239 } 240 spec.EndpointSpec.Mode = swarm.ResolutionMode(value) 241 } 242 243 if anyChanged(flags, flagGroupAdd, flagGroupRemove) { 244 if err := updateGroups(flags, &cspec.Groups); err != nil { 245 return err 246 } 247 } 248 249 if anyChanged(flags, flagPublishAdd, flagPublishRemove) { 250 if spec.EndpointSpec == nil { 251 spec.EndpointSpec = &swarm.EndpointSpec{} 252 } 253 if err := updatePorts(flags, &spec.EndpointSpec.Ports); err != nil { 254 return err 255 } 256 } 257 258 if err := updateLogDriver(flags, &spec.TaskTemplate); err != nil { 259 return err 260 } 261 262 force, err := flags.GetBool("force") 263 if err != nil { 264 return err 265 } 266 267 if force { 268 spec.TaskTemplate.ForceUpdate++ 269 } 270 271 if err := updateHealthcheck(flags, cspec); err != nil { 272 return err 273 } 274 275 return nil 276 } 277 278 func updateStringToSlice(flags *pflag.FlagSet, flag string, field *[]string) error { 279 if !flags.Changed(flag) { 280 return nil 281 } 282 283 value, _ := flags.GetString(flag) 284 valueSlice, err := shlex.Split(value) 285 *field = valueSlice 286 return err 287 } 288 289 func anyChanged(flags *pflag.FlagSet, fields ...string) bool { 290 for _, flag := range fields { 291 if flags.Changed(flag) { 292 return true 293 } 294 } 295 return false 296 } 297 298 func updatePlacement(flags *pflag.FlagSet, placement *swarm.Placement) { 299 field, _ := flags.GetStringSlice(flagConstraintAdd) 300 placement.Constraints = append(placement.Constraints, field...) 301 302 toRemove := buildToRemoveSet(flags, flagConstraintRemove) 303 placement.Constraints = removeItems(placement.Constraints, toRemove, itemKey) 304 } 305 306 func updateContainerLabels(flags *pflag.FlagSet, field *map[string]string) { 307 if flags.Changed(flagContainerLabelAdd) { 308 if *field == nil { 309 *field = map[string]string{} 310 } 311 312 values := flags.Lookup(flagContainerLabelAdd).Value.(*opts.ListOpts).GetAll() 313 for key, value := range runconfigopts.ConvertKVStringsToMap(values) { 314 (*field)[key] = value 315 } 316 } 317 318 if *field != nil && flags.Changed(flagContainerLabelRemove) { 319 toRemove := flags.Lookup(flagContainerLabelRemove).Value.(*opts.ListOpts).GetAll() 320 for _, label := range toRemove { 321 delete(*field, label) 322 } 323 } 324 } 325 326 func updateLabels(flags *pflag.FlagSet, field *map[string]string) { 327 if flags.Changed(flagLabelAdd) { 328 if *field == nil { 329 *field = map[string]string{} 330 } 331 332 values := flags.Lookup(flagLabelAdd).Value.(*opts.ListOpts).GetAll() 333 for key, value := range runconfigopts.ConvertKVStringsToMap(values) { 334 (*field)[key] = value 335 } 336 } 337 338 if *field != nil && flags.Changed(flagLabelRemove) { 339 toRemove := flags.Lookup(flagLabelRemove).Value.(*opts.ListOpts).GetAll() 340 for _, label := range toRemove { 341 delete(*field, label) 342 } 343 } 344 } 345 346 func updateEnvironment(flags *pflag.FlagSet, field *[]string) { 347 envSet := map[string]string{} 348 for _, v := range *field { 349 envSet[envKey(v)] = v 350 } 351 if flags.Changed(flagEnvAdd) { 352 value := flags.Lookup(flagEnvAdd).Value.(*opts.ListOpts) 353 for _, v := range value.GetAll() { 354 envSet[envKey(v)] = v 355 } 356 } 357 358 *field = []string{} 359 for _, v := range envSet { 360 *field = append(*field, v) 361 } 362 363 toRemove := buildToRemoveSet(flags, flagEnvRemove) 364 *field = removeItems(*field, toRemove, envKey) 365 } 366 367 func envKey(value string) string { 368 kv := strings.SplitN(value, "=", 2) 369 return kv[0] 370 } 371 372 func itemKey(value string) string { 373 return value 374 } 375 376 func buildToRemoveSet(flags *pflag.FlagSet, flag string) map[string]struct{} { 377 var empty struct{} 378 toRemove := make(map[string]struct{}) 379 380 if !flags.Changed(flag) { 381 return toRemove 382 } 383 384 toRemoveSlice := flags.Lookup(flag).Value.(*opts.ListOpts).GetAll() 385 for _, key := range toRemoveSlice { 386 toRemove[key] = empty 387 } 388 return toRemove 389 } 390 391 func removeItems( 392 seq []string, 393 toRemove map[string]struct{}, 394 keyFunc func(string) string, 395 ) []string { 396 newSeq := []string{} 397 for _, item := range seq { 398 if _, exists := toRemove[keyFunc(item)]; !exists { 399 newSeq = append(newSeq, item) 400 } 401 } 402 return newSeq 403 } 404 405 func updateMounts(flags *pflag.FlagSet, mounts *[]mounttypes.Mount) { 406 if flags.Changed(flagMountAdd) { 407 values := flags.Lookup(flagMountAdd).Value.(*MountOpt).Value() 408 *mounts = append(*mounts, values...) 409 } 410 toRemove := buildToRemoveSet(flags, flagMountRemove) 411 412 newMounts := []mounttypes.Mount{} 413 for _, mount := range *mounts { 414 if _, exists := toRemove[mount.Target]; !exists { 415 newMounts = append(newMounts, mount) 416 } 417 } 418 *mounts = newMounts 419 } 420 421 func updateGroups(flags *pflag.FlagSet, groups *[]string) error { 422 if flags.Changed(flagGroupAdd) { 423 values, err := flags.GetStringSlice(flagGroupAdd) 424 if err != nil { 425 return err 426 } 427 *groups = append(*groups, values...) 428 } 429 toRemove := buildToRemoveSet(flags, flagGroupRemove) 430 431 newGroups := []string{} 432 for _, group := range *groups { 433 if _, exists := toRemove[group]; !exists { 434 newGroups = append(newGroups, group) 435 } 436 } 437 // Sort so that result is predictable. 438 sort.Strings(newGroups) 439 440 *groups = newGroups 441 return nil 442 } 443 444 type byPortConfig []swarm.PortConfig 445 446 func (r byPortConfig) Len() int { return len(r) } 447 func (r byPortConfig) Swap(i, j int) { r[i], r[j] = r[j], r[i] } 448 func (r byPortConfig) Less(i, j int) bool { 449 // We convert PortConfig into `port/protocol`, e.g., `80/tcp` 450 // In updatePorts we already filter out with map so there is duplicate entries 451 return portConfigToString(&r[i]) < portConfigToString(&r[j]) 452 } 453 454 func portConfigToString(portConfig *swarm.PortConfig) string { 455 protocol := portConfig.Protocol 456 if protocol == "" { 457 protocol = "tcp" 458 } 459 return fmt.Sprintf("%v/%s", portConfig.PublishedPort, protocol) 460 } 461 462 func updatePorts(flags *pflag.FlagSet, portConfig *[]swarm.PortConfig) error { 463 // The key of the map is `port/protocol`, e.g., `80/tcp` 464 portSet := map[string]swarm.PortConfig{} 465 // Check to see if there are any conflict in flags. 466 if flags.Changed(flagPublishAdd) { 467 values := flags.Lookup(flagPublishAdd).Value.(*opts.ListOpts).GetAll() 468 ports, portBindings, _ := nat.ParsePortSpecs(values) 469 470 for port := range ports { 471 newConfigs := convertPortToPortConfig(port, portBindings) 472 for _, entry := range newConfigs { 473 if v, ok := portSet[portConfigToString(&entry)]; ok && v != entry { 474 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) 475 } 476 portSet[portConfigToString(&entry)] = entry 477 } 478 } 479 } 480 481 // Override previous PortConfig in service if there is any duplicate 482 for _, entry := range *portConfig { 483 if _, ok := portSet[portConfigToString(&entry)]; !ok { 484 portSet[portConfigToString(&entry)] = entry 485 } 486 } 487 488 toRemove := flags.Lookup(flagPublishRemove).Value.(*opts.ListOpts).GetAll() 489 newPorts := []swarm.PortConfig{} 490 portLoop: 491 for _, port := range portSet { 492 for _, rawTargetPort := range toRemove { 493 targetPort := nat.Port(rawTargetPort) 494 if equalPort(targetPort, port) { 495 continue portLoop 496 } 497 } 498 newPorts = append(newPorts, port) 499 } 500 // Sort the PortConfig to avoid unnecessary updates 501 sort.Sort(byPortConfig(newPorts)) 502 *portConfig = newPorts 503 return nil 504 } 505 506 func equalPort(targetPort nat.Port, port swarm.PortConfig) bool { 507 return (string(port.Protocol) == targetPort.Proto() && 508 port.TargetPort == uint32(targetPort.Int())) 509 } 510 511 func updateReplicas(flags *pflag.FlagSet, serviceMode *swarm.ServiceMode) error { 512 if !flags.Changed(flagReplicas) { 513 return nil 514 } 515 516 if serviceMode == nil || serviceMode.Replicated == nil { 517 return fmt.Errorf("replicas can only be used with replicated mode") 518 } 519 serviceMode.Replicated.Replicas = flags.Lookup(flagReplicas).Value.(*Uint64Opt).Value() 520 return nil 521 } 522 523 // updateLogDriver updates the log driver only if the log driver flag is set. 524 // All options will be replaced with those provided on the command line. 525 func updateLogDriver(flags *pflag.FlagSet, taskTemplate *swarm.TaskSpec) error { 526 if !flags.Changed(flagLogDriver) { 527 return nil 528 } 529 530 name, err := flags.GetString(flagLogDriver) 531 if err != nil { 532 return err 533 } 534 535 if name == "" { 536 return nil 537 } 538 539 taskTemplate.LogDriver = &swarm.Driver{ 540 Name: name, 541 Options: runconfigopts.ConvertKVStringsToMap(flags.Lookup(flagLogOpt).Value.(*opts.ListOpts).GetAll()), 542 } 543 544 return nil 545 } 546 547 func updateHealthcheck(flags *pflag.FlagSet, containerSpec *swarm.ContainerSpec) error { 548 if !anyChanged(flags, flagNoHealthcheck, flagHealthCmd, flagHealthInterval, flagHealthRetries, flagHealthTimeout) { 549 return nil 550 } 551 if containerSpec.Healthcheck == nil { 552 containerSpec.Healthcheck = &container.HealthConfig{} 553 } 554 noHealthcheck, err := flags.GetBool(flagNoHealthcheck) 555 if err != nil { 556 return err 557 } 558 if noHealthcheck { 559 if !anyChanged(flags, flagHealthCmd, flagHealthInterval, flagHealthRetries, flagHealthTimeout) { 560 containerSpec.Healthcheck = &container.HealthConfig{ 561 Test: []string{"NONE"}, 562 } 563 return nil 564 } 565 return fmt.Errorf("--%s conflicts with --health-* options", flagNoHealthcheck) 566 } 567 if len(containerSpec.Healthcheck.Test) > 0 && containerSpec.Healthcheck.Test[0] == "NONE" { 568 containerSpec.Healthcheck.Test = nil 569 } 570 if flags.Changed(flagHealthInterval) { 571 val := *flags.Lookup(flagHealthInterval).Value.(*PositiveDurationOpt).Value() 572 containerSpec.Healthcheck.Interval = val 573 } 574 if flags.Changed(flagHealthTimeout) { 575 val := *flags.Lookup(flagHealthTimeout).Value.(*PositiveDurationOpt).Value() 576 containerSpec.Healthcheck.Timeout = val 577 } 578 if flags.Changed(flagHealthRetries) { 579 containerSpec.Healthcheck.Retries, _ = flags.GetInt(flagHealthRetries) 580 } 581 if flags.Changed(flagHealthCmd) { 582 cmd, _ := flags.GetString(flagHealthCmd) 583 if cmd != "" { 584 containerSpec.Healthcheck.Test = []string{"CMD-SHELL", cmd} 585 } else { 586 containerSpec.Healthcheck.Test = nil 587 } 588 } 589 return nil 590 }