service.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  1. package convert
  2. import (
  3. "fmt"
  4. "os"
  5. "sort"
  6. "time"
  7. "github.com/docker/docker/api/types"
  8. "github.com/docker/docker/api/types/container"
  9. "github.com/docker/docker/api/types/swarm"
  10. servicecli "github.com/docker/docker/cli/command/service"
  11. composetypes "github.com/docker/docker/cli/compose/types"
  12. "github.com/docker/docker/client"
  13. "github.com/docker/docker/opts"
  14. runconfigopts "github.com/docker/docker/runconfig/opts"
  15. )
  16. // Services from compose-file types to engine API types
  17. // TODO: fix secrets API so that SecretAPIClient is not required here
  18. func Services(
  19. namespace Namespace,
  20. config *composetypes.Config,
  21. client client.SecretAPIClient,
  22. ) (map[string]swarm.ServiceSpec, error) {
  23. result := make(map[string]swarm.ServiceSpec)
  24. services := config.Services
  25. volumes := config.Volumes
  26. networks := config.Networks
  27. for _, service := range services {
  28. secrets, err := convertServiceSecrets(client, namespace, service.Secrets, config.Secrets)
  29. if err != nil {
  30. return nil, err
  31. }
  32. serviceSpec, err := convertService(namespace, service, networks, volumes, secrets)
  33. if err != nil {
  34. return nil, err
  35. }
  36. result[service.Name] = serviceSpec
  37. }
  38. return result, nil
  39. }
  40. func convertService(
  41. namespace Namespace,
  42. service composetypes.ServiceConfig,
  43. networkConfigs map[string]composetypes.NetworkConfig,
  44. volumes map[string]composetypes.VolumeConfig,
  45. secrets []*swarm.SecretReference,
  46. ) (swarm.ServiceSpec, error) {
  47. name := namespace.Scope(service.Name)
  48. endpoint, err := convertEndpointSpec(service.Ports)
  49. if err != nil {
  50. return swarm.ServiceSpec{}, err
  51. }
  52. mode, err := convertDeployMode(service.Deploy.Mode, service.Deploy.Replicas)
  53. if err != nil {
  54. return swarm.ServiceSpec{}, err
  55. }
  56. mounts, err := Volumes(service.Volumes, volumes, namespace)
  57. if err != nil {
  58. // TODO: better error message (include service name)
  59. return swarm.ServiceSpec{}, err
  60. }
  61. resources, err := convertResources(service.Deploy.Resources)
  62. if err != nil {
  63. return swarm.ServiceSpec{}, err
  64. }
  65. restartPolicy, err := convertRestartPolicy(
  66. service.Restart, service.Deploy.RestartPolicy)
  67. if err != nil {
  68. return swarm.ServiceSpec{}, err
  69. }
  70. healthcheck, err := convertHealthcheck(service.HealthCheck)
  71. if err != nil {
  72. return swarm.ServiceSpec{}, err
  73. }
  74. networks, err := convertServiceNetworks(service.Networks, networkConfigs, namespace, service.Name)
  75. if err != nil {
  76. return swarm.ServiceSpec{}, err
  77. }
  78. var logDriver *swarm.Driver
  79. if service.Logging != nil {
  80. logDriver = &swarm.Driver{
  81. Name: service.Logging.Driver,
  82. Options: service.Logging.Options,
  83. }
  84. }
  85. serviceSpec := swarm.ServiceSpec{
  86. Annotations: swarm.Annotations{
  87. Name: name,
  88. Labels: AddStackLabel(namespace, service.Deploy.Labels),
  89. },
  90. TaskTemplate: swarm.TaskSpec{
  91. ContainerSpec: swarm.ContainerSpec{
  92. Image: service.Image,
  93. Command: service.Entrypoint,
  94. Args: service.Command,
  95. Hostname: service.Hostname,
  96. Hosts: sortStrings(convertExtraHosts(service.ExtraHosts)),
  97. Healthcheck: healthcheck,
  98. Env: sortStrings(convertEnvironment(service.Environment)),
  99. Labels: AddStackLabel(namespace, service.Labels),
  100. Dir: service.WorkingDir,
  101. User: service.User,
  102. Mounts: mounts,
  103. StopGracePeriod: service.StopGracePeriod,
  104. TTY: service.Tty,
  105. OpenStdin: service.StdinOpen,
  106. Secrets: secrets,
  107. },
  108. LogDriver: logDriver,
  109. Resources: resources,
  110. RestartPolicy: restartPolicy,
  111. Placement: &swarm.Placement{
  112. Constraints: service.Deploy.Placement.Constraints,
  113. },
  114. },
  115. EndpointSpec: endpoint,
  116. Mode: mode,
  117. Networks: networks,
  118. UpdateConfig: convertUpdateConfig(service.Deploy.UpdateConfig),
  119. }
  120. return serviceSpec, nil
  121. }
  122. func sortStrings(strs []string) []string {
  123. sort.Strings(strs)
  124. return strs
  125. }
  126. type byNetworkTarget []swarm.NetworkAttachmentConfig
  127. func (a byNetworkTarget) Len() int { return len(a) }
  128. func (a byNetworkTarget) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
  129. func (a byNetworkTarget) Less(i, j int) bool { return a[i].Target < a[j].Target }
  130. func convertServiceNetworks(
  131. networks map[string]*composetypes.ServiceNetworkConfig,
  132. networkConfigs networkMap,
  133. namespace Namespace,
  134. name string,
  135. ) ([]swarm.NetworkAttachmentConfig, error) {
  136. if len(networks) == 0 {
  137. return []swarm.NetworkAttachmentConfig{
  138. {
  139. Target: namespace.Scope("default"),
  140. Aliases: []string{name},
  141. },
  142. }, nil
  143. }
  144. nets := []swarm.NetworkAttachmentConfig{}
  145. for networkName, network := range networks {
  146. networkConfig, ok := networkConfigs[networkName]
  147. if !ok {
  148. return []swarm.NetworkAttachmentConfig{}, fmt.Errorf(
  149. "service %q references network %q, which is not declared", name, networkName)
  150. }
  151. var aliases []string
  152. if network != nil {
  153. aliases = network.Aliases
  154. }
  155. target := namespace.Scope(networkName)
  156. if networkConfig.External.External {
  157. target = networkConfig.External.Name
  158. }
  159. nets = append(nets, swarm.NetworkAttachmentConfig{
  160. Target: target,
  161. Aliases: append(aliases, name),
  162. })
  163. }
  164. sort.Sort(byNetworkTarget(nets))
  165. return nets, nil
  166. }
  167. // TODO: fix secrets API so that SecretAPIClient is not required here
  168. func convertServiceSecrets(
  169. client client.SecretAPIClient,
  170. namespace Namespace,
  171. secrets []composetypes.ServiceSecretConfig,
  172. secretSpecs map[string]composetypes.SecretConfig,
  173. ) ([]*swarm.SecretReference, error) {
  174. opts := []*types.SecretRequestOption{}
  175. for _, secret := range secrets {
  176. target := secret.Target
  177. if target == "" {
  178. target = secret.Source
  179. }
  180. source := namespace.Scope(secret.Source)
  181. secretSpec := secretSpecs[secret.Source]
  182. if secretSpec.External.External {
  183. source = secretSpec.External.Name
  184. }
  185. uid := secret.UID
  186. gid := secret.GID
  187. if uid == "" {
  188. uid = "0"
  189. }
  190. if gid == "" {
  191. gid = "0"
  192. }
  193. opts = append(opts, &types.SecretRequestOption{
  194. Source: source,
  195. Target: target,
  196. UID: uid,
  197. GID: gid,
  198. Mode: os.FileMode(secret.Mode),
  199. })
  200. }
  201. return servicecli.ParseSecrets(client, opts)
  202. }
  203. func convertExtraHosts(extraHosts map[string]string) []string {
  204. hosts := []string{}
  205. for host, ip := range extraHosts {
  206. hosts = append(hosts, fmt.Sprintf("%s %s", ip, host))
  207. }
  208. return hosts
  209. }
  210. func convertHealthcheck(healthcheck *composetypes.HealthCheckConfig) (*container.HealthConfig, error) {
  211. if healthcheck == nil {
  212. return nil, nil
  213. }
  214. var (
  215. err error
  216. timeout, interval time.Duration
  217. retries int
  218. )
  219. if healthcheck.Disable {
  220. if len(healthcheck.Test) != 0 {
  221. return nil, fmt.Errorf("test and disable can't be set at the same time")
  222. }
  223. return &container.HealthConfig{
  224. Test: []string{"NONE"},
  225. }, nil
  226. }
  227. if healthcheck.Timeout != "" {
  228. timeout, err = time.ParseDuration(healthcheck.Timeout)
  229. if err != nil {
  230. return nil, err
  231. }
  232. }
  233. if healthcheck.Interval != "" {
  234. interval, err = time.ParseDuration(healthcheck.Interval)
  235. if err != nil {
  236. return nil, err
  237. }
  238. }
  239. if healthcheck.Retries != nil {
  240. retries = int(*healthcheck.Retries)
  241. }
  242. return &container.HealthConfig{
  243. Test: healthcheck.Test,
  244. Timeout: timeout,
  245. Interval: interval,
  246. Retries: retries,
  247. }, nil
  248. }
  249. func convertRestartPolicy(restart string, source *composetypes.RestartPolicy) (*swarm.RestartPolicy, error) {
  250. // TODO: log if restart is being ignored
  251. if source == nil {
  252. policy, err := runconfigopts.ParseRestartPolicy(restart)
  253. if err != nil {
  254. return nil, err
  255. }
  256. switch {
  257. case policy.IsNone():
  258. return nil, nil
  259. case policy.IsAlways(), policy.IsUnlessStopped():
  260. return &swarm.RestartPolicy{
  261. Condition: swarm.RestartPolicyConditionAny,
  262. }, nil
  263. case policy.IsOnFailure():
  264. attempts := uint64(policy.MaximumRetryCount)
  265. return &swarm.RestartPolicy{
  266. Condition: swarm.RestartPolicyConditionOnFailure,
  267. MaxAttempts: &attempts,
  268. }, nil
  269. default:
  270. return nil, fmt.Errorf("unknown restart policy: %s", restart)
  271. }
  272. }
  273. return &swarm.RestartPolicy{
  274. Condition: swarm.RestartPolicyCondition(source.Condition),
  275. Delay: source.Delay,
  276. MaxAttempts: source.MaxAttempts,
  277. Window: source.Window,
  278. }, nil
  279. }
  280. func convertUpdateConfig(source *composetypes.UpdateConfig) *swarm.UpdateConfig {
  281. if source == nil {
  282. return nil
  283. }
  284. parallel := uint64(1)
  285. if source.Parallelism != nil {
  286. parallel = *source.Parallelism
  287. }
  288. return &swarm.UpdateConfig{
  289. Parallelism: parallel,
  290. Delay: source.Delay,
  291. FailureAction: source.FailureAction,
  292. Monitor: source.Monitor,
  293. MaxFailureRatio: source.MaxFailureRatio,
  294. }
  295. }
  296. func convertResources(source composetypes.Resources) (*swarm.ResourceRequirements, error) {
  297. resources := &swarm.ResourceRequirements{}
  298. var err error
  299. if source.Limits != nil {
  300. var cpus int64
  301. if source.Limits.NanoCPUs != "" {
  302. cpus, err = opts.ParseCPUs(source.Limits.NanoCPUs)
  303. if err != nil {
  304. return nil, err
  305. }
  306. }
  307. resources.Limits = &swarm.Resources{
  308. NanoCPUs: cpus,
  309. MemoryBytes: int64(source.Limits.MemoryBytes),
  310. }
  311. }
  312. if source.Reservations != nil {
  313. var cpus int64
  314. if source.Reservations.NanoCPUs != "" {
  315. cpus, err = opts.ParseCPUs(source.Reservations.NanoCPUs)
  316. if err != nil {
  317. return nil, err
  318. }
  319. }
  320. resources.Reservations = &swarm.Resources{
  321. NanoCPUs: cpus,
  322. MemoryBytes: int64(source.Reservations.MemoryBytes),
  323. }
  324. }
  325. return resources, nil
  326. }
  327. type byPublishedPort []swarm.PortConfig
  328. func (a byPublishedPort) Len() int { return len(a) }
  329. func (a byPublishedPort) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
  330. func (a byPublishedPort) Less(i, j int) bool { return a[i].PublishedPort < a[j].PublishedPort }
  331. func convertEndpointSpec(source []composetypes.ServicePortConfig) (*swarm.EndpointSpec, error) {
  332. portConfigs := []swarm.PortConfig{}
  333. for _, port := range source {
  334. portConfig := swarm.PortConfig{
  335. Protocol: swarm.PortConfigProtocol(port.Protocol),
  336. TargetPort: port.Target,
  337. PublishedPort: port.Published,
  338. PublishMode: swarm.PortConfigPublishMode(port.Mode),
  339. }
  340. portConfigs = append(portConfigs, portConfig)
  341. }
  342. sort.Sort(byPublishedPort(portConfigs))
  343. return &swarm.EndpointSpec{Ports: portConfigs}, nil
  344. }
  345. func convertEnvironment(source map[string]string) []string {
  346. var output []string
  347. for name, value := range source {
  348. output = append(output, fmt.Sprintf("%s=%s", name, value))
  349. }
  350. return output
  351. }
  352. func convertDeployMode(mode string, replicas *uint64) (swarm.ServiceMode, error) {
  353. serviceMode := swarm.ServiceMode{}
  354. switch mode {
  355. case "global":
  356. if replicas != nil {
  357. return serviceMode, fmt.Errorf("replicas can only be used with replicated mode")
  358. }
  359. serviceMode.Global = &swarm.GlobalService{}
  360. case "replicated", "":
  361. serviceMode.Replicated = &swarm.ReplicatedService{Replicas: replicas}
  362. default:
  363. return serviceMode, fmt.Errorf("Unknown mode: %s", mode)
  364. }
  365. return serviceMode, nil
  366. }