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