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