forked from LaconicNetwork/kompose
367 lines
11 KiB
Go
367 lines
11 KiB
Go
package kubernetes
|
|
|
|
import (
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
|
|
mapset "github.com/deckarep/golang-set"
|
|
"github.com/kubernetes/kompose/pkg/kobject"
|
|
"github.com/pkg/errors"
|
|
log "github.com/sirupsen/logrus"
|
|
api "k8s.io/api/core/v1"
|
|
"k8s.io/apimachinery/pkg/api/resource"
|
|
"k8s.io/apimachinery/pkg/util/intstr"
|
|
)
|
|
|
|
// PodSpec holds the spec of k8s pod.
|
|
type PodSpec struct {
|
|
api.PodSpec
|
|
}
|
|
|
|
// PodSpecOption holds the function to apply on a PodSpec
|
|
type PodSpecOption func(*PodSpec)
|
|
|
|
// AddContainer method is responsible for adding a new container to a k8s Pod.
|
|
func AddContainer(service kobject.ServiceConfig, opt kobject.ConvertOptions) PodSpecOption {
|
|
return func(podSpec *PodSpec) {
|
|
name := GetContainerName(service)
|
|
image := service.Image
|
|
|
|
if image == "" {
|
|
image = name
|
|
}
|
|
|
|
envs, err := ConfigEnvs(service, opt)
|
|
if err != nil {
|
|
panic("Unable to load env variables")
|
|
}
|
|
|
|
podSpec.Containers = append(podSpec.Containers, api.Container{
|
|
Name: name,
|
|
Image: image,
|
|
Env: envs,
|
|
Command: service.Command,
|
|
Args: service.Args,
|
|
WorkingDir: service.WorkingDir,
|
|
Stdin: service.Stdin,
|
|
TTY: service.Tty,
|
|
LivenessProbe: configProbe(service.HealthChecks.Liveness),
|
|
ReadinessProbe: configProbe(service.HealthChecks.Readiness),
|
|
})
|
|
if service.ImagePullSecret != "" {
|
|
podSpec.ImagePullSecrets = append(podSpec.ImagePullSecrets, api.LocalObjectReference{
|
|
Name: service.ImagePullSecret,
|
|
})
|
|
}
|
|
podSpec.Affinity = ConfigAffinity(service)
|
|
}
|
|
}
|
|
|
|
// TerminationGracePeriodSeconds method is responsible for attributing the grace period seconds option to a pod
|
|
func TerminationGracePeriodSeconds(name string, service kobject.ServiceConfig) PodSpecOption {
|
|
return func(podSpec *PodSpec) {
|
|
var err error
|
|
if service.StopGracePeriod != "" {
|
|
podSpec.TerminationGracePeriodSeconds, err = DurationStrToSecondsInt(service.StopGracePeriod)
|
|
if err != nil {
|
|
log.Warningf("Failed to parse duration \"%v\" for service \"%v\"", service.StopGracePeriod, name)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ResourcesLimits Configure the resource limits
|
|
func ResourcesLimits(service kobject.ServiceConfig) PodSpecOption {
|
|
return func(podSpec *PodSpec) {
|
|
if service.MemLimit != 0 || service.CPULimit != 0 {
|
|
resourceLimit := api.ResourceList{}
|
|
|
|
if service.MemLimit != 0 {
|
|
resourceLimit[api.ResourceMemory] = *resource.NewQuantity(int64(service.MemLimit), "RandomStringForFormat")
|
|
}
|
|
|
|
if service.CPULimit != 0 {
|
|
resourceLimit[api.ResourceCPU] = *resource.NewMilliQuantity(service.CPULimit, resource.DecimalSI)
|
|
}
|
|
|
|
for i := range podSpec.Containers {
|
|
podSpec.Containers[i].Resources.Limits = resourceLimit
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ResourcesRequests Configure the resource requests
|
|
func ResourcesRequests(service kobject.ServiceConfig) PodSpecOption {
|
|
return func(podSpec *PodSpec) {
|
|
if service.MemReservation != 0 || service.CPUReservation != 0 {
|
|
resourceRequests := api.ResourceList{}
|
|
|
|
if service.MemReservation != 0 {
|
|
resourceRequests[api.ResourceMemory] = *resource.NewQuantity(int64(service.MemReservation), "RandomStringForFormat")
|
|
}
|
|
|
|
if service.CPUReservation != 0 {
|
|
resourceRequests[api.ResourceCPU] = *resource.NewMilliQuantity(service.CPUReservation, resource.DecimalSI)
|
|
}
|
|
|
|
for i := range podSpec.Containers {
|
|
podSpec.Containers[i].Resources.Requests = resourceRequests
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// SecurityContext Configure SecurityContext
|
|
func SecurityContext(name string, service kobject.ServiceConfig) PodSpecOption {
|
|
return func(podSpec *PodSpec) {
|
|
// Configure resource reservations
|
|
podSecurityContext := &api.PodSecurityContext{}
|
|
|
|
//set pid namespace mode
|
|
if service.Pid != "" {
|
|
if service.Pid == "host" {
|
|
// podSecurityContext.HostPID = true
|
|
} else {
|
|
log.Warningf("Ignoring PID key for service \"%v\". Invalid value \"%v\".", name, service.Pid)
|
|
}
|
|
}
|
|
|
|
//set supplementalGroups
|
|
if service.GroupAdd != nil {
|
|
podSecurityContext.SupplementalGroups = service.GroupAdd
|
|
}
|
|
|
|
//set Pod FsGroup
|
|
if service.FsGroup != 0 {
|
|
podSecurityContext.FSGroup = &service.FsGroup
|
|
}
|
|
|
|
// Setup security context
|
|
securityContext := &api.SecurityContext{}
|
|
if service.Privileged {
|
|
securityContext.Privileged = &service.Privileged
|
|
}
|
|
if service.User != "" {
|
|
switch userparts := strings.Split(service.User, ":"); len(userparts) {
|
|
default:
|
|
log.Warn("Ignoring ill-formed user directive. Must be in format UID or UID:GID.")
|
|
case 1:
|
|
uid, err := strconv.ParseInt(userparts[0], 10, 64)
|
|
if err != nil {
|
|
log.Warn("Ignoring user directive. User to be specified as a UID (numeric).")
|
|
} else {
|
|
securityContext.RunAsUser = &uid
|
|
}
|
|
case 2:
|
|
uid, err := strconv.ParseInt(userparts[0], 10, 64)
|
|
if err != nil {
|
|
log.Warn("Ignoring user name in user directive. User to be specified as a UID (numeric).")
|
|
} else {
|
|
securityContext.RunAsUser = &uid
|
|
}
|
|
|
|
gid, err := strconv.ParseInt(userparts[1], 10, 64)
|
|
if err != nil {
|
|
log.Warn("Ignoring group name in user directive. Group to be specified as a GID (numeric).")
|
|
} else {
|
|
securityContext.RunAsGroup = &gid
|
|
}
|
|
}
|
|
}
|
|
|
|
// Configure capabilities
|
|
capabilities := ConfigCapabilities(service)
|
|
|
|
//set capabilities if it is not empty
|
|
if len(capabilities.Add) > 0 || len(capabilities.Drop) > 0 {
|
|
securityContext.Capabilities = capabilities
|
|
}
|
|
|
|
// update template only if securityContext is not empty
|
|
if *securityContext != (api.SecurityContext{}) {
|
|
podSpec.Containers[0].SecurityContext = securityContext
|
|
}
|
|
if !reflect.DeepEqual(*podSecurityContext, api.PodSecurityContext{}) {
|
|
podSpec.SecurityContext = podSecurityContext
|
|
}
|
|
}
|
|
}
|
|
|
|
// SetVolumeNames method return a set of volume names
|
|
func SetVolumeNames(volumes []api.Volume) mapset.Set {
|
|
set := mapset.NewSet()
|
|
for _, volume := range volumes {
|
|
set.Add(volume.Name)
|
|
}
|
|
return set
|
|
}
|
|
|
|
// SetVolumes method returns a method that adds the volumes to the pod spec
|
|
func SetVolumes(volumes []api.Volume) PodSpecOption {
|
|
return func(podSpec *PodSpec) {
|
|
volumesSet := SetVolumeNames(volumes)
|
|
containerVolumesSet := SetVolumeNames(podSpec.Volumes)
|
|
for diffVolumeName := range volumesSet.Difference(containerVolumesSet).Iter() {
|
|
for _, volume := range volumes {
|
|
if volume.Name == diffVolumeName {
|
|
podSpec.Volumes = append(podSpec.Volumes, volume)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// SetVolumeMountPaths method returns a set of volumes mount path
|
|
func SetVolumeMountPaths(volumesMount []api.VolumeMount) mapset.Set {
|
|
set := mapset.NewSet()
|
|
for _, volumeMount := range volumesMount {
|
|
set.Add(volumeMount.MountPath)
|
|
}
|
|
|
|
return set
|
|
}
|
|
|
|
// SetVolumeMounts returns a function which adds the volume mounts option to the pod spec
|
|
func SetVolumeMounts(volumesMount []api.VolumeMount) PodSpecOption {
|
|
return func(podSpec *PodSpec) {
|
|
volumesMountSet := SetVolumeMountPaths(volumesMount)
|
|
for i := range podSpec.Containers {
|
|
containerVolumeMountsSet := SetVolumeMountPaths(podSpec.Containers[i].VolumeMounts)
|
|
for diffVolumeMountPath := range volumesMountSet.Difference(containerVolumeMountsSet).Iter() {
|
|
for _, volumeMount := range volumesMount {
|
|
if volumeMount.MountPath == diffVolumeMountPath {
|
|
podSpec.Containers[i].VolumeMounts = append(podSpec.Containers[i].VolumeMounts, volumeMount)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// SetPorts Configure ports
|
|
func SetPorts(service kobject.ServiceConfig) PodSpecOption {
|
|
return func(podSpec *PodSpec) {
|
|
// Configure the container ports.
|
|
ports := ConfigPorts(service)
|
|
for i := range podSpec.Containers {
|
|
if GetContainerName(service) == podSpec.Containers[i].Name {
|
|
podSpec.Containers[i].Ports = ports
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ImagePullPolicy Configure the image pull policy
|
|
func ImagePullPolicy(name string, service kobject.ServiceConfig) PodSpecOption {
|
|
return func(podSpec *PodSpec) {
|
|
if policy, err := GetImagePullPolicy(name, service.ImagePullPolicy); err != nil {
|
|
panic(err)
|
|
} else {
|
|
for i := range podSpec.Containers {
|
|
podSpec.Containers[i].ImagePullPolicy = policy
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// RestartPolicy Configure the container restart policy.
|
|
func RestartPolicy(name string, service kobject.ServiceConfig) PodSpecOption {
|
|
return func(podSpec *PodSpec) {
|
|
if restart, err := GetRestartPolicy(name, service.Restart); err != nil {
|
|
panic(err)
|
|
} else {
|
|
podSpec.RestartPolicy = restart
|
|
}
|
|
}
|
|
}
|
|
|
|
// HostName configure the host name of a pod
|
|
func HostName(service kobject.ServiceConfig) PodSpecOption {
|
|
return func(podSpec *PodSpec) {
|
|
// Configure hostname/domain_name settings
|
|
if service.HostName != "" {
|
|
podSpec.Hostname = service.HostName
|
|
}
|
|
}
|
|
}
|
|
|
|
// DomainName configure the domain name of a pod
|
|
func DomainName(service kobject.ServiceConfig) PodSpecOption {
|
|
return func(podSpec *PodSpec) {
|
|
if service.DomainName != "" {
|
|
podSpec.Subdomain = service.DomainName
|
|
}
|
|
}
|
|
}
|
|
|
|
func configProbe(healthCheck kobject.HealthCheck) *api.Probe {
|
|
probe := api.Probe{}
|
|
// We check to see if it's blank or disable
|
|
if reflect.DeepEqual(healthCheck, kobject.HealthCheck{}) || healthCheck.Disable {
|
|
return nil
|
|
}
|
|
|
|
if len(healthCheck.Test) > 0 {
|
|
probe.ProbeHandler = api.ProbeHandler{
|
|
Exec: &api.ExecAction{
|
|
Command: healthCheck.Test,
|
|
},
|
|
}
|
|
} else if !reflect.ValueOf(healthCheck.HTTPPath).IsZero() && !reflect.ValueOf(healthCheck.HTTPPort).IsZero() {
|
|
probe.ProbeHandler = api.ProbeHandler{
|
|
HTTPGet: &api.HTTPGetAction{
|
|
Path: healthCheck.HTTPPath,
|
|
Port: intstr.FromInt(int(healthCheck.HTTPPort)),
|
|
},
|
|
}
|
|
} else if !reflect.ValueOf(healthCheck.TCPPort).IsZero() {
|
|
probe.ProbeHandler = api.ProbeHandler{
|
|
TCPSocket: &api.TCPSocketAction{
|
|
Port: intstr.FromInt(int(healthCheck.TCPPort)),
|
|
},
|
|
}
|
|
} else {
|
|
panic(errors.New("Health check must contain a command"))
|
|
}
|
|
|
|
probe.TimeoutSeconds = healthCheck.Timeout
|
|
probe.PeriodSeconds = healthCheck.Interval
|
|
probe.FailureThreshold = healthCheck.Retries
|
|
|
|
// See issue: https://github.com/docker/cli/issues/116
|
|
// StartPeriod has been added to v3.4 of the compose
|
|
probe.InitialDelaySeconds = healthCheck.StartPeriod
|
|
return &probe
|
|
}
|
|
|
|
// ServiceAccountName is responsible for setting the service account name to the pod spec
|
|
func ServiceAccountName(serviceAccountName string) PodSpecOption {
|
|
return func(podSpec *PodSpec) {
|
|
podSpec.ServiceAccountName = serviceAccountName
|
|
}
|
|
}
|
|
|
|
// TopologySpreadConstraints is responsible for setting the topology spread constraints to the pod spec
|
|
func TopologySpreadConstraints(service kobject.ServiceConfig) PodSpecOption {
|
|
return func(podSpec *PodSpec) {
|
|
podSpec.TopologySpreadConstraints = ConfigTopologySpreadConstraints(service)
|
|
}
|
|
}
|
|
|
|
// Append is responsible for adding the pod spec options to the particular pod
|
|
func (podSpec *PodSpec) Append(ops ...PodSpecOption) *PodSpec {
|
|
for _, option := range ops {
|
|
option(podSpec)
|
|
}
|
|
return podSpec
|
|
}
|
|
|
|
// Get is responsible for returning the pod spec of a particular pod
|
|
func (podSpec *PodSpec) Get() api.PodSpec {
|
|
return podSpec.PodSpec
|
|
}
|