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