github.com/brahmaroutu/docker@v1.2.1-0.20160809185609-eb28dde01f16/api/client/service/update.go (about) 1 package service 2 3 import ( 4 "fmt" 5 "strings" 6 "time" 7 8 "golang.org/x/net/context" 9 10 "github.com/docker/docker/api/client" 11 "github.com/docker/docker/cli" 12 "github.com/docker/docker/opts" 13 runconfigopts "github.com/docker/docker/runconfig/opts" 14 "github.com/docker/engine-api/types" 15 "github.com/docker/engine-api/types/swarm" 16 "github.com/docker/go-connections/nat" 17 shlex "github.com/flynn-archive/go-shlex" 18 "github.com/spf13/cobra" 19 "github.com/spf13/pflag" 20 ) 21 22 func newUpdateCommand(dockerCli *client.DockerCli) *cobra.Command { 23 opts := newServiceOptions() 24 25 cmd := &cobra.Command{ 26 Use: "update [OPTIONS] SERVICE", 27 Short: "Update a service", 28 Args: cli.ExactArgs(1), 29 RunE: func(cmd *cobra.Command, args []string) error { 30 return runUpdate(dockerCli, cmd.Flags(), args[0]) 31 }, 32 } 33 34 flags := cmd.Flags() 35 flags.String("image", "", "Service image tag") 36 flags.String("args", "", "Service command args") 37 addServiceFlags(cmd, opts) 38 39 flags.Var(newListOptsVar(), flagEnvRemove, "Remove an environment variable") 40 flags.Var(newListOptsVar(), flagLabelRemove, "Remove a label by its key") 41 flags.Var(newListOptsVar(), flagContainerLabelRemove, "Remove a container label by its key") 42 flags.Var(newListOptsVar(), flagMountRemove, "Remove a mount by its target path") 43 flags.Var(newListOptsVar(), flagPublishRemove, "Remove a published port by its target port") 44 flags.Var(newListOptsVar(), flagNetworkRemove, "Remove a network by name") 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.StringSliceVar(&opts.networks, flagNetworkAdd, []string{}, "Add or update network attachments") 52 flags.Var(&opts.endpoint.ports, flagPublishAdd, "Add or update a published port") 53 return cmd 54 } 55 56 func newListOptsVar() *opts.ListOpts { 57 return opts.NewListOptsRef(&[]string{}, nil) 58 } 59 60 func runUpdate(dockerCli *client.DockerCli, flags *pflag.FlagSet, serviceID string) error { 61 apiClient := dockerCli.Client() 62 ctx := context.Background() 63 updateOpts := types.ServiceUpdateOptions{} 64 65 service, _, err := apiClient.ServiceInspectWithRaw(ctx, serviceID) 66 if err != nil { 67 return err 68 } 69 70 err = updateService(flags, &service.Spec) 71 if err != nil { 72 return err 73 } 74 75 // only send auth if flag was set 76 sendAuth, err := flags.GetBool(flagRegistryAuth) 77 if err != nil { 78 return err 79 } 80 if sendAuth { 81 // Retrieve encoded auth token from the image reference 82 // This would be the old image if it didn't change in this update 83 image := service.Spec.TaskTemplate.ContainerSpec.Image 84 encodedAuth, err := dockerCli.RetrieveAuthTokenFromImage(ctx, image) 85 if err != nil { 86 return err 87 } 88 updateOpts.EncodedRegistryAuth = encodedAuth 89 } 90 91 err = apiClient.ServiceUpdate(ctx, service.ID, service.Version, service.Spec, updateOpts) 92 if err != nil { 93 return err 94 } 95 96 fmt.Fprintf(dockerCli.Out(), "%s\n", serviceID) 97 return nil 98 } 99 100 func updateService(flags *pflag.FlagSet, spec *swarm.ServiceSpec) error { 101 updateString := func(flag string, field *string) { 102 if flags.Changed(flag) { 103 *field, _ = flags.GetString(flag) 104 } 105 } 106 107 updateInt64Value := func(flag string, field *int64) { 108 if flags.Changed(flag) { 109 *field = flags.Lookup(flag).Value.(int64Value).Value() 110 } 111 } 112 113 updateDuration := func(flag string, field *time.Duration) { 114 if flags.Changed(flag) { 115 *field, _ = flags.GetDuration(flag) 116 } 117 } 118 119 updateDurationOpt := func(flag string, field **time.Duration) { 120 if flags.Changed(flag) { 121 val := *flags.Lookup(flag).Value.(*DurationOpt).Value() 122 *field = &val 123 } 124 } 125 126 updateUint64 := func(flag string, field *uint64) { 127 if flags.Changed(flag) { 128 *field, _ = flags.GetUint64(flag) 129 } 130 } 131 132 updateUint64Opt := func(flag string, field **uint64) { 133 if flags.Changed(flag) { 134 val := *flags.Lookup(flag).Value.(*Uint64Opt).Value() 135 *field = &val 136 } 137 } 138 139 cspec := &spec.TaskTemplate.ContainerSpec 140 task := &spec.TaskTemplate 141 142 taskResources := func() *swarm.ResourceRequirements { 143 if task.Resources == nil { 144 task.Resources = &swarm.ResourceRequirements{} 145 } 146 return task.Resources 147 } 148 149 updateString(flagName, &spec.Name) 150 updateLabels(flags, &spec.Labels) 151 updateContainerLabels(flags, &cspec.Labels) 152 updateString("image", &cspec.Image) 153 updateStringToSlice(flags, "args", &cspec.Args) 154 updateEnvironment(flags, &cspec.Env) 155 updateString(flagWorkdir, &cspec.Dir) 156 updateString(flagUser, &cspec.User) 157 updateMounts(flags, &cspec.Mounts) 158 159 if flags.Changed(flagLimitCPU) || flags.Changed(flagLimitMemory) { 160 taskResources().Limits = &swarm.Resources{} 161 updateInt64Value(flagLimitCPU, &task.Resources.Limits.NanoCPUs) 162 updateInt64Value(flagLimitMemory, &task.Resources.Limits.MemoryBytes) 163 } 164 if flags.Changed(flagReserveCPU) || flags.Changed(flagReserveMemory) { 165 taskResources().Reservations = &swarm.Resources{} 166 updateInt64Value(flagReserveCPU, &task.Resources.Reservations.NanoCPUs) 167 updateInt64Value(flagReserveMemory, &task.Resources.Reservations.MemoryBytes) 168 } 169 170 updateDurationOpt(flagStopGracePeriod, &cspec.StopGracePeriod) 171 172 if anyChanged(flags, flagRestartCondition, flagRestartDelay, flagRestartMaxAttempts, flagRestartWindow) { 173 if task.RestartPolicy == nil { 174 task.RestartPolicy = &swarm.RestartPolicy{} 175 } 176 177 if flags.Changed(flagRestartCondition) { 178 value, _ := flags.GetString(flagRestartCondition) 179 task.RestartPolicy.Condition = swarm.RestartPolicyCondition(value) 180 } 181 updateDurationOpt(flagRestartDelay, &task.RestartPolicy.Delay) 182 updateUint64Opt(flagRestartMaxAttempts, &task.RestartPolicy.MaxAttempts) 183 updateDurationOpt(flagRestartWindow, &task.RestartPolicy.Window) 184 } 185 186 if anyChanged(flags, flagConstraintAdd, flagConstraintRemove) { 187 if task.Placement == nil { 188 task.Placement = &swarm.Placement{} 189 } 190 updatePlacement(flags, task.Placement) 191 } 192 193 if err := updateReplicas(flags, &spec.Mode); err != nil { 194 return err 195 } 196 197 if anyChanged(flags, flagUpdateParallelism, flagUpdateDelay, flagUpdateFailureAction) { 198 if spec.UpdateConfig == nil { 199 spec.UpdateConfig = &swarm.UpdateConfig{} 200 } 201 updateUint64(flagUpdateParallelism, &spec.UpdateConfig.Parallelism) 202 updateDuration(flagUpdateDelay, &spec.UpdateConfig.Delay) 203 updateString(flagUpdateFailureAction, &spec.UpdateConfig.FailureAction) 204 } 205 206 updateNetworks(flags, &spec.Networks) 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, flagPublishAdd, flagPublishRemove) { 216 if spec.EndpointSpec == nil { 217 spec.EndpointSpec = &swarm.EndpointSpec{} 218 } 219 updatePorts(flags, &spec.EndpointSpec.Ports) 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 if flags.Changed(flagEnvAdd) { 299 value := flags.Lookup(flagEnvAdd).Value.(*opts.ListOpts) 300 *field = append(*field, value.GetAll()...) 301 } 302 toRemove := buildToRemoveSet(flags, flagEnvRemove) 303 *field = removeItems(*field, toRemove, envKey) 304 } 305 306 func envKey(value string) string { 307 kv := strings.SplitN(value, "=", 2) 308 return kv[0] 309 } 310 311 func itemKey(value string) string { 312 return value 313 } 314 315 func buildToRemoveSet(flags *pflag.FlagSet, flag string) map[string]struct{} { 316 var empty struct{} 317 toRemove := make(map[string]struct{}) 318 319 if !flags.Changed(flag) { 320 return toRemove 321 } 322 323 toRemoveSlice := flags.Lookup(flag).Value.(*opts.ListOpts).GetAll() 324 for _, key := range toRemoveSlice { 325 toRemove[key] = empty 326 } 327 return toRemove 328 } 329 330 func removeItems( 331 seq []string, 332 toRemove map[string]struct{}, 333 keyFunc func(string) string, 334 ) []string { 335 newSeq := []string{} 336 for _, item := range seq { 337 if _, exists := toRemove[keyFunc(item)]; !exists { 338 newSeq = append(newSeq, item) 339 } 340 } 341 return newSeq 342 } 343 344 func updateMounts(flags *pflag.FlagSet, mounts *[]swarm.Mount) { 345 if flags.Changed(flagMountAdd) { 346 values := flags.Lookup(flagMountAdd).Value.(*MountOpt).Value() 347 *mounts = append(*mounts, values...) 348 } 349 toRemove := buildToRemoveSet(flags, flagMountRemove) 350 351 newMounts := []swarm.Mount{} 352 for _, mount := range *mounts { 353 if _, exists := toRemove[mount.Target]; !exists { 354 newMounts = append(newMounts, mount) 355 } 356 } 357 *mounts = newMounts 358 } 359 360 func updatePorts(flags *pflag.FlagSet, portConfig *[]swarm.PortConfig) { 361 if flags.Changed(flagPublishAdd) { 362 values := flags.Lookup(flagPublishAdd).Value.(*opts.ListOpts).GetAll() 363 ports, portBindings, _ := nat.ParsePortSpecs(values) 364 365 for port := range ports { 366 *portConfig = append(*portConfig, convertPortToPortConfig(port, portBindings)...) 367 } 368 } 369 370 if !flags.Changed(flagPublishRemove) { 371 return 372 } 373 toRemove := flags.Lookup(flagPublishRemove).Value.(*opts.ListOpts).GetAll() 374 newPorts := []swarm.PortConfig{} 375 portLoop: 376 for _, port := range *portConfig { 377 for _, rawTargetPort := range toRemove { 378 targetPort := nat.Port(rawTargetPort) 379 if equalPort(targetPort, port) { 380 continue portLoop 381 } 382 } 383 newPorts = append(newPorts, port) 384 } 385 *portConfig = newPorts 386 } 387 388 func equalPort(targetPort nat.Port, port swarm.PortConfig) bool { 389 return (string(port.Protocol) == targetPort.Proto() && 390 port.TargetPort == uint32(targetPort.Int())) 391 } 392 393 func updateNetworks(flags *pflag.FlagSet, attachments *[]swarm.NetworkAttachmentConfig) { 394 if flags.Changed(flagNetworkAdd) { 395 networks, _ := flags.GetStringSlice(flagNetworkAdd) 396 for _, network := range networks { 397 *attachments = append(*attachments, swarm.NetworkAttachmentConfig{Target: network}) 398 } 399 } 400 toRemove := buildToRemoveSet(flags, flagNetworkRemove) 401 newNetworks := []swarm.NetworkAttachmentConfig{} 402 for _, network := range *attachments { 403 if _, exists := toRemove[network.Target]; !exists { 404 newNetworks = append(newNetworks, network) 405 } 406 } 407 *attachments = newNetworks 408 } 409 410 func updateReplicas(flags *pflag.FlagSet, serviceMode *swarm.ServiceMode) error { 411 if !flags.Changed(flagReplicas) { 412 return nil 413 } 414 415 if serviceMode == nil || serviceMode.Replicated == nil { 416 return fmt.Errorf("replicas can only be used with replicated mode") 417 } 418 serviceMode.Replicated.Replicas = flags.Lookup(flagReplicas).Value.(*Uint64Opt).Value() 419 return nil 420 } 421 422 // updateLogDriver updates the log driver only if the log driver flag is set. 423 // All options will be replaced with those provided on the command line. 424 func updateLogDriver(flags *pflag.FlagSet, taskTemplate *swarm.TaskSpec) error { 425 if !flags.Changed(flagLogDriver) { 426 return nil 427 } 428 429 name, err := flags.GetString(flagLogDriver) 430 if err != nil { 431 return err 432 } 433 434 if name == "" { 435 return nil 436 } 437 438 taskTemplate.LogDriver = &swarm.Driver{ 439 Name: name, 440 Options: runconfigopts.ConvertKVStringsToMap(flags.Lookup(flagLogOpt).Value.(*opts.ListOpts).GetAll()), 441 } 442 443 return nil 444 }