update.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625
  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 previously added supplementary user groups 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 service labels")
  45. flags.Var(&opts.containerLabels, flagContainerLabelAdd, "Add or update container labels")
  46. flags.Var(&opts.env, flagEnvAdd, "Add or update environment variables")
  47. flags.Var(&opts.mounts, flagMountAdd, "Add or update a mount on a service")
  48. flags.StringSliceVar(&opts.constraints, flagConstraintAdd, []string{}, "Add or update placement constraints")
  49. flags.Var(&opts.endpoint.ports, flagPublishAdd, "Add or update a published port")
  50. flags.StringSliceVar(&opts.groups, flagGroupAdd, []string{}, "Add additional supplementary user groups 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. return nil
  237. }
  238. func updateStringToSlice(flags *pflag.FlagSet, flag string, field *[]string) error {
  239. if !flags.Changed(flag) {
  240. return nil
  241. }
  242. value, _ := flags.GetString(flag)
  243. valueSlice, err := shlex.Split(value)
  244. *field = valueSlice
  245. return err
  246. }
  247. func anyChanged(flags *pflag.FlagSet, fields ...string) bool {
  248. for _, flag := range fields {
  249. if flags.Changed(flag) {
  250. return true
  251. }
  252. }
  253. return false
  254. }
  255. func updatePlacement(flags *pflag.FlagSet, placement *swarm.Placement) {
  256. field, _ := flags.GetStringSlice(flagConstraintAdd)
  257. placement.Constraints = append(placement.Constraints, field...)
  258. toRemove := buildToRemoveSet(flags, flagConstraintRemove)
  259. placement.Constraints = removeItems(placement.Constraints, toRemove, itemKey)
  260. }
  261. func updateContainerLabels(flags *pflag.FlagSet, field *map[string]string) {
  262. if flags.Changed(flagContainerLabelAdd) {
  263. if *field == nil {
  264. *field = map[string]string{}
  265. }
  266. values := flags.Lookup(flagContainerLabelAdd).Value.(*opts.ListOpts).GetAll()
  267. for key, value := range runconfigopts.ConvertKVStringsToMap(values) {
  268. (*field)[key] = value
  269. }
  270. }
  271. if *field != nil && flags.Changed(flagContainerLabelRemove) {
  272. toRemove := flags.Lookup(flagContainerLabelRemove).Value.(*opts.ListOpts).GetAll()
  273. for _, label := range toRemove {
  274. delete(*field, label)
  275. }
  276. }
  277. }
  278. func updateLabels(flags *pflag.FlagSet, field *map[string]string) {
  279. if flags.Changed(flagLabelAdd) {
  280. if *field == nil {
  281. *field = map[string]string{}
  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. if *field != nil && flags.Changed(flagLabelRemove) {
  289. toRemove := flags.Lookup(flagLabelRemove).Value.(*opts.ListOpts).GetAll()
  290. for _, label := range toRemove {
  291. delete(*field, label)
  292. }
  293. }
  294. }
  295. func updateEnvironment(flags *pflag.FlagSet, field *[]string) {
  296. envSet := map[string]string{}
  297. for _, v := range *field {
  298. envSet[envKey(v)] = v
  299. }
  300. if flags.Changed(flagEnvAdd) {
  301. value := flags.Lookup(flagEnvAdd).Value.(*opts.ListOpts)
  302. for _, v := range value.GetAll() {
  303. envSet[envKey(v)] = v
  304. }
  305. }
  306. *field = []string{}
  307. for _, v := range envSet {
  308. *field = append(*field, v)
  309. }
  310. toRemove := buildToRemoveSet(flags, flagEnvRemove)
  311. *field = removeItems(*field, toRemove, envKey)
  312. }
  313. func envKey(value string) string {
  314. kv := strings.SplitN(value, "=", 2)
  315. return kv[0]
  316. }
  317. func itemKey(value string) string {
  318. return value
  319. }
  320. func buildToRemoveSet(flags *pflag.FlagSet, flag string) map[string]struct{} {
  321. var empty struct{}
  322. toRemove := make(map[string]struct{})
  323. if !flags.Changed(flag) {
  324. return toRemove
  325. }
  326. toRemoveSlice := flags.Lookup(flag).Value.(*opts.ListOpts).GetAll()
  327. for _, key := range toRemoveSlice {
  328. toRemove[key] = empty
  329. }
  330. return toRemove
  331. }
  332. func removeItems(
  333. seq []string,
  334. toRemove map[string]struct{},
  335. keyFunc func(string) string,
  336. ) []string {
  337. newSeq := []string{}
  338. for _, item := range seq {
  339. if _, exists := toRemove[keyFunc(item)]; !exists {
  340. newSeq = append(newSeq, item)
  341. }
  342. }
  343. return newSeq
  344. }
  345. type byMountSource []mounttypes.Mount
  346. func (m byMountSource) Len() int { return len(m) }
  347. func (m byMountSource) Swap(i, j int) { m[i], m[j] = m[j], m[i] }
  348. func (m byMountSource) Less(i, j int) bool {
  349. a, b := m[i], m[j]
  350. if a.Source == b.Source {
  351. return a.Target < b.Target
  352. }
  353. return a.Source < b.Source
  354. }
  355. func updateMounts(flags *pflag.FlagSet, mounts *[]mounttypes.Mount) error {
  356. mountsByTarget := map[string]mounttypes.Mount{}
  357. if flags.Changed(flagMountAdd) {
  358. values := flags.Lookup(flagMountAdd).Value.(*opts.MountOpt).Value()
  359. for _, mount := range values {
  360. if _, ok := mountsByTarget[mount.Target]; ok {
  361. return fmt.Errorf("duplicate mount target")
  362. }
  363. mountsByTarget[mount.Target] = mount
  364. }
  365. }
  366. // Add old list of mount points minus updated one.
  367. for _, mount := range *mounts {
  368. if _, ok := mountsByTarget[mount.Target]; !ok {
  369. mountsByTarget[mount.Target] = mount
  370. }
  371. }
  372. newMounts := []mounttypes.Mount{}
  373. toRemove := buildToRemoveSet(flags, flagMountRemove)
  374. for _, mount := range mountsByTarget {
  375. if _, exists := toRemove[mount.Target]; !exists {
  376. newMounts = append(newMounts, mount)
  377. }
  378. }
  379. sort.Sort(byMountSource(newMounts))
  380. *mounts = newMounts
  381. return nil
  382. }
  383. func updateGroups(flags *pflag.FlagSet, groups *[]string) error {
  384. if flags.Changed(flagGroupAdd) {
  385. values, err := flags.GetStringSlice(flagGroupAdd)
  386. if err != nil {
  387. return err
  388. }
  389. *groups = append(*groups, values...)
  390. }
  391. toRemove := buildToRemoveSet(flags, flagGroupRemove)
  392. newGroups := []string{}
  393. for _, group := range *groups {
  394. if _, exists := toRemove[group]; !exists {
  395. newGroups = append(newGroups, group)
  396. }
  397. }
  398. // Sort so that result is predictable.
  399. sort.Strings(newGroups)
  400. *groups = newGroups
  401. return nil
  402. }
  403. type byPortConfig []swarm.PortConfig
  404. func (r byPortConfig) Len() int { return len(r) }
  405. func (r byPortConfig) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
  406. func (r byPortConfig) Less(i, j int) bool {
  407. // We convert PortConfig into `port/protocol`, e.g., `80/tcp`
  408. // In updatePorts we already filter out with map so there is duplicate entries
  409. return portConfigToString(&r[i]) < portConfigToString(&r[j])
  410. }
  411. func portConfigToString(portConfig *swarm.PortConfig) string {
  412. protocol := portConfig.Protocol
  413. if protocol == "" {
  414. protocol = "tcp"
  415. }
  416. return fmt.Sprintf("%v/%s", portConfig.PublishedPort, protocol)
  417. }
  418. func updatePorts(flags *pflag.FlagSet, portConfig *[]swarm.PortConfig) error {
  419. // The key of the map is `port/protocol`, e.g., `80/tcp`
  420. portSet := map[string]swarm.PortConfig{}
  421. // Check to see if there are any conflict in flags.
  422. if flags.Changed(flagPublishAdd) {
  423. values := flags.Lookup(flagPublishAdd).Value.(*opts.ListOpts).GetAll()
  424. ports, portBindings, _ := nat.ParsePortSpecs(values)
  425. for port := range ports {
  426. newConfigs := convertPortToPortConfig(port, portBindings)
  427. for _, entry := range newConfigs {
  428. if v, ok := portSet[portConfigToString(&entry)]; ok && v != entry {
  429. 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)
  430. }
  431. portSet[portConfigToString(&entry)] = entry
  432. }
  433. }
  434. }
  435. // Override previous PortConfig in service if there is any duplicate
  436. for _, entry := range *portConfig {
  437. if _, ok := portSet[portConfigToString(&entry)]; !ok {
  438. portSet[portConfigToString(&entry)] = entry
  439. }
  440. }
  441. toRemove := flags.Lookup(flagPublishRemove).Value.(*opts.ListOpts).GetAll()
  442. newPorts := []swarm.PortConfig{}
  443. portLoop:
  444. for _, port := range portSet {
  445. for _, rawTargetPort := range toRemove {
  446. targetPort := nat.Port(rawTargetPort)
  447. if equalPort(targetPort, port) {
  448. continue portLoop
  449. }
  450. }
  451. newPorts = append(newPorts, port)
  452. }
  453. // Sort the PortConfig to avoid unnecessary updates
  454. sort.Sort(byPortConfig(newPorts))
  455. *portConfig = newPorts
  456. return nil
  457. }
  458. func equalPort(targetPort nat.Port, port swarm.PortConfig) bool {
  459. return (string(port.Protocol) == targetPort.Proto() &&
  460. port.TargetPort == uint32(targetPort.Int()))
  461. }
  462. func updateReplicas(flags *pflag.FlagSet, serviceMode *swarm.ServiceMode) error {
  463. if !flags.Changed(flagReplicas) {
  464. return nil
  465. }
  466. if serviceMode == nil || serviceMode.Replicated == nil {
  467. return fmt.Errorf("replicas can only be used with replicated mode")
  468. }
  469. serviceMode.Replicated.Replicas = flags.Lookup(flagReplicas).Value.(*Uint64Opt).Value()
  470. return nil
  471. }
  472. // updateLogDriver updates the log driver only if the log driver flag is set.
  473. // All options will be replaced with those provided on the command line.
  474. func updateLogDriver(flags *pflag.FlagSet, taskTemplate *swarm.TaskSpec) error {
  475. if !flags.Changed(flagLogDriver) {
  476. return nil
  477. }
  478. name, err := flags.GetString(flagLogDriver)
  479. if err != nil {
  480. return err
  481. }
  482. if name == "" {
  483. return nil
  484. }
  485. taskTemplate.LogDriver = &swarm.Driver{
  486. Name: name,
  487. Options: runconfigopts.ConvertKVStringsToMap(flags.Lookup(flagLogOpt).Value.(*opts.ListOpts).GetAll()),
  488. }
  489. return nil
  490. }
  491. func updateHealthcheck(flags *pflag.FlagSet, containerSpec *swarm.ContainerSpec) error {
  492. if !anyChanged(flags, flagNoHealthcheck, flagHealthCmd, flagHealthInterval, flagHealthRetries, flagHealthTimeout) {
  493. return nil
  494. }
  495. if containerSpec.Healthcheck == nil {
  496. containerSpec.Healthcheck = &container.HealthConfig{}
  497. }
  498. noHealthcheck, err := flags.GetBool(flagNoHealthcheck)
  499. if err != nil {
  500. return err
  501. }
  502. if noHealthcheck {
  503. if !anyChanged(flags, flagHealthCmd, flagHealthInterval, flagHealthRetries, flagHealthTimeout) {
  504. containerSpec.Healthcheck = &container.HealthConfig{
  505. Test: []string{"NONE"},
  506. }
  507. return nil
  508. }
  509. return fmt.Errorf("--%s conflicts with --health-* options", flagNoHealthcheck)
  510. }
  511. if len(containerSpec.Healthcheck.Test) > 0 && containerSpec.Healthcheck.Test[0] == "NONE" {
  512. containerSpec.Healthcheck.Test = nil
  513. }
  514. if flags.Changed(flagHealthInterval) {
  515. val := *flags.Lookup(flagHealthInterval).Value.(*PositiveDurationOpt).Value()
  516. containerSpec.Healthcheck.Interval = val
  517. }
  518. if flags.Changed(flagHealthTimeout) {
  519. val := *flags.Lookup(flagHealthTimeout).Value.(*PositiveDurationOpt).Value()
  520. containerSpec.Healthcheck.Timeout = val
  521. }
  522. if flags.Changed(flagHealthRetries) {
  523. containerSpec.Healthcheck.Retries, _ = flags.GetInt(flagHealthRetries)
  524. }
  525. if flags.Changed(flagHealthCmd) {
  526. cmd, _ := flags.GetString(flagHealthCmd)
  527. if cmd != "" {
  528. containerSpec.Healthcheck.Test = []string{"CMD-SHELL", cmd}
  529. } else {
  530. containerSpec.Healthcheck.Test = nil
  531. }
  532. }
  533. return nil
  534. }