opts.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648
  1. package service
  2. import (
  3. "encoding/csv"
  4. "fmt"
  5. "os"
  6. "path/filepath"
  7. "strconv"
  8. "strings"
  9. "time"
  10. "github.com/docker/docker/api/types/container"
  11. "github.com/docker/docker/api/types/swarm"
  12. "github.com/docker/docker/opts"
  13. runconfigopts "github.com/docker/docker/runconfig/opts"
  14. "github.com/docker/go-connections/nat"
  15. units "github.com/docker/go-units"
  16. "github.com/spf13/cobra"
  17. )
  18. type int64Value interface {
  19. Value() int64
  20. }
  21. type memBytes int64
  22. func (m *memBytes) String() string {
  23. return units.BytesSize(float64(m.Value()))
  24. }
  25. func (m *memBytes) Set(value string) error {
  26. val, err := units.RAMInBytes(value)
  27. *m = memBytes(val)
  28. return err
  29. }
  30. func (m *memBytes) Type() string {
  31. return "bytes"
  32. }
  33. func (m *memBytes) Value() int64 {
  34. return int64(*m)
  35. }
  36. // PositiveDurationOpt is an option type for time.Duration that uses a pointer.
  37. // It bahave similarly to DurationOpt but only allows positive duration values.
  38. type PositiveDurationOpt struct {
  39. DurationOpt
  40. }
  41. // Set a new value on the option. Setting a negative duration value will cause
  42. // an error to be returned.
  43. func (d *PositiveDurationOpt) Set(s string) error {
  44. err := d.DurationOpt.Set(s)
  45. if err != nil {
  46. return err
  47. }
  48. if *d.DurationOpt.value < 0 {
  49. return fmt.Errorf("duration cannot be negative")
  50. }
  51. return nil
  52. }
  53. // DurationOpt is an option type for time.Duration that uses a pointer. This
  54. // allows us to get nil values outside, instead of defaulting to 0
  55. type DurationOpt struct {
  56. value *time.Duration
  57. }
  58. // Set a new value on the option
  59. func (d *DurationOpt) Set(s string) error {
  60. v, err := time.ParseDuration(s)
  61. d.value = &v
  62. return err
  63. }
  64. // Type returns the type of this option, which will be displayed in `--help` output
  65. func (d *DurationOpt) Type() string {
  66. return "duration"
  67. }
  68. // String returns a string repr of this option
  69. func (d *DurationOpt) String() string {
  70. if d.value != nil {
  71. return d.value.String()
  72. }
  73. return ""
  74. }
  75. // Value returns the time.Duration
  76. func (d *DurationOpt) Value() *time.Duration {
  77. return d.value
  78. }
  79. // Uint64Opt represents a uint64.
  80. type Uint64Opt struct {
  81. value *uint64
  82. }
  83. // Set a new value on the option
  84. func (i *Uint64Opt) Set(s string) error {
  85. v, err := strconv.ParseUint(s, 0, 64)
  86. i.value = &v
  87. return err
  88. }
  89. // Type returns the type of this option, which will be displayed in `--help` output
  90. func (i *Uint64Opt) Type() string {
  91. return "uint"
  92. }
  93. // String returns a string repr of this option
  94. func (i *Uint64Opt) String() string {
  95. if i.value != nil {
  96. return fmt.Sprintf("%v", *i.value)
  97. }
  98. return ""
  99. }
  100. // Value returns the uint64
  101. func (i *Uint64Opt) Value() *uint64 {
  102. return i.value
  103. }
  104. type floatValue float32
  105. func (f *floatValue) Set(s string) error {
  106. v, err := strconv.ParseFloat(s, 32)
  107. *f = floatValue(v)
  108. return err
  109. }
  110. func (f *floatValue) Type() string {
  111. return "float"
  112. }
  113. func (f *floatValue) String() string {
  114. return strconv.FormatFloat(float64(*f), 'g', -1, 32)
  115. }
  116. func (f *floatValue) Value() float32 {
  117. return float32(*f)
  118. }
  119. // SecretRequestSpec is a type for requesting secrets
  120. type SecretRequestSpec struct {
  121. source string
  122. target string
  123. uid string
  124. gid string
  125. mode os.FileMode
  126. }
  127. // SecretOpt is a Value type for parsing secrets
  128. type SecretOpt struct {
  129. values []*SecretRequestSpec
  130. }
  131. // Set a new secret value
  132. func (o *SecretOpt) Set(value string) error {
  133. csvReader := csv.NewReader(strings.NewReader(value))
  134. fields, err := csvReader.Read()
  135. if err != nil {
  136. return err
  137. }
  138. spec := &SecretRequestSpec{
  139. source: "",
  140. target: "",
  141. uid: "0",
  142. gid: "0",
  143. mode: 0444,
  144. }
  145. for _, field := range fields {
  146. parts := strings.SplitN(field, "=", 2)
  147. key := strings.ToLower(parts[0])
  148. if len(parts) != 2 {
  149. return fmt.Errorf("invalid field '%s' must be a key=value pair", field)
  150. }
  151. value := parts[1]
  152. switch key {
  153. case "source", "src":
  154. spec.source = value
  155. case "target":
  156. tDir, _ := filepath.Split(value)
  157. if tDir != "" {
  158. return fmt.Errorf("target must not have a path")
  159. }
  160. spec.target = value
  161. case "uid":
  162. spec.uid = value
  163. case "gid":
  164. spec.gid = value
  165. case "mode":
  166. m, err := strconv.ParseUint(value, 0, 32)
  167. if err != nil {
  168. return fmt.Errorf("invalid mode specified: %v", err)
  169. }
  170. spec.mode = os.FileMode(m)
  171. default:
  172. return fmt.Errorf("invalid field in secret request: %s", key)
  173. }
  174. }
  175. if spec.source == "" {
  176. return fmt.Errorf("source is required")
  177. }
  178. o.values = append(o.values, spec)
  179. return nil
  180. }
  181. // Type returns the type of this option
  182. func (o *SecretOpt) Type() string {
  183. return "secret"
  184. }
  185. // String returns a string repr of this option
  186. func (o *SecretOpt) String() string {
  187. secrets := []string{}
  188. for _, secret := range o.values {
  189. repr := fmt.Sprintf("%s -> %s", secret.source, secret.target)
  190. secrets = append(secrets, repr)
  191. }
  192. return strings.Join(secrets, ", ")
  193. }
  194. // Value returns the secret requests
  195. func (o *SecretOpt) Value() []*SecretRequestSpec {
  196. return o.values
  197. }
  198. type updateOptions struct {
  199. parallelism uint64
  200. delay time.Duration
  201. monitor time.Duration
  202. onFailure string
  203. maxFailureRatio floatValue
  204. }
  205. type resourceOptions struct {
  206. limitCPU opts.NanoCPUs
  207. limitMemBytes memBytes
  208. resCPU opts.NanoCPUs
  209. resMemBytes memBytes
  210. }
  211. func (r *resourceOptions) ToResourceRequirements() *swarm.ResourceRequirements {
  212. return &swarm.ResourceRequirements{
  213. Limits: &swarm.Resources{
  214. NanoCPUs: r.limitCPU.Value(),
  215. MemoryBytes: r.limitMemBytes.Value(),
  216. },
  217. Reservations: &swarm.Resources{
  218. NanoCPUs: r.resCPU.Value(),
  219. MemoryBytes: r.resMemBytes.Value(),
  220. },
  221. }
  222. }
  223. type restartPolicyOptions struct {
  224. condition string
  225. delay DurationOpt
  226. maxAttempts Uint64Opt
  227. window DurationOpt
  228. }
  229. func (r *restartPolicyOptions) ToRestartPolicy() *swarm.RestartPolicy {
  230. return &swarm.RestartPolicy{
  231. Condition: swarm.RestartPolicyCondition(r.condition),
  232. Delay: r.delay.Value(),
  233. MaxAttempts: r.maxAttempts.Value(),
  234. Window: r.window.Value(),
  235. }
  236. }
  237. func convertNetworks(networks []string) []swarm.NetworkAttachmentConfig {
  238. nets := []swarm.NetworkAttachmentConfig{}
  239. for _, network := range networks {
  240. nets = append(nets, swarm.NetworkAttachmentConfig{Target: network})
  241. }
  242. return nets
  243. }
  244. type endpointOptions struct {
  245. mode string
  246. publishPorts opts.PortOpt
  247. }
  248. func (e *endpointOptions) ToEndpointSpec() *swarm.EndpointSpec {
  249. return &swarm.EndpointSpec{
  250. Mode: swarm.ResolutionMode(strings.ToLower(e.mode)),
  251. Ports: e.publishPorts.Value(),
  252. }
  253. }
  254. type logDriverOptions struct {
  255. name string
  256. opts opts.ListOpts
  257. }
  258. func newLogDriverOptions() logDriverOptions {
  259. return logDriverOptions{opts: opts.NewListOpts(runconfigopts.ValidateEnv)}
  260. }
  261. func (ldo *logDriverOptions) toLogDriver() *swarm.Driver {
  262. if ldo.name == "" {
  263. return nil
  264. }
  265. // set the log driver only if specified.
  266. return &swarm.Driver{
  267. Name: ldo.name,
  268. Options: runconfigopts.ConvertKVStringsToMap(ldo.opts.GetAll()),
  269. }
  270. }
  271. type healthCheckOptions struct {
  272. cmd string
  273. interval PositiveDurationOpt
  274. timeout PositiveDurationOpt
  275. retries int
  276. noHealthcheck bool
  277. }
  278. func (opts *healthCheckOptions) toHealthConfig() (*container.HealthConfig, error) {
  279. var healthConfig *container.HealthConfig
  280. haveHealthSettings := opts.cmd != "" ||
  281. opts.interval.Value() != nil ||
  282. opts.timeout.Value() != nil ||
  283. opts.retries != 0
  284. if opts.noHealthcheck {
  285. if haveHealthSettings {
  286. return nil, fmt.Errorf("--%s conflicts with --health-* options", flagNoHealthcheck)
  287. }
  288. healthConfig = &container.HealthConfig{Test: []string{"NONE"}}
  289. } else if haveHealthSettings {
  290. var test []string
  291. if opts.cmd != "" {
  292. test = []string{"CMD-SHELL", opts.cmd}
  293. }
  294. var interval, timeout time.Duration
  295. if ptr := opts.interval.Value(); ptr != nil {
  296. interval = *ptr
  297. }
  298. if ptr := opts.timeout.Value(); ptr != nil {
  299. timeout = *ptr
  300. }
  301. healthConfig = &container.HealthConfig{
  302. Test: test,
  303. Interval: interval,
  304. Timeout: timeout,
  305. Retries: opts.retries,
  306. }
  307. }
  308. return healthConfig, nil
  309. }
  310. // ValidatePort validates a string is in the expected format for a port definition
  311. func ValidatePort(value string) (string, error) {
  312. portMappings, err := nat.ParsePortSpec(value)
  313. for _, portMapping := range portMappings {
  314. if portMapping.Binding.HostIP != "" {
  315. return "", fmt.Errorf("HostIP is not supported by a service.")
  316. }
  317. }
  318. return value, err
  319. }
  320. // convertExtraHostsToSwarmHosts converts an array of extra hosts in cli
  321. // <host>:<ip>
  322. // into a swarmkit host format:
  323. // IP_address canonical_hostname [aliases...]
  324. // This assumes input value (<host>:<ip>) has already been validated
  325. func convertExtraHostsToSwarmHosts(extraHosts []string) []string {
  326. hosts := []string{}
  327. for _, extraHost := range extraHosts {
  328. parts := strings.SplitN(extraHost, ":", 2)
  329. hosts = append(hosts, fmt.Sprintf("%s %s", parts[1], parts[0]))
  330. }
  331. return hosts
  332. }
  333. type serviceOptions struct {
  334. name string
  335. labels opts.ListOpts
  336. containerLabels opts.ListOpts
  337. image string
  338. args []string
  339. hostname string
  340. env opts.ListOpts
  341. envFile opts.ListOpts
  342. workdir string
  343. user string
  344. groups opts.ListOpts
  345. tty bool
  346. mounts opts.MountOpt
  347. dns opts.ListOpts
  348. dnsSearch opts.ListOpts
  349. dnsOption opts.ListOpts
  350. hosts opts.ListOpts
  351. resources resourceOptions
  352. stopGrace DurationOpt
  353. replicas Uint64Opt
  354. mode string
  355. restartPolicy restartPolicyOptions
  356. constraints opts.ListOpts
  357. update updateOptions
  358. networks opts.ListOpts
  359. endpoint endpointOptions
  360. registryAuth bool
  361. logDriver logDriverOptions
  362. healthcheck healthCheckOptions
  363. secrets opts.SecretOpt
  364. }
  365. func newServiceOptions() *serviceOptions {
  366. return &serviceOptions{
  367. labels: opts.NewListOpts(runconfigopts.ValidateEnv),
  368. constraints: opts.NewListOpts(nil),
  369. containerLabels: opts.NewListOpts(runconfigopts.ValidateEnv),
  370. env: opts.NewListOpts(runconfigopts.ValidateEnv),
  371. envFile: opts.NewListOpts(nil),
  372. groups: opts.NewListOpts(nil),
  373. logDriver: newLogDriverOptions(),
  374. dns: opts.NewListOpts(opts.ValidateIPAddress),
  375. dnsOption: opts.NewListOpts(nil),
  376. dnsSearch: opts.NewListOpts(opts.ValidateDNSSearch),
  377. hosts: opts.NewListOpts(runconfigopts.ValidateExtraHost),
  378. networks: opts.NewListOpts(nil),
  379. }
  380. }
  381. func (opts *serviceOptions) ToService() (swarm.ServiceSpec, error) {
  382. var service swarm.ServiceSpec
  383. envVariables, err := runconfigopts.ReadKVStrings(opts.envFile.GetAll(), opts.env.GetAll())
  384. if err != nil {
  385. return service, err
  386. }
  387. currentEnv := make([]string, 0, len(envVariables))
  388. for _, env := range envVariables { // need to process each var, in order
  389. k := strings.SplitN(env, "=", 2)[0]
  390. for i, current := range currentEnv { // remove duplicates
  391. if current == env {
  392. continue // no update required, may hide this behind flag to preserve order of envVariables
  393. }
  394. if strings.HasPrefix(current, k+"=") {
  395. currentEnv = append(currentEnv[:i], currentEnv[i+1:]...)
  396. }
  397. }
  398. currentEnv = append(currentEnv, env)
  399. }
  400. service = swarm.ServiceSpec{
  401. Annotations: swarm.Annotations{
  402. Name: opts.name,
  403. Labels: runconfigopts.ConvertKVStringsToMap(opts.labels.GetAll()),
  404. },
  405. TaskTemplate: swarm.TaskSpec{
  406. ContainerSpec: swarm.ContainerSpec{
  407. Image: opts.image,
  408. Args: opts.args,
  409. Env: currentEnv,
  410. Hostname: opts.hostname,
  411. Labels: runconfigopts.ConvertKVStringsToMap(opts.containerLabels.GetAll()),
  412. Dir: opts.workdir,
  413. User: opts.user,
  414. Groups: opts.groups.GetAll(),
  415. TTY: opts.tty,
  416. Mounts: opts.mounts.Value(),
  417. DNSConfig: &swarm.DNSConfig{
  418. Nameservers: opts.dns.GetAll(),
  419. Search: opts.dnsSearch.GetAll(),
  420. Options: opts.dnsOption.GetAll(),
  421. },
  422. Hosts: convertExtraHostsToSwarmHosts(opts.hosts.GetAll()),
  423. StopGracePeriod: opts.stopGrace.Value(),
  424. Secrets: nil,
  425. },
  426. Networks: convertNetworks(opts.networks.GetAll()),
  427. Resources: opts.resources.ToResourceRequirements(),
  428. RestartPolicy: opts.restartPolicy.ToRestartPolicy(),
  429. Placement: &swarm.Placement{
  430. Constraints: opts.constraints.GetAll(),
  431. },
  432. LogDriver: opts.logDriver.toLogDriver(),
  433. },
  434. Networks: convertNetworks(opts.networks.GetAll()),
  435. Mode: swarm.ServiceMode{},
  436. UpdateConfig: &swarm.UpdateConfig{
  437. Parallelism: opts.update.parallelism,
  438. Delay: opts.update.delay,
  439. Monitor: opts.update.monitor,
  440. FailureAction: opts.update.onFailure,
  441. MaxFailureRatio: opts.update.maxFailureRatio.Value(),
  442. },
  443. EndpointSpec: opts.endpoint.ToEndpointSpec(),
  444. }
  445. healthConfig, err := opts.healthcheck.toHealthConfig()
  446. if err != nil {
  447. return service, err
  448. }
  449. service.TaskTemplate.ContainerSpec.Healthcheck = healthConfig
  450. switch opts.mode {
  451. case "global":
  452. if opts.replicas.Value() != nil {
  453. return service, fmt.Errorf("replicas can only be used with replicated mode")
  454. }
  455. service.Mode.Global = &swarm.GlobalService{}
  456. case "replicated":
  457. service.Mode.Replicated = &swarm.ReplicatedService{
  458. Replicas: opts.replicas.Value(),
  459. }
  460. default:
  461. return service, fmt.Errorf("Unknown mode: %s", opts.mode)
  462. }
  463. return service, nil
  464. }
  465. // addServiceFlags adds all flags that are common to both `create` and `update`.
  466. // Any flags that are not common are added separately in the individual command
  467. func addServiceFlags(cmd *cobra.Command, opts *serviceOptions) {
  468. flags := cmd.Flags()
  469. flags.StringVarP(&opts.workdir, flagWorkdir, "w", "", "Working directory inside the container")
  470. flags.StringVarP(&opts.user, flagUser, "u", "", "Username or UID (format: <name|uid>[:<group|gid>])")
  471. flags.StringVar(&opts.hostname, flagHostname, "", "Container hostname")
  472. flags.Var(&opts.resources.limitCPU, flagLimitCPU, "Limit CPUs")
  473. flags.Var(&opts.resources.limitMemBytes, flagLimitMemory, "Limit Memory")
  474. flags.Var(&opts.resources.resCPU, flagReserveCPU, "Reserve CPUs")
  475. flags.Var(&opts.resources.resMemBytes, flagReserveMemory, "Reserve Memory")
  476. flags.Var(&opts.stopGrace, flagStopGracePeriod, "Time to wait before force killing a container (ns|us|ms|s|m|h)")
  477. flags.Var(&opts.replicas, flagReplicas, "Number of tasks")
  478. flags.StringVar(&opts.restartPolicy.condition, flagRestartCondition, "", "Restart when condition is met (none, on-failure, or any)")
  479. flags.Var(&opts.restartPolicy.delay, flagRestartDelay, "Delay between restart attempts (ns|us|ms|s|m|h)")
  480. flags.Var(&opts.restartPolicy.maxAttempts, flagRestartMaxAttempts, "Maximum number of restarts before giving up")
  481. flags.Var(&opts.restartPolicy.window, flagRestartWindow, "Window used to evaluate the restart policy (ns|us|ms|s|m|h)")
  482. flags.Uint64Var(&opts.update.parallelism, flagUpdateParallelism, 1, "Maximum number of tasks updated simultaneously (0 to update all at once)")
  483. flags.DurationVar(&opts.update.delay, flagUpdateDelay, time.Duration(0), "Delay between updates (ns|us|ms|s|m|h) (default 0s)")
  484. flags.DurationVar(&opts.update.monitor, flagUpdateMonitor, time.Duration(0), "Duration after each task update to monitor for failure (ns|us|ms|s|m|h) (default 0s)")
  485. flags.StringVar(&opts.update.onFailure, flagUpdateFailureAction, "pause", "Action on update failure (pause|continue)")
  486. flags.Var(&opts.update.maxFailureRatio, flagUpdateMaxFailureRatio, "Failure rate to tolerate during an update")
  487. flags.StringVar(&opts.endpoint.mode, flagEndpointMode, "", "Endpoint mode (vip or dnsrr)")
  488. flags.BoolVar(&opts.registryAuth, flagRegistryAuth, false, "Send registry authentication details to swarm agents")
  489. flags.StringVar(&opts.logDriver.name, flagLogDriver, "", "Logging driver for service")
  490. flags.Var(&opts.logDriver.opts, flagLogOpt, "Logging driver options")
  491. flags.StringVar(&opts.healthcheck.cmd, flagHealthCmd, "", "Command to run to check health")
  492. flags.Var(&opts.healthcheck.interval, flagHealthInterval, "Time between running the check (ns|us|ms|s|m|h)")
  493. flags.Var(&opts.healthcheck.timeout, flagHealthTimeout, "Maximum time to allow one check to run (ns|us|ms|s|m|h)")
  494. flags.IntVar(&opts.healthcheck.retries, flagHealthRetries, 0, "Consecutive failures needed to report unhealthy")
  495. flags.BoolVar(&opts.healthcheck.noHealthcheck, flagNoHealthcheck, false, "Disable any container-specified HEALTHCHECK")
  496. flags.BoolVarP(&opts.tty, flagTTY, "t", false, "Allocate a pseudo-TTY")
  497. }
  498. const (
  499. flagConstraint = "constraint"
  500. flagConstraintRemove = "constraint-rm"
  501. flagConstraintAdd = "constraint-add"
  502. flagContainerLabel = "container-label"
  503. flagContainerLabelRemove = "container-label-rm"
  504. flagContainerLabelAdd = "container-label-add"
  505. flagDNS = "dns"
  506. flagDNSRemove = "dns-rm"
  507. flagDNSAdd = "dns-add"
  508. flagDNSOption = "dns-option"
  509. flagDNSOptionRemove = "dns-option-rm"
  510. flagDNSOptionAdd = "dns-option-add"
  511. flagDNSSearch = "dns-search"
  512. flagDNSSearchRemove = "dns-search-rm"
  513. flagDNSSearchAdd = "dns-search-add"
  514. flagEndpointMode = "endpoint-mode"
  515. flagHost = "host"
  516. flagHostAdd = "host-add"
  517. flagHostRemove = "host-rm"
  518. flagHostname = "hostname"
  519. flagEnv = "env"
  520. flagEnvFile = "env-file"
  521. flagEnvRemove = "env-rm"
  522. flagEnvAdd = "env-add"
  523. flagGroup = "group"
  524. flagGroupAdd = "group-add"
  525. flagGroupRemove = "group-rm"
  526. flagLabel = "label"
  527. flagLabelRemove = "label-rm"
  528. flagLabelAdd = "label-add"
  529. flagLimitCPU = "limit-cpu"
  530. flagLimitMemory = "limit-memory"
  531. flagMode = "mode"
  532. flagMount = "mount"
  533. flagMountRemove = "mount-rm"
  534. flagMountAdd = "mount-add"
  535. flagName = "name"
  536. flagNetwork = "network"
  537. flagPublish = "publish"
  538. flagPublishRemove = "publish-rm"
  539. flagPublishAdd = "publish-add"
  540. flagReplicas = "replicas"
  541. flagReserveCPU = "reserve-cpu"
  542. flagReserveMemory = "reserve-memory"
  543. flagRestartCondition = "restart-condition"
  544. flagRestartDelay = "restart-delay"
  545. flagRestartMaxAttempts = "restart-max-attempts"
  546. flagRestartWindow = "restart-window"
  547. flagStopGracePeriod = "stop-grace-period"
  548. flagTTY = "tty"
  549. flagUpdateDelay = "update-delay"
  550. flagUpdateFailureAction = "update-failure-action"
  551. flagUpdateMaxFailureRatio = "update-max-failure-ratio"
  552. flagUpdateMonitor = "update-monitor"
  553. flagUpdateParallelism = "update-parallelism"
  554. flagUser = "user"
  555. flagWorkdir = "workdir"
  556. flagRegistryAuth = "with-registry-auth"
  557. flagLogDriver = "log-driver"
  558. flagLogOpt = "log-opt"
  559. flagHealthCmd = "health-cmd"
  560. flagHealthInterval = "health-interval"
  561. flagHealthRetries = "health-retries"
  562. flagHealthTimeout = "health-timeout"
  563. flagNoHealthcheck = "no-healthcheck"
  564. flagSecret = "secret"
  565. flagSecretAdd = "secret-add"
  566. flagSecretRemove = "secret-rm"
  567. )