opts.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519
  1. package service
  2. import (
  3. "encoding/csv"
  4. "fmt"
  5. "math/big"
  6. "strconv"
  7. "strings"
  8. "time"
  9. "github.com/docker/docker/opts"
  10. runconfigopts "github.com/docker/docker/runconfig/opts"
  11. "github.com/docker/engine-api/types/swarm"
  12. "github.com/docker/go-connections/nat"
  13. units "github.com/docker/go-units"
  14. "github.com/spf13/cobra"
  15. )
  16. var (
  17. // DefaultReplicas is the default replicas to use for a replicated service
  18. DefaultReplicas uint64 = 1
  19. )
  20. type int64Value interface {
  21. Value() int64
  22. }
  23. type memBytes int64
  24. func (m *memBytes) String() string {
  25. return units.BytesSize(float64(m.Value()))
  26. }
  27. func (m *memBytes) Set(value string) error {
  28. val, err := units.RAMInBytes(value)
  29. *m = memBytes(val)
  30. return err
  31. }
  32. func (m *memBytes) Type() string {
  33. return "MemoryBytes"
  34. }
  35. func (m *memBytes) Value() int64 {
  36. return int64(*m)
  37. }
  38. type nanoCPUs int64
  39. func (c *nanoCPUs) String() string {
  40. return big.NewRat(c.Value(), 1e9).FloatString(3)
  41. }
  42. func (c *nanoCPUs) Set(value string) error {
  43. cpu, ok := new(big.Rat).SetString(value)
  44. if !ok {
  45. return fmt.Errorf("Failed to parse %v as a rational number", value)
  46. }
  47. nano := cpu.Mul(cpu, big.NewRat(1e9, 1))
  48. if !nano.IsInt() {
  49. return fmt.Errorf("value is too precise")
  50. }
  51. *c = nanoCPUs(nano.Num().Int64())
  52. return nil
  53. }
  54. func (c *nanoCPUs) Type() string {
  55. return "NanoCPUs"
  56. }
  57. func (c *nanoCPUs) Value() int64 {
  58. return int64(*c)
  59. }
  60. // DurationOpt is an option type for time.Duration that uses a pointer. This
  61. // allows us to get nil values outside, instead of defaulting to 0
  62. type DurationOpt struct {
  63. value *time.Duration
  64. }
  65. // Set a new value on the option
  66. func (d *DurationOpt) Set(s string) error {
  67. v, err := time.ParseDuration(s)
  68. d.value = &v
  69. return err
  70. }
  71. // Type returns the type of this option
  72. func (d *DurationOpt) Type() string {
  73. return "duration-ptr"
  74. }
  75. // String returns a string repr of this option
  76. func (d *DurationOpt) String() string {
  77. if d.value != nil {
  78. return d.value.String()
  79. }
  80. return "none"
  81. }
  82. // Value returns the time.Duration
  83. func (d *DurationOpt) Value() *time.Duration {
  84. return d.value
  85. }
  86. // Uint64Opt represents a uint64.
  87. type Uint64Opt struct {
  88. value *uint64
  89. }
  90. // Set a new value on the option
  91. func (i *Uint64Opt) Set(s string) error {
  92. v, err := strconv.ParseUint(s, 0, 64)
  93. i.value = &v
  94. return err
  95. }
  96. // Type returns the type of this option
  97. func (i *Uint64Opt) Type() string {
  98. return "uint64-ptr"
  99. }
  100. // String returns a string repr of this option
  101. func (i *Uint64Opt) String() string {
  102. if i.value != nil {
  103. return fmt.Sprintf("%v", *i.value)
  104. }
  105. return "none"
  106. }
  107. // Value returns the uint64
  108. func (i *Uint64Opt) Value() *uint64 {
  109. return i.value
  110. }
  111. // MountOpt is a Value type for parsing mounts
  112. type MountOpt struct {
  113. values []swarm.Mount
  114. }
  115. // Set a new mount value
  116. func (m *MountOpt) Set(value string) error {
  117. csvReader := csv.NewReader(strings.NewReader(value))
  118. fields, err := csvReader.Read()
  119. if err != nil {
  120. return err
  121. }
  122. mount := swarm.Mount{}
  123. volumeOptions := func() *swarm.VolumeOptions {
  124. if mount.VolumeOptions == nil {
  125. mount.VolumeOptions = &swarm.VolumeOptions{
  126. Labels: make(map[string]string),
  127. }
  128. }
  129. if mount.VolumeOptions.DriverConfig == nil {
  130. mount.VolumeOptions.DriverConfig = &swarm.Driver{}
  131. }
  132. return mount.VolumeOptions
  133. }
  134. bindOptions := func() *swarm.BindOptions {
  135. if mount.BindOptions == nil {
  136. mount.BindOptions = new(swarm.BindOptions)
  137. }
  138. return mount.BindOptions
  139. }
  140. setValueOnMap := func(target map[string]string, value string) {
  141. parts := strings.SplitN(value, "=", 2)
  142. if len(parts) == 1 {
  143. target[value] = ""
  144. } else {
  145. target[parts[0]] = parts[1]
  146. }
  147. }
  148. // Set writable as the default
  149. for _, field := range fields {
  150. parts := strings.SplitN(field, "=", 2)
  151. if len(parts) == 1 && strings.ToLower(parts[0]) == "readonly" {
  152. mount.ReadOnly = true
  153. continue
  154. }
  155. if len(parts) == 1 && strings.ToLower(parts[0]) == "volume-nocopy" {
  156. volumeOptions().NoCopy = true
  157. continue
  158. }
  159. if len(parts) != 2 {
  160. return fmt.Errorf("invalid field '%s' must be a key=value pair", field)
  161. }
  162. key, value := parts[0], parts[1]
  163. switch strings.ToLower(key) {
  164. case "type":
  165. mount.Type = swarm.MountType(strings.ToUpper(value))
  166. case "source":
  167. mount.Source = value
  168. case "target":
  169. mount.Target = value
  170. case "readonly":
  171. ro, err := strconv.ParseBool(value)
  172. if err != nil {
  173. return fmt.Errorf("invalid value for readonly: %s", value)
  174. }
  175. mount.ReadOnly = ro
  176. case "bind-propagation":
  177. bindOptions().Propagation = swarm.MountPropagation(strings.ToUpper(value))
  178. case "volume-nocopy":
  179. volumeOptions().NoCopy, err = strconv.ParseBool(value)
  180. if err != nil {
  181. return fmt.Errorf("invalid value for populate: %s", value)
  182. }
  183. case "volume-label":
  184. setValueOnMap(volumeOptions().Labels, value)
  185. case "volume-driver":
  186. volumeOptions().DriverConfig.Name = value
  187. case "volume-driver-opt":
  188. if volumeOptions().DriverConfig.Options == nil {
  189. volumeOptions().DriverConfig.Options = make(map[string]string)
  190. }
  191. setValueOnMap(volumeOptions().DriverConfig.Options, value)
  192. default:
  193. return fmt.Errorf("unexpected key '%s' in '%s'", key, field)
  194. }
  195. }
  196. if mount.Type == "" {
  197. return fmt.Errorf("type is required")
  198. }
  199. if mount.Target == "" {
  200. return fmt.Errorf("target is required")
  201. }
  202. if mount.VolumeOptions != nil && mount.Source == "" {
  203. return fmt.Errorf("source is required when specifying volume-* options")
  204. }
  205. if mount.Type == swarm.MountType("BIND") && mount.VolumeOptions != nil {
  206. return fmt.Errorf("cannot mix 'volume-*' options with mount type '%s'", swarm.MountTypeBind)
  207. }
  208. if mount.Type == swarm.MountType("VOLUME") && mount.BindOptions != nil {
  209. return fmt.Errorf("cannot mix 'bind-*' options with mount type '%s'", swarm.MountTypeVolume)
  210. }
  211. m.values = append(m.values, mount)
  212. return nil
  213. }
  214. // Type returns the type of this option
  215. func (m *MountOpt) Type() string {
  216. return "mount"
  217. }
  218. // String returns a string repr of this option
  219. func (m *MountOpt) String() string {
  220. mounts := []string{}
  221. for _, mount := range m.values {
  222. repr := fmt.Sprintf("%s %s %s", mount.Type, mount.Source, mount.Target)
  223. mounts = append(mounts, repr)
  224. }
  225. return strings.Join(mounts, ", ")
  226. }
  227. // Value returns the mounts
  228. func (m *MountOpt) Value() []swarm.Mount {
  229. return m.values
  230. }
  231. type updateOptions struct {
  232. parallelism uint64
  233. delay time.Duration
  234. }
  235. type resourceOptions struct {
  236. limitCPU nanoCPUs
  237. limitMemBytes memBytes
  238. resCPU nanoCPUs
  239. resMemBytes memBytes
  240. }
  241. func (r *resourceOptions) ToResourceRequirements() *swarm.ResourceRequirements {
  242. return &swarm.ResourceRequirements{
  243. Limits: &swarm.Resources{
  244. NanoCPUs: r.limitCPU.Value(),
  245. MemoryBytes: r.limitMemBytes.Value(),
  246. },
  247. Reservations: &swarm.Resources{
  248. NanoCPUs: r.resCPU.Value(),
  249. MemoryBytes: r.resMemBytes.Value(),
  250. },
  251. }
  252. }
  253. type restartPolicyOptions struct {
  254. condition string
  255. delay DurationOpt
  256. maxAttempts Uint64Opt
  257. window DurationOpt
  258. }
  259. func (r *restartPolicyOptions) ToRestartPolicy() *swarm.RestartPolicy {
  260. return &swarm.RestartPolicy{
  261. Condition: swarm.RestartPolicyCondition(r.condition),
  262. Delay: r.delay.Value(),
  263. MaxAttempts: r.maxAttempts.Value(),
  264. Window: r.window.Value(),
  265. }
  266. }
  267. func convertNetworks(networks []string) []swarm.NetworkAttachmentConfig {
  268. nets := []swarm.NetworkAttachmentConfig{}
  269. for _, network := range networks {
  270. nets = append(nets, swarm.NetworkAttachmentConfig{Target: network})
  271. }
  272. return nets
  273. }
  274. type endpointOptions struct {
  275. mode string
  276. ports opts.ListOpts
  277. }
  278. func (e *endpointOptions) ToEndpointSpec() *swarm.EndpointSpec {
  279. portConfigs := []swarm.PortConfig{}
  280. // We can ignore errors because the format was already validated by ValidatePort
  281. ports, portBindings, _ := nat.ParsePortSpecs(e.ports.GetAll())
  282. for port := range ports {
  283. portConfigs = append(portConfigs, convertPortToPortConfig(port, portBindings)...)
  284. }
  285. return &swarm.EndpointSpec{
  286. Mode: swarm.ResolutionMode(strings.ToLower(e.mode)),
  287. Ports: portConfigs,
  288. }
  289. }
  290. func convertPortToPortConfig(
  291. port nat.Port,
  292. portBindings map[nat.Port][]nat.PortBinding,
  293. ) []swarm.PortConfig {
  294. ports := []swarm.PortConfig{}
  295. for _, binding := range portBindings[port] {
  296. hostPort, _ := strconv.ParseUint(binding.HostPort, 10, 16)
  297. ports = append(ports, swarm.PortConfig{
  298. //TODO Name: ?
  299. Protocol: swarm.PortConfigProtocol(strings.ToLower(port.Proto())),
  300. TargetPort: uint32(port.Int()),
  301. PublishedPort: uint32(hostPort),
  302. })
  303. }
  304. return ports
  305. }
  306. // ValidatePort validates a string is in the expected format for a port definition
  307. func ValidatePort(value string) (string, error) {
  308. portMappings, err := nat.ParsePortSpec(value)
  309. for _, portMapping := range portMappings {
  310. if portMapping.Binding.HostIP != "" {
  311. return "", fmt.Errorf("HostIP is not supported by a service.")
  312. }
  313. }
  314. return value, err
  315. }
  316. type serviceOptions struct {
  317. name string
  318. labels opts.ListOpts
  319. image string
  320. command []string
  321. args []string
  322. env opts.ListOpts
  323. workdir string
  324. user string
  325. mounts MountOpt
  326. resources resourceOptions
  327. stopGrace DurationOpt
  328. replicas Uint64Opt
  329. mode string
  330. restartPolicy restartPolicyOptions
  331. constraints []string
  332. update updateOptions
  333. networks []string
  334. endpoint endpointOptions
  335. registryAuth bool
  336. }
  337. func newServiceOptions() *serviceOptions {
  338. return &serviceOptions{
  339. labels: opts.NewListOpts(runconfigopts.ValidateEnv),
  340. env: opts.NewListOpts(runconfigopts.ValidateEnv),
  341. endpoint: endpointOptions{
  342. ports: opts.NewListOpts(ValidatePort),
  343. },
  344. }
  345. }
  346. func (opts *serviceOptions) ToService() (swarm.ServiceSpec, error) {
  347. var service swarm.ServiceSpec
  348. service = swarm.ServiceSpec{
  349. Annotations: swarm.Annotations{
  350. Name: opts.name,
  351. Labels: runconfigopts.ConvertKVStringsToMap(opts.labels.GetAll()),
  352. },
  353. TaskTemplate: swarm.TaskSpec{
  354. ContainerSpec: swarm.ContainerSpec{
  355. Image: opts.image,
  356. Command: opts.command,
  357. Args: opts.args,
  358. Env: opts.env.GetAll(),
  359. Dir: opts.workdir,
  360. User: opts.user,
  361. Mounts: opts.mounts.Value(),
  362. StopGracePeriod: opts.stopGrace.Value(),
  363. },
  364. Resources: opts.resources.ToResourceRequirements(),
  365. RestartPolicy: opts.restartPolicy.ToRestartPolicy(),
  366. Placement: &swarm.Placement{
  367. Constraints: opts.constraints,
  368. },
  369. },
  370. Mode: swarm.ServiceMode{},
  371. UpdateConfig: &swarm.UpdateConfig{
  372. Parallelism: opts.update.parallelism,
  373. Delay: opts.update.delay,
  374. },
  375. Networks: convertNetworks(opts.networks),
  376. EndpointSpec: opts.endpoint.ToEndpointSpec(),
  377. }
  378. switch opts.mode {
  379. case "global":
  380. if opts.replicas.Value() != nil {
  381. return service, fmt.Errorf("replicas can only be used with replicated mode")
  382. }
  383. service.Mode.Global = &swarm.GlobalService{}
  384. case "replicated":
  385. service.Mode.Replicated = &swarm.ReplicatedService{
  386. Replicas: opts.replicas.Value(),
  387. }
  388. default:
  389. return service, fmt.Errorf("Unknown mode: %s", opts.mode)
  390. }
  391. return service, nil
  392. }
  393. // addServiceFlags adds all flags that are common to both `create` and `update`.
  394. // Any flags that are not common are added separately in the individual command
  395. func addServiceFlags(cmd *cobra.Command, opts *serviceOptions) {
  396. flags := cmd.Flags()
  397. flags.StringVar(&opts.name, flagName, "", "Service name")
  398. flags.VarP(&opts.labels, flagLabel, "l", "Service labels")
  399. flags.VarP(&opts.env, "env", "e", "Set environment variables")
  400. flags.StringVarP(&opts.workdir, "workdir", "w", "", "Working directory inside the container")
  401. flags.StringVarP(&opts.user, flagUser, "u", "", "Username or UID")
  402. flags.VarP(&opts.mounts, flagMount, "m", "Attach a mount to the service")
  403. flags.Var(&opts.resources.limitCPU, flagLimitCPU, "Limit CPUs")
  404. flags.Var(&opts.resources.limitMemBytes, flagLimitMemory, "Limit Memory")
  405. flags.Var(&opts.resources.resCPU, flagReserveCPU, "Reserve CPUs")
  406. flags.Var(&opts.resources.resMemBytes, flagReserveMemory, "Reserve Memory")
  407. flags.Var(&opts.stopGrace, flagStopGracePeriod, "Time to wait before force killing a container")
  408. flags.Var(&opts.replicas, flagReplicas, "Number of tasks")
  409. flags.StringVar(&opts.restartPolicy.condition, flagRestartCondition, "", "Restart when condition is met (none, on-failure, or any)")
  410. flags.Var(&opts.restartPolicy.delay, flagRestartDelay, "Delay between restart attempts")
  411. flags.Var(&opts.restartPolicy.maxAttempts, flagRestartMaxAttempts, "Maximum number of restarts before giving up")
  412. flags.Var(&opts.restartPolicy.window, flagRestartWindow, "Window used to evaluate the restart policy")
  413. flags.StringSliceVar(&opts.constraints, flagConstraint, []string{}, "Placement constraints")
  414. flags.Uint64Var(&opts.update.parallelism, flagUpdateParallelism, 0, "Maximum number of tasks updated simultaneously")
  415. flags.DurationVar(&opts.update.delay, flagUpdateDelay, time.Duration(0), "Delay between updates")
  416. flags.StringSliceVar(&opts.networks, flagNetwork, []string{}, "Network attachments")
  417. flags.StringVar(&opts.endpoint.mode, flagEndpointMode, "", "Endpoint mode (vip or dnsrr)")
  418. flags.VarP(&opts.endpoint.ports, flagPublish, "p", "Publish a port as a node port")
  419. flags.BoolVar(&opts.registryAuth, flagRegistryAuth, false, "Send registry authentication details to Swarm agents")
  420. }
  421. const (
  422. flagConstraint = "constraint"
  423. flagEndpointMode = "endpoint-mode"
  424. flagLabel = "label"
  425. flagLimitCPU = "limit-cpu"
  426. flagLimitMemory = "limit-memory"
  427. flagMode = "mode"
  428. flagMount = "mount"
  429. flagName = "name"
  430. flagNetwork = "network"
  431. flagPublish = "publish"
  432. flagReplicas = "replicas"
  433. flagReserveCPU = "reserve-cpu"
  434. flagReserveMemory = "reserve-memory"
  435. flagRestartCondition = "restart-condition"
  436. flagRestartDelay = "restart-delay"
  437. flagRestartMaxAttempts = "restart-max-attempts"
  438. flagRestartWindow = "restart-window"
  439. flagStopGracePeriod = "stop-grace-period"
  440. flagUpdateDelay = "update-delay"
  441. flagUpdateParallelism = "update-parallelism"
  442. flagUser = "user"
  443. flagRegistryAuth = "registry-auth"
  444. )