support multiple containers in a pod (#1394)

This commit is contained in:
tk42
2021-07-08 09:48:05 +08:00
committed by GitHub
parent c51d59566b
commit deb00f3407
12 changed files with 801 additions and 87 deletions
+5
View File
@@ -77,6 +77,8 @@ type ConvertOptions struct {
YAMLIndent int
WithKomposeAnnotation bool
MultipleContainerMode bool
}
// IsPodController indicate if the user want to use a controller
@@ -84,8 +86,11 @@ func (opt *ConvertOptions) IsPodController() bool {
return opt.IsDeploymentFlag || opt.IsDaemonSetFlag || opt.IsReplicationControllerFlag || opt.Controller != ""
}
type ServiceConfigGroup []ServiceConfig
// ServiceConfig holds the basic struct of a container
type ServiceConfig struct {
Name string
ContainerName string
Image string `compose:"image"`
Environment []EnvVar `compose:"environment"`
+2
View File
@@ -32,6 +32,8 @@ import (
const (
// LabelServiceType defines the type of service to be created
LabelServiceType = "kompose.service.type"
// LabelServiceGroup defines the group of services in a single pod
LabelServiceGroup = "kompose.service.group"
// LabelNodePortPort defines the port value for NodePort service
LabelNodePortPort = "kompose.service.nodeport.port"
// LabelServiceExpose defines if the service needs to be made accessible from outside the cluster or not
+1
View File
@@ -178,6 +178,7 @@ func libComposeToKomposeMapping(composeObject *project.Project) (kobject.Kompose
// all relevant information as well as avoid the unsupported keys as well.
for name, composeServiceConfig := range composeObject.ServiceConfigs.All() {
serviceConfig := kobject.ServiceConfig{}
serviceConfig.Name = name
serviceConfig.Image = composeServiceConfig.Image
serviceConfig.Build = composeServiceConfig.Build.Context
newName := normalizeContainerNames(composeServiceConfig.ContainerName)
+1
View File
@@ -364,6 +364,7 @@ func dockerComposeToKomposeMapping(composeObject *types.Config) (kobject.Kompose
// No need to modify before importation
name := composeServiceConfig.Name
serviceConfig := kobject.ServiceConfig{}
serviceConfig.Name = name
serviceConfig.Image = composeServiceConfig.Image
serviceConfig.WorkingDir = composeServiceConfig.WorkingDir
serviceConfig.Annotations = map[string]string(composeServiceConfig.Labels)
+51 -3
View File
@@ -33,6 +33,7 @@ import (
"github.com/joho/godotenv"
"github.com/kubernetes/kompose/pkg/kobject"
"github.com/kubernetes/kompose/pkg/loader/compose"
"github.com/kubernetes/kompose/pkg/transformer"
deployapi "github.com/openshift/api/apps/v1"
"github.com/pkg/errors"
@@ -440,11 +441,44 @@ func (k *Kubernetes) CreateHeadlessService(name string, service kobject.ServiceC
return svc
}
func (k *Kubernetes) UpdateKubernetesObjectsMultipleContainers(name string, service kobject.ServiceConfig, opt kobject.ConvertOptions, objects *[]runtime.Object, podSpec PodSpec) error {
// Configure annotations
annotations := transformer.ConfigAnnotations(service)
// fillTemplate fills the pod template with the value calculated from config
fillTemplate := func(template *api.PodTemplateSpec) error {
template.ObjectMeta.Labels = transformer.ConfigLabelsWithNetwork(name, service.Network)
template.Spec = podSpec.Get()
return nil
}
// fillObjectMeta fills the metadata with the value calculated from config
fillObjectMeta := func(meta *metav1.ObjectMeta) {
meta.Annotations = annotations
}
// update supported controller
for _, obj := range *objects {
err := k.UpdateController(obj, fillTemplate, fillObjectMeta)
if err != nil {
return errors.Wrap(err, "k.UpdateController failed")
}
if len(service.Volumes) > 0 {
switch objType := obj.(type) {
case *appsv1.Deployment:
objType.Spec.Strategy.Type = appsv1.RecreateDeploymentStrategyType
case *deployapi.DeploymentConfig:
objType.Spec.Strategy.Type = deployapi.DeploymentStrategyTypeRecreate
}
}
}
return nil
}
// UpdateKubernetesObjects loads configurations to k8s objects
func (k *Kubernetes) UpdateKubernetesObjects(name string, service kobject.ServiceConfig, opt kobject.ConvertOptions, objects *[]runtime.Object) error {
// Configure the environment variables.
envs, err := k.ConfigEnvs(name, service, opt)
envs, err := ConfigEnvs(name, service, opt)
if err != nil {
return errors.Wrap(err, "Unable to load env variables")
}
@@ -479,10 +513,10 @@ func (k *Kubernetes) UpdateKubernetesObjects(name string, service kobject.Servic
}
// Configure the container ports.
ports := k.ConfigPorts(name, service)
ports := ConfigPorts(name, service)
// Configure capabilities
capabilities := k.ConfigCapabilities(service)
capabilities := ConfigCapabilities(service)
// Configure annotations
annotations := transformer.ConfigAnnotations(service)
@@ -662,6 +696,20 @@ func (k *Kubernetes) UpdateKubernetesObjects(name string, service kobject.Servic
return nil
}
// KomposeObjectToServiceConfigGroupMapping returns the service config group by name
func KomposeObjectToServiceConfigGroupMapping(komposeObject kobject.KomposeObject) map[string]kobject.ServiceConfigGroup {
serviceConfigGroup := make(map[string]kobject.ServiceConfigGroup)
for name, service := range komposeObject.ServiceConfigs {
if groupID, ok := service.Labels[compose.LabelServiceGroup]; ok {
service.Name = name
serviceConfigGroup[groupID] = append(serviceConfigGroup[groupID], service)
} else {
serviceConfigGroup[name] = append(serviceConfigGroup[name], service)
}
}
return serviceConfigGroup
}
// TranslatePodResource config pod resources
func TranslatePodResource(service *kobject.ServiceConfig, template *api.PodTemplateSpec) {
// Configure the resource limits
+225 -74
View File
@@ -536,7 +536,7 @@ func (k *Kubernetes) CreatePVC(name string, mode string, size string, selectorVa
}
// ConfigPorts configures the container ports.
func (k *Kubernetes) ConfigPorts(name string, service kobject.ServiceConfig) []api.ContainerPort {
func ConfigPorts(name string, service kobject.ServiceConfig) []api.ContainerPort {
ports := []api.ContainerPort{}
exist := map[string]bool{}
for _, port := range service.Port {
@@ -641,7 +641,7 @@ func (k *Kubernetes) ConfigServicePorts(name string, service kobject.ServiceConf
}
//ConfigCapabilities configure POSIX capabilities that can be added or removed to a container
func (k *Kubernetes) ConfigCapabilities(service kobject.ServiceConfig) *api.Capabilities {
func ConfigCapabilities(service kobject.ServiceConfig) *api.Capabilities {
capsAdd := []api.Capability{}
capsDrop := []api.Capability{}
for _, capAdd := range service.CapAdd {
@@ -947,7 +947,7 @@ func (k *Kubernetes) ConfigPVCVolumeSource(name string, readonly bool) *api.Volu
}
// ConfigEnvs configures the environment variables.
func (k *Kubernetes) ConfigEnvs(name string, service kobject.ServiceConfig, opt kobject.ConvertOptions) ([]api.EnvVar, error) {
func ConfigEnvs(name string, service kobject.ServiceConfig, opt kobject.ConvertOptions) ([]api.EnvVar, error) {
envs := transformer.EnvSort{}
keysFromEnvFile := make(map[string]bool)
@@ -1132,98 +1132,249 @@ func (k *Kubernetes) Transform(komposeObject kobject.KomposeObject, opt kobject.
}
}
sortedKeys := SortedKeys(komposeObject)
for _, name := range sortedKeys {
service := komposeObject.ServiceConfigs[name]
var objects []runtime.Object
if opt.MultipleContainerMode {
komposeObjectToServiceConfigGroupMapping := KomposeObjectToServiceConfigGroupMapping(komposeObject)
for name, group := range komposeObjectToServiceConfigGroupMapping {
service := komposeObject.ServiceConfigs[name]
var objects []runtime.Object
service.WithKomposeAnnotation = opt.WithKomposeAnnotation
service.WithKomposeAnnotation = opt.WithKomposeAnnotation
// Must build the images before conversion (got to add service.Image in case 'image' key isn't provided
// Check that --build is set to true
// Check to see if there is an InputFile (required!) before we build the container
// Check that there's actually a Build key
// Lastly, we must have an Image name to continue
if opt.Build == "local" && opt.InputFiles != nil && service.Build != "" {
// If there's no "image" key, use the name of the container that's built
if service.Image == "" {
service.Image = name
}
// Must build the images before conversion (got to add service.Image in case 'image' key isn't provided
// Check that --build is set to true
// Check to see if there is an InputFile (required!) before we build the container
// Check that there's actually a Build key
// Lastly, we must have an Image name to continue
if opt.Build == "local" && opt.InputFiles != nil && service.Build != "" {
// If there's no "image" key, use the name of the container that's built
if service.Image == "" {
service.Image = name
}
if service.Image == "" {
return nil, fmt.Errorf("image key required within build parameters in order to build and push service '%s'", name)
}
if service.Image == "" {
return nil, fmt.Errorf("image key required within build parameters in order to build and push service '%s'", name)
}
log.Infof("Build key detected. Attempting to build image '%s'", service.Image)
log.Infof("Build key detected. Attempting to build image '%s'", service.Image)
// Build the image!
err := transformer.BuildDockerImage(service, name)
if err != nil {
return nil, errors.Wrapf(err, "Unable to build Docker image for service %v", name)
}
// Push the built image to the repo!
if opt.PushImage {
log.Infof("Push image enabled. Attempting to push image '%s'", service.Image)
err = transformer.PushDockerImage(service, name)
// Build the image!
err := transformer.BuildDockerImage(service, name)
if err != nil {
return nil, errors.Wrapf(err, "Unable to push Docker image for service %v", name)
return nil, errors.Wrapf(err, "Unable to build Docker image for service %v", name)
}
// Push the built image to the repo!
if opt.PushImage {
log.Infof("Push image enabled. Attempting to push image '%s'", service.Image)
err = transformer.PushDockerImage(service, name)
if err != nil {
return nil, errors.Wrapf(err, "Unable to push Docker image for service %v", name)
}
}
}
}
// Generate pod only and nothing more
if (service.Restart == "no" || service.Restart == "on-failure") && !opt.IsPodController() {
log.Infof("Create kubernetes pod instead of pod controller due to restart policy: %s", service.Restart)
pod := k.InitPod(name, service)
objects = append(objects, pod)
} else {
objects = k.CreateKubernetesObjects(name, service, opt)
}
podSpec := PodSpec{}
if k.PortsExist(service) {
if service.ServiceType == "LoadBalancer" {
svcs := k.CreateLBService(name, service, objects)
for _, svc := range svcs {
// added a container
for _, service := range group {
podSpec.Append(AddContainer(service, opt))
// Generate pod only and nothing more
if (service.Restart == "no" || service.Restart == "on-failure") && !opt.IsPodController() {
log.Infof("Create kubernetes pod instead of pod controller due to restart policy: %s", service.Restart)
pod := k.InitPod(name, service)
objects = append(objects, pod)
} else {
objects = k.CreateKubernetesObjects(name, service, opt)
}
if k.PortsExist(service) {
if service.ServiceType == "LoadBalancer" {
svcs := k.CreateLBService(name, service, objects)
for _, svc := range svcs {
objects = append(objects, svc)
}
if len(svcs) > 1 {
log.Warningf("Create multiple service to avoid using mixed protocol in the same service when it's loadbalander type")
}
} else {
svc := k.CreateService(name, service, objects)
objects = append(objects, svc)
if service.ExposeService != "" {
objects = append(objects, k.initIngress(name, service, svc.Spec.Ports[0].Port))
}
}
} else {
if service.ServiceType == "Headless" {
svc := k.CreateHeadlessService(name, service, objects)
objects = append(objects, svc)
} else {
log.Warnf("Service %q won't be created because 'ports' is not specified", name)
}
}
// Configure the container volumes.
volumesMount, volumes, pvc, cms, err := k.ConfigVolumes(name, service)
if err != nil {
return nil, errors.Wrap(err, "k.ConfigVolumes failed")
}
podSpec.Append(
SetVolumeMounts(volumesMount),
SetVolumes(volumes),
)
// Configure Tmpfs
if len(service.TmpFs) > 0 {
TmpVolumesMount, TmpVolumes := k.ConfigTmpfs(name, service)
volumes = append(volumes, TmpVolumes...)
volumesMount = append(volumesMount, TmpVolumesMount...)
}
if pvc != nil {
// Looping on the slice pvc instead of `*objects = append(*objects, pvc...)`
// because the type of objects and pvc is different, but when doing append
// one element at a time it gets converted to runtime.Object for objects slice
for _, p := range pvc {
objects = append(objects, p)
}
}
if cms != nil {
for _, c := range cms {
objects = append(objects, c)
}
}
podSpec.Append(
SetPorts(name, service),
ImagePullPolicy(name, service),
RestartPolicy(name, service),
SecurityContext(name, service),
LivenessProbe(service),
ReadinessProbe(service),
HostName(service),
DomainName(service),
ResourcesLimits(service),
ResourcesRequests(service),
TerminationGracePeriodSeconds(name, service),
)
err = k.UpdateKubernetesObjectsMultipleContainers(name, service, opt, &objects, podSpec)
if err != nil {
return nil, errors.Wrap(err, "Error transforming Kubernetes objects")
}
}
if len(service.Network) > 0 {
for _, net := range service.Network {
log.Infof("Network %s is detected at Source, shall be converted to equivalent NetworkPolicy at Destination", net)
np, err := k.CreateNetworkPolicy(name, net)
if err != nil {
return nil, errors.Wrapf(err, "Unable to create Network Policy for network %v for service %v", net, name)
}
objects = append(objects, np)
}
}
allobjects = append(allobjects, objects...)
}
} else {
sortedKeys := SortedKeys(komposeObject)
for _, name := range sortedKeys {
service := komposeObject.ServiceConfigs[name]
var objects []runtime.Object
service.WithKomposeAnnotation = opt.WithKomposeAnnotation
// Must build the images before conversion (got to add service.Image in case 'image' key isn't provided
// Check that --build is set to true
// Check to see if there is an InputFile (required!) before we build the container
// Check that there's actually a Build key
// Lastly, we must have an Image name to continue
if opt.Build == "local" && opt.InputFiles != nil && service.Build != "" {
// If there's no "image" key, use the name of the container that's built
if service.Image == "" {
service.Image = name
}
if service.Image == "" {
return nil, fmt.Errorf("image key required within build parameters in order to build and push service '%s'", name)
}
log.Infof("Build key detected. Attempting to build image '%s'", service.Image)
// Build the image!
err := transformer.BuildDockerImage(service, name)
if err != nil {
return nil, errors.Wrapf(err, "Unable to build Docker image for service %v", name)
}
// Push the built image to the repo!
if opt.PushImage {
log.Infof("Push image enabled. Attempting to push image '%s'", service.Image)
err = transformer.PushDockerImage(service, name)
if err != nil {
return nil, errors.Wrapf(err, "Unable to push Docker image for service %v", name)
}
}
}
// Generate pod only and nothing more
if (service.Restart == "no" || service.Restart == "on-failure") && !opt.IsPodController() {
log.Infof("Create kubernetes pod instead of pod controller due to restart policy: %s", service.Restart)
pod := k.InitPod(name, service)
objects = append(objects, pod)
} else {
objects = k.CreateKubernetesObjects(name, service, opt)
}
if k.PortsExist(service) {
if service.ServiceType == "LoadBalancer" {
svcs := k.CreateLBService(name, service, objects)
for _, svc := range svcs {
objects = append(objects, svc)
}
if len(svcs) > 1 {
log.Warningf("Create multiple service to avoid using mixed protocol in the same service when it's loadbalander type")
}
} else {
svc := k.CreateService(name, service, objects)
objects = append(objects, svc)
}
if len(svcs) > 1 {
log.Warningf("Create multiple service to avoid using mixed protocol in the same service when it's loadbalander type")
if service.ExposeService != "" {
objects = append(objects, k.initIngress(name, service, svc.Spec.Ports[0].Port))
}
}
} else {
svc := k.CreateService(name, service, objects)
objects = append(objects, svc)
if service.ExposeService != "" {
objects = append(objects, k.initIngress(name, service, svc.Spec.Ports[0].Port))
if service.ServiceType == "Headless" {
svc := k.CreateHeadlessService(name, service, objects)
objects = append(objects, svc)
} else {
log.Warnf("Service %q won't be created because 'ports' is not specified", name)
}
}
} else {
if service.ServiceType == "Headless" {
svc := k.CreateHeadlessService(name, service, objects)
objects = append(objects, svc)
} else {
log.Warnf("Service %q won't be created because 'ports' is not specified", name)
err := k.UpdateKubernetesObjects(name, service, opt, &objects)
if err != nil {
return nil, errors.Wrap(err, "Error transforming Kubernetes objects")
}
}
err := k.UpdateKubernetesObjects(name, service, opt, &objects)
if err != nil {
return nil, errors.Wrap(err, "Error transforming Kubernetes objects")
}
if len(service.Network) > 0 {
for _, net := range service.Network {
log.Infof("Network %s is detected at Source, shall be converted to equivalent NetworkPolicy at Destination", net)
np, err := k.CreateNetworkPolicy(name, net)
if len(service.Network) > 0 {
for _, net := range service.Network {
log.Infof("Network %s is detected at Source, shall be converted to equivalent NetworkPolicy at Destination", net)
np, err := k.CreateNetworkPolicy(name, net)
if err != nil {
return nil, errors.Wrapf(err, "Unable to create Network Policy for network %v for service %v", net, name)
if err != nil {
return nil, errors.Wrapf(err, "Unable to create Network Policy for network %v for service %v", net, name)
}
objects = append(objects, np)
}
objects = append(objects, np)
}
}
allobjects = append(allobjects, objects...)
allobjects = append(allobjects, objects...)
}
}
// sort all object so Services are first
+97 -2
View File
@@ -24,6 +24,7 @@ import (
"testing"
"github.com/kubernetes/kompose/pkg/kobject"
"github.com/kubernetes/kompose/pkg/loader/compose"
"github.com/kubernetes/kompose/pkg/transformer"
deployapi "github.com/openshift/api/apps/v1"
"github.com/pkg/errors"
@@ -535,12 +536,106 @@ func TestConfigCapabilities(t *testing.T) {
"ConfigCapsNoAddDrop": {kobject.ServiceConfig{CapAdd: nil, CapDrop: nil}, api.Capabilities{Add: []api.Capability{}, Drop: []api.Capability{}}},
}
k := Kubernetes{}
for name, test := range testCases {
t.Log("Test case:", name)
result := k.ConfigCapabilities(test.service)
result := ConfigCapabilities(test.service)
if !reflect.DeepEqual(result.Add, test.result.Add) || !reflect.DeepEqual(result.Drop, test.result.Drop) {
t.Errorf("Not expected result for ConfigCapabilities")
}
}
}
func TestMultipleContainersInPod(t *testing.T) {
groupName := "pod_group"
containerName := ""
createConfig := func(name string, containerName *string) kobject.ServiceConfig {
config := newServiceConfig()
config.Labels = map[string]string{compose.LabelServiceGroup: groupName}
config.Name = name
if containerName != nil {
config.ContainerName = *containerName
}
config.Volumes = []kobject.Volumes{
{
VolumeName: "mountVolume",
MountPath: "/data",
},
}
return config
}
testCases := map[string]struct {
komposeObject kobject.KomposeObject
opt kobject.ConvertOptions
expectedNumObjs int
expectedNames []string
}{
"Converted multiple containers": {
kobject.KomposeObject{
ServiceConfigs: map[string]kobject.ServiceConfig{
"app1": createConfig("app1", &containerName),
"app2": createConfig("app2", &containerName),
},
}, kobject.ConvertOptions{MultipleContainerMode: true}, 2, []string{"app1", "app2"}},
"Converted multiple containers to Deployments (D)": {
kobject.KomposeObject{
ServiceConfigs: map[string]kobject.ServiceConfig{
"app1": createConfig("app1", &containerName),
"app2": createConfig("app2", &containerName),
},
}, kobject.ConvertOptions{MultipleContainerMode: true, CreateD: true}, 3, []string{"app1", "app2"}},
"Converted multiple containers (ContainerName are nil) to Deployments (D)": {
kobject.KomposeObject{
ServiceConfigs: map[string]kobject.ServiceConfig{
"app1": createConfig("app1", nil),
"app2": createConfig("app2", nil),
},
}, kobject.ConvertOptions{MultipleContainerMode: true, CreateD: true}, 3, []string{"name", "name"}},
// TODO: add more tests
}
for name, test := range testCases {
t.Log("Test case:", name)
k := Kubernetes{}
// Run Transform
objs, err := k.Transform(test.komposeObject, test.opt)
if err != nil {
t.Error(errors.Wrap(err, "k.Transform failed"))
}
if len(objs) != test.expectedNumObjs {
t.Errorf("Expected %d objects returned, got %d", test.expectedNumObjs, len(objs))
}
// Check results
for _, obj := range objs {
if svc, ok := obj.(*api.Service); ok {
if svc.Name != groupName {
t.Errorf("Expected %v returned, got %v", groupName, svc.Name)
}
}
if deployment, ok := obj.(*appsv1.Deployment); ok {
if deployment.Name != groupName {
t.Errorf("Expected %v returned, got %v", groupName, deployment.Name)
}
if len(deployment.Spec.Template.Spec.Containers) != 2 {
t.Errorf("Expected %d returned, got %d", 2, len(deployment.Spec.Template.Spec.Containers))
}
nameSet := make(map[string]api.Container)
for _, container := range deployment.Spec.Template.Spec.Containers {
nameSet[container.Name] = container
}
if container, ok := nameSet[test.expectedNames[0]]; !ok {
t.Errorf("Expected %v returned, got %v", test.expectedNames[0], container.Name)
} else if len(container.VolumeMounts) != 1 {
t.Errorf("Expected %v returned, got %v", 1, len(container.VolumeMounts))
}
if container, ok := nameSet[test.expectedNames[1]]; !ok {
t.Errorf("Expected %v returned, got %v", test.expectedNames[1], container.Name)
} else if len(container.VolumeMounts) != 1 {
t.Errorf("Expected %v returned, got %v", 1, len(container.VolumeMounts))
}
}
}
}
}
+352
View File
@@ -0,0 +1,352 @@
package kubernetes
import (
"reflect"
"strconv"
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"
)
type PodSpec struct {
api.PodSpec
}
type PodSpecOption func(*PodSpec)
func AddContainer(service kobject.ServiceConfig, opt kobject.ConvertOptions) PodSpecOption {
return func(podSpec *PodSpec) {
name := service.Name
image := service.Image
if image == "" {
image = name
}
// do not override in openshift case?
if len(service.ContainerName) > 0 {
name = FormatContainerName(service.ContainerName)
}
envs, err := ConfigEnvs(name, 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,
})
podSpec.NodeSelector = service.Placement
}
}
func ImagePullSecrets(pullSecret string) PodSpecOption {
return func(podSpec *PodSpec) {
podSpec.ImagePullSecrets = append(podSpec.ImagePullSecrets,
api.LocalObjectReference{
Name: pullSecret,
},
)
}
}
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)
}
}
}
}
// 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
}
}
}
}
// 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
}
}
}
}
// 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
}
// Setup security context
securityContext := &api.SecurityContext{}
if service.Privileged {
securityContext.Privileged = &service.Privileged
}
if service.User != "" {
uid, err := strconv.ParseInt(service.User, 10, 64)
if err != nil {
log.Warn("Ignoring user directive. User to be specified as a UID (numeric).")
} else {
securityContext.RunAsUser = &uid
}
}
// 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
}
}
}
func SetVolumeNames(volumes []api.Volume) mapset.Set {
set := mapset.NewSet()
for _, volume := range volumes {
set.Add(volume.Name)
}
return set
}
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
}
}
}
}
}
func SetVolumeMountPaths(volumesMount []api.VolumeMount) mapset.Set {
set := mapset.NewSet()
for _, volumeMount := range volumesMount {
set.Add(volumeMount.MountPath)
}
return set
}
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
}
}
}
}
}
}
// Configure ports
func SetPorts(name string, service kobject.ServiceConfig) PodSpecOption {
return func(podSpec *PodSpec) {
// Configure the container ports.
ports := ConfigPorts(name, service)
for i := range podSpec.Containers {
podSpec.Containers[i].Ports = ports
}
}
}
// 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
}
}
}
}
// 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
}
}
}
func HostName(service kobject.ServiceConfig) PodSpecOption {
return func(podSpec *PodSpec) {
// Configure hostname/domain_name settings
if service.HostName != "" {
podSpec.Hostname = service.HostName
}
}
}
func DomainName(service kobject.ServiceConfig) PodSpecOption {
return func(podSpec *PodSpec) {
if service.DomainName != "" {
podSpec.Subdomain = service.DomainName
}
}
}
func LivenessProbe(service kobject.ServiceConfig) PodSpecOption {
return func(podSpec *PodSpec) {
// Configure the HealthCheck
// We check to see if it's blank
if !reflect.DeepEqual(service.HealthChecks.Liveness, kobject.HealthCheck{}) {
probe := api.Probe{}
if len(service.HealthChecks.Liveness.Test) > 0 {
probe.Handler = api.Handler{
Exec: &api.ExecAction{
Command: service.HealthChecks.Liveness.Test,
},
}
} else if !reflect.ValueOf(service.HealthChecks.Liveness.HTTPPath).IsZero() &&
!reflect.ValueOf(service.HealthChecks.Liveness.HTTPPort).IsZero() {
probe.Handler = api.Handler{
HTTPGet: &api.HTTPGetAction{
Path: service.HealthChecks.Liveness.HTTPPath,
Port: intstr.FromInt(int(service.HealthChecks.Liveness.HTTPPort)),
},
}
} else {
panic(errors.New("Health check must contain a command"))
}
probe.TimeoutSeconds = service.HealthChecks.Liveness.Timeout
probe.PeriodSeconds = service.HealthChecks.Liveness.Interval
probe.FailureThreshold = service.HealthChecks.Liveness.Retries
// See issue: https://github.com/docker/cli/issues/116
// StartPeriod has been added to docker/cli however, it is not yet added
// to compose. Once the feature has been implemented, this will automatically work
probe.InitialDelaySeconds = service.HealthChecks.Liveness.StartPeriod
for i := range podSpec.Containers {
podSpec.Containers[i].LivenessProbe = &probe
}
}
}
}
func ReadinessProbe(service kobject.ServiceConfig) PodSpecOption {
return func(podSpec *PodSpec) {
if !reflect.DeepEqual(service.HealthChecks.Readiness, kobject.HealthCheck{}) {
probeHealthCheckReadiness := api.Probe{}
if len(service.HealthChecks.Readiness.Test) > 0 {
probeHealthCheckReadiness.Handler = api.Handler{
Exec: &api.ExecAction{
Command: service.HealthChecks.Readiness.Test,
},
}
} else {
panic(errors.New("Health check must contain a command"))
}
probeHealthCheckReadiness.TimeoutSeconds = service.HealthChecks.Readiness.Timeout
probeHealthCheckReadiness.PeriodSeconds = service.HealthChecks.Readiness.Interval
probeHealthCheckReadiness.FailureThreshold = service.HealthChecks.Readiness.Retries
// See issue: https://github.com/docker/cli/issues/116
// StartPeriod has been added to docker/cli however, it is not yet added
// to compose. Once the feature has been implemented, this will automatically work
probeHealthCheckReadiness.InitialDelaySeconds = service.HealthChecks.Readiness.StartPeriod
for i := range podSpec.Containers {
podSpec.Containers[i].ReadinessProbe = &probeHealthCheckReadiness
}
}
}
}
func (podSpec *PodSpec) Append(ops ...PodSpecOption) *PodSpec {
for _, option := range ops {
option(podSpec)
}
return podSpec
}
func (podSpec *PodSpec) Get() api.PodSpec {
return podSpec.PodSpec
}