opts.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682
  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 "none"
  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 "none"
  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.ListOpts
  247. expandedPorts opts.PortOpt
  248. }
  249. func (e *endpointOptions) ToEndpointSpec() *swarm.EndpointSpec {
  250. portConfigs := []swarm.PortConfig{}
  251. // We can ignore errors because the format was already validated by ValidatePort
  252. ports, portBindings, _ := nat.ParsePortSpecs(e.publishPorts.GetAll())
  253. for port := range ports {
  254. portConfigs = append(portConfigs, ConvertPortToPortConfig(port, portBindings)...)
  255. }
  256. return &swarm.EndpointSpec{
  257. Mode: swarm.ResolutionMode(strings.ToLower(e.mode)),
  258. Ports: append(portConfigs, e.expandedPorts.Value()...),
  259. }
  260. }
  261. // ConvertPortToPortConfig converts ports to the swarm type
  262. func ConvertPortToPortConfig(
  263. port nat.Port,
  264. portBindings map[nat.Port][]nat.PortBinding,
  265. ) []swarm.PortConfig {
  266. ports := []swarm.PortConfig{}
  267. for _, binding := range portBindings[port] {
  268. hostPort, _ := strconv.ParseUint(binding.HostPort, 10, 16)
  269. ports = append(ports, swarm.PortConfig{
  270. //TODO Name: ?
  271. Protocol: swarm.PortConfigProtocol(strings.ToLower(port.Proto())),
  272. TargetPort: uint32(port.Int()),
  273. PublishedPort: uint32(hostPort),
  274. })
  275. }
  276. return ports
  277. }
  278. type logDriverOptions struct {
  279. name string
  280. opts opts.ListOpts
  281. }
  282. func newLogDriverOptions() logDriverOptions {
  283. return logDriverOptions{opts: opts.NewListOpts(runconfigopts.ValidateEnv)}
  284. }
  285. func (ldo *logDriverOptions) toLogDriver() *swarm.Driver {
  286. if ldo.name == "" {
  287. return nil
  288. }
  289. // set the log driver only if specified.
  290. return &swarm.Driver{
  291. Name: ldo.name,
  292. Options: runconfigopts.ConvertKVStringsToMap(ldo.opts.GetAll()),
  293. }
  294. }
  295. type healthCheckOptions struct {
  296. cmd string
  297. interval PositiveDurationOpt
  298. timeout PositiveDurationOpt
  299. retries int
  300. noHealthcheck bool
  301. }
  302. func (opts *healthCheckOptions) toHealthConfig() (*container.HealthConfig, error) {
  303. var healthConfig *container.HealthConfig
  304. haveHealthSettings := opts.cmd != "" ||
  305. opts.interval.Value() != nil ||
  306. opts.timeout.Value() != nil ||
  307. opts.retries != 0
  308. if opts.noHealthcheck {
  309. if haveHealthSettings {
  310. return nil, fmt.Errorf("--%s conflicts with --health-* options", flagNoHealthcheck)
  311. }
  312. healthConfig = &container.HealthConfig{Test: []string{"NONE"}}
  313. } else if haveHealthSettings {
  314. var test []string
  315. if opts.cmd != "" {
  316. test = []string{"CMD-SHELL", opts.cmd}
  317. }
  318. var interval, timeout time.Duration
  319. if ptr := opts.interval.Value(); ptr != nil {
  320. interval = *ptr
  321. }
  322. if ptr := opts.timeout.Value(); ptr != nil {
  323. timeout = *ptr
  324. }
  325. healthConfig = &container.HealthConfig{
  326. Test: test,
  327. Interval: interval,
  328. Timeout: timeout,
  329. Retries: opts.retries,
  330. }
  331. }
  332. return healthConfig, nil
  333. }
  334. // ValidatePort validates a string is in the expected format for a port definition
  335. func ValidatePort(value string) (string, error) {
  336. portMappings, err := nat.ParsePortSpec(value)
  337. for _, portMapping := range portMappings {
  338. if portMapping.Binding.HostIP != "" {
  339. return "", fmt.Errorf("HostIP is not supported by a service.")
  340. }
  341. }
  342. return value, err
  343. }
  344. // convertExtraHostsToSwarmHosts converts an array of extra hosts in cli
  345. // <host>:<ip>
  346. // into a swarmkit host format:
  347. // IP_address canonical_hostname [aliases...]
  348. // This assumes input value (<host>:<ip>) has already been validated
  349. func convertExtraHostsToSwarmHosts(extraHosts []string) []string {
  350. hosts := []string{}
  351. for _, extraHost := range extraHosts {
  352. parts := strings.SplitN(extraHost, ":", 2)
  353. hosts = append(hosts, fmt.Sprintf("%s %s", parts[1], parts[0]))
  354. }
  355. return hosts
  356. }
  357. type serviceOptions struct {
  358. name string
  359. labels opts.ListOpts
  360. containerLabels opts.ListOpts
  361. image string
  362. args []string
  363. hostname string
  364. env opts.ListOpts
  365. envFile opts.ListOpts
  366. workdir string
  367. user string
  368. groups opts.ListOpts
  369. tty bool
  370. mounts opts.MountOpt
  371. dns opts.ListOpts
  372. dnsSearch opts.ListOpts
  373. dnsOption opts.ListOpts
  374. hosts opts.ListOpts
  375. resources resourceOptions
  376. stopGrace DurationOpt
  377. replicas Uint64Opt
  378. mode string
  379. restartPolicy restartPolicyOptions
  380. constraints opts.ListOpts
  381. update updateOptions
  382. networks opts.ListOpts
  383. endpoint endpointOptions
  384. registryAuth bool
  385. logDriver logDriverOptions
  386. healthcheck healthCheckOptions
  387. secrets opts.SecretOpt
  388. }
  389. func newServiceOptions() *serviceOptions {
  390. return &serviceOptions{
  391. labels: opts.NewListOpts(runconfigopts.ValidateEnv),
  392. constraints: opts.NewListOpts(nil),
  393. containerLabels: opts.NewListOpts(runconfigopts.ValidateEnv),
  394. env: opts.NewListOpts(runconfigopts.ValidateEnv),
  395. envFile: opts.NewListOpts(nil),
  396. endpoint: endpointOptions{
  397. publishPorts: opts.NewListOpts(ValidatePort),
  398. },
  399. groups: opts.NewListOpts(nil),
  400. logDriver: newLogDriverOptions(),
  401. dns: opts.NewListOpts(opts.ValidateIPAddress),
  402. dnsOption: opts.NewListOpts(nil),
  403. dnsSearch: opts.NewListOpts(opts.ValidateDNSSearch),
  404. hosts: opts.NewListOpts(runconfigopts.ValidateExtraHost),
  405. networks: opts.NewListOpts(nil),
  406. }
  407. }
  408. func (opts *serviceOptions) ToService() (swarm.ServiceSpec, error) {
  409. var service swarm.ServiceSpec
  410. envVariables, err := runconfigopts.ReadKVStrings(opts.envFile.GetAll(), opts.env.GetAll())
  411. if err != nil {
  412. return service, err
  413. }
  414. currentEnv := make([]string, 0, len(envVariables))
  415. for _, env := range envVariables { // need to process each var, in order
  416. k := strings.SplitN(env, "=", 2)[0]
  417. for i, current := range currentEnv { // remove duplicates
  418. if current == env {
  419. continue // no update required, may hide this behind flag to preserve order of envVariables
  420. }
  421. if strings.HasPrefix(current, k+"=") {
  422. currentEnv = append(currentEnv[:i], currentEnv[i+1:]...)
  423. }
  424. }
  425. currentEnv = append(currentEnv, env)
  426. }
  427. service = swarm.ServiceSpec{
  428. Annotations: swarm.Annotations{
  429. Name: opts.name,
  430. Labels: runconfigopts.ConvertKVStringsToMap(opts.labels.GetAll()),
  431. },
  432. TaskTemplate: swarm.TaskSpec{
  433. ContainerSpec: swarm.ContainerSpec{
  434. Image: opts.image,
  435. Args: opts.args,
  436. Env: currentEnv,
  437. Hostname: opts.hostname,
  438. Labels: runconfigopts.ConvertKVStringsToMap(opts.containerLabels.GetAll()),
  439. Dir: opts.workdir,
  440. User: opts.user,
  441. Groups: opts.groups.GetAll(),
  442. TTY: opts.tty,
  443. Mounts: opts.mounts.Value(),
  444. DNSConfig: &swarm.DNSConfig{
  445. Nameservers: opts.dns.GetAll(),
  446. Search: opts.dnsSearch.GetAll(),
  447. Options: opts.dnsOption.GetAll(),
  448. },
  449. Hosts: convertExtraHostsToSwarmHosts(opts.hosts.GetAll()),
  450. StopGracePeriod: opts.stopGrace.Value(),
  451. Secrets: nil,
  452. },
  453. Networks: convertNetworks(opts.networks.GetAll()),
  454. Resources: opts.resources.ToResourceRequirements(),
  455. RestartPolicy: opts.restartPolicy.ToRestartPolicy(),
  456. Placement: &swarm.Placement{
  457. Constraints: opts.constraints.GetAll(),
  458. },
  459. LogDriver: opts.logDriver.toLogDriver(),
  460. },
  461. Networks: convertNetworks(opts.networks.GetAll()),
  462. Mode: swarm.ServiceMode{},
  463. UpdateConfig: &swarm.UpdateConfig{
  464. Parallelism: opts.update.parallelism,
  465. Delay: opts.update.delay,
  466. Monitor: opts.update.monitor,
  467. FailureAction: opts.update.onFailure,
  468. MaxFailureRatio: opts.update.maxFailureRatio.Value(),
  469. },
  470. EndpointSpec: opts.endpoint.ToEndpointSpec(),
  471. }
  472. healthConfig, err := opts.healthcheck.toHealthConfig()
  473. if err != nil {
  474. return service, err
  475. }
  476. service.TaskTemplate.ContainerSpec.Healthcheck = healthConfig
  477. switch opts.mode {
  478. case "global":
  479. if opts.replicas.Value() != nil {
  480. return service, fmt.Errorf("replicas can only be used with replicated mode")
  481. }
  482. service.Mode.Global = &swarm.GlobalService{}
  483. case "replicated":
  484. service.Mode.Replicated = &swarm.ReplicatedService{
  485. Replicas: opts.replicas.Value(),
  486. }
  487. default:
  488. return service, fmt.Errorf("Unknown mode: %s", opts.mode)
  489. }
  490. return service, nil
  491. }
  492. // addServiceFlags adds all flags that are common to both `create` and `update`.
  493. // Any flags that are not common are added separately in the individual command
  494. func addServiceFlags(cmd *cobra.Command, opts *serviceOptions) {
  495. flags := cmd.Flags()
  496. flags.StringVarP(&opts.workdir, flagWorkdir, "w", "", "Working directory inside the container")
  497. flags.StringVarP(&opts.user, flagUser, "u", "", "Username or UID (format: <name|uid>[:<group|gid>])")
  498. flags.StringVar(&opts.hostname, flagHostname, "", "Container hostname")
  499. flags.Var(&opts.resources.limitCPU, flagLimitCPU, "Limit CPUs")
  500. flags.Var(&opts.resources.limitMemBytes, flagLimitMemory, "Limit Memory")
  501. flags.Var(&opts.resources.resCPU, flagReserveCPU, "Reserve CPUs")
  502. flags.Var(&opts.resources.resMemBytes, flagReserveMemory, "Reserve Memory")
  503. flags.Var(&opts.stopGrace, flagStopGracePeriod, "Time to wait before force killing a container (ns|us|ms|s|m|h)")
  504. flags.Var(&opts.replicas, flagReplicas, "Number of tasks")
  505. flags.StringVar(&opts.restartPolicy.condition, flagRestartCondition, "", "Restart when condition is met (none, on-failure, or any)")
  506. flags.Var(&opts.restartPolicy.delay, flagRestartDelay, "Delay between restart attempts (ns|us|ms|s|m|h)")
  507. flags.Var(&opts.restartPolicy.maxAttempts, flagRestartMaxAttempts, "Maximum number of restarts before giving up")
  508. flags.Var(&opts.restartPolicy.window, flagRestartWindow, "Window used to evaluate the restart policy (ns|us|ms|s|m|h)")
  509. flags.Uint64Var(&opts.update.parallelism, flagUpdateParallelism, 1, "Maximum number of tasks updated simultaneously (0 to update all at once)")
  510. flags.DurationVar(&opts.update.delay, flagUpdateDelay, time.Duration(0), "Delay between updates (ns|us|ms|s|m|h) (default 0s)")
  511. 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)")
  512. flags.StringVar(&opts.update.onFailure, flagUpdateFailureAction, "pause", "Action on update failure (pause|continue)")
  513. flags.Var(&opts.update.maxFailureRatio, flagUpdateMaxFailureRatio, "Failure rate to tolerate during an update")
  514. flags.StringVar(&opts.endpoint.mode, flagEndpointMode, "", "Endpoint mode (vip or dnsrr)")
  515. flags.BoolVar(&opts.registryAuth, flagRegistryAuth, false, "Send registry authentication details to swarm agents")
  516. flags.StringVar(&opts.logDriver.name, flagLogDriver, "", "Logging driver for service")
  517. flags.Var(&opts.logDriver.opts, flagLogOpt, "Logging driver options")
  518. flags.StringVar(&opts.healthcheck.cmd, flagHealthCmd, "", "Command to run to check health")
  519. flags.Var(&opts.healthcheck.interval, flagHealthInterval, "Time between running the check (ns|us|ms|s|m|h)")
  520. flags.Var(&opts.healthcheck.timeout, flagHealthTimeout, "Maximum time to allow one check to run (ns|us|ms|s|m|h)")
  521. flags.IntVar(&opts.healthcheck.retries, flagHealthRetries, 0, "Consecutive failures needed to report unhealthy")
  522. flags.BoolVar(&opts.healthcheck.noHealthcheck, flagNoHealthcheck, false, "Disable any container-specified HEALTHCHECK")
  523. flags.BoolVarP(&opts.tty, flagTTY, "t", false, "Allocate a pseudo-TTY")
  524. }
  525. const (
  526. flagConstraint = "constraint"
  527. flagConstraintRemove = "constraint-rm"
  528. flagConstraintAdd = "constraint-add"
  529. flagContainerLabel = "container-label"
  530. flagContainerLabelRemove = "container-label-rm"
  531. flagContainerLabelAdd = "container-label-add"
  532. flagDNS = "dns"
  533. flagDNSRemove = "dns-rm"
  534. flagDNSAdd = "dns-add"
  535. flagDNSOption = "dns-option"
  536. flagDNSOptionRemove = "dns-option-rm"
  537. flagDNSOptionAdd = "dns-option-add"
  538. flagDNSSearch = "dns-search"
  539. flagDNSSearchRemove = "dns-search-rm"
  540. flagDNSSearchAdd = "dns-search-add"
  541. flagEndpointMode = "endpoint-mode"
  542. flagHost = "host"
  543. flagHostAdd = "host-add"
  544. flagHostRemove = "host-rm"
  545. flagHostname = "hostname"
  546. flagEnv = "env"
  547. flagEnvFile = "env-file"
  548. flagEnvRemove = "env-rm"
  549. flagEnvAdd = "env-add"
  550. flagGroup = "group"
  551. flagGroupAdd = "group-add"
  552. flagGroupRemove = "group-rm"
  553. flagLabel = "label"
  554. flagLabelRemove = "label-rm"
  555. flagLabelAdd = "label-add"
  556. flagLimitCPU = "limit-cpu"
  557. flagLimitMemory = "limit-memory"
  558. flagMode = "mode"
  559. flagMount = "mount"
  560. flagMountRemove = "mount-rm"
  561. flagMountAdd = "mount-add"
  562. flagName = "name"
  563. flagNetwork = "network"
  564. flagPublish = "publish"
  565. flagPublishRemove = "publish-rm"
  566. flagPublishAdd = "publish-add"
  567. flagPort = "port"
  568. flagPortAdd = "port-add"
  569. flagPortRemove = "port-rm"
  570. flagReplicas = "replicas"
  571. flagReserveCPU = "reserve-cpu"
  572. flagReserveMemory = "reserve-memory"
  573. flagRestartCondition = "restart-condition"
  574. flagRestartDelay = "restart-delay"
  575. flagRestartMaxAttempts = "restart-max-attempts"
  576. flagRestartWindow = "restart-window"
  577. flagStopGracePeriod = "stop-grace-period"
  578. flagTTY = "tty"
  579. flagUpdateDelay = "update-delay"
  580. flagUpdateFailureAction = "update-failure-action"
  581. flagUpdateMaxFailureRatio = "update-max-failure-ratio"
  582. flagUpdateMonitor = "update-monitor"
  583. flagUpdateParallelism = "update-parallelism"
  584. flagUser = "user"
  585. flagWorkdir = "workdir"
  586. flagRegistryAuth = "with-registry-auth"
  587. flagLogDriver = "log-driver"
  588. flagLogOpt = "log-opt"
  589. flagHealthCmd = "health-cmd"
  590. flagHealthInterval = "health-interval"
  591. flagHealthRetries = "health-retries"
  592. flagHealthTimeout = "health-timeout"
  593. flagNoHealthcheck = "no-healthcheck"
  594. flagSecret = "secret"
  595. flagSecretAdd = "secret-add"
  596. flagSecretRemove = "secret-rm"
  597. )