pod.go 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. // +build kube
  2. /*
  3. Copyright 2020 Docker Compose CLI authors
  4. Licensed under the Apache License, Version 2.0 (the "License");
  5. you may not use this file except in compliance with the License.
  6. You may obtain a copy of the License at
  7. http://www.apache.org/licenses/LICENSE-2.0
  8. Unless required by applicable law or agreed to in writing, software
  9. distributed under the License is distributed on an "AS IS" BASIS,
  10. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11. See the License for the specific language governing permissions and
  12. limitations under the License.
  13. */
  14. package kubernetes
  15. import (
  16. "fmt"
  17. "sort"
  18. "strconv"
  19. "strings"
  20. "time"
  21. "github.com/compose-spec/compose-go/types"
  22. "github.com/docker/docker/api/types/swarm"
  23. "github.com/pkg/errors"
  24. apiv1 "k8s.io/api/core/v1"
  25. "k8s.io/apimachinery/pkg/api/resource"
  26. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  27. )
  28. func toPodTemplate(project *types.Project, serviceConfig types.ServiceConfig, labels map[string]string) (apiv1.PodTemplateSpec, error) {
  29. tpl := apiv1.PodTemplateSpec{}
  30. //nodeAffinity, err := toNodeAffinity(serviceConfig.Deploy)
  31. //if err != nil {
  32. // return apiv1.PodTemplateSpec{}, err
  33. //}
  34. hostAliases, err := toHostAliases(serviceConfig.ExtraHosts)
  35. if err != nil {
  36. return apiv1.PodTemplateSpec{}, err
  37. }
  38. env, err := toEnv(serviceConfig.Environment)
  39. if err != nil {
  40. return apiv1.PodTemplateSpec{}, err
  41. }
  42. restartPolicy, err := toRestartPolicy(serviceConfig)
  43. if err != nil {
  44. return apiv1.PodTemplateSpec{}, err
  45. }
  46. var limits apiv1.ResourceList
  47. if serviceConfig.Deploy != nil && serviceConfig.Deploy.Resources.Limits != nil {
  48. limits, err = toResource(serviceConfig.Deploy.Resources.Limits)
  49. if err != nil {
  50. return apiv1.PodTemplateSpec{}, err
  51. }
  52. }
  53. var requests apiv1.ResourceList
  54. if serviceConfig.Deploy != nil && serviceConfig.Deploy.Resources.Reservations != nil {
  55. requests, err = toResource(serviceConfig.Deploy.Resources.Reservations)
  56. if err != nil {
  57. return apiv1.PodTemplateSpec{}, err
  58. }
  59. }
  60. volumes, err := toVolumes(project, serviceConfig)
  61. if err != nil {
  62. return apiv1.PodTemplateSpec{}, err
  63. }
  64. volumeMounts, err := toVolumeMounts(project, serviceConfig)
  65. if err != nil {
  66. return apiv1.PodTemplateSpec{}, err
  67. }
  68. /* pullPolicy, err := toImagePullPolicy(serviceConfig.Image, x-kubernetes-pull-policy)
  69. if err != nil {
  70. return apiv1.PodTemplateSpec{}, err
  71. } */
  72. tpl.ObjectMeta = metav1.ObjectMeta{
  73. Labels: labels,
  74. Annotations: serviceConfig.Labels,
  75. }
  76. tpl.Spec.RestartPolicy = restartPolicy
  77. tpl.Spec.Volumes = volumes
  78. tpl.Spec.HostPID = toHostPID(serviceConfig.Pid)
  79. tpl.Spec.HostIPC = toHostIPC(serviceConfig.Ipc)
  80. tpl.Spec.Hostname = serviceConfig.Hostname
  81. tpl.Spec.TerminationGracePeriodSeconds = toTerminationGracePeriodSeconds(serviceConfig.StopGracePeriod)
  82. tpl.Spec.HostAliases = hostAliases
  83. //tpl.Spec.Affinity = nodeAffinity
  84. // we dont want to remove all containers and recreate them because:
  85. // an admission plugin can add sidecar containers
  86. // we for sure want to keep the main container to be additive
  87. if len(tpl.Spec.Containers) == 0 {
  88. tpl.Spec.Containers = []apiv1.Container{{}}
  89. }
  90. containerIX := 0
  91. for ix, c := range tpl.Spec.Containers {
  92. if c.Name == serviceConfig.Name {
  93. containerIX = ix
  94. break
  95. }
  96. }
  97. tpl.Spec.Containers[containerIX].Name = serviceConfig.Name
  98. tpl.Spec.Containers[containerIX].Image = serviceConfig.Image
  99. // FIXME tpl.Spec.Containers[containerIX].ImagePullPolicy = pullPolicy
  100. tpl.Spec.Containers[containerIX].Command = serviceConfig.Entrypoint
  101. tpl.Spec.Containers[containerIX].Args = serviceConfig.Command
  102. tpl.Spec.Containers[containerIX].WorkingDir = serviceConfig.WorkingDir
  103. tpl.Spec.Containers[containerIX].TTY = serviceConfig.Tty
  104. tpl.Spec.Containers[containerIX].Stdin = serviceConfig.StdinOpen
  105. tpl.Spec.Containers[containerIX].Ports = toPorts(serviceConfig.Ports)
  106. tpl.Spec.Containers[containerIX].LivenessProbe = toLivenessProbe(serviceConfig.HealthCheck)
  107. tpl.Spec.Containers[containerIX].Env = env
  108. tpl.Spec.Containers[containerIX].VolumeMounts = volumeMounts
  109. tpl.Spec.Containers[containerIX].SecurityContext = toSecurityContext(serviceConfig)
  110. tpl.Spec.Containers[containerIX].Resources = apiv1.ResourceRequirements{
  111. Limits: limits,
  112. Requests: requests,
  113. }
  114. /* FIXME
  115. if serviceConfig.PullSecret != "" {
  116. pullSecrets := map[string]struct{}{}
  117. for _, ps := range tpl.Spec.ImagePullSecrets {
  118. pullSecrets[ps.Name] = struct{}{}
  119. }
  120. if _, ok := pullSecrets[serviceConfig.PullSecret]; !ok {
  121. tpl.Spec.ImagePullSecrets = append(tpl.Spec.ImagePullSecrets, apiv1.LocalObjectReference{Name: serviceConfig.PullSecret})
  122. }
  123. }
  124. */
  125. return tpl, nil
  126. }
  127. func toHostAliases(extraHosts []string) ([]apiv1.HostAlias, error) {
  128. if extraHosts == nil {
  129. return nil, nil
  130. }
  131. byHostnames := map[string]string{}
  132. for _, host := range extraHosts {
  133. split := strings.SplitN(host, ":", 2)
  134. if len(split) != 2 {
  135. return nil, errors.Errorf("malformed host %s", host)
  136. }
  137. byHostnames[split[0]] = split[1]
  138. }
  139. byIPs := map[string][]string{}
  140. for k, v := range byHostnames {
  141. byIPs[v] = append(byIPs[v], k)
  142. }
  143. aliases := make([]apiv1.HostAlias, len(byIPs))
  144. i := 0
  145. for key, hosts := range byIPs {
  146. sort.Strings(hosts)
  147. aliases[i] = apiv1.HostAlias{
  148. IP: key,
  149. Hostnames: hosts,
  150. }
  151. i++
  152. }
  153. sort.Slice(aliases, func(i, j int) bool { return aliases[i].IP < aliases[j].IP })
  154. return aliases, nil
  155. }
  156. func toHostPID(pid string) bool {
  157. return "host" == pid
  158. }
  159. func toHostIPC(ipc string) bool {
  160. return "host" == ipc
  161. }
  162. func toTerminationGracePeriodSeconds(duration *types.Duration) *int64 {
  163. if duration == nil {
  164. return nil
  165. }
  166. gracePeriod := int64(time.Duration(*duration).Seconds())
  167. return &gracePeriod
  168. }
  169. func toLivenessProbe(hc *types.HealthCheckConfig) *apiv1.Probe {
  170. if hc == nil || len(hc.Test) < 1 || hc.Test[0] == "NONE" {
  171. return nil
  172. }
  173. command := hc.Test[1:]
  174. if hc.Test[0] == "CMD-SHELL" {
  175. command = append([]string{"sh", "-c"}, command...)
  176. }
  177. return &apiv1.Probe{
  178. TimeoutSeconds: toSecondsOrDefault(hc.Timeout, 1),
  179. PeriodSeconds: toSecondsOrDefault(hc.Interval, 1),
  180. FailureThreshold: int32(defaultUint64(hc.Retries, 3)),
  181. Handler: apiv1.Handler{
  182. Exec: &apiv1.ExecAction{
  183. Command: command,
  184. },
  185. },
  186. }
  187. }
  188. func toEnv(env map[string]*string) ([]apiv1.EnvVar, error) {
  189. var envVars []apiv1.EnvVar
  190. for k, v := range env {
  191. if v == nil {
  192. return nil, errors.Errorf("%s has no value, unsetting an environment variable is not supported", k)
  193. }
  194. envVars = append(envVars, toEnvVar(k, *v))
  195. }
  196. sort.Slice(envVars, func(i, j int) bool { return envVars[i].Name < envVars[j].Name })
  197. return envVars, nil
  198. }
  199. func toEnvVar(key, value string) apiv1.EnvVar {
  200. return apiv1.EnvVar{
  201. Name: key,
  202. Value: value,
  203. }
  204. }
  205. func toPorts(list []types.ServicePortConfig) []apiv1.ContainerPort {
  206. var ports []apiv1.ContainerPort
  207. for _, v := range list {
  208. ports = append(ports, apiv1.ContainerPort{
  209. ContainerPort: int32(v.Target),
  210. Protocol: toProtocol(v.Protocol),
  211. })
  212. }
  213. return ports
  214. }
  215. func toProtocol(value string) apiv1.Protocol {
  216. if value == "udp" {
  217. return apiv1.ProtocolUDP
  218. }
  219. return apiv1.ProtocolTCP
  220. }
  221. func toRestartPolicy(s types.ServiceConfig) (apiv1.RestartPolicy, error) {
  222. if s.Deploy == nil || s.Deploy.RestartPolicy == nil {
  223. return apiv1.RestartPolicyAlways, nil
  224. }
  225. policy := s.Deploy.RestartPolicy
  226. switch policy.Condition {
  227. case string(swarm.RestartPolicyConditionAny):
  228. return apiv1.RestartPolicyAlways, nil
  229. case string(swarm.RestartPolicyConditionNone):
  230. return apiv1.RestartPolicyNever, nil
  231. case string(swarm.RestartPolicyConditionOnFailure):
  232. return apiv1.RestartPolicyOnFailure, nil
  233. default:
  234. return "", errors.Errorf("unsupported restart policy %s", policy.Condition)
  235. }
  236. }
  237. func toResource(res *types.Resource) (apiv1.ResourceList, error) {
  238. list := make(apiv1.ResourceList)
  239. if res.NanoCPUs != "" {
  240. cpus, err := resource.ParseQuantity(res.NanoCPUs)
  241. if err != nil {
  242. return nil, err
  243. }
  244. list[apiv1.ResourceCPU] = cpus
  245. }
  246. if res.MemoryBytes != 0 {
  247. memory, err := resource.ParseQuantity(fmt.Sprintf("%v", res.MemoryBytes))
  248. if err != nil {
  249. return nil, err
  250. }
  251. list[apiv1.ResourceMemory] = memory
  252. }
  253. return list, nil
  254. }
  255. func toSecurityContext(s types.ServiceConfig) *apiv1.SecurityContext {
  256. isPrivileged := toBoolPointer(s.Privileged)
  257. isReadOnly := toBoolPointer(s.ReadOnly)
  258. var capabilities *apiv1.Capabilities
  259. if s.CapAdd != nil || s.CapDrop != nil {
  260. capabilities = &apiv1.Capabilities{
  261. Add: toCapabilities(s.CapAdd),
  262. Drop: toCapabilities(s.CapDrop),
  263. }
  264. }
  265. var userID *int64
  266. if s.User != "" {
  267. numerical, err := strconv.Atoi(s.User)
  268. if err == nil {
  269. unixUserID := int64(numerical)
  270. userID = &unixUserID
  271. }
  272. }
  273. if isPrivileged == nil && isReadOnly == nil && capabilities == nil && userID == nil {
  274. return nil
  275. }
  276. return &apiv1.SecurityContext{
  277. RunAsUser: userID,
  278. Privileged: isPrivileged,
  279. ReadOnlyRootFilesystem: isReadOnly,
  280. Capabilities: capabilities,
  281. }
  282. }
  283. func toBoolPointer(value bool) *bool {
  284. if value {
  285. return &value
  286. }
  287. return nil
  288. }
  289. func defaultUint64(v *uint64, defaultValue uint64) uint64 { //nolint: unparam
  290. if v == nil {
  291. return defaultValue
  292. }
  293. return *v
  294. }
  295. func toCapabilities(list []string) (capabilities []apiv1.Capability) {
  296. for _, c := range list {
  297. capabilities = append(capabilities, apiv1.Capability(c))
  298. }
  299. return
  300. }