opts.go 18 KB

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