update.go 18 KB

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