opts.go 17 KB

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